From f04ce348aee70000080621a6a854c230439f500d Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 28 May 2026 20:30:16 +0700 Subject: [PATCH] add appeal modal window, add file status --- package.json | 2 +- src/app/actions/fileEntity.ts | 40 ++++++ .../components/tanstak-table/TanstakTable.tsx | 114 +++++++++++++----- src/app/styles/global-styles.scss | 15 ++- src/app/styles/pages-styles.scss | 101 +++++++++++++++- .../file-appeal-modal-window.tsx | 104 ++++++++++++++++ src/i18n/messages/en.json | 14 ++- src/i18n/messages/ru.json | 14 ++- 8 files changed, 362 insertions(+), 42 deletions(-) create mode 100644 src/app/ui/modal-windows/file-appeal-modal-window.tsx diff --git a/package.json b/package.json index ce1a404..fc366d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.118.0", + "version": "0.119.0", "private": true, "scripts": { "dev": "next dev -p 2999", diff --git a/src/app/actions/fileEntity.ts b/src/app/actions/fileEntity.ts index b3578c2..e51fb39 100644 --- a/src/app/actions/fileEntity.ts +++ b/src/app/actions/fileEntity.ts @@ -162,4 +162,44 @@ export async function downloadFile(fileId: string, fileName: string) { contentType: response.headers.get('content-type') || 'application/octet-stream', fileName: fileName } +} + +export async function FileAnAppeal(fileId: string, appealReason: string, additionalInfo: string) { + const token = await getSessionData('token'); + + try { + const response = await fetch(`${API_BASE_URL}/api/v1/data`, { + method: 'POST', + body: JSON.stringify({ + version: 1, + msg_id: 20005, + message_body: { + action: 'submit_appeal', + file_id: fileId, + token: token, + appeal_reason: appealReason, + additional_info: additionalInfo + } + }), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + if (response.ok) { + let parsed = await response.json(); + + if (parsed.message_code === 0) { + return parsed.message_body; + } else { + throw parsed; + } + } else { + throw (`${response.status}`); + } + + } catch (error) { + return error + } } \ No newline at end of file diff --git a/src/app/components/tanstak-table/TanstakTable.tsx b/src/app/components/tanstak-table/TanstakTable.tsx index 2cfb77d..97e522d 100644 --- a/src/app/components/tanstak-table/TanstakTable.tsx +++ b/src/app/components/tanstak-table/TanstakTable.tsx @@ -33,6 +33,7 @@ import Link from 'next/link'; import { SelectedFilesAction } from '@/app/components/tanstak-table/SelectedFilesActions'; import Image from 'next/image'; import { filePermisionChange } from '@/app/actions/trackingActions'; +import {FileAppealModalWindow} from '@/app/ui/modal-windows/file-appeal-modal-window'; export type FileItem = { id: string; @@ -41,7 +42,7 @@ export type FileItem = { violations?: number | undefined; size?: number | undefined; uploadDate?: number; - status?: string; + status: string; monitoring: string; protectStatus: string; _original?: ApiFile; @@ -295,6 +296,32 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType }); }, [apiResponse]); + const StatusBadge = ({ status, protectStatus }: { status: string, protectStatus: string }) => { + + switch (status) { + case 'ACTIVE': + return + {protectStatus ? t(protectStatus) : t('error')} + ; + case 'MODERATION': + return + {t('pending-moderation')} + ; + case 'BLOCKED': + return + {t('blocked')} + ; + case 'REMOVED': + return + {t('file-has-been-deleted')} + ; + default: + return + {protectStatus ? t(protectStatus) : t('error')} + ; + } + } + const isAllSelected = useMemo(() => { if (!apiResponse?.items?.length) return false; const allIds = new Set(apiResponse.items.map(item => item.id)); @@ -431,9 +458,7 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType {cutFileName(row.original.fileName)}
- {cutFileExtension(row.original.fileName)} - {row.original?.protectStatus ? t(row.original?.protectStatus) : t('error')} - + {cutFileExtension(row.original.fileName)} @@ -614,36 +639,53 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType ) : ( <> -
- - - -
-
- {row.original.protectStatus === 'PROTECTED' && ( + {/* "BLOCKED" "REMOVED" */} + {(row.original.status !== 'BLOCKED' && row.original.status !== 'REMOVED') ? ( + <> +
+ + + +
+
+ {row.original.protectStatus === 'PROTECTED' && ( + + )} + +
+ + ) : ( +
- )} - -
+
+ )} )} @@ -666,6 +708,16 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType setOpenWindow(true); }; + const handleAppeal = async (file: FileItem) => { + + setOpenWindowChildren(() => { + return ( + + ) + }) + setOpenWindow(true); + }; + const handleDownload = async (file: FileItem) => { setIsFileLoading(true) diff --git a/src/app/styles/global-styles.scss b/src/app/styles/global-styles.scss index 5f71437..0016e92 100644 --- a/src/app/styles/global-styles.scss +++ b/src/app/styles/global-styles.scss @@ -163,7 +163,7 @@ .btn, .btn-s, -.btn-m { +.btn-l { border: none; cursor: pointer; font-weight: 600; @@ -197,7 +197,8 @@ } .btn-primary { - background: linear-gradient(135deg, v.$p-color, v.$s-color); + /* background: linear-gradient(135deg, v.$p-color, v.$s-color); */ + background: #6366f1; color: v.$white; border: 2px solid transparent; @@ -222,6 +223,16 @@ } } +.btn-cancel { + background: v.$bg-hover; + color: v.$text-p; + border: 2px solid v.$border-color-1; + + &:hover { + transform: translateY(-2px); + } +} + .btn-outline { background: transparent; border: 2px solid v.$p-color; diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index 9ab4bb7..720c77f 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -930,13 +930,24 @@ } } - .table-item-protected { - color: #10b981; + .table-item-status-badge { font-size: 12px; position: relative; margin-left: 5px; padding-left: 5px; + &.protected { + color: #10b981; + } + + &.moderation { + color: v.$color-warning; + } + + &.blocked { + color: v.$red; + } + &::before { content: '•'; color: v.$text-s; @@ -1016,11 +1027,11 @@ } } - .table-action-delete { - background-color: #e80a14; + .table-action-appeal { + background-color: #9f0712; &:hover { - background-color: #9f0712; + background-color: v.$red; } } @@ -3799,7 +3810,7 @@ } .info-header { - background: linear-gradient(0deg, #6366f1 50%, #8b5cf6 130%); + background: #6366f1; color: white; padding: 16px 20px; border-bottom: 1px solid v.$border-color-1; @@ -3847,6 +3858,84 @@ padding: 12px 20px; border-bottom: 1px solid #f1f5f9; } + + .appeal-form { + margin-top: 20px; + } + + .appeal-textarea { + width: 100%; + padding: 10px; + border: 1px solid #ddd; + border-radius: 6px; + font-family: inherit; + font-size: 14px; + resize: vertical; + } + + .appeal-textarea:focus { + outline: none; + border-color: #6366f1; + box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.1); + } + + .appeal-textarea:disabled { + background-color: #f3f4f6; + cursor: not-allowed; + } + + .required { + color: #ef4444; + } + + .appeal-actions { + display: flex; + gap: 12px; + justify-content: center; + margin: 10px 0; + } + + .appeal-submit-btn { + padding: 8px 20px; + background-color: #6366f1; + color: white; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: background-color 0.2s; + } + + .appeal-submit-btn:hover:not(:disabled) { + background-color: #4f46e5; + } + + .appeal-submit-btn:disabled { + background-color: #9ca3af; + cursor: not-allowed; + } + + .appeal-cancel-btn { + padding: 8px 20px; + background-color: #f3f4f6; + color: #374151; + border: 1px solid #d1d5db; + border-radius: 6px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: all 0.2s; + } + + .appeal-cancel-btn:hover:not(:disabled) { + background-color: #e5e7eb; + } + + .appeal-cancel-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + } } } } diff --git a/src/app/ui/modal-windows/file-appeal-modal-window.tsx b/src/app/ui/modal-windows/file-appeal-modal-window.tsx new file mode 100644 index 0000000..fd548d0 --- /dev/null +++ b/src/app/ui/modal-windows/file-appeal-modal-window.tsx @@ -0,0 +1,104 @@ +'use client'; + +import { convertBytes } from '@/app/lib/convertBytes'; +import { formatDate, formatDateTime } from '@/app/lib/formatDate'; +import { useTranslations } from 'next-intl'; +import { FileDetails } from '@/app/[locale]/pages/file/[id]/page'; +import { FileAnAppeal } from '@/app/actions/fileEntity'; +import { useState } from 'react'; +import { toast } from 'sonner'; + +export function FileAppealModalWindow({ fileId, setWindowClose, setWindowChildren }: { + fileId: string, + setWindowClose: any, + setWindowChildren: any +}) { + const t = useTranslations('Global'); + const [appealReason, setAppealReason] = useState(''); + const [additionalInfo, setAdditionalInfo] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleSubmitAppeal = async () => { + if (!appealReason.trim()) { + toast.error(t('please-enter-appeal-reason')); + return; + } + + setIsSubmitting(true); + + try { + const result = await FileAnAppeal(fileId, appealReason, additionalInfo); + + if (result && result.status === 'success') { + toast.success(t('appeal-submitted-successfully')); + setWindowClose(false); + } else { + toast.error(result?.message || t('appeal-submission-failed')); + } + } catch (error) { + console.error('Appeal submission error:', error); + toast.error(t('appeal-submission-failed')); + } finally { + setIsSubmitting(false); + } + }; + + return ( +
+
+ {/* Форма подачи апелляции */} +
+
+

{t('file-an-appeal')}

+
+ +
+
+ {t('appeal-reason')} * +
+
+