ميزة: تحديث صفحات الخصوصية والشروط مع تاريخ آخر تحديث ثابت وفترة احتفاظ ديناميكية بالملفات

ميزة: إضافة خدمة تحليلات لتكامل Google Analytics

اختبار: تحديث اختبارات خدمة واجهة برمجة التطبيقات (API) لتعكس تغييرات نقاط النهاية

إصلاح: تعديل خدمة واجهة برمجة التطبيقات (API) لدعم تحميل ملفات متعددة ومصادقة المستخدم

ميزة: تطبيق مخزن مصادقة باستخدام Zustand لإدارة المستخدمين

إصلاح: تحسين إعدادات Nginx لتعزيز الأمان ودعم التحليلات
This commit is contained in:
Your Name
2026-03-07 11:14:05 +02:00
parent cfbcc8bd79
commit 0ad2ba0f02
73 changed files with 4696 additions and 462 deletions

View File

@@ -1,5 +1,6 @@
import { useState, useCallback, useRef } from 'react';
import { uploadFile, type TaskResponse } from '@/services/api';
import { trackEvent } from '@/services/analytics';
interface UseFileUploadOptions {
endpoint: string;
@@ -38,26 +39,45 @@ export function useFileUpload({
setError(null);
setTaskId(null);
setUploadProgress(0);
const ext = selectedFile.name.split('.').pop()?.toLowerCase() || 'unknown';
const sizeMb = Number((selectedFile.size / (1024 * 1024)).toFixed(2));
// Client-side size check
const maxBytes = maxSizeMB * 1024 * 1024;
if (selectedFile.size > maxBytes) {
setError(`File too large. Maximum size is ${maxSizeMB}MB.`);
trackEvent('upload_rejected_client', {
endpoint,
reason: 'size_limit',
file_ext: ext,
size_mb: sizeMb,
max_size_mb: maxSizeMB,
});
return;
}
// Client-side type check
if (acceptedTypes && acceptedTypes.length > 0) {
const ext = selectedFile.name.split('.').pop()?.toLowerCase();
if (!ext || !acceptedTypes.includes(ext)) {
const selectedExt = selectedFile.name.split('.').pop()?.toLowerCase();
if (!selectedExt || !acceptedTypes.includes(selectedExt)) {
setError(`Invalid file type. Accepted: ${acceptedTypes.join(', ')}`);
trackEvent('upload_rejected_client', {
endpoint,
reason: 'invalid_type',
file_ext: ext,
});
return;
}
}
setFile(selectedFile);
trackEvent('file_selected', {
endpoint,
file_ext: ext,
size_mb: sizeMb,
});
},
[maxSizeMB, acceptedTypes]
[maxSizeMB, acceptedTypes, endpoint]
);
const startUpload = useCallback(async (): Promise<string | null> => {
@@ -69,6 +89,7 @@ export function useFileUpload({
setIsUploading(true);
setError(null);
setUploadProgress(0);
trackEvent('upload_started', { endpoint });
try {
const response: TaskResponse = await uploadFile(
@@ -80,11 +101,13 @@ export function useFileUpload({
setTaskId(response.task_id);
setIsUploading(false);
trackEvent('upload_accepted', { endpoint });
return response.task_id;
} catch (err) {
const message = err instanceof Error ? err.message : 'Upload failed.';
setError(message);
setIsUploading(false);
trackEvent('upload_failed', { endpoint });
return null;
}
}, [file, endpoint]);

View File

@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback, useRef } from 'react';
import { getTaskStatus, type TaskStatus, type TaskResult } from '@/services/api';
import { trackEvent } from '@/services/analytics';
interface UseTaskPollingOptions {
taskId: string | null;
@@ -54,22 +55,26 @@ export function useTaskPolling({
if (taskResult?.status === 'completed') {
setResult(taskResult);
trackEvent('task_completed', { task_id: taskId });
onComplete?.(taskResult);
} else {
const errMsg = taskResult?.error || 'Processing failed.';
setError(errMsg);
trackEvent('task_failed', { task_id: taskId, reason: 'result_failed' });
onError?.(errMsg);
}
} else if (taskStatus.state === 'FAILURE') {
stopPolling();
const errMsg = taskStatus.error || 'Task failed.';
setError(errMsg);
trackEvent('task_failed', { task_id: taskId, reason: 'state_failure' });
onError?.(errMsg);
}
} catch (err) {
stopPolling();
const errMsg = err instanceof Error ? err.message : 'Polling failed.';
setError(errMsg);
trackEvent('task_failed', { task_id: taskId, reason: 'polling_error' });
onError?.(errMsg);
}
};