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

ميزة: إضافة خدمة تحليلات لتكامل 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

@@ -6,6 +6,7 @@ import AdSlot from '@/components/layout/AdSlot';
import ProgressBar from '@/components/shared/ProgressBar';
import DownloadButton from '@/components/shared/DownloadButton';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { uploadFiles } from '@/services/api';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
@@ -61,20 +62,7 @@ export default function ImagesToPdf() {
setError(null);
try {
const formData = new FormData();
files.forEach((f) => formData.append('files', f));
const response = await fetch('/api/pdf-tools/images-to-pdf', {
method: 'POST',
body: formData,
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Upload failed.');
}
const data = await uploadFiles('/pdf-tools/images-to-pdf', files, 'files');
setTaskId(data.task_id);
setPhase('processing');
} catch (err) {

View File

@@ -7,7 +7,7 @@ import ProgressBar from '@/components/shared/ProgressBar';
import DownloadButton from '@/components/shared/DownloadButton';
import AdSlot from '@/components/layout/AdSlot';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { uploadFile, type TaskResponse } from '@/services/api';
import { uploadFiles } from '@/services/api';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
@@ -62,20 +62,7 @@ export default function MergePdf() {
setError(null);
try {
const formData = new FormData();
files.forEach((f) => formData.append('files', f));
const response = await fetch('/api/pdf-tools/merge', {
method: 'POST',
body: formData,
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Upload failed.');
}
const data = await uploadFiles('/pdf-tools/merge', files, 'files');
setTaskId(data.task_id);
setPhase('processing');
} catch (err) {

View File

@@ -4,14 +4,6 @@ import { Helmet } from 'react-helmet-async';
import {
PenLine,
Save,
Download,
Undo2,
Redo2,
PlusCircle,
Trash2,
RotateCw,
FileOutput,
PanelLeft,
Share2,
ShieldCheck,
Info,
@@ -24,6 +16,7 @@ import { useFileUpload } from '@/hooks/useFileUpload';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
import { TOOL_LIMITS_MB } from '@/config/toolLimits';
export default function PdfEditor() {
const { t } = useTranslation();
@@ -40,7 +33,7 @@ export default function PdfEditor() {
reset,
} = useFileUpload({
endpoint: '/compress/pdf',
maxSizeMB: 200,
maxSizeMB: TOOL_LIMITS_MB.pdf,
acceptedTypes: ['pdf'],
extraData: { quality: 'high' },
});
@@ -77,16 +70,6 @@ export default function PdfEditor() {
url: `${window.location.origin}/tools/pdf-editor`,
});
const toolbarButtons = [
{ icon: Undo2, label: t('tools.pdfEditor.undo'), shortcut: 'Ctrl+Z' },
{ icon: Redo2, label: t('tools.pdfEditor.redo'), shortcut: 'Ctrl+Y' },
{ icon: PlusCircle, label: t('tools.pdfEditor.addPage') },
{ icon: Trash2, label: t('tools.pdfEditor.deletePage') },
{ icon: RotateCw, label: t('tools.pdfEditor.rotate') },
{ icon: FileOutput, label: t('tools.pdfEditor.extractPage') },
{ icon: PanelLeft, label: t('tools.pdfEditor.thumbnails') },
];
return (
<>
<Helmet>
@@ -117,7 +100,7 @@ export default function PdfEditor() {
onFileSelect={selectFile}
file={file}
accept={{ 'application/pdf': ['.pdf'] }}
maxSizeMB={200}
maxSizeMB={TOOL_LIMITS_MB.pdf}
isUploading={isUploading}
uploadProgress={uploadProgress}
error={uploadError}
@@ -145,28 +128,6 @@ export default function PdfEditor() {
</ol>
</div>
{/* Toolbar Preview */}
<div className="rounded-2xl bg-white p-4 ring-1 ring-slate-200 dark:bg-slate-800 dark:ring-slate-700">
<p className="mb-3 text-xs font-medium uppercase tracking-wide text-slate-400 dark:text-slate-500">
{t('tools.pdfEditor.thumbnails')}
</p>
<div className="flex flex-wrap gap-2">
{toolbarButtons.map((btn) => {
const Icon = btn.icon;
return (
<div
key={btn.label}
className="flex items-center gap-1.5 rounded-lg bg-slate-50 px-3 py-2 text-xs font-medium text-slate-600 ring-1 ring-slate-200 dark:bg-slate-700 dark:text-slate-300 dark:ring-slate-600"
title={btn.shortcut ? `${btn.label} (${btn.shortcut})` : btn.label}
>
<Icon className="h-4 w-4" />
<span className="hidden sm:inline">{btn.label}</span>
</div>
);
})}
</div>
</div>
{/* Upload Button */}
<button
onClick={handleUpload}

View File

@@ -4,6 +4,7 @@ import { Helmet } from 'react-helmet-async';
import { GitBranch } from 'lucide-react';
import AdSlot from '@/components/layout/AdSlot';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { startTask, uploadFile } from '@/services/api';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
@@ -65,8 +66,8 @@ export default function PdfFlowchart() {
setUploading(false);
}
},
onError: () => {
setError(taskError || t('common.error'));
onError: (err) => {
setError(err || t('common.error'));
setStep(0);
setUploading(false);
},
@@ -86,16 +87,7 @@ export default function PdfFlowchart() {
setError(null);
try {
const formData = new FormData();
formData.append('file', file);
const res = await fetch('/api/flowchart/extract', {
method: 'POST',
body: formData,
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || 'Upload failed.');
const data = await uploadFile('/flowchart/extract', file);
setTaskId(data.task_id);
} catch (err) {
setError(err instanceof Error ? err.message : 'Upload failed.');
@@ -108,11 +100,7 @@ export default function PdfFlowchart() {
setError(null);
try {
const res = await fetch('/api/flowchart/extract-sample', {
method: 'POST',
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || 'Sample failed.');
const data = await startTask('/flowchart/extract-sample');
setTaskId(data.task_id);
} catch (err) {
setError(err instanceof Error ? err.message : 'Sample failed.');

View File

@@ -2,6 +2,7 @@ import { useState, useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Send, Bot, User, Sparkles, X, Loader2 } from 'lucide-react';
import type { Flowchart, ChatMessage } from './types';
import api from '@/services/api';
interface FlowChatProps {
flow: Flowchart;
@@ -42,16 +43,12 @@ export default function FlowChat({ flow, onClose, onFlowUpdate }: FlowChatProps)
setIsTyping(true);
try {
const res = await fetch('/api/flowchart/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
const res = await api.post('/flowchart/chat', {
message: text,
flow_id: flow.id,
flow_data: flow,
}),
});
const data = await res.json();
const data = res.data;
const assistantMsg: ChatMessage = {
id: (Date.now() + 1).toString(),