diff --git a/src/app/[locale]/plagat/page.tsx b/src/app/[locale]/plagat/page.tsx
index 130c2f5..f4920e8 100644
--- a/src/app/[locale]/plagat/page.tsx
+++ b/src/app/[locale]/plagat/page.tsx
@@ -1,5 +1,5 @@
-import { PlagiarismCheckForm } from '@/widgets/fileUpload/ui/Plagiraismcheckform';
import { HistoryPage } from '@/widgets/history';
+import { PlagiarismCheckForm } from '@/widgets/plagiatCheck/ui/Plagiraismcheckform';
export default function Page() {
return (
diff --git a/src/features/modals/paymentModal/lib/types.ts b/src/features/modals/paymentModal/lib/types.ts
index 9567007..9bacd42 100644
--- a/src/features/modals/paymentModal/lib/types.ts
+++ b/src/features/modals/paymentModal/lib/types.ts
@@ -14,7 +14,6 @@ export interface PriceCalculate {
service_fee: number;
discount?: number;
total_price: number;
- currency: string;
}
export interface PaymentModalProps {
diff --git a/src/features/modals/paymentModal/ui/Pricesummary.tsx b/src/features/modals/paymentModal/ui/Pricesummary.tsx
index 65c57c4..ed11757 100644
--- a/src/features/modals/paymentModal/ui/Pricesummary.tsx
+++ b/src/features/modals/paymentModal/ui/Pricesummary.tsx
@@ -59,21 +59,21 @@ export const PriceSummary = ({
{priceCalculate.discount && (
)}
diff --git a/src/features/modals/siModal/ui/fileUploadModal.tsx b/src/features/modals/siModal/ui/fileUploadModal.tsx
index a6daca2..467fd5e 100644
--- a/src/features/modals/siModal/ui/fileUploadModal.tsx
+++ b/src/features/modals/siModal/ui/fileUploadModal.tsx
@@ -2,7 +2,7 @@
import { useEffect } from 'react';
import { X } from 'lucide-react';
-import { calculatePrice, DEFAULT_PRICING, formatPrice } from '../utils/pricing';
+import { DEFAULT_PRICING, formatPrice } from '../utils/pricing';
import { FileUploadModalProps } from '../utils/tyeps';
import { useFileUpload } from '../utils/useFileUpload';
import { SUPPORTED_EXTENSIONS } from '../utils/wordCount';
@@ -28,6 +28,8 @@ export function FileUploadModal({
handleDragLeave,
handleRemoveFile,
openFilePicker,
+ canSubmit,
+ handleSubmit,
} = useFileUpload();
// Close on Escape key
@@ -48,17 +50,8 @@ export function FileUploadModal({
if (!isOpen) return null;
- const canSubmit =
- documentName.trim().length > 0 &&
- uploadedFile?.status === 'done' &&
- !isProcessing;
-
- const wordCount = uploadedFile?.wordCount ?? 0;
- const totalPrice = calculatePrice(wordCount, pricing);
-
- const handleSubmit = () => {
- if (!canSubmit || !uploadedFile) return;
- };
+ const wordCount = uploadedFile?.word_count ?? 0;
+ const totalPrice = uploadedFile?.total_price ?? 0;
return (
// Backdrop
diff --git a/src/features/modals/siModal/utils/pricing.ts b/src/features/modals/siModal/utils/pricing.ts
index 5cdfebf..db40e75 100644
--- a/src/features/modals/siModal/utils/pricing.ts
+++ b/src/features/modals/siModal/utils/pricing.ts
@@ -5,14 +5,6 @@ const DEFAULT_PRICING: PricingConfig = {
minimumPayment: 10000, // 10 000 so'm minimum
};
-export function calculatePrice(
- wordCount: number,
- config: PricingConfig = DEFAULT_PRICING,
-): number {
- const calculated = wordCount * config.pricePerWord;
- return Math.max(calculated, config.minimumPayment);
-}
-
export function formatPrice(amount: number): string {
return new Intl.NumberFormat('uz-UZ').format(amount) + " so'm";
}
diff --git a/src/features/modals/siModal/utils/tyeps.ts b/src/features/modals/siModal/utils/tyeps.ts
index 2a8aa44..c6a4eb8 100644
--- a/src/features/modals/siModal/utils/tyeps.ts
+++ b/src/features/modals/siModal/utils/tyeps.ts
@@ -2,7 +2,8 @@ export interface UploadedFile {
file: File;
name: string;
sizeKB: number;
- wordCount: number;
+ word_count: number;
+ total_price: number;
status: 'uploading' | 'done' | 'error';
}
diff --git a/src/features/modals/siModal/utils/useFileUpload.ts b/src/features/modals/siModal/utils/useFileUpload.ts
index b16fa56..b45e20c 100644
--- a/src/features/modals/siModal/utils/useFileUpload.ts
+++ b/src/features/modals/siModal/utils/useFileUpload.ts
@@ -2,12 +2,30 @@
import { useState, useCallback, useRef } from 'react';
import { UploadedFile } from './tyeps';
-import { countWordsFromFile, SUPPORTED_EXTENSIONS } from './wordCount';
+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;
@@ -16,92 +34,135 @@ interface UseFileUploadReturn {
isProcessing: boolean;
error: string | null;
fileInputRef: React.RefObject;
- handleFileSelect: (file: File) => Promise;
+ 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 [isProcessing, setIsProcessing] = useState(false);
const [error, setError] = useState(null);
const fileInputRef = useRef(null);
- const wordCount = useMutation({
- mutationFn: (data: FormData) => apiRequest('POST', links.si_create, data),
+ // ── Step 3: Payment ────────────────────────────────────────────────────────
+ const siPayment = useMutation({
+ mutationKey: ['si-payment'],
+ mutationFn: (order_id: number) =>
+ apiRequest('POST', links.si_payment(order_id)),
onSuccess: (res) => {
- console.log(res);
+ window.open(res.data.payment_link, '_self');
},
onError: (err) => {
- console.log(err instanceof Error ? err.message : 'Unknown error');
- toast.error(err instanceof Error ? err.message : 'Unknown error');
+ 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 `Unsupported file type. Allowed: ${SUPPORTED_EXTENSIONS.join(', ')}`;
+ return `Qo'llab-quvvatlanmaydigan fayl turi. Ruxsat etilgan: ${SUPPORTED_EXTENSIONS.join(', ')}`;
}
if (file.size > 50 * 1024 * 1024) {
- return 'File size must be less than 50 MB';
+ return 'Fayl hajmi 50 MB dan oshmasligi kerak';
}
return null;
};
- const handleFileSelect = useCallback(async (file: File) => {
- setError(null);
+ // ── Step 1 trigger ─────────────────────────────────────────────────────────
- const validationError = validateFile(file);
- if (validationError) {
- setError(validationError);
- return;
- }
+ const handleFileSelect = useCallback(
+ (file: File) => {
+ setError(null);
- // Optimistic UI: show file immediately
- const optimistic: UploadedFile = {
- file,
- name: file.name,
- sizeKB: Math.round(file.size / 1024),
- wordCount: 0,
- status: 'uploading',
- };
- setUploadedFile(optimistic);
- setIsProcessing(true);
+ const validationError = validateFile(file);
+ if (validationError) {
+ setError(validationError);
+ return;
+ }
- // Auto-fill document name if empty
- setDocumentName((prev) =>
- prev.trim() === '' ? file.name.replace(/\.[^/.]+$/, '') : prev,
- );
-
- // Count words on the frontend (no round-trip needed)
- const result = await countWordsFromFile(file);
-
- if (result.error) {
- setError(result.error);
- setUploadedFile({ ...optimistic, status: 'error', wordCount: 0 });
- } else {
+ // Optimistic: show file chip immediately while backend responds
setUploadedFile({
- ...optimistic,
- status: 'done',
- wordCount: result.count,
+ file,
+ name: file.name,
+ sizeKB: Math.round(file.size / 1024),
+ word_count: 0,
+ total_price: 0,
+ status: 'uploading',
});
- }
- console.log('running');
- if (!file) return;
- console.log('running inner');
+
+ // 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', file.name.replace(/\.[^/.]+$/, ''));
- fd.append('file', file);
- wordCount.mutate(fd);
- console.log('stop');
- setIsProcessing(false);
- }, []);
+ fd.append('title', documentName.trim());
+ fd.append('file', uploadedFile.file);
+ createSIOrder.mutate(fd);
+ }, [uploadedFile, documentName, createSIOrder]);
+
+ // ── Drag & drop ────────────────────────────────────────────────────────────
const handleDrop = useCallback(
(e: React.DragEvent) => {
@@ -132,6 +193,18 @@ export function useFileUpload(): UseFileUploadReturn {
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,
@@ -146,5 +219,7 @@ export function useFileUpload(): UseFileUploadReturn {
handleDragLeave,
handleRemoveFile,
openFilePicker,
+ handleSubmit,
+ canSubmit,
};
}
diff --git a/src/shared/config/i18n/messages/en.json b/src/shared/config/i18n/messages/en.json
index 8b03f3a..1cb89c5 100644
--- a/src/shared/config/i18n/messages/en.json
+++ b/src/shared/config/i18n/messages/en.json
@@ -231,7 +231,7 @@
"paymentMethod": "Payment Method",
"security": "Secured by Payme · SSL encrypted",
"serviceFee": "Service fee",
- "certificateLabel": "Certificate",
+ "discountLabel": "Discount",
"total": "Total",
"paymentRequired": "Payment not completed",
"connecting": "Connecting to Payme…",
diff --git a/src/shared/config/i18n/messages/ru.json b/src/shared/config/i18n/messages/ru.json
index a18b0ba..45a733f 100644
--- a/src/shared/config/i18n/messages/ru.json
+++ b/src/shared/config/i18n/messages/ru.json
@@ -230,7 +230,7 @@
"paymentMethod": "Способ оплаты",
"security": "Защищено Payme · SSL шифрование",
"serviceFee": "Стоимость услуги",
- "certificateLabel": "Сертификат",
+ "discountLabel": "Скидка",
"total": "Итого",
"paymentRequired": "Оплата не произведена",
"connecting": "Подключение к Payme…",
diff --git a/src/shared/config/i18n/messages/uz.d.json.ts b/src/shared/config/i18n/messages/uz.d.json.ts
index 3ce275f..0b80fbe 100644
--- a/src/shared/config/i18n/messages/uz.d.json.ts
+++ b/src/shared/config/i18n/messages/uz.d.json.ts
@@ -234,7 +234,7 @@ declare const messages: {
paymentMethod: "To'lov usuli";
security: 'Payme tomonidan himoyalangan · SSL shifrlash';
serviceFee: "Xizmat to'lovi";
- certificateLabel: 'Sertifikat';
+ discountLabel: 'Chegirma';
total: 'Jami';
paymentRequired: "To'lov qilinmagan";
connecting: 'Paymega ulanmoqda…';
diff --git a/src/shared/config/i18n/messages/uz.json b/src/shared/config/i18n/messages/uz.json
index e096022..dd6bb09 100644
--- a/src/shared/config/i18n/messages/uz.json
+++ b/src/shared/config/i18n/messages/uz.json
@@ -231,7 +231,7 @@
"paymentMethod": "To'lov usuli",
"security": "Payme tomonidan himoyalangan · SSL shifrlash",
"serviceFee": "Xizmat to'lovi",
- "certificateLabel": "Sertifikat",
+ "discountLabel": "Chegirma",
"total": "Jami",
"paymentRequired":"To'lov qilinmagan",
"connecting": "Paymega ulanmoqda…",
diff --git a/src/shared/request/links.ts b/src/shared/request/links.ts
index d83b629..36cc386 100644
--- a/src/shared/request/links.ts
+++ b/src/shared/request/links.ts
@@ -9,9 +9,12 @@ export const links = {
`/shared/certificate/${document_id}/pdf/`,
si: '/shared/ai_document/list/',
si_id: (si_id: number) => `/shared/ai_document/list/${si_id}/`,
- si_payment: (document_id: number) => `/shared/ai_document/pay/${document_id}`,
+ si_payment: (document_id: number) =>
+ `/shared/ai_document/pay/${document_id}/`,
si_create: '/shared/ai_document/create/',
document_types: '/shared/document_types/',
pay_history: '/shared/orders/all/',
statistics: '/shared/statistics/',
+ wordCount: '/shared/check_file/',
+ users: '/users/profile/',
};
diff --git a/src/widgets/fileUpload/index.ts b/src/widgets/plagiatCheck/index.ts
similarity index 100%
rename from src/widgets/fileUpload/index.ts
rename to src/widgets/plagiatCheck/index.ts
diff --git a/src/widgets/fileUpload/lib/types.ts b/src/widgets/plagiatCheck/lib/types.ts
similarity index 88%
rename from src/widgets/fileUpload/lib/types.ts
rename to src/widgets/plagiatCheck/lib/types.ts
index 3068939..b710991 100644
--- a/src/widgets/fileUpload/lib/types.ts
+++ b/src/widgets/plagiatCheck/lib/types.ts
@@ -1,5 +1,7 @@
// ─── Domain Types ───────────────────────────────────────────────────────────
+import { PriceCalculate } from '@/features/modals/paymentModal/lib/types';
+
export interface User {
id: string;
firstName: string;
@@ -21,6 +23,11 @@ export interface PlagiarismSubmissionResponse {
certificateUrl?: string;
}
+export interface CheckDocumentRequestResponse extends PriceCalculate {
+ id: number;
+ order_id: number;
+}
+
// ─── Form State Types ────────────────────────────────────────────────────────
export interface PlagiarismFormState {
diff --git a/src/widgets/fileUpload/lib/usePlagiraism.ts b/src/widgets/plagiatCheck/lib/usePlagiraism.ts
similarity index 90%
rename from src/widgets/fileUpload/lib/usePlagiraism.ts
rename to src/widgets/plagiatCheck/lib/usePlagiraism.ts
index d520bbd..6e47545 100644
--- a/src/widgets/fileUpload/lib/usePlagiraism.ts
+++ b/src/widgets/plagiatCheck/lib/usePlagiraism.ts
@@ -1,6 +1,7 @@
'use client';
import { useState, useCallback, useEffect } from 'react';
import {
+ CheckDocumentRequestResponse,
PlagiarismFormErrors,
PlagiarismFormState,
SubmissionState,
@@ -11,6 +12,7 @@ import { useUserPlagiatStore } from '@/shared/zustand/user';
import { useMutation } from '@tanstack/react-query';
import { links } from '@/shared/request/links';
import { apiRequest } from '@/shared/request/apiRequest';
+import { PriceCalculate } from '@/features/modals/paymentModal/lib/types';
// ─── Initial States ──────────────────────────────────────────────────────────
@@ -23,6 +25,12 @@ const INITIAL_FORM: PlagiarismFormState = {
document_type: 'boshqa',
};
+const PRICE: PriceCalculate = {
+ service_fee: 0,
+ discount: 0,
+ total_price: 0,
+};
+
const INITIAL_SUBMISSION: SubmissionState = {
status: 'idle',
error: null,
@@ -51,9 +59,8 @@ export function usePlagiarismForm() {
const [isPaymentOpen, setIsPaymentOpen] = useState(false);
const [submission, setSubmission] =
useState(INITIAL_SUBMISSION);
- // const route = useRouter();
- // const [document_id, setDocument_id] = useState(0);
const [order_id, setOrder_id] = useState(0);
+ const [prices, setPrices] = useState(PRICE);
const checkdocumentRequest = useMutation({
mutationKey: ['plagiarismCheck'],
@@ -61,9 +68,14 @@ export function usePlagiarismForm() {
apiRequest('POST', links.plagiarismCheck, data),
onSuccess: (res) => {
console.log('uploda: ', res);
- const resdata = res.data as { id: number; order_id: number };
+ const resdata = res.data as CheckDocumentRequestResponse;
+ const priceInfo: PriceCalculate = {
+ total_price: resdata?.total_price || 0,
+ discount: resdata?.discount || 0,
+ service_fee: resdata?.service_fee || 0,
+ };
+ setPrices(priceInfo);
console.log('order_id:', resdata.id);
- // setDocument_id(resdata.id);
setOrder_id(resdata.order_id);
setSubmission({ status: 'success', error: null });
setForm(INITIAL_FORM);
@@ -83,7 +95,6 @@ export function usePlagiarismForm() {
onSuccess: (res) => {
console.log('payment res: ', res);
window.open(res.data.payment_link, '_self');
- //route.push(`/${document_id}`);
setIsPaymentOpen(false);
},
onError: (err) => {
@@ -140,7 +151,7 @@ export function usePlagiarismForm() {
fd.append('file', form.file!); // File object — multipart/form-data
fd.append('certificate', String(form.certificate));
fd.append('total_price', '41200');
- fd.append('document_type', form.document_type);
+ fd.append('type', form.document_type);
checkdocumentRequest.mutate(fd);
},
[form],
@@ -177,5 +188,6 @@ export function usePlagiarismForm() {
setIsPaymentOpen,
isPaymentOpen,
setOption,
+ prices,
};
}
diff --git a/src/widgets/fileUpload/lib/userStore.ts b/src/widgets/plagiatCheck/lib/userStore.ts
similarity index 100%
rename from src/widgets/fileUpload/lib/userStore.ts
rename to src/widgets/plagiatCheck/lib/userStore.ts
diff --git a/src/widgets/fileUpload/lib/validation.ts b/src/widgets/plagiatCheck/lib/validation.ts
similarity index 100%
rename from src/widgets/fileUpload/lib/validation.ts
rename to src/widgets/plagiatCheck/lib/validation.ts
diff --git a/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx b/src/widgets/plagiatCheck/ui/Plagiraismcheckform.tsx
similarity index 98%
rename from src/widgets/fileUpload/ui/Plagiraismcheckform.tsx
rename to src/widgets/plagiatCheck/ui/Plagiraismcheckform.tsx
index 7e202b8..5ff04f5 100644
--- a/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx
+++ b/src/widgets/plagiatCheck/ui/Plagiraismcheckform.tsx
@@ -63,6 +63,7 @@ export function PlagiarismCheckForm() {
isPaymentOpen,
setOption,
setIsPaymentOpen,
+ prices,
} = usePlagiarismForm();
return (
@@ -203,12 +204,7 @@ export function PlagiarismCheckForm() {
setIsPaymentOpen(false)}
- price={{
- service_fee: 41200,
- discount: 5200,
- total_price: 36000,
- currency: 'UZS',
- }}
+ price={prices}
onConfirmPayment={handleSubmit}
isLoading={isLoading}
/>
diff --git a/src/widgets/fileUpload/ui/Plagiraismui.tsx b/src/widgets/plagiatCheck/ui/Plagiraismui.tsx
similarity index 100%
rename from src/widgets/fileUpload/ui/Plagiraismui.tsx
rename to src/widgets/plagiatCheck/ui/Plagiraismui.tsx
diff --git a/src/widgets/fileUpload/ui/documentsType.tsx b/src/widgets/plagiatCheck/ui/documentsType.tsx
similarity index 100%
rename from src/widgets/fileUpload/ui/documentsType.tsx
rename to src/widgets/plagiatCheck/ui/documentsType.tsx