From da876d7e90bad6d92b8b6691797402a512988f4b Mon Sep 17 00:00:00 2001 From: "Thomas F. K. Jorna" Date: Tue, 10 Feb 2026 16:33:18 +0100 Subject: [PATCH 01/16] feat: allow blocking users --- .../UserSpam/MarkSpamStatusButton.tsx | 47 ++++++ .../SuperAdminDashboard/UserSpam/UserSpam.tsx | 104 ++++++++++++ .../UserSpam/UserSpamEntry.tsx | 150 ++++++++++++++++++ .../SuperAdminDashboard/UserSpam/filters.ts | 45 ++++++ .../SuperAdminDashboard/UserSpam/index.ts | 1 + .../SuperAdminDashboard/UserSpam/types.ts | 3 + .../UserSpam/useSpamUsers.ts | 59 +++++++ .../UserSpam/userSpam.scss | 20 +++ .../UserSpam/userSpamEntry.scss | 53 +++++++ .../containers/SuperAdminDashboard/tabs.tsx | 5 + server/routes/superAdminDashboard.tsx | 16 ++ server/spamTag/api.ts | 115 ++++++++++++++ server/spamTag/uploadScamKeywords.ts | 19 +++ server/spamTag/userQueries.ts | 86 ++++++++++ server/spamTag/userScore.ts | 79 +++++++++ server/spamTag/users.ts | 60 +++++++ server/upload/api.ts | 27 +++- server/uploadPolicy/api.ts | 27 +++- server/uploadPolicy/queries.ts | 11 +- server/user/model.ts | 8 + server/utils/email/index.ts | 1 + server/utils/email/spam.ts | 51 ++++++ server/utils/session.ts | 18 +++ server/utils/slack.ts | 32 ++++ .../migrations/2026_02_10_addSpamTagToUser.js | 10 ++ types/request.ts | 7 + types/spam.ts | 18 +++ types/user.ts | 2 + utils/api/contract.ts | 5 + utils/api/contracts/auth.ts | 3 + utils/api/contracts/publicPermissions.ts | 23 +++ utils/api/contracts/upload.ts | 2 + utils/api/server.ts | 2 + utils/superAdmin.ts | 2 +- 34 files changed, 1107 insertions(+), 4 deletions(-) create mode 100644 client/containers/SuperAdminDashboard/UserSpam/MarkSpamStatusButton.tsx create mode 100644 client/containers/SuperAdminDashboard/UserSpam/UserSpam.tsx create mode 100644 client/containers/SuperAdminDashboard/UserSpam/UserSpamEntry.tsx create mode 100644 client/containers/SuperAdminDashboard/UserSpam/filters.ts create mode 100644 client/containers/SuperAdminDashboard/UserSpam/index.ts create mode 100644 client/containers/SuperAdminDashboard/UserSpam/types.ts create mode 100644 client/containers/SuperAdminDashboard/UserSpam/useSpamUsers.ts create mode 100644 client/containers/SuperAdminDashboard/UserSpam/userSpam.scss create mode 100644 client/containers/SuperAdminDashboard/UserSpam/userSpamEntry.scss create mode 100644 server/spamTag/uploadScamKeywords.ts create mode 100644 server/spamTag/userQueries.ts create mode 100644 server/spamTag/userScore.ts create mode 100644 server/spamTag/users.ts create mode 100644 server/utils/email/spam.ts create mode 100644 server/utils/session.ts create mode 100644 tools/migrations/2026_02_10_addSpamTagToUser.js create mode 100644 utils/api/contracts/publicPermissions.ts diff --git a/client/containers/SuperAdminDashboard/UserSpam/MarkSpamStatusButton.tsx b/client/containers/SuperAdminDashboard/UserSpam/MarkSpamStatusButton.tsx new file mode 100644 index 0000000000..150b5535f4 --- /dev/null +++ b/client/containers/SuperAdminDashboard/UserSpam/MarkSpamStatusButton.tsx @@ -0,0 +1,47 @@ +import type { SpamStatus } from 'types'; + +import React, { useCallback, useState } from 'react'; + +import { Button } from '@blueprintjs/core'; + +import { apiFetch } from 'client/utils/apiFetch'; + +type Props = { + userId: string; + status: SpamStatus; + onStatusChanged: (status: SpamStatus) => unknown; +}; + +const propsForStatuses: Record>> = { + 'confirmed-not-spam': { + icon: 'tick', + children: 'Not spam', + }, + 'confirmed-spam': { + icon: 'cross', + intent: 'danger', + children: 'Spam', + }, + unreviewed: { + icon: 'undo', + children: 'Mark unreviewed', + }, +}; + +const MarkSpamStatusButton = (props: Props) => { + const { status, userId, onStatusChanged } = props; + const [isLoading, setIsLoading] = useState(false); + + const handleClick = useCallback(async () => { + setIsLoading(true); + await apiFetch.put('/api/spamTags/user', { status, userId }); + setIsLoading(false); + onStatusChanged(status); + }, [status, userId, onStatusChanged]); + + return ( + + + ); + } + if (status === 'confirmed-spam') { + return ( + + + + ); } return ( - + + + + + ); }; const renderSuspiciousFiles = () => { + const list = fields?.suspiciousFiles; + if (!list?.length) return null; return (

Suspicious files