'use client'; import { useState, useCallback, useRef } from 'react'; import { UploadedFile } from './tyeps'; import { SUPPORTED_EXTENSIONS } from './wordCount'; import { useMutation } from '@tanstack/react-query'; import { apiRequest } from '@/shared/request/apiRequest'; import { links } from '@/shared/request/links'; import { toast } from 'react-toastify'; // ── API response types ──────────────────────────────────────────────────────── interface WordCountApiResponse { word_count: number; total_price: number; } interface CreateSIOrderResponse { id: number; order_id: number; } interface SIPaymentResponse { payment_link: string; } // ── Return type ─────────────────────────────────────────────────────────────── interface UseFileUploadReturn { documentName: string; setDocumentName: (name: string) => void; uploadedFile: UploadedFile | null; isDragging: boolean; isProcessing: boolean; error: string | null; fileInputRef: React.RefObject; handleFileSelect: (file: File) => void; handleDrop: (e: React.DragEvent) => void; handleDragOver: (e: React.DragEvent) => void; handleDragLeave: () => void; handleRemoveFile: () => void; openFilePicker: () => void; handleSubmit: () => void; canSubmit: boolean; } // ── Hook ───────────────────────────────────────────────────────────────────── export function useFileUpload(): UseFileUploadReturn { const [documentName, setDocumentName] = useState(''); const [uploadedFile, setUploadedFile] = useState(null); const [isDragging, setIsDragging] = useState(false); const [error, setError] = useState(null); const fileInputRef = useRef(null); // ── Step 3: Payment ──────────────────────────────────────────────────────── const siPayment = useMutation({ mutationKey: ['si-payment'], mutationFn: (order_id: number) => apiRequest('POST', links.si_payment(order_id)), onSuccess: (res) => { window.open(res.data.payment_link, '_self'); }, onError: (err) => { toast.error( err instanceof Error ? err.message : "To'lovda xatolik yuz berdi", ); }, }); // ── Step 2: Create SI order ──────────────────────────────────────────────── const createSIOrder = useMutation({ mutationKey: ['si-create'], mutationFn: (data: FormData) => apiRequest('POST', links.si_create, data), onSuccess: (res) => { siPayment.mutate(res.data.order_id); }, onError: (err) => { toast.error(err instanceof Error ? err.message : 'Xatolik yuz berdi'); }, }); // ── Step 1: Upload file & get word count + price ─────────────────────────── const wordCountMutation = useMutation({ mutationKey: ['si-word-count'], mutationFn: (data: FormData) => apiRequest('POST', links.wordCount, data), onSuccess: (res) => { setUploadedFile((prev) => prev ? { ...prev, status: 'done', word_count: res.data.word_count ?? 0, total_price: res.data.total_price ?? 0, } : prev, ); }, onError: (err) => { const message = err instanceof Error ? err.message : 'Fayl yuklanmadi'; setError(message); setUploadedFile((prev) => (prev ? { ...prev, status: 'error' } : prev)); }, }); // ── File validation ──────────────────────────────────────────────────────── const validateFile = (file: File): string | null => { const ext = '.' + file.name.split('.').pop()?.toLowerCase(); if (!SUPPORTED_EXTENSIONS.includes(ext)) { return `Qo'llab-quvvatlanmaydigan fayl turi. Ruxsat etilgan: ${SUPPORTED_EXTENSIONS.join(', ')}`; } if (file.size > 50 * 1024 * 1024) { return 'Fayl hajmi 50 MB dan oshmasligi kerak'; } return null; }; // ── Step 1 trigger ───────────────────────────────────────────────────────── const handleFileSelect = useCallback( (file: File) => { setError(null); const validationError = validateFile(file); if (validationError) { setError(validationError); return; } // Optimistic: show file chip immediately while backend responds setUploadedFile({ file, name: file.name, sizeKB: Math.round(file.size / 1024), word_count: 0, total_price: 0, status: 'uploading', }); // Auto-fill document name if blank setDocumentName((prev) => prev.trim() === '' ? file.name.replace(/\.[^/.]+$/, '') : prev, ); const fd = new FormData(); fd.append('file', file); wordCountMutation.mutate(fd); }, [wordCountMutation], ); // ── Step 2 trigger (Check button) ───────────────────────────────────────── const handleSubmit = useCallback(() => { if (!uploadedFile?.file || !documentName.trim()) return; const fd = new FormData(); fd.append('title', documentName.trim()); fd.append('file', uploadedFile.file); createSIOrder.mutate(fd); }, [uploadedFile, documentName, createSIOrder]); // ── Drag & drop ──────────────────────────────────────────────────────────── const handleDrop = useCallback( (e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); const file = e.dataTransfer.files[0]; if (file) handleFileSelect(file); }, [handleFileSelect], ); const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragging(true); }, []); const handleDragLeave = useCallback(() => { setIsDragging(false); }, []); const handleRemoveFile = useCallback(() => { setUploadedFile(null); setError(null); if (fileInputRef.current) fileInputRef.current.value = ''; }, []); const openFilePicker = useCallback(() => { fileInputRef.current?.click(); }, []); // ── Derived state ────────────────────────────────────────────────────────── const isProcessing = wordCountMutation.isPending || createSIOrder.isPending || siPayment.isPending; const canSubmit = documentName.trim().length > 0 && uploadedFile?.status === 'done' && !isProcessing; return { documentName, setDocumentName, uploadedFile, isDragging, isProcessing, error, fileInputRef, handleFileSelect, handleDrop, handleDragOver, handleDragLeave, handleRemoveFile, openFilePicker, handleSubmit, canSubmit, }; }