- Restructured docker-compose for Nginx Proxy Manager (no custom nginx) - Added domain wordly.art configuration - Added Prometheus + Grafana monitoring stack with pre-configured dashboards - Added PostgreSQL backup script to NAS (daily/weekly/monthly rotation) - Added alert rules for backend, system, and Docker metrics - Updated deployment guide for NPM + IONOS DNS homelab setup - Added marketing plan document - PDF translator and watermark support - Enhanced middleware, routes, and translator modules Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
89 lines
2.3 KiB
TypeScript
89 lines
2.3 KiB
TypeScript
import { useState, useCallback } from 'react';
|
|
import type { UseFileUploadReturn } from './types';
|
|
|
|
const ACCEPTED_EXTENSIONS = ['xlsx', 'docx', 'pptx', 'pdf'];
|
|
const MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
|
|
export const ERROR_MESSAGES = {
|
|
INVALID_FORMAT: 'Format non supporté. Formats acceptés : .xlsx, .docx, .pptx, .pdf',
|
|
FILE_TOO_LARGE: 'Fichier trop volumineux (max 50 MB)',
|
|
} as const;
|
|
|
|
export function useFileUpload(): UseFileUploadReturn {
|
|
const [file, setFile] = useState<File | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [isDragOver, setIsDragOver] = useState(false);
|
|
|
|
const validateFile = useCallback((file: File): string | null => {
|
|
const ext = file.name.split('.').pop()?.toLowerCase();
|
|
|
|
if (!ext || !ACCEPTED_EXTENSIONS.includes(ext)) {
|
|
return ERROR_MESSAGES.INVALID_FORMAT;
|
|
}
|
|
|
|
if (file.size > MAX_FILE_SIZE) {
|
|
return ERROR_MESSAGES.FILE_TOO_LARGE;
|
|
}
|
|
|
|
return null;
|
|
}, []);
|
|
|
|
const handleDrop = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault();
|
|
setIsDragOver(false);
|
|
|
|
const droppedFile = e.dataTransfer.files[0];
|
|
if (droppedFile) {
|
|
const validationError = validateFile(droppedFile);
|
|
if (validationError) {
|
|
setError(validationError);
|
|
setFile(null);
|
|
} else {
|
|
setFile(droppedFile);
|
|
setError(null);
|
|
}
|
|
}
|
|
}, [validateFile]);
|
|
|
|
const handleDragOver = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault();
|
|
setIsDragOver(true);
|
|
}, []);
|
|
|
|
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault();
|
|
setIsDragOver(false);
|
|
}, []);
|
|
|
|
const handleFileSelect = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const selected = e.target.files?.[0];
|
|
if (selected) {
|
|
const validationError = validateFile(selected);
|
|
if (validationError) {
|
|
setError(validationError);
|
|
setFile(null);
|
|
} else {
|
|
setFile(selected);
|
|
setError(null);
|
|
}
|
|
}
|
|
}, [validateFile]);
|
|
|
|
const removeFile = useCallback(() => {
|
|
setFile(null);
|
|
setError(null);
|
|
setIsDragOver(false);
|
|
}, []);
|
|
|
|
return {
|
|
file,
|
|
error,
|
|
isDragOver,
|
|
handleDrop,
|
|
handleDragOver,
|
|
handleDragLeave,
|
|
handleFileSelect,
|
|
removeFile,
|
|
};
|
|
}
|