add navagation handler while file upload

This commit is contained in:
smanylov
2025-12-25 13:19:32 +07:00
parent 87b8d68f41
commit e0420fa6d3
6 changed files with 136 additions and 35 deletions
@@ -1,9 +1,10 @@
import ProtectionSummary from '@/app/ui/marking-page/protection-summary';
import FilesTable from '@/app/ui/dashboard/files-table';
import PageTitle from '@/app/ui/page-title';
import UploadSectionFile from '@/app/components/upload-section-file';
const ALLOWED_EXTENSIONS: string[] = ['jpg', 'jpeg', 'png', 'gif', 'bmp'];
const FILE_TYPE = "VIDEO"
const FILE_TYPE = "AUDIO"
const MAX_SIZE = 1048576000;
export default function Page() {
@@ -12,6 +13,7 @@ export default function Page() {
<div>
<PageTitle title="audio-protection" />
<ProtectionSummary />
<UploadSectionFile fileType={FILE_TYPE} allowedExtensions={ALLOWED_EXTENSIONS} maxFileSize={MAX_SIZE} />
{/* <TestSection /> */}
<FilesTable />
</div>
+3
View File
@@ -66,6 +66,7 @@ export async function fileUpload(messageBody: initMessageBody) {
export async function cancelUpload(udloadId: string) {
try {
console.log('cancelUpload start');
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
method: 'POST',
body: JSON.stringify({
@@ -93,6 +94,7 @@ export async function cancelUpload(udloadId: string) {
};
if (parsed.message_desc === 'Upload cancelled successfully') {
console.log('cancelUpload done');
return parsed.message_body;
} else {
throw parsed;
@@ -101,6 +103,7 @@ export async function cancelUpload(udloadId: string) {
throw (`${response.status}`);
}
} catch (error) {
console.log('cancelUpload error');
return error;
}
}
+42 -29
View File
@@ -1,10 +1,10 @@
'use client'
import React, { useRef, useState, useCallback, ChangeEvent, DragEvent } from 'react';
import { useRef, useState, useCallback, ChangeEvent, DragEvent, useEffect } from 'react';
import { IconShieldAdd } from '@/app/ui/icons/icons';
import { fileUpload, cancelUpload, chunkUpload, checkChunkStatus } from '@/app/actions/file-upload';
import { useTranslations } from 'next-intl';
import { useNavigationBlocker } from '@/app/hooks/useNavigationBlocker';
interface SelectedFile {
file: File;
preview: string;
@@ -94,7 +94,6 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile
const ALLOWED_EXTENSIONS: string[] = allowedExtensions;
const validateFile = (file: File): { isValid: boolean; errorMessage?: string } => {
if (!SUPPORTED_FORMATS.includes(file.type as string)) {
const extension = file.name.split('.').pop()?.toLowerCase();
@@ -308,6 +307,42 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile
}
}
useEffect(() => {
// Обработка закрытия вкладки
// Тут можно попробовать изучить вопрос с navigator.sendBeacon('', blob)
// он нужен для того что бы послать запрос на бек при закрытии вкладки,
// но у него есть свои особенности и стандартные запросы не работают.
const handleUnload = () => {
if (uploadId) {
//const data = JSON.stringify({ uploadId, reason: 'tab_closed' });
//const blob = new Blob([data], { type: 'application/json' });
//navigator.sendBeacon('/api/v1/data/cancel', blob);
console.log('sendBeacon');
}
};
if (uploadId) {
window.addEventListener('unload', handleUnload);
}
return () => {
window.removeEventListener('unload', handleUnload);
};
}, [uploadId]);
useNavigationBlocker({
shouldBlock: !!uploadId,
message: t('have-unsaved-changes'),
onConfirm: async () => {
console.log('User confirmed navigation');
if (uploadId) {
await cancelUpload(uploadId);
}
},
onCancel: () => {
console.log('User cancelled navigation');
}
});
return (
<div className="upload-section">
<h3>
@@ -354,14 +389,16 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile
{t('select-files-to-protect')}
</button>
{fileType === "IMAGE" && (
<p className="description">
Разрешение изображение: 100x100 - 10000x10000
</p>
)}
<p className="description">
Размер файла: до 20 МБ
Размер файла: до {(maxFileSize / 1024 / 1024)} МБ
</p>
<p className="description">
Формат файла: JPG, PNG, GIF, BMP (WEBP НЕ поддерживается)
Формат файла: {ALLOWED_EXTENSIONS.join(', ')}
</p>
{error && (
@@ -411,17 +448,6 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile
/>
)}
{fileType === 'VIDEO' && (
<video
src={selectedFile.preview}
className="uploaded-image"
controls
muted
onError={() => setError('Не удалось загрузить превью видео')}
onLoadedData={() => console.log('Видео загружено')}
/>
)}
<div className="mt-4 flex gap-3 justify-center relative">
<button
className="btn btn-cancel"
@@ -440,19 +466,6 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile
>
{t('upload-file')}
</button>
<button
className="btn btn-cancel"
type="button"
onClick={
async () => {
if (uploadId) {
await checkChunkStatus(uploadId);
}
}
}
>
dev-test-button
</button>
{uploadProgress !== 0 && (
<div
className={`absolute top-5 right-5 font-medium ${isFileUploaded ? "text-green-600" : ""}`}
+81
View File
@@ -0,0 +1,81 @@
'use client';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
interface UseNavigationBlockerProps {
shouldBlock: boolean;
message?: string;
onConfirm?: () => void;
onCancel?: () => void;
}
export const useNavigationBlocker = ({
shouldBlock,
message,
onConfirm,
onCancel
}: UseNavigationBlockerProps) => {
const router = useRouter();
useEffect(() => {
if (!shouldBlock) return;
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
e.preventDefault();
e.returnValue = message;
return message;
};
const handleClick = (e: MouseEvent) => {
const target = e.target as HTMLElement;
const link = target.closest('a');
if (link && link.href) {
const currentUrl = window.location.href;
if (link.href !== currentUrl && !link.href.startsWith('#')) {
e.preventDefault();
e.stopPropagation();
const confirmed = window.confirm(message);
if (confirmed) {
onConfirm?.();
window.location.href = link.href;
} else {
onCancel?.();
}
}
}
};
window.addEventListener('beforeunload', handleBeforeUnload);
document.addEventListener('click', handleClick, true);
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
document.removeEventListener('click', handleClick, true);
};
}, [shouldBlock, message, onConfirm, onCancel]);
useEffect(() => {
if (!shouldBlock) return;
const originalPush = router.push;
router.push = async (...args) => {
const confirmed = window.confirm(message);
if (confirmed) {
onConfirm?.();
return originalPush.apply(router, args);
} else {
onCancel?.();
return Promise.resolve(false);
}
};
return () => {
router.push = originalPush;
};
}, [shouldBlock, message, router, onConfirm, onCancel]);
};
+2 -1
View File
@@ -128,7 +128,8 @@
"select-files-to-protect": "Select files to protect",
"upload-for-protection": "Upload {fileType} for protection",
"image": "image",
"file-has-no-extension": "File has no extension"
"file-has-no-extension": "File has no extension",
"have-unsaved-changes": "You have unsaved changes. Are you sure you want to delete this page?"
},
"Login-register-form": {
"and": "and",
+2 -1
View File
@@ -128,7 +128,8 @@
"select-files-to-protect": "Выбрать файлы для защиты",
"upload-for-protection": "Загрузить {fileType} для защиты",
"image": "изображений",
"file-has-no-extension": "Файл не имеет расширения"
"file-has-no-extension": "Файл не имеет расширения",
"have-unsaved-changes": "У вас есть несохраненные изменения. Вы уверены, что хотите покинуть страницу?"
},
"Login-register-form": {
"and": "и",