From d70360841aa2da8fc37b0164fbd699401485e788 Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Mon, 6 Apr 2026 09:50:48 +0500 Subject: [PATCH 01/29] added new fieldds to file upload and get sertificate components --- public/sitemap.xml | 57 +++++ src/app/sitemap.ts | 2 +- src/widgets/detail/pageDetail.tsx | 6 +- src/widgets/detail/sertifikat.tsx | 67 ------ .../detail/ui/sertificate/modalField.tsx | 37 +++ .../ui/sertificate/sertificateModal.tsx | 219 ++++++++++++++++++ .../detail/ui/sertificate/sertifikat.tsx | 79 +++++++ src/widgets/detail/ui/sertificate/types.ts | 23 ++ .../ui/sertificate/useSertificateModal.ts | 112 +++++++++ src/widgets/fileUpload/lib/types.ts | 3 + src/widgets/fileUpload/lib/usePlagiraism.ts | 8 + .../fileUpload/ui/Plagiraismcheckform.tsx | 37 ++- src/widgets/fileUpload/ui/Plagiraismui.tsx | 2 +- 13 files changed, 578 insertions(+), 74 deletions(-) create mode 100644 public/sitemap.xml delete mode 100644 src/widgets/detail/sertifikat.tsx create mode 100644 src/widgets/detail/ui/sertificate/modalField.tsx create mode 100644 src/widgets/detail/ui/sertificate/sertificateModal.tsx create mode 100644 src/widgets/detail/ui/sertificate/sertifikat.tsx create mode 100644 src/widgets/detail/ui/sertificate/types.ts create mode 100644 src/widgets/detail/ui/sertificate/useSertificateModal.ts diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..1e280d0 --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,57 @@ + + + + https://antiplagiat.uz/uz + 2026-04-04 + daily + 1.0 + + + + + + https://antiplagiat.uz/uz/plagat + 2026-04-04 + weekly + 0.8 + + + + + + https://antiplagiat.uz/ru + 2026-04-04 + daily + 1.0 + + + + + + https://antiplagiat.uz/ru/plagat + 2026-04-04 + weekly + 0.8 + + + + + + https://antiplagiat.uz/en + 2026-04-04 + daily + 1.0 + + + + + + https://antiplagiat.uz/en/plagat + 2026-04-04 + weekly + 0.8 + + + + + \ No newline at end of file diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index ec12d90..9d2e03f 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -4,7 +4,7 @@ const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://antiplagiat.uz'; const LOCALES = ['uz', 'ru', 'en'] as const; // Add your static page slugs here -const STATIC_ROUTES = ['', '/about', '/history', '/contact']; +const STATIC_ROUTES = ['', '/plagat']; export default function sitemap(): MetadataRoute.Sitemap { const entries: MetadataRoute.Sitemap = []; diff --git a/src/widgets/detail/pageDetail.tsx b/src/widgets/detail/pageDetail.tsx index 2880f44..9b214d2 100644 --- a/src/widgets/detail/pageDetail.tsx +++ b/src/widgets/detail/pageDetail.tsx @@ -6,8 +6,8 @@ import { useTranslations } from 'next-intl'; import { useParams } from 'next/navigation'; import { links } from '@/shared/request/links'; import { apiRequest } from '@/shared/request/apiRequest'; -import Sertifikat from './sertifikat'; import PaymentStatus from './paidStatus'; +import Sertifikat from './ui/sertificate/sertifikat'; // ── Types ──────────────────────────────────────────────────────────────────── @@ -365,7 +365,7 @@ export default function DocumentDetailPage({ id }: { id: number }) {
{/* ── Header ── */}
-
+
-
+
{doc.certificate && } {doc.file && ( diff --git a/src/widgets/detail/sertifikat.tsx b/src/widgets/detail/sertifikat.tsx deleted file mode 100644 index 25d40ee..0000000 --- a/src/widgets/detail/sertifikat.tsx +++ /dev/null @@ -1,67 +0,0 @@ -'use client'; -import { useTranslations } from 'next-intl'; -import { FileDown, Loader2 } from 'lucide-react'; -import React, { useState } from 'react'; - -// const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL; -const baseUrl = 'https://api.anti-plagiat.uz/api/v1'; - -export default function Sertifikat({ document_id }: { document_id: number }) { - const t = useTranslations(); - const [loading, setLoading] = useState(false); - - const handleClick = async () => { - setLoading(true); - try { - const url = `${baseUrl}/shared/certificate/${document_id}/pdf/`; - const res = await fetch(url); - const blob = await res.blob(); - const objectUrl = URL.createObjectURL(blob); - - // ✅ window.open o'rniga tag bilan download - const a = document.createElement('a'); - a.href = objectUrl; - a.download = `certificate-${document_id}.pdf`; - a.click(); - - URL.revokeObjectURL(objectUrl); - } finally { - setLoading(false); - } - }; - - // const handleClick = () => { - // const url = `${baseUrl}/documents/${document_id}/pdf/`; - // window.open(url, '_blank'); - // }; - - return ( - - ); -} diff --git a/src/widgets/detail/ui/sertificate/modalField.tsx b/src/widgets/detail/ui/sertificate/modalField.tsx new file mode 100644 index 0000000..3f2305e --- /dev/null +++ b/src/widgets/detail/ui/sertificate/modalField.tsx @@ -0,0 +1,37 @@ +'use client'; + +import { type ReactNode } from 'react'; + +/* ── Field wrapper ─────────────────────────────────────────── */ +interface FieldProps { + htmlFor: string; + icon: ReactNode; + label: string; + children: ReactNode; +} + +export function Field({ htmlFor, icon, label, children }: FieldProps) { + return ( +
+ + {children} +
+ ); +} + +/* ── Shared input class ────────────────────────────────────── */ +export const inputCls = ` + w-full px-3.5 py-2.5 text-[14px] text-slate-800 + bg-slate-50 border border-slate-200 rounded-xl + placeholder:text-slate-400 + focus:outline-none focus:ring-2 focus:ring-emerald-400/40 focus:border-emerald-400 + hover:border-slate-300 + transition-all duration-150 + disabled:opacity-60 disabled:cursor-not-allowed +`.trim(); diff --git a/src/widgets/detail/ui/sertificate/sertificateModal.tsx b/src/widgets/detail/ui/sertificate/sertificateModal.tsx new file mode 100644 index 0000000..29cfcde --- /dev/null +++ b/src/widgets/detail/ui/sertificate/sertificateModal.tsx @@ -0,0 +1,219 @@ +'use client'; + +import { + X, + Award, + User, + FileText, + BookOpen, + Layers, + Loader2, + CheckCircle2, +} from 'lucide-react'; + +import { useCertificateModal } from './useSertificateModal'; +import { Field, inputCls } from './modalField'; +import { DOCUMENT_TYPES, SertificateModalProps } from './types'; + +export default function SertificateModal({ + document_id, + open, + setOpen, +}: SertificateModalProps) { + const { + form, + updateField, + loading, + success, + visible, + isFormValid, + inputRef, + handleSubmit, + handleKeyDown, + handleBackdropClick, + } = useCertificateModal({ document_id, open, setOpen }); + + if (!visible) return null; + + return ( +
+ {/* Backdrop */} +
+ + {/* Modal panel */} +
+ {/* Top accent bar */} +
+ + {/* Header */} +
+
+
+ +
+

+ Sertifikat yaratish +

+
+ +
+ + {/* Divider */} +
+ + {/* Body */} +
+ {/* Full name */} + + } + label="Muallifning to'liq ismi" + > + updateField('fullname', e.target.value)} + disabled={loading || success} + placeholder="Ismingizni kiriting..." + className={inputCls} + /> + + + {/* Document theme */} + + } + label="Hujjat mavzusi" + > + updateField('document_theme', e.target.value)} + disabled={loading || success} + placeholder="Mavzuni kiriting..." + className={inputCls} + /> + + + {/* Document type */} + + } + label="Hujjat turi" + > + + + + {/* Document ID (read-only) */} +
+ +
+ Hujjat ID + + #{document_id} + +
+
+
+ + {/* Footer */} +
+ +
+
+
+ ); +} diff --git a/src/widgets/detail/ui/sertificate/sertifikat.tsx b/src/widgets/detail/ui/sertificate/sertifikat.tsx new file mode 100644 index 0000000..a1fd43c --- /dev/null +++ b/src/widgets/detail/ui/sertificate/sertifikat.tsx @@ -0,0 +1,79 @@ +'use client'; +import { useTranslations } from 'next-intl'; +import { FileDown, Loader2 } from 'lucide-react'; +import React, { useEffect, useState } from 'react'; +import SertificateModal from './sertificateModal'; + +// const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL; +// const baseUrl = 'https://api.anti-plagiat.uz/api/v1'; + +export default function Sertifikat({ document_id }: { document_id: number }) { + const t = useTranslations(); + const [loading, setLoading] = useState(false); + const [openModal, setOpenModal] = useState(false); + useEffect(() => { + setLoading(false); + console.log(loading); + }, []); + + // const handleClick = async () => { + // setLoading(true); + // try { + // const url = `${baseUrl}/shared/certificate/${document_id}/pdf/`; + // const res = await fetch(url); + // const blob = await res.blob(); + // const objectUrl = URL.createObjectURL(blob); + + // // ✅ window.open o'rniga
tag bilan download + // const a = document.createElement('a'); + // a.href = objectUrl; + // a.download = `certificate-${document_id}.pdf`; + // a.click(); + + // URL.revokeObjectURL(objectUrl); + // } finally { + // setLoading(false); + // } + // }; + + return ( + <> + + { + setOpenModal(false); + }} + /> + + ); +} diff --git a/src/widgets/detail/ui/sertificate/types.ts b/src/widgets/detail/ui/sertificate/types.ts new file mode 100644 index 0000000..d535caa --- /dev/null +++ b/src/widgets/detail/ui/sertificate/types.ts @@ -0,0 +1,23 @@ +export const DOCUMENT_TYPES = [ + { value: 'metodik_ishlanma', label: 'Metodik ishlanma' }, + { value: 'ilmiy_maqola', label: 'Ilmiy maqola' }, + { value: 'bmi', label: 'BMI' }, + { value: 'magistrlik', label: 'Magistrlik' }, + { value: 'kurs_ishi', label: 'Kurs ishi' }, + { value: 'boshqa', label: 'Boshqa' }, +] as const; + +export type DocumentTypeValue = (typeof DOCUMENT_TYPES)[number]['value']; + +export interface CertificateFormData { + fullname: string; + document_theme: string; + document_type: DocumentTypeValue | ''; + document_id: number; +} + +export interface SertificateModalProps { + document_id: number; + open: boolean; + setOpen: () => void; +} diff --git a/src/widgets/detail/ui/sertificate/useSertificateModal.ts b/src/widgets/detail/ui/sertificate/useSertificateModal.ts new file mode 100644 index 0000000..c0de35f --- /dev/null +++ b/src/widgets/detail/ui/sertificate/useSertificateModal.ts @@ -0,0 +1,112 @@ +import { useState, useEffect, useRef } from 'react'; +import { CertificateFormData } from './types'; + +interface UseCertificateModalProps { + document_id: number; + open: boolean; + setOpen: () => void; +} + +export function useCertificateModal({ + document_id, + open, + setOpen, +}: UseCertificateModalProps) { + const [form, setForm] = useState({ + fullname: '', + document_theme: '', + document_type: '', + document_id, + }); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + const [visible, setVisible] = useState(false); + const inputRef = useRef(null); + + useEffect(() => { + if (open) { + setVisible(true); + setSuccess(false); + setForm((prev) => ({ ...prev, document_id })); + setTimeout(() => inputRef.current?.focus(), 300); + + const data = localStorage.getItem('user'); + if (data) { + const user = JSON.parse(data); + setForm((prev) => ({ + ...prev, + fullname: `${user.name} ${user.surname}`, + })); + } + } else { + setTimeout(() => setVisible(false), 300); + } + }, [open, document_id]); + + const updateField = ( + field: K, + value: CertificateFormData[K], + ) => setForm((prev) => ({ ...prev, [field]: value })); + + const isFormValid = + !!form.fullname.trim() && + !!form.document_theme.trim() && + !!form.document_type; + + /** Payload ready to send to backend */ + const buildPayload = (): CertificateFormData => ({ ...form }); + + const handleSubmit = async () => { + if (!isFormValid || loading) return; + setLoading(true); + + try { + const payload = buildPayload(); + + const response = await fetch(`/api/certificates`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + + if (!response.ok) throw new Error('Failed'); + + setSuccess(true); + setTimeout(() => { + setOpen(); + setSuccess(false); + }, 1800); + } catch { + // Demo mode: simulate success + setSuccess(true); + setTimeout(() => { + setOpen(); + setSuccess(false); + }, 1800); + } finally { + setLoading(false); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Escape') setOpen(); + if (e.key === 'Enter') handleSubmit(); + }; + + const handleBackdropClick = (e: React.MouseEvent) => { + if (e.target === e.currentTarget) setOpen(); + }; + + return { + form, + updateField, + loading, + success, + visible, + isFormValid, + inputRef, + handleSubmit, + handleKeyDown, + handleBackdropClick, + }; +} diff --git a/src/widgets/fileUpload/lib/types.ts b/src/widgets/fileUpload/lib/types.ts index f515e91..59be66d 100644 --- a/src/widgets/fileUpload/lib/types.ts +++ b/src/widgets/fileUpload/lib/types.ts @@ -1,5 +1,7 @@ // ─── Domain Types ─────────────────────────────────────────────────────────── +import { DocumentTypeValue } from '@/widgets/detail/ui/sertificate/types'; + export interface User { id: string; firstName: string; @@ -29,6 +31,7 @@ export interface PlagiarismFormState { certificate: boolean; text?: string; total_price: number; + document_type: DocumentTypeValue; } export type PlagiarismFormErrors = Partial< diff --git a/src/widgets/fileUpload/lib/usePlagiraism.ts b/src/widgets/fileUpload/lib/usePlagiraism.ts index a031b0a..defe8ca 100644 --- a/src/widgets/fileUpload/lib/usePlagiraism.ts +++ b/src/widgets/fileUpload/lib/usePlagiraism.ts @@ -11,6 +11,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 { DocumentTypeValue } from '@/widgets/detail/ui/sertificate/types'; // ─── Initial States ────────────────────────────────────────────────────────── @@ -20,6 +21,7 @@ const INITIAL_FORM: PlagiarismFormState = { certificate: true, text: '', total_price: 41200, + document_type: 'boshqa', }; const INITIAL_SUBMISSION: SubmissionState = { @@ -105,6 +107,11 @@ export function usePlagiarismForm() { setErrors((prev) => ({ ...prev, file: undefined })); }, []); + const setOption = useCallback((option: DocumentTypeValue) => { + setForm((prev) => ({ ...prev, document_type: option })); + setErrors((prev) => ({ ...prev, document_type: undefined })); + }, []); + const toggleCertificate = useCallback(() => { setForm((prev) => ({ ...prev, certificate: !prev.certificate })); }, []); @@ -169,5 +176,6 @@ export function usePlagiarismForm() { handleSubmitWithModal, setIsPaymentOpen, isPaymentOpen, + setOption, }; } diff --git a/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx b/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx index 7cf519a..8058650 100644 --- a/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx +++ b/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx @@ -12,7 +12,17 @@ import { import { usePlagiarismForm } from '../lib/usePlagiraism'; import { PaymentModal } from '@/widgets/paymentModal/ui/Paymentmodal'; import { useTranslations } from 'next-intl'; +import { DOCUMENT_TYPES } from '@/widgets/detail/ui/sertificate/types'; +const inputCls = ` + w-full px-3.5 py-3.5 text-[14px] text-slate-800 + bg-blue-50 border border-blue-200 rounded-xl + placeholder:text-blue-400 + focus:outline-none focus:ring-2 focus:ring-blue-400/40 focus:border-blue-400 + hover:border-blue-300 + transition-all duration-150 + disabled:opacity-60 disabled:cursor-not-allowed +`.trim(); // ─── UserIcon (inline) ─────────────────────────────────────────────────────── function UserIcon() { @@ -51,6 +61,7 @@ export function PlagiarismCheckForm() { resetSubmission, handleSubmitWithModal, isPaymentOpen, + setOption, setIsPaymentOpen, } = usePlagiarismForm(); @@ -101,7 +112,7 @@ export function PlagiarismCheckForm() { )} {/* left part */} -
+
{/* Topic */} {/* right part */} -
+
{/* File Upload */} + {/* Document type */} + + + + {/* Submit */} Date: Mon, 6 Apr 2026 10:38:17 +0500 Subject: [PATCH 02/29] payme price calculation update on modal --- .../modals}/paymentModal/lib/constant.ts | 0 .../modals}/paymentModal/lib/types.ts | 24 ++----- .../modals}/paymentModal/lib/usePayment.ts | 0 src/features/modals/paymentModal/lib/utils.ts | 20 ++++++ .../modals}/paymentModal/ui/Paymebutton.tsx | 0 .../modals}/paymentModal/ui/Paymentmodal.tsx | 32 ++++----- .../modals}/paymentModal/ui/Pricesummary.tsx | 27 ++++--- .../modals/sertificateModal}/modalField.tsx | 0 .../sertificateModal}/sertificateModal.tsx | 0 .../modals/sertificateModal}/sertifikat.tsx | 0 .../modals/sertificateModal}/types.ts | 0 .../sertificateModal}/useSertificateModal.ts | 0 src/widgets/detail/pageDetail.tsx | 2 +- .../fileUpload/ui/Plagiraismcheckform.tsx | 11 ++- src/widgets/history/lib/types.ts | 3 + src/widgets/history/ui/historyTableRow.tsx | 9 ++- src/widgets/paymentModal/lib/utils.ts | 71 ------------------- 17 files changed, 73 insertions(+), 126 deletions(-) rename src/{widgets => features/modals}/paymentModal/lib/constant.ts (100%) rename src/{widgets => features/modals}/paymentModal/lib/types.ts (78%) rename src/{widgets => features/modals}/paymentModal/lib/usePayment.ts (100%) create mode 100644 src/features/modals/paymentModal/lib/utils.ts rename src/{widgets => features/modals}/paymentModal/ui/Paymebutton.tsx (100%) rename src/{widgets => features/modals}/paymentModal/ui/Paymentmodal.tsx (89%) rename src/{widgets => features/modals}/paymentModal/ui/Pricesummary.tsx (78%) rename src/{widgets/detail/ui/sertificate => features/modals/sertificateModal}/modalField.tsx (100%) rename src/{widgets/detail/ui/sertificate => features/modals/sertificateModal}/sertificateModal.tsx (100%) rename src/{widgets/detail/ui/sertificate => features/modals/sertificateModal}/sertifikat.tsx (100%) rename src/{widgets/detail/ui/sertificate => features/modals/sertificateModal}/types.ts (100%) rename src/{widgets/detail/ui/sertificate => features/modals/sertificateModal}/useSertificateModal.ts (100%) delete mode 100644 src/widgets/paymentModal/lib/utils.ts diff --git a/src/widgets/paymentModal/lib/constant.ts b/src/features/modals/paymentModal/lib/constant.ts similarity index 100% rename from src/widgets/paymentModal/lib/constant.ts rename to src/features/modals/paymentModal/lib/constant.ts diff --git a/src/widgets/paymentModal/lib/types.ts b/src/features/modals/paymentModal/lib/types.ts similarity index 78% rename from src/widgets/paymentModal/lib/types.ts rename to src/features/modals/paymentModal/lib/types.ts index f93a154..135ed38 100644 --- a/src/widgets/paymentModal/lib/types.ts +++ b/src/features/modals/paymentModal/lib/types.ts @@ -1,16 +1,5 @@ // ─── Domain Types ────────────────────────────────────────────────────────────── -export interface ServicePricing { - serviceFee: number; - certificateFee: number; - currency: string; -} - -export interface OrderSummary { - hasCertificate: boolean; - pricing: ServicePricing; -} - export interface PaymePaymentRequest { amount: number; // in tiyin (1 UZS = 100 tiyin) orderId: string; @@ -26,20 +15,21 @@ export interface PaymePaymentResponse { export type PaymentStatus = 'idle' | 'loading' | 'success' | 'error'; // ─── Component Props ─────────────────────────────────────────────────────────── +export interface PriceCalculate { + service_fee: number; + discount?: number; + total_price: number; + currency: string; +} export interface PaymentModalProps { isOpen: boolean; onClose: () => void; - hasCertificate: boolean; + price: PriceCalculate; onConfirmPayment: () => void; isLoading: boolean; } -export interface PriceSummaryProps { - hasCertificate: boolean; - pricing: ServicePricing; -} - export interface PaymeButtonProps { amount: number; orderId: string; diff --git a/src/widgets/paymentModal/lib/usePayment.ts b/src/features/modals/paymentModal/lib/usePayment.ts similarity index 100% rename from src/widgets/paymentModal/lib/usePayment.ts rename to src/features/modals/paymentModal/lib/usePayment.ts diff --git a/src/features/modals/paymentModal/lib/utils.ts b/src/features/modals/paymentModal/lib/utils.ts new file mode 100644 index 0000000..244e300 --- /dev/null +++ b/src/features/modals/paymentModal/lib/utils.ts @@ -0,0 +1,20 @@ +// ─── Pricing Utilities ───────────────────────────────────────────────────────── +export const formatPrice = (amount: number, currency: string): string => + `${amount.toLocaleString('uz-UZ')} ${currency}`; + +// ─── Order ID Generator ──────────────────────────────────────────────────────── + +export const generateOrderId = (): string => { + const timestamp = Date.now(); + const random = Math.random().toString(36).slice(2, 8).toUpperCase(); + return `ORDER-${timestamp}-${random}`; +}; + +// ─── Payme API ───────────────────────────────────────────────────────────────── + +/** + * Redirects the user to the Payme checkout page. + */ +export const redirectToPayme = (redirectUrl: string): void => { + window.location.href = redirectUrl; +}; diff --git a/src/widgets/paymentModal/ui/Paymebutton.tsx b/src/features/modals/paymentModal/ui/Paymebutton.tsx similarity index 100% rename from src/widgets/paymentModal/ui/Paymebutton.tsx rename to src/features/modals/paymentModal/ui/Paymebutton.tsx diff --git a/src/widgets/paymentModal/ui/Paymentmodal.tsx b/src/features/modals/paymentModal/ui/Paymentmodal.tsx similarity index 89% rename from src/widgets/paymentModal/ui/Paymentmodal.tsx rename to src/features/modals/paymentModal/ui/Paymentmodal.tsx index ac01019..0df24e8 100644 --- a/src/widgets/paymentModal/ui/Paymentmodal.tsx +++ b/src/features/modals/paymentModal/ui/Paymentmodal.tsx @@ -1,7 +1,6 @@ 'use client'; import React, { useEffect, useRef } from 'react'; import { PaymentModalProps } from '../lib/types'; -import { getPricing } from '../lib/utils'; import { PriceSummary } from './Pricesummary'; import { PaymeButton } from './Paymebutton'; import { useTranslations } from 'next-intl'; @@ -85,12 +84,11 @@ const SecurityBadge: React.FC<{ securityText: string }> = ({ export const PaymentModal: React.FC = ({ isOpen, onClose, - hasCertificate, + price, onConfirmPayment, isLoading, }) => { const dialogRef = useRef(null); - const pricing = getPricing(); const status = isLoading ? 'loading' : 'idle'; const t = useTranslations('Payment'); @@ -174,24 +172,22 @@ export const PaymentModal: React.FC = ({

{t('orderSummary')}

- +
{/* Certificate badge */} - {hasCertificate && ( -
- - - - {t('certificateIncluded')} -
- )} +
+ + + + {t('certificateIncluded')} +
{/* Payment method label */}
diff --git a/src/widgets/paymentModal/ui/Pricesummary.tsx b/src/features/modals/paymentModal/ui/Pricesummary.tsx similarity index 78% rename from src/widgets/paymentModal/ui/Pricesummary.tsx rename to src/features/modals/paymentModal/ui/Pricesummary.tsx index 2ae25e7..65c57c4 100644 --- a/src/widgets/paymentModal/ui/Pricesummary.tsx +++ b/src/features/modals/paymentModal/ui/Pricesummary.tsx @@ -1,8 +1,8 @@ 'use client'; import React from 'react'; import { formatPrice } from '../lib/utils'; -import { PriceSummaryProps } from '../lib/types'; import { useTranslations } from 'next-intl'; +import { PriceCalculate } from '../lib/types'; // ─── Price Row ───────────────────────────────────────────────────────────────── @@ -47,34 +47,33 @@ const PriceRow: React.FC = ({ // ─── Price Summary ───────────────────────────────────────────────────────────── -export const PriceSummary: React.FC = ({ - hasCertificate, - pricing, +export const PriceSummary = ({ + priceCalculate, +}: { + priceCalculate: PriceCalculate; }) => { - console.log(hasCertificate); - const total = 41200; const t = useTranslations('Payment'); return (
- {/* {hasCertificate && ( + {priceCalculate.discount && ( - )} */} + )}
diff --git a/src/widgets/detail/ui/sertificate/modalField.tsx b/src/features/modals/sertificateModal/modalField.tsx similarity index 100% rename from src/widgets/detail/ui/sertificate/modalField.tsx rename to src/features/modals/sertificateModal/modalField.tsx diff --git a/src/widgets/detail/ui/sertificate/sertificateModal.tsx b/src/features/modals/sertificateModal/sertificateModal.tsx similarity index 100% rename from src/widgets/detail/ui/sertificate/sertificateModal.tsx rename to src/features/modals/sertificateModal/sertificateModal.tsx diff --git a/src/widgets/detail/ui/sertificate/sertifikat.tsx b/src/features/modals/sertificateModal/sertifikat.tsx similarity index 100% rename from src/widgets/detail/ui/sertificate/sertifikat.tsx rename to src/features/modals/sertificateModal/sertifikat.tsx diff --git a/src/widgets/detail/ui/sertificate/types.ts b/src/features/modals/sertificateModal/types.ts similarity index 100% rename from src/widgets/detail/ui/sertificate/types.ts rename to src/features/modals/sertificateModal/types.ts diff --git a/src/widgets/detail/ui/sertificate/useSertificateModal.ts b/src/features/modals/sertificateModal/useSertificateModal.ts similarity index 100% rename from src/widgets/detail/ui/sertificate/useSertificateModal.ts rename to src/features/modals/sertificateModal/useSertificateModal.ts diff --git a/src/widgets/detail/pageDetail.tsx b/src/widgets/detail/pageDetail.tsx index 9b214d2..af0e291 100644 --- a/src/widgets/detail/pageDetail.tsx +++ b/src/widgets/detail/pageDetail.tsx @@ -7,7 +7,7 @@ import { useParams } from 'next/navigation'; import { links } from '@/shared/request/links'; import { apiRequest } from '@/shared/request/apiRequest'; import PaymentStatus from './paidStatus'; -import Sertifikat from './ui/sertificate/sertifikat'; +import Sertifikat from '@/features/modals/sertificateModal/sertifikat'; // ── Types ──────────────────────────────────────────────────────────────────── diff --git a/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx b/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx index 8058650..21de95b 100644 --- a/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx +++ b/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx @@ -10,9 +10,9 @@ import { StatusBanner, } from './Plagiraismui'; import { usePlagiarismForm } from '../lib/usePlagiraism'; -import { PaymentModal } from '@/widgets/paymentModal/ui/Paymentmodal'; import { useTranslations } from 'next-intl'; -import { DOCUMENT_TYPES } from '@/widgets/detail/ui/sertificate/types'; +import { DOCUMENT_TYPES } from '@/features/modals/sertificateModal/types'; +import { PaymentModal } from '@/features/modals/paymentModal/ui/Paymentmodal'; const inputCls = ` w-full px-3.5 py-3.5 text-[14px] text-slate-800 @@ -218,7 +218,12 @@ export function PlagiarismCheckForm() { setIsPaymentOpen(false)} - hasCertificate={form.certificate} + price={{ + service_fee: 41200, + discount: 5200, + total_price: 36000, + currency: 'UZS', + }} onConfirmPayment={handleSubmit} isLoading={isLoading} /> diff --git a/src/widgets/history/lib/types.ts b/src/widgets/history/lib/types.ts index 5cbb900..4083097 100644 --- a/src/widgets/history/lib/types.ts +++ b/src/widgets/history/lib/types.ts @@ -1,5 +1,7 @@ // ─── Domain Types ────────────────────────────────────────────────────────────── +import { PriceCalculate } from '@/features/modals/paymentModal/lib/types'; + export type CheckResult = 'clean' | 'plagiarism_found' | 'pending' | 'failed'; export interface DocumentData { @@ -13,6 +15,7 @@ export interface DocumentData { results: []; state: 'paid' | 'unpaid'; order_id: number; + price_calculation?: PriceCalculate; } export interface PlagiarismCheckDetail extends DocumentData { diff --git a/src/widgets/history/ui/historyTableRow.tsx b/src/widgets/history/ui/historyTableRow.tsx index 65524c1..d698843 100644 --- a/src/widgets/history/ui/historyTableRow.tsx +++ b/src/widgets/history/ui/historyTableRow.tsx @@ -6,11 +6,11 @@ import { formatDate } from '../lib/utils'; import { useRouter } from '@/shared/config/i18n/navigation'; import { useUserPlagiatStore } from '@/shared/zustand/user'; import PaymentStatus from '@/widgets/detail/paidStatus'; -import { PaymentModal } from '@/widgets/paymentModal/ui/Paymentmodal'; import { useMutation } from '@tanstack/react-query'; import { apiRequest } from '@/shared/request/apiRequest'; import { links } from '@/shared/request/links'; import { toast } from 'react-toastify'; +import { PaymentModal } from '@/features/modals/paymentModal/ui/Paymentmodal'; export const HistoryTableRow: React.FC = ({ item }) => { const router = useRouter(); @@ -160,7 +160,12 @@ export const HistoryTableRow: React.FC = ({ item }) => { setIsPaymentOpen(false)} - hasCertificate={false} + price={{ + service_fee: 41200, + discount: 5200, + total_price: 36000, + currency: 'UZS', + }} onConfirmPayment={() => { handleSubmit({ document_id: Number(item.order_id) }); }} diff --git a/src/widgets/paymentModal/lib/utils.ts b/src/widgets/paymentModal/lib/utils.ts deleted file mode 100644 index 0051dab..0000000 --- a/src/widgets/paymentModal/lib/utils.ts +++ /dev/null @@ -1,71 +0,0 @@ -// ─── Pricing Utilities ───────────────────────────────────────────────────────── - -import { PAYME_CONFIG, PRICING } from './constant'; -import { - PaymePaymentRequest, - PaymePaymentResponse, - ServicePricing, -} from './types'; - -export const getPricing = (): ServicePricing => ({ - serviceFee: PRICING.SERVICE_FEE, - certificateFee: PRICING.CERTIFICATE_FEE, - currency: PRICING.CURRENCY, -}); - -export const calculateTotal = (hasCertificate: boolean): number => { - const base = PRICING.SERVICE_FEE; - return hasCertificate ? base + PRICING.CERTIFICATE_FEE : base; -}; - -export const toTiyin = (uzs: number): number => uzs * PRICING.TIYIN_MULTIPLIER; - -export const formatPrice = (amount: number, currency: string): string => - `${amount.toLocaleString('uz-UZ')} ${currency}`; - -// ─── Order ID Generator ──────────────────────────────────────────────────────── - -export const generateOrderId = (): string => { - const timestamp = Date.now(); - const random = Math.random().toString(36).slice(2, 8).toUpperCase(); - return `ORDER-${timestamp}-${random}`; -}; - -// ─── Payme API ───────────────────────────────────────────────────────────────── - -/** - * Sends payment details to the backend, which creates a Payme transaction - * and returns a redirect URL to the Payme checkout page. - */ -export const createPaymePayment = async ( - request: PaymePaymentRequest, -): Promise => { - const response = await fetch(PAYME_CONFIG.API_ENDPOINT, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - amount: request.amount, // in tiyin - order_id: request.orderId, - description: request.description, - return_url: request.returnUrl, - }), - }); - - if (!response.ok) { - const errorBody = await response.json().catch(() => ({})); - throw new Error( - (errorBody as { message?: string }).message ?? - `Payment request failed with status ${response.status}`, - ); - } - - const data = (await response.json()) as PaymePaymentResponse; - return data; -}; - -/** - * Redirects the user to the Payme checkout page. - */ -export const redirectToPayme = (redirectUrl: string): void => { - window.location.href = redirectUrl; -}; From e95c8e9961098e6f23f4eab6ffd2033ba035724a Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Mon, 6 Apr 2026 11:48:31 +0500 Subject: [PATCH 03/29] patment part ui complated --- .../modals/paymentModal/lib/constant.ts | 22 ------ src/features/modals/paymentModal/lib/types.ts | 12 ---- .../modals/paymentModal/lib/usePayment.ts | 71 ------------------- src/features/modals/paymentModal/lib/utils.ts | 8 --- .../modals/paymentModal/ui/Paymentmodal.tsx | 32 --------- 5 files changed, 145 deletions(-) delete mode 100644 src/features/modals/paymentModal/lib/constant.ts delete mode 100644 src/features/modals/paymentModal/lib/usePayment.ts diff --git a/src/features/modals/paymentModal/lib/constant.ts b/src/features/modals/paymentModal/lib/constant.ts deleted file mode 100644 index b7a68f4..0000000 --- a/src/features/modals/paymentModal/lib/constant.ts +++ /dev/null @@ -1,22 +0,0 @@ -// ─── Pricing Constants ───────────────────────────────────────────────────────── - -export const PRICING = { - SERVICE_FEE: 45_000, - CERTIFICATE_FEE: 15_000, - CURRENCY: 'UZS', - // Payme works in tiyin (1 UZS = 100 tiyin) - TIYIN_MULTIPLIER: 100, -} as const; - -// ─── Payme Config ────────────────────────────────────────────────────────────── - -export const PAYME_CONFIG = { - MERCHANT_ID: process.env.NEXT_PUBLIC_PAYME_MERCHANT_ID ?? 'your_merchant_id', - BASE_URL: 'https://checkout.paycom.uz', - // In development, point to your own backend - API_ENDPOINT: '/api/payments/payme/create', - RETURN_URL: - typeof window !== 'undefined' - ? `${window.location.origin}/payment/success` - : 'https://yourapp.uz/payment/success', -} as const; diff --git a/src/features/modals/paymentModal/lib/types.ts b/src/features/modals/paymentModal/lib/types.ts index 135ed38..9567007 100644 --- a/src/features/modals/paymentModal/lib/types.ts +++ b/src/features/modals/paymentModal/lib/types.ts @@ -7,11 +7,6 @@ export interface PaymePaymentRequest { returnUrl: string; } -export interface PaymePaymentResponse { - redirectUrl: string; - transactionId: string; -} - export type PaymentStatus = 'idle' | 'loading' | 'success' | 'error'; // ─── Component Props ─────────────────────────────────────────────────────────── @@ -29,10 +24,3 @@ export interface PaymentModalProps { onConfirmPayment: () => void; isLoading: boolean; } - -export interface PaymeButtonProps { - amount: number; - orderId: string; - onSuccess?: (response: PaymePaymentResponse) => void; - onError?: (error: Error) => void; -} diff --git a/src/features/modals/paymentModal/lib/usePayment.ts b/src/features/modals/paymentModal/lib/usePayment.ts deleted file mode 100644 index 195e3aa..0000000 --- a/src/features/modals/paymentModal/lib/usePayment.ts +++ /dev/null @@ -1,71 +0,0 @@ -'use client'; -import { useState, useCallback } from 'react'; -import { PaymentStatus, PaymePaymentResponse } from './types'; -import { - calculateTotal, - createPaymePayment, - generateOrderId, - redirectToPayme, - toTiyin, -} from './utils'; -import { PAYME_CONFIG } from './constant'; - -interface UsePaymentOptions { - hasCertificate: boolean; - onSuccess?: (response: PaymePaymentResponse) => void; - onError?: (error: Error) => void; -} - -interface UsePaymentReturn { - status: PaymentStatus; - error: string | null; - totalAmount: number; - handlePaymePayment: () => Promise; - resetError: () => void; -} - -export const usePayment = ({ - hasCertificate, - onSuccess, - onError, -}: UsePaymentOptions): UsePaymentReturn => { - const [status, setStatus] = useState('idle'); - const [error, setError] = useState(null); - - const totalAmount = calculateTotal(hasCertificate); - - const handlePaymePayment = useCallback(async () => { - setStatus('loading'); - setError(null); - - const orderId = generateOrderId(); - - try { - const response = await createPaymePayment({ - amount: toTiyin(totalAmount), - orderId, - description: `Service fee${hasCertificate ? ' + Certificate' : ''}`, - returnUrl: PAYME_CONFIG.RETURN_URL, - }); - - setStatus('success'); - onSuccess?.(response); - redirectToPayme(response.redirectUrl); - } catch (err) { - const paymentError = - err instanceof Error - ? err - : new Error('Payment failed. Please try again.'); - setStatus('error'); - setError(paymentError.message); - onError?.(paymentError); - } - }, [totalAmount, hasCertificate, onSuccess, onError]); - - const resetError = useCallback(() => { - setError(null); - setStatus('idle'); - }, []); - - return { status, error, totalAmount, handlePaymePayment, resetError }; -}; diff --git a/src/features/modals/paymentModal/lib/utils.ts b/src/features/modals/paymentModal/lib/utils.ts index 244e300..1eb56d7 100644 --- a/src/features/modals/paymentModal/lib/utils.ts +++ b/src/features/modals/paymentModal/lib/utils.ts @@ -2,14 +2,6 @@ export const formatPrice = (amount: number, currency: string): string => `${amount.toLocaleString('uz-UZ')} ${currency}`; -// ─── Order ID Generator ──────────────────────────────────────────────────────── - -export const generateOrderId = (): string => { - const timestamp = Date.now(); - const random = Math.random().toString(36).slice(2, 8).toUpperCase(); - return `ORDER-${timestamp}-${random}`; -}; - // ─── Payme API ───────────────────────────────────────────────────────────────── /** diff --git a/src/features/modals/paymentModal/ui/Paymentmodal.tsx b/src/features/modals/paymentModal/ui/Paymentmodal.tsx index 0df24e8..970e8df 100644 --- a/src/features/modals/paymentModal/ui/Paymentmodal.tsx +++ b/src/features/modals/paymentModal/ui/Paymentmodal.tsx @@ -34,38 +34,6 @@ const CloseButton: React.FC<{ onClick: () => void }> = ({ onClick }) => ( ); -// ─── Error Banner ────────────────────────────────────────────────────────────── - -// const ErrorBanner: React.FC<{ message: string; onDismiss: () => void }> = ({ -// message, -// onDismiss, -// }) => ( -//
-// -// -// -//

{message}

-// -//
-// ); - // ─── Security Badge ──────────────────────────────────────────────────────────── const SecurityBadge: React.FC<{ securityText: string }> = ({ From 5c07519e53f9aad3319e53b911c448b99668dd40 Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Mon, 6 Apr 2026 11:52:22 +0500 Subject: [PATCH 04/29] remove emprty type --- src/widgets/fileUpload/lib/types.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/widgets/fileUpload/lib/types.ts b/src/widgets/fileUpload/lib/types.ts index 59be66d..3068939 100644 --- a/src/widgets/fileUpload/lib/types.ts +++ b/src/widgets/fileUpload/lib/types.ts @@ -1,7 +1,5 @@ // ─── Domain Types ─────────────────────────────────────────────────────────── -import { DocumentTypeValue } from '@/widgets/detail/ui/sertificate/types'; - export interface User { id: string; firstName: string; @@ -31,7 +29,7 @@ export interface PlagiarismFormState { certificate: boolean; text?: string; total_price: number; - document_type: DocumentTypeValue; + document_type: string; } export type PlagiarismFormErrors = Partial< From 49b316140bc02a0a046849ebe0a5b2c940f1525b Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Mon, 6 Apr 2026 11:57:35 +0500 Subject: [PATCH 05/29] remove unneccessery type --- src/widgets/fileUpload/lib/usePlagiraism.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/widgets/fileUpload/lib/usePlagiraism.ts b/src/widgets/fileUpload/lib/usePlagiraism.ts index defe8ca..02c8152 100644 --- a/src/widgets/fileUpload/lib/usePlagiraism.ts +++ b/src/widgets/fileUpload/lib/usePlagiraism.ts @@ -11,7 +11,6 @@ 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 { DocumentTypeValue } from '@/widgets/detail/ui/sertificate/types'; // ─── Initial States ────────────────────────────────────────────────────────── @@ -107,7 +106,7 @@ export function usePlagiarismForm() { setErrors((prev) => ({ ...prev, file: undefined })); }, []); - const setOption = useCallback((option: DocumentTypeValue) => { + const setOption = useCallback((option: string) => { setForm((prev) => ({ ...prev, document_type: option })); setErrors((prev) => ({ ...prev, document_type: undefined })); }, []); From 89c5552c4e4553f9aa51d22d7f5402bd22985e7e Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Mon, 6 Apr 2026 12:17:17 +0500 Subject: [PATCH 06/29] profile button added --- src/widgets/navbar/ui/authButtons.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/widgets/navbar/ui/authButtons.tsx b/src/widgets/navbar/ui/authButtons.tsx index c70a636..4192569 100644 --- a/src/widgets/navbar/ui/authButtons.tsx +++ b/src/widgets/navbar/ui/authButtons.tsx @@ -13,7 +13,7 @@ import { ChangeLang } from './ChangeLang'; import { useLoginModal, useRegisterModal } from '@/shared/zustand/auth'; import { useTranslations } from 'next-intl'; import { useUserPlagiatStore } from '@/shared/zustand/user'; -import { LogOut } from 'lucide-react'; +import { LogOut, User } from 'lucide-react'; import { useEffect, useState } from 'react'; function AuthButtons() { @@ -29,7 +29,10 @@ function AuthButtons() { signup: { title: t('signup'), url: '#' }, }; - const userItem = [{ title: t('logout'), url: '/', icon: LogOut }]; + const userItem = [ + { title: t('profile'), url: '/profile', icon: User }, + { title: t('logout'), url: '/', icon: LogOut }, + ]; const toggleLoginModal = useLoginModal((state) => state.toggleLoginModal); const toggleRegisterModal = useRegisterModal( From 27b15108421e86e33c53de4fc8b06956a91e8f73 Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Mon, 6 Apr 2026 15:43:51 +0500 Subject: [PATCH 07/29] changed --- public/modules.html | 178 ++++++++++++++ src/app/[locale]/cabinet/page.tsx | 5 + src/shared/ui/navigation-menu.tsx | 2 +- src/widgets/cabinet/lib/hooks/useCabinet.ts | 18 ++ src/widgets/cabinet/lib/hooks/useProfile.ts | 35 +++ src/widgets/cabinet/lib/mock.ts | 151 ++++++++++++ src/widgets/cabinet/lib/modules.ts | 230 ++++++++++++++++++ src/widgets/cabinet/lib/types.ts | 55 +++++ src/widgets/cabinet/ui/Sidebar.tsx | 145 +++++++++++ src/widgets/cabinet/ui/dashboard/CtaCards.tsx | 49 ++++ .../cabinet/ui/dashboard/ModulesSection.tsx | 128 ++++++++++ .../cabinet/ui/dashboard/StatsCards.tsx | 81 ++++++ src/widgets/cabinet/ui/dashboard/index.tsx | 41 ++++ src/widgets/cabinet/ui/index.tsx | 114 +++++++++ .../cabinet/ui/profile/DiscountProgress.tsx | 52 ++++ .../cabinet/ui/profile/ProfileForm.tsx | 170 +++++++++++++ src/widgets/cabinet/ui/profile/index.tsx | 26 ++ .../cabinet/ui/tables/PaymentsTable.tsx | 101 ++++++++ .../cabinet/ui/tables/PlagiatTable.tsx | 128 ++++++++++ src/widgets/cabinet/ui/tables/SiTable.tsx | 128 ++++++++++ src/widgets/footer/ui/index.tsx | 6 +- src/widgets/navbar/ui/authButtons.tsx | 46 ++-- src/widgets/navbar/ui/index.tsx | 8 +- 23 files changed, 1871 insertions(+), 26 deletions(-) create mode 100644 public/modules.html create mode 100644 src/app/[locale]/cabinet/page.tsx create mode 100644 src/widgets/cabinet/lib/hooks/useCabinet.ts create mode 100644 src/widgets/cabinet/lib/hooks/useProfile.ts create mode 100644 src/widgets/cabinet/lib/mock.ts create mode 100644 src/widgets/cabinet/lib/modules.ts create mode 100644 src/widgets/cabinet/lib/types.ts create mode 100644 src/widgets/cabinet/ui/Sidebar.tsx create mode 100644 src/widgets/cabinet/ui/dashboard/CtaCards.tsx create mode 100644 src/widgets/cabinet/ui/dashboard/ModulesSection.tsx create mode 100644 src/widgets/cabinet/ui/dashboard/StatsCards.tsx create mode 100644 src/widgets/cabinet/ui/dashboard/index.tsx create mode 100644 src/widgets/cabinet/ui/index.tsx create mode 100644 src/widgets/cabinet/ui/profile/DiscountProgress.tsx create mode 100644 src/widgets/cabinet/ui/profile/ProfileForm.tsx create mode 100644 src/widgets/cabinet/ui/profile/index.tsx create mode 100644 src/widgets/cabinet/ui/tables/PaymentsTable.tsx create mode 100644 src/widgets/cabinet/ui/tables/PlagiatTable.tsx create mode 100644 src/widgets/cabinet/ui/tables/SiTable.tsx diff --git a/public/modules.html b/public/modules.html new file mode 100644 index 0000000..7e24569 --- /dev/null +++ b/public/modules.html @@ -0,0 +1,178 @@ + + + +
+ +
+
30
Jami modullar
+
10
Bepul internet manbalari
+
5
AI tahlil modullari
+
4
Kategoriya
+
+ +
Ilmiy va ta'lim bazalari
+
+
+
+
eLIBRARY.RU
Rus va xorijiy tillardagi ilmiy maqolalarning to'liq matnlari bazasi
Ilmiy
+
+
+
+
Публикации eLIBRARY (tarjima va qayta bayon)
Tarjima va parafraz qilingan maqolalarni aniqlash
AI tahlil
+
+
+
+
RDK to'plami
Rossiya Davlat kutubxonasidan dissertatsiya va avtoreferatlar
Ilmiy
+
+
+
+
BMK dissertatsiyalari
Belarus milliy kutubxonasi dissertatsiyalari va avtoreferatlari
Ilmiy
+
+
+
+
IEEE
Xalqaro elektrotexnika va elektronika muhandislari instituti bazasi
Ilmiy
+
+
+
+
IEEE parafraz moduli
IEEE maqolalarining qayta bayon qilingan variantlarini aniqlash
AI tahlil
+
+
+
+
Elektron-kutubxona tizimlari
Book.ru, Юрайт, Лань, Айбукс va boshqa ELS bazalari
Ilmiy
+
+
+
+
OTMlar halqasi
O'zbekiston oliy ta'lim muassasalari birgalikdagi bazasi
Ilmiy
+
+
+
+
Коллекция НБУ
O'zbekiston milliy kutubxonasi to'plami
Ilmiy
+
+
+ +
Huquqiy va normativ bazalar
+
+
+
+
Patentlar
SSSR, O'zbekiston, Rossiya va MDH davlatlari patentlari bazasi
Huquqiy
+
+
+
+
ИПС Адилет
O'zbekiston qonunchilik bazasi hujjatlari
Huquqiy
+
+
+ +
Internet tekshiruv modullari
+
+
+
+
Internet PLUS moduli
Internet bo'ylab kengaytirilgan chuqur skanerlash
Internet
+
+
+
+
Internet RU — parafraz
Rus internet segmentidagi qayta bayon qilingan qarzlar
InternetAI tahlil
+
+
+
+
Internet EN — parafraz
Ingliz internet segmentidagi qayta bayon qilingan qarzlar
InternetAI tahlil
+
+
+
+
Internet RU — tarjima
Rus internet segmentidagi tarjima qilingan qarzlar
InternetAI tahlil
+
+
+
+
Internet EN — tarjima
Ingliz internet segmentidagi tarjima qilingan qarzlar
InternetAI tahlil
+
+
+
+
СМИ России и СНГ
Rossiya va MDH ommaviy axborot vositalari maqolalari
OAV
+
+
+
+
Собственная коллекция компании
Antiplag.uz ichki hujjatlar to'plami
Ichki baza
+
+
+ +
Yangi bepul internet manbalari
+
+
+
+
consultant.ru
Rossiya qonunchiligining elektron bazasi
BepulHuquqiy
+
+
+
+
kremlin.ru
Rossiya prezidenti farmonlari va rasmiy qonunlar
BepulHuquqiy
+
+
+
+
pravo.gov.ru
Rossiya rasmiy huquqiy hujjatlar nashriyoti portali
BepulHuquqiy
+
+
+
+
docs.cntd.ru
Texnik normalar va standartlar hujjatlari bazasi
BepulStandartlar
+
+
+
+
rumc.mininuniver.ru
Minin universiteti adaptiv ta'lim dasturlari resursi
BepulTa'lim
+
+
+
+
moodle.kstu.ru
KSTU universiteti Moodle platformasi o'quv resurslari
BepulTa'lim
+
+
+
+
freereferats.ru
Dissertatsiya avtoreferatlari ochiq to'plami (PDF)
BepulIlmiy
+
+
+
+
ktzszmoik.gov.by
Belarus nogironlarni ijtimoiy himoya qilish davlat portali
BepulInternet
+
+
+
+
bizlog.ru
Iqtisodiy-boshqaruv terminologiyasi va izohli lug'at
BepulInternet
+
+
+
+
disabilityartsinternational.org
Nogironlik va madaniyat bo'yicha xalqaro resurs
BepulXalqaro
+
+
+ +
Yordamchi modullar
+
+
+
+
Shablon iboralar
Standart kirish so'zlari, universitet nomlari va klişe iboralarni aniqlash
Avtomatik
+
+
+
+
Iqtibos keltirish moduli
Hujjatda to'g'ri rasmiylashtirilgan iqtiboslarni avtomatik aniqlash
Avtomatik
+
+
+ +
diff --git a/src/app/[locale]/cabinet/page.tsx b/src/app/[locale]/cabinet/page.tsx new file mode 100644 index 0000000..9e6af1b --- /dev/null +++ b/src/app/[locale]/cabinet/page.tsx @@ -0,0 +1,5 @@ +import { CabinetLayout } from '@/widgets/cabinet/ui'; + +export default function CabinetPage() { + return ; +} diff --git a/src/shared/ui/navigation-menu.tsx b/src/shared/ui/navigation-menu.tsx index c6a844d..37bad3d 100644 --- a/src/shared/ui/navigation-menu.tsx +++ b/src/shared/ui/navigation-menu.tsx @@ -75,7 +75,7 @@ function NavigationMenuTrigger({ > {children}{' '}
+ )} + + + +); diff --git a/src/widgets/cabinet/ui/dashboard/CtaCards.tsx b/src/widgets/cabinet/ui/dashboard/CtaCards.tsx new file mode 100644 index 0000000..6ecf177 --- /dev/null +++ b/src/widgets/cabinet/ui/dashboard/CtaCards.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { FileSearch, BrainCircuit, ArrowRight } from 'lucide-react'; +import type { CabinetSection } from '../../lib/types'; + +interface CtaCardsProps { + onNavigate: (section: CabinetSection) => void; +} + +export const CtaCards: React.FC = ({ onNavigate }) => ( +
+ {/* Plagiat */} + + + {/* SI */} + +
+); diff --git a/src/widgets/cabinet/ui/dashboard/ModulesSection.tsx b/src/widgets/cabinet/ui/dashboard/ModulesSection.tsx new file mode 100644 index 0000000..78c7f63 --- /dev/null +++ b/src/widgets/cabinet/ui/dashboard/ModulesSection.tsx @@ -0,0 +1,128 @@ +import React from 'react'; +import { + MODULE_CATEGORIES, + MODULE_STATS, + TAG_STYLES, + type ModuleTag, +} from '../../lib/modules'; + +// ─── Module stats mini-cards ─────────────────────────────────────────────────── + +const ModuleStats: React.FC = () => ( +
+ {[ + { value: MODULE_STATS.total, label: 'Jami modullar' }, + { value: MODULE_STATS.freeInternet, label: 'Bepul internet manbalari' }, + { value: MODULE_STATS.aiModules, label: 'AI tahlil modullari' }, + { value: MODULE_STATS.categories, label: 'Kategoriya' }, + ].map(({ value, label }) => ( +
+

+ {value} +

+

{label}

+
+ ))} +
+); + +// ─── Tag badge ───────────────────────────────────────────────────────────────── + +const Tag: React.FC<{ tag: ModuleTag }> = ({ tag }) => ( + + {tag} + +); + +// ─── Category divider ────────────────────────────────────────────────────────── + +const CategoryHeader: React.FC<{ label: string }> = ({ label }) => ( +
+
+ + {label} + +
+
+); + +// ─── Single module card ──────────────────────────────────────────────────────── + +interface ModuleCardProps { + name: string; + desc: string; + tags: ModuleTag[]; + index: number; +} + +const ModuleCard: React.FC = ({ name, desc, tags, index }) => ( +
+ {/* Index number */} + + {index} + +
+

+ {name} +

+

{desc}

+
+ {tags.map((tag) => ( + + ))} +
+
+
+); + +// ─── Modules section ─────────────────────────────────────────────────────────── + +export const ModulesSection: React.FC = () => { + // running counter across all categories + let counter = 0; + + return ( +
+
+
+

+ Tekshiruv modullari +

+

+ Plagiat aniqlashda foydalaniladigan barcha manbalar +

+
+ + {MODULE_STATS.total} ta modul + +
+ + + + {MODULE_CATEGORIES.map((cat) => ( +
+ +
+ {cat.modules.map((mod) => { + counter += 1; + return ( + + ); + })} +
+
+ ))} +
+ ); +}; diff --git a/src/widgets/cabinet/ui/dashboard/StatsCards.tsx b/src/widgets/cabinet/ui/dashboard/StatsCards.tsx new file mode 100644 index 0000000..a04f6df --- /dev/null +++ b/src/widgets/cabinet/ui/dashboard/StatsCards.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { TrendingUp, Calendar, Tag, Wallet } from 'lucide-react'; +import type { CabinetStats } from '../../lib/types'; + +// ─── Single stat card ────────────────────────────────────────────────────────── + +interface StatCardProps { + icon: React.ElementType; + label: string; + value: string; + sub?: string; + iconColor: string; + iconBg: string; +} + +const StatCard: React.FC = ({ + icon: Icon, + label, + value, + sub, + iconColor, + iconBg, +}) => ( +
+
+ +
+

{value}

+

{label}

+ {sub &&

{sub}

} +
+); + +// ─── Grid ────────────────────────────────────────────────────────────────────── + +interface StatsCardsProps { + stats: CabinetStats; +} + +export const StatsCards: React.FC = ({ stats }) => { + const discountPct = Math.round( + (stats.discountUsed / stats.discountTotal) * 100, + ); + + return ( +
+ + + + +
+ ); +}; diff --git a/src/widgets/cabinet/ui/dashboard/index.tsx b/src/widgets/cabinet/ui/dashboard/index.tsx new file mode 100644 index 0000000..3f1a96e --- /dev/null +++ b/src/widgets/cabinet/ui/dashboard/index.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { CtaCards } from './CtaCards'; +import { StatsCards } from './StatsCards'; +import { ModulesSection } from './ModulesSection'; +import type { CabinetSection, CabinetStats } from '../../lib/types'; + +interface DashboardProps { + stats: CabinetStats; + onNavigate: (section: CabinetSection) => void; + userName: string; +} + +export const Dashboard: React.FC = ({ + stats, + onNavigate, + userName, +}) => ( +
+
+

+ Xush kelibsiz, {userName} 👋 +

+

+ Shaxsiy kabinetingizga xush kelibsiz +

+
+ + + +
+

+ Tezkor harakatlar +

+ +
+ +
+ +
+
+); diff --git a/src/widgets/cabinet/ui/index.tsx b/src/widgets/cabinet/ui/index.tsx new file mode 100644 index 0000000..1e626ec --- /dev/null +++ b/src/widgets/cabinet/ui/index.tsx @@ -0,0 +1,114 @@ +'use client'; +import React from 'react'; +import dynamic from 'next/dynamic'; +import { AnimatePresence, motion } from 'framer-motion'; +import { Sidebar } from './Sidebar'; +import { Dashboard } from './dashboard'; +import { useCabinet } from '../lib/hooks/useCabinet'; +import { + MOCK_USER, + MOCK_STATS, + MOCK_PLAGIAT, + MOCK_SI, + MOCK_PAYMENTS, +} from '../lib/mock'; +import type { CabinetSection } from '../lib/types'; + +// ─── Lazy sections (separate JS chunks) ─────────────────────────────────────── + +const Skeleton = () => ( +
+ {Array.from({ length: 4 }).map((_, i) => ( +
+ ))} +
+); + +const PlagiatTable = dynamic( + () => + import('./tables/PlagiatTable').then((m) => ({ default: m.PlagiatTable })), + { loading: Skeleton }, +); +const SiTable = dynamic( + () => import('./tables/SiTable').then((m) => ({ default: m.SiTable })), + { loading: Skeleton }, +); +const PaymentsTable = dynamic( + () => + import('./tables/PaymentsTable').then((m) => ({ + default: m.PaymentsTable, + })), + { loading: Skeleton }, +); +const ProfileSection = dynamic( + () => import('./profile').then((m) => ({ default: m.ProfileSection })), + { loading: Skeleton }, +); + +// ─── Section switcher ────────────────────────────────────────────────────────── + +function SectionContent({ + section, + onNavigate, +}: { + section: CabinetSection; + onNavigate: (s: CabinetSection) => void; +}) { + switch (section) { + case 'dashboard': + return ( + + ); + case 'plagiat': + return ; + case 'si': + return ; + case 'payments': + return ; + case 'profile': + return ; + } +} + +// ─── Animation ──────────────────────────────────────────────────────────────── + +const FADE = { + initial: { opacity: 0, y: 10 }, + animate: { opacity: 1, y: 0 }, + exit: { opacity: 0, y: -6 }, + transition: { duration: 0.18, ease: 'easeOut' }, +} as const; + +// ─── CabinetLayout ──────────────────────────────────────────────────────────── + +export const CabinetLayout: React.FC = () => { + const { activeSection, navigate, isSidebarOpen, toggleSidebar } = + useCabinet(); + const fullName = `${MOCK_USER.name} ${MOCK_USER.surname}`; + + return ( +
+ + +
+
+ + + + + +
+
+
+ ); +}; diff --git a/src/widgets/cabinet/ui/profile/DiscountProgress.tsx b/src/widgets/cabinet/ui/profile/DiscountProgress.tsx new file mode 100644 index 0000000..bf3154e --- /dev/null +++ b/src/widgets/cabinet/ui/profile/DiscountProgress.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { Tag } from 'lucide-react'; +import type { CabinetStats } from '../../lib/types'; + +interface DiscountProgressProps { + stats: CabinetStats; +} + +export const DiscountProgress: React.FC = ({ + stats, +}) => { + const pct = Math.round((stats.discountUsed / stats.discountTotal) * 100); + const remaining = stats.discountTotal - stats.discountUsed; + + return ( +
+
+
+
+ +
+
+

+ Bu oyda chegirma +

+

+ {remaining > 0 + ? `${remaining} ta hujjatdan keyin chegirma tugaydi` + : 'Bu oyda barcha chegirmalar ishlatildi'} +

+
+
+ + {stats.discountUsed}/{stats.discountTotal} + +
+ + {/* Bar */} +
+
+
+ +
+ {stats.discountUsed} ta ishlatildi + {pct}% +
+
+ ); +}; diff --git a/src/widgets/cabinet/ui/profile/ProfileForm.tsx b/src/widgets/cabinet/ui/profile/ProfileForm.tsx new file mode 100644 index 0000000..35cb0dd --- /dev/null +++ b/src/widgets/cabinet/ui/profile/ProfileForm.tsx @@ -0,0 +1,170 @@ +'use client'; +import React from 'react'; +import { User, Mail, Phone, Lock, Save, CheckCircle } from 'lucide-react'; +import { useProfile } from '../../lib/hooks/useProfile'; +import type { UserProfile } from '../../lib/types'; + +// ─── Input field ─────────────────────────────────────────────────────────────── + +interface InputFieldProps { + label: string; + value: string; + onChange: (val: string) => void; + type?: string; + icon: React.ElementType; + placeholder?: string; +} + +const InputField: React.FC = ({ + label, + value, + onChange, + type = 'text', + icon: Icon, + placeholder, +}) => ( +
+ +
+ + onChange(e.target.value)} + placeholder={placeholder} + className=" + w-full pl-9 pr-4 py-2.5 text-sm rounded-xl + border border-slate-200 bg-white + text-slate-800 placeholder:text-slate-300 + focus:outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-400 + transition-all duration-150 + " + /> +
+
+); + +// ─── Form ────────────────────────────────────────────────────────────────────── + +interface ProfileFormProps { + initial: UserProfile; +} + +export const ProfileForm: React.FC = ({ initial }) => { + const { form, isSaving, saved, handleChange, handleSave } = + useProfile(initial); + + return ( +
+ {/* Personal info */} +
+

+ Shaxsiy ma'lumotlar +

+
+ handleChange('name', v)} + icon={User} + placeholder="Ali" + /> + handleChange('surname', v)} + icon={User} + placeholder="Karimov" + /> + handleChange('email', v)} + type="email" + icon={Mail} + placeholder="ali@example.com" + /> + handleChange('phone', v)} + icon={Phone} + placeholder="+998 90 123 45 67" + /> +
+
+ + {/* Password */} +
+

+ Parol o'zgartirish +

+
+ handleChange('currentPassword', v)} + type="password" + icon={Lock} + placeholder="••••••••" + /> + handleChange('newPassword', v)} + type="password" + icon={Lock} + placeholder="••••••••" + /> + handleChange('confirmPassword', v)} + type="password" + icon={Lock} + placeholder="••••••••" + /> +
+
+ + {/* Save */} +
+ +
+
+ ); +}; diff --git a/src/widgets/cabinet/ui/profile/index.tsx b/src/widgets/cabinet/ui/profile/index.tsx new file mode 100644 index 0000000..a47c388 --- /dev/null +++ b/src/widgets/cabinet/ui/profile/index.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { DiscountProgress } from './DiscountProgress'; +import { ProfileForm } from './ProfileForm'; +import type { CabinetStats, UserProfile } from '../../lib/types'; + +interface ProfileSectionProps { + user: UserProfile; + stats: CabinetStats; +} + +export const ProfileSection: React.FC = ({ + user, + stats, +}) => ( +
+
+

Profil

+

+ Ma'lumotlaringizni boshqaring +

+
+ + + +
+); diff --git a/src/widgets/cabinet/ui/tables/PaymentsTable.tsx b/src/widgets/cabinet/ui/tables/PaymentsTable.tsx new file mode 100644 index 0000000..8542127 --- /dev/null +++ b/src/widgets/cabinet/ui/tables/PaymentsTable.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { CheckCircle, Clock, XCircle } from 'lucide-react'; +import type { Payment } from '../../lib/types'; + +// ─── Helpers ─────────────────────────────────────────────────────────────────── + +const STATUS_MAP = { + paid: { + label: "To'landi", + icon: CheckCircle, + cls: 'text-emerald-600 bg-emerald-50', + }, + pending: { + label: 'Kutilmoqda', + icon: Clock, + cls: 'text-amber-600 bg-amber-50', + }, + failed: { label: 'Xato', icon: XCircle, cls: 'text-red-600 bg-red-50' }, +} as const; + +// ─── Component ───────────────────────────────────────────────────────────────── + +interface PaymentsTableProps { + data: Payment[]; +} + +export const PaymentsTable: React.FC = ({ data }) => ( +
+
+

+ To'lovlar tarixi +

+

+ {data.length} ta to'lov +

+
+ +
+
+ + + + {['#', 'Xizmat', 'Summa', 'Chegirma', 'Sana', 'Holat'].map( + (h) => ( + + ), + )} + + + + {data.map((row) => { + const s = STATUS_MAP[row.status]; + const Icon = s.icon; + return ( + + + + + + + + + ); + })} + +
+ {h} +
+ {String(row.id).padStart(2, '0')} + + {row.service} + + {row.amount.toLocaleString()} UZS + + {row.discount > 0 ? ( + + -{row.discount.toLocaleString()} UZS + + ) : ( + + )} + + {row.date} + + + + {s.label} + +
+
+
+
+); diff --git a/src/widgets/cabinet/ui/tables/PlagiatTable.tsx b/src/widgets/cabinet/ui/tables/PlagiatTable.tsx new file mode 100644 index 0000000..787e669 --- /dev/null +++ b/src/widgets/cabinet/ui/tables/PlagiatTable.tsx @@ -0,0 +1,128 @@ +import React from 'react'; +import { Download, Clock, CheckCircle, XCircle } from 'lucide-react'; +import type { PlagiatCheck } from '../../lib/types'; + +// ─── Helpers ─────────────────────────────────────────────────────────────────── + +const STATUS_MAP = { + completed: { + label: 'Yakunlandi', + icon: CheckCircle, + cls: 'text-emerald-600 bg-emerald-50', + }, + pending: { + label: 'Kutilmoqda', + icon: Clock, + cls: 'text-amber-600 bg-amber-50', + }, + failed: { label: 'Xato', icon: XCircle, cls: 'text-red-600 bg-red-50' }, +} as const; + +const PercentBadge: React.FC<{ value: number }> = ({ value }) => { + const cls = + value < 15 + ? 'text-emerald-700 bg-emerald-50' + : value < 30 + ? 'text-amber-700 bg-amber-50' + : 'text-red-700 bg-red-50'; + return ( + + {value}% + + ); +}; + +// ─── Component ───────────────────────────────────────────────────────────────── + +interface PlagiatTableProps { + data: PlagiatCheck[]; +} + +export const PlagiatTable: React.FC = ({ data }) => ( +
+
+

Plagiat tekshiruvlar

+

+ {data.length} ta tekshiruv +

+
+ +
+
+ + + + {['#', 'Fayl', 'Turi', '%', 'Sana', 'Holat', 'Yuklab olish'].map( + (h) => ( + + ), + )} + + + + {data.map((row) => { + const s = STATUS_MAP[row.status]; + const Icon = s.icon; + return ( + + + + + + + + + + ); + })} + +
+ {h} +
+ {String(row.id).padStart(2, '0')} + + + {row.file} + + + {row.type} + + {row.status === 'pending' ? ( + + ) : ( + + )} + + {row.date} + + + + {s.label} + + + {row.downloadUrl ? ( + + + + ) : ( + + )} +
+
+
+
+); diff --git a/src/widgets/cabinet/ui/tables/SiTable.tsx b/src/widgets/cabinet/ui/tables/SiTable.tsx new file mode 100644 index 0000000..0f5e840 --- /dev/null +++ b/src/widgets/cabinet/ui/tables/SiTable.tsx @@ -0,0 +1,128 @@ +import React from 'react'; +import { FileText, Clock, CheckCircle, XCircle } from 'lucide-react'; +import type { SiCheck } from '../../lib/types'; + +// ─── Helpers ─────────────────────────────────────────────────────────────────── + +const STATUS_MAP = { + completed: { + label: 'Yakunlandi', + icon: CheckCircle, + cls: 'text-emerald-600 bg-emerald-50', + }, + pending: { + label: 'Kutilmoqda', + icon: Clock, + cls: 'text-amber-600 bg-amber-50', + }, + failed: { label: 'Xato', icon: XCircle, cls: 'text-red-600 bg-red-50' }, +} as const; + +const SiPercentBadge: React.FC<{ value: number }> = ({ value }) => { + const cls = + value < 20 + ? 'text-emerald-700 bg-emerald-50' + : value < 40 + ? 'text-amber-700 bg-amber-50' + : 'text-red-700 bg-red-50'; + return ( + + {value}% + + ); +}; + +// ─── Component ───────────────────────────────────────────────────────────────── + +interface SiTableProps { + data: SiCheck[]; +} + +export const SiTable: React.FC = ({ data }) => ( +
+
+

SI detektor

+

+ {data.length} ta tekshiruv +

+
+ +
+
+ + + + {['#', 'Fayl', "So'z", 'SI%', 'Sana', 'Holat', 'Hisobot'].map( + (h) => ( + + ), + )} + + + + {data.map((row) => { + const s = STATUS_MAP[row.status]; + const Icon = s.icon; + return ( + + + + + + + + + + ); + })} + +
+ {h} +
+ {String(row.id).padStart(2, '0')} + + + {row.file} + + + {row.words.toLocaleString()} + + {row.status === 'pending' ? ( + + ) : ( + + )} + + {row.date} + + + + {s.label} + + + {row.reportUrl ? ( + + + + ) : ( + + )} +
+
+
+
+); diff --git a/src/widgets/footer/ui/index.tsx b/src/widgets/footer/ui/index.tsx index 14aaa84..0bf7b65 100644 --- a/src/widgets/footer/ui/index.tsx +++ b/src/widgets/footer/ui/index.tsx @@ -8,9 +8,9 @@ const Footer = () => { // { name: 'Contact', href: '/contact' }, // ]; return ( -
-
-
+
+
+

{t('copyright', { year: new Date().getFullYear() })}

  • diff --git a/src/widgets/navbar/ui/authButtons.tsx b/src/widgets/navbar/ui/authButtons.tsx index 4192569..5cd7de9 100644 --- a/src/widgets/navbar/ui/authButtons.tsx +++ b/src/widgets/navbar/ui/authButtons.tsx @@ -6,6 +6,7 @@ import { NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, + NavigationMenuList, NavigationMenuTrigger, } from '@/shared/ui/navigation-menu'; import SubMenuLink from './SubMenuLink'; @@ -30,7 +31,7 @@ function AuthButtons() { }; const userItem = [ - { title: t('profile'), url: '/profile', icon: User }, + { title: t('profile'), url: '/cabinet', icon: User }, { title: t('logout'), url: '/', icon: LogOut }, ]; @@ -64,23 +65,32 @@ function AuthButtons() {
    - - - - {localUser.name} {localUser.surname} - - - {userItem.map((subItem) => ( - - - - ))} - - + + + + + {localUser.name} + + + {userItem.map((subItem) => ( + + { + if (subItem.url !== '/cabinet') { + clearTokens(); + } + }} + item={subItem} + /> + + ))} + + +
); diff --git a/src/widgets/navbar/ui/index.tsx b/src/widgets/navbar/ui/index.tsx index d4b9b68..3e33edb 100644 --- a/src/widgets/navbar/ui/index.tsx +++ b/src/widgets/navbar/ui/index.tsx @@ -22,7 +22,7 @@ const Navbar = () => { const menu = getMenu(t); return ( -
+
{/* Desktop Menu */} + + {/* Footer */} +
+

+ © 2026 Plagat.uz +

+
); diff --git a/src/widgets/cabinet/ui/dashboard/CtaCards.tsx b/src/widgets/cabinet/ui/dashboard/CtaCards.tsx index 6ecf177..c8cfcec 100644 --- a/src/widgets/cabinet/ui/dashboard/CtaCards.tsx +++ b/src/widgets/cabinet/ui/dashboard/CtaCards.tsx @@ -1,49 +1,33 @@ import React from 'react'; -import { FileSearch, BrainCircuit, ArrowRight } from 'lucide-react'; -import type { CabinetSection } from '../../lib/types'; +import { FileSearch, ArrowRight } from 'lucide-react'; +import Link from 'next/link'; +import SiCTACard from '@/features/modals/siModal/page'; -interface CtaCardsProps { - onNavigate: (section: CabinetSection) => void; -} +export const CtaCards = () => ( + <> +
+ {/* Plagiat */} + +
+ +
+ +

+ Plagiat tekshiruvi +

+

+ Hujjatingizni originallik uchun tekshiring +

+ + Yuborish + + -export const CtaCards: React.FC = ({ onNavigate }) => ( -
- {/* Plagiat */} - - - {/* SI */} - -
+ {/* SI */} + +
+ ); diff --git a/src/widgets/cabinet/ui/index.tsx b/src/widgets/cabinet/ui/index.tsx index 1e626ec..8cd585c 100644 --- a/src/widgets/cabinet/ui/index.tsx +++ b/src/widgets/cabinet/ui/index.tsx @@ -3,6 +3,7 @@ import React from 'react'; import dynamic from 'next/dynamic'; import { AnimatePresence, motion } from 'framer-motion'; import { Sidebar } from './Sidebar'; +import { CabinetNav } from './CabinetNav'; import { Dashboard } from './dashboard'; import { useCabinet } from '../lib/hooks/useCabinet'; import { @@ -91,7 +92,7 @@ export const CabinetLayout: React.FC = () => { const fullName = `${MOCK_USER.name} ${MOCK_USER.surname}`; return ( -
+
{ />
+ +
From d17b30d6ea1ad256abeaa80a1741ac10ff0449c5 Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Mon, 6 Apr 2026 18:04:56 +0500 Subject: [PATCH 09/29] profile page compleated --- src/widgets/cabinet/ui/Sidebar.tsx | 10 ++-------- src/widgets/cabinet/ui/dashboard/index.tsx | 11 +++-------- src/widgets/cabinet/ui/index.tsx | 18 +++--------------- 3 files changed, 8 insertions(+), 31 deletions(-) diff --git a/src/widgets/cabinet/ui/Sidebar.tsx b/src/widgets/cabinet/ui/Sidebar.tsx index e269c86..019ffd0 100644 --- a/src/widgets/cabinet/ui/Sidebar.tsx +++ b/src/widgets/cabinet/ui/Sidebar.tsx @@ -65,16 +65,10 @@ export const Sidebar: React.FC = ({ `} > {/* Brand */} -
-
-
- P -
- Plagat -
+
@@ -160,15 +134,10 @@ export const HistoryTableRow: React.FC = ({ item }) => { setIsPaymentOpen(false)} - price={{ - service_fee: 41200, - discount: 5200, - total_price: 36000, - currency: 'UZS', - }} - onConfirmPayment={() => { - handleSubmit({ document_id: Number(item.order_id) }); - }} + price={price} + onConfirmPayment={() => + payment.mutate({ order_id: Number(item.order_id) }) + } isLoading={payment.isPending} /> From 3dd915734617647358f930d03c06ccc44e327d2e Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Mon, 6 Apr 2026 21:04:04 +0500 Subject: [PATCH 11/29] si table connected to backend --- src/shared/request/apiRequest.ts | 2 +- src/shared/request/links.ts | 4 + src/widgets/cabinet/lib/hooks/useSiHistory.ts | 20 ++ src/widgets/cabinet/lib/types.ts | 15 + src/widgets/cabinet/ui/index.tsx | 4 +- src/widgets/cabinet/ui/tables/SiTable.tsx | 303 ++++++++++++------ src/widgets/history/ui/historyPage.tsx | 29 +- 7 files changed, 270 insertions(+), 107 deletions(-) create mode 100644 src/widgets/cabinet/lib/hooks/useSiHistory.ts diff --git a/src/shared/request/apiRequest.ts b/src/shared/request/apiRequest.ts index 2e55df5..129da6e 100644 --- a/src/shared/request/apiRequest.ts +++ b/src/shared/request/apiRequest.ts @@ -9,7 +9,7 @@ import { getRouteLang } from './getLanguage'; // ─── Constants ───────────────────────────────────────────────────────────────── // const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL; -const baseUrl = 'https://api.anti-plagiat.uz/api/v1'; +const baseUrl = 'https://dev-api.anti-plagiat.uz/api/v1'; const DEFAULT_LOCALE = 'uz'; // fallback locale for redirect // ─── Token helpers ───────────────────────────────────────────────────────────── diff --git a/src/shared/request/links.ts b/src/shared/request/links.ts index f9a684e..921667d 100644 --- a/src/shared/request/links.ts +++ b/src/shared/request/links.ts @@ -7,4 +7,8 @@ export const links = { payment: (order_id: number) => `/users/payme/link/${order_id}/`, sertifikat: (document_id: number) => `/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_create: '/shared/ai_document/create/', }; diff --git a/src/widgets/cabinet/lib/hooks/useSiHistory.ts b/src/widgets/cabinet/lib/hooks/useSiHistory.ts new file mode 100644 index 0000000..9ce2dcb --- /dev/null +++ b/src/widgets/cabinet/lib/hooks/useSiHistory.ts @@ -0,0 +1,20 @@ +'use client'; +import { useQuery } from '@tanstack/react-query'; +import { apiRequest } from '@/shared/request/apiRequest'; +import { links } from '@/shared/request/links'; +import type { SiDocument } from '../types'; + +export const useSiHistory = () => { + const { data, isLoading, isError, refetch } = useQuery({ + queryKey: ['si-history'], + queryFn: () => apiRequest('GET', links.si), + select: (res) => res.data, + }); + + return { + items: data ?? [], + isLoading, + isError, + refetch, + }; +}; diff --git a/src/widgets/cabinet/lib/types.ts b/src/widgets/cabinet/lib/types.ts index 13f8b5b..f69beaa 100644 --- a/src/widgets/cabinet/lib/types.ts +++ b/src/widgets/cabinet/lib/types.ts @@ -45,6 +45,21 @@ export interface Payment { status: 'paid' | 'pending' | 'failed'; } +// ─── SI Document (API) ───────────────────────────────────────────────────────── + +export interface SiDocument { + id: number; + title: string; + file: string; + created_at: string; + updated_at: string; + result: null | unknown; + state: 'paid' | 'unpaid'; + ai_order_id: string; + total_words: number; + si_percantage: number | null; +} + export interface CabinetStats { total: number; thisMonth: number; diff --git a/src/widgets/cabinet/ui/index.tsx b/src/widgets/cabinet/ui/index.tsx index ff62ab3..1d51bd2 100644 --- a/src/widgets/cabinet/ui/index.tsx +++ b/src/widgets/cabinet/ui/index.tsx @@ -6,7 +6,7 @@ import { Sidebar } from './Sidebar'; import { CabinetNav } from './CabinetNav'; import { Dashboard } from './dashboard'; import { useCabinet } from '../lib/hooks/useCabinet'; -import { MOCK_USER, MOCK_STATS, MOCK_SI, MOCK_PAYMENTS } from '../lib/mock'; +import { MOCK_USER, MOCK_STATS, MOCK_PAYMENTS } from '../lib/mock'; import type { CabinetSection } from '../lib/types'; // ─── Lazy sections (separate JS chunks) ─────────────────────────────────────── @@ -49,7 +49,7 @@ function SectionContent({ section }: { section: CabinetSection }) { case 'plagiat': return ; case 'si': - return ; + return ; case 'payments': return ; case 'profile': diff --git a/src/widgets/cabinet/ui/tables/SiTable.tsx b/src/widgets/cabinet/ui/tables/SiTable.tsx index 0f5e840..94b7b07 100644 --- a/src/widgets/cabinet/ui/tables/SiTable.tsx +++ b/src/widgets/cabinet/ui/tables/SiTable.tsx @@ -1,22 +1,37 @@ +'use client'; import React from 'react'; -import { FileText, Clock, CheckCircle, XCircle } from 'lucide-react'; -import type { SiCheck } from '../../lib/types'; +import { Download, CreditCard } from 'lucide-react'; +import { useMutation } from '@tanstack/react-query'; +import { useSiHistory } from '../../lib/hooks/useSiHistory'; +import { formatDate } from '@/widgets/history/lib/utils'; +import { apiRequest } from '@/shared/request/apiRequest'; +import { links } from '@/shared/request/links'; +import { toast } from 'react-toastify'; +import type { SiDocument } from '../../lib/types'; -// ─── Helpers ─────────────────────────────────────────────────────────────────── +// ─── State badge ─────────────────────────────────────────────────────────────── -const STATUS_MAP = { - completed: { - label: 'Yakunlandi', - icon: CheckCircle, - cls: 'text-emerald-600 bg-emerald-50', - }, - pending: { - label: 'Kutilmoqda', - icon: Clock, - cls: 'text-amber-600 bg-amber-50', - }, - failed: { label: 'Xato', icon: XCircle, cls: 'text-red-600 bg-red-50' }, -} as const; +const StateBadge: React.FC<{ state: 'paid' | 'unpaid' }> = ({ state }) => { + const isPaid = state === 'paid'; + return ( + + + {isPaid ? "To'langan" : "To'lanmagan"} + + ); +}; + +// ─── SI% badge ───────────────────────────────────────────────────────────────── const SiPercentBadge: React.FC<{ value: number }> = ({ value }) => { const cls = @@ -34,95 +49,187 @@ const SiPercentBadge: React.FC<{ value: number }> = ({ value }) => { ); }; -// ─── Component ───────────────────────────────────────────────────────────────── +// ─── Skeleton ────────────────────────────────────────────────────────────────── -interface SiTableProps { - data: SiCheck[]; -} +const SkeletonRow = () => ( + + {Array.from({ length: 7 }).map((_, i) => ( + +
+ + ))} + +); -export const SiTable: React.FC = ({ data }) => ( -
-
-

SI detektor

-

- {data.length} ta tekshiruv -

-
+// ─── Empty / Error states ────────────────────────────────────────────────────── -
-
- - - - {['#', 'Fayl', "So'z", 'SI%', 'Sana', 'Holat', 'Hisobot'].map( - (h) => ( +const EmptyState = () => ( + + + +); + +const ErrorState = () => ( + + + +); + +// ─── Row ─────────────────────────────────────────────────────────────────────── + +const SiRow: React.FC<{ item: SiDocument; index: number }> = ({ + item, + index, +}) => { + const pay = useMutation({ + mutationKey: ['si-payment', item.id], + mutationFn: () => + apiRequest<{ payment_link: string }>('POST', links.si_payment(item.id)), + onSuccess: (res) => { + window.open(res.data.payment_link, '_self'); + }, + onError: (err) => { + toast.error(err instanceof Error ? err.message : 'Xatolik yuz berdi'); + }, + }); + + return ( + + {/* # */} + + + {/* Sarlavha */} + + + {/* Fayl */} + + + {/* So'z */} + + + {/* SI% */} + + + {/* Sana */} + + + {/* Holat */} + + + {/* Amal */} + + + ); +}; + +// ─── SiTable ─────────────────────────────────────────────────────────────────── + +export const SiTable: React.FC = () => { + const { items, isLoading, isError } = useSiHistory(); + + return ( +
+
+
+

SI detektor

+

+ {isLoading ? '...' : `${items.length} ta tekshiruv`} +

+
+
+ +
+
+
+ Hozircha SI tekshiruvlar yo'q +
+ Ma'lumotlarni yuklashda xatolik yuz berdi +
+ {String(index).padStart(2, '0')} + + + {item.title || '—'} + + + {item.file ? ( + + + Fayl + + ) : ( + + )} + + {item.total_words > 0 ? item.total_words.toLocaleString() : '—'} + + {item.si_percantage != null && item.result != null ? ( + + ) : ( + + )} + + {formatDate(item.created_at)} + + + + {item.state === 'unpaid' ? ( + + ) : ( + + )} +
+ + + {[ + '#', + 'Sarlavha', + 'Fayl', + "So'z", + 'SI%', + 'Sana', + 'Holat', + 'Amal', + ].map((h) => ( - ), - )} - - - - {data.map((row) => { - const s = STATUS_MAP[row.status]; - const Icon = s.icon; - return ( - - - - - - - - - - ); - })} - -
{h}
- {String(row.id).padStart(2, '0')} - - - {row.file} - - - {row.words.toLocaleString()} - - {row.status === 'pending' ? ( - - ) : ( - - )} - - {row.date} - - - - {s.label} - - - {row.reportUrl ? ( - - - - ) : ( - - )} -
+ ))} + + + + {isLoading && + Array.from({ length: 5 }).map((_, i) => ( + + ))} + {isError && } + {!isLoading && !isError && items.length === 0 && } + {!isLoading && + !isError && + items.map((item, i) => ( + + ))} + + +
-
-); + ); +}; diff --git a/src/widgets/history/ui/historyPage.tsx b/src/widgets/history/ui/historyPage.tsx index 6423a44..b486613 100644 --- a/src/widgets/history/ui/historyPage.tsx +++ b/src/widgets/history/ui/historyPage.tsx @@ -4,18 +4,35 @@ import { useTranslations } from 'next-intl'; import { useHistory } from '../lib/useHistory'; import { HistoryTable } from './historyTable'; import { Pagination } from './pagination'; +import Link from 'next/link'; +import { Plus } from 'lucide-react'; +import { usePathname } from '@/shared/config/i18n/navigation'; // ─── Page Header ─────────────────────────────────────────────────────────────── const PageHeader: React.FC = () => { const t = useTranslations('HistoryPage'); - + const pathname = usePathname(); return ( -
-

- {t('title')} -

-

{t('description')}

+
+
+

+ {t('title')} +

+

{t('description')}

+
+ + +

+ Plagiat tekshiruvi +

+
); }; From 88f22a342a3be7d53794e2a2ca7622bf0df8bb6f Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Tue, 7 Apr 2026 11:52:29 +0500 Subject: [PATCH 12/29] complated si check request --- src/features/auth/login/lib/useLoginForm.ts | 1 - src/features/modals/siModal/page.tsx | 43 ++++++++----------- .../modals/siModal/ui/fileUploadModal.tsx | 2 - src/features/modals/siModal/utils/tyeps.ts | 1 - .../modals/siModal/utils/useFileUpload.ts | 24 ++++++++++- src/shared/request/links.ts | 2 + src/widgets/cabinet/ui/dashboard/CtaCards.tsx | 2 +- src/widgets/cabinet/ui/tables/SiTable.tsx | 4 +- 8 files changed, 48 insertions(+), 31 deletions(-) diff --git a/src/features/auth/login/lib/useLoginForm.ts b/src/features/auth/login/lib/useLoginForm.ts index a141fe6..3473f87 100644 --- a/src/features/auth/login/lib/useLoginForm.ts +++ b/src/features/auth/login/lib/useLoginForm.ts @@ -74,7 +74,6 @@ export function useLoginForm() { } loginReqest.mutate({ phone: `998${phone}`, password: password }); - sessionStorage.setItem('prev_page', 'login'); }; return { diff --git a/src/features/modals/siModal/page.tsx b/src/features/modals/siModal/page.tsx index 2254d0c..a74c03e 100644 --- a/src/features/modals/siModal/page.tsx +++ b/src/features/modals/siModal/page.tsx @@ -1,28 +1,11 @@ 'use client'; import { useState } from 'react'; -import { ArrowRight, BrainCircuit } from 'lucide-react'; +import { ArrowRight, BrainCircuit, Plus } from 'lucide-react'; import { FileUploadModal } from './ui/fileUploadModal'; -export default function SiCTACard() { +export function SiCTACard() { const [isOpen, setIsOpen] = useState(false); - const [lastSubmission, setLastSubmission] = useState<{ - name: string; - words: number; - } | null>(null); - - const handleSubmit = ( - documentName: string, - _file: File, - wordCount: number, - ) => { - // Here you would send the file to your backend for plagiarism check. - // The word count is already computed client-side for instant pricing display. - console.log(lastSubmission); - console.log('Submitting:', { documentName, wordCount }); - setLastSubmission({ name: documentName, words: wordCount }); - setIsOpen(false); - }; return ( <> @@ -42,11 +25,23 @@ export default function SiCTACard() { Yuborish - setIsOpen(false)} - onSubmit={handleSubmit} - /> + setIsOpen(false)} /> + + ); +} + +export function SiButton() { + const [isOpen, setIsOpen] = useState(false); + return ( + <> + + setIsOpen(false)} /> ); } diff --git a/src/features/modals/siModal/ui/fileUploadModal.tsx b/src/features/modals/siModal/ui/fileUploadModal.tsx index 7abb502..a6daca2 100644 --- a/src/features/modals/siModal/ui/fileUploadModal.tsx +++ b/src/features/modals/siModal/ui/fileUploadModal.tsx @@ -12,7 +12,6 @@ import { DropZone } from './dropZone'; export function FileUploadModal({ isOpen, onClose, - onSubmit, pricing = DEFAULT_PRICING, }: FileUploadModalProps) { const { @@ -59,7 +58,6 @@ export function FileUploadModal({ const handleSubmit = () => { if (!canSubmit || !uploadedFile) return; - onSubmit(documentName.trim(), uploadedFile.file, wordCount); }; return ( diff --git a/src/features/modals/siModal/utils/tyeps.ts b/src/features/modals/siModal/utils/tyeps.ts index 1c705e0..2a8aa44 100644 --- a/src/features/modals/siModal/utils/tyeps.ts +++ b/src/features/modals/siModal/utils/tyeps.ts @@ -14,7 +14,6 @@ export interface PricingConfig { export interface FileUploadModalProps { isOpen: boolean; onClose: () => void; - onSubmit: (documentName: string, file: File, wordCount: number) => void; pricing?: PricingConfig; } diff --git a/src/features/modals/siModal/utils/useFileUpload.ts b/src/features/modals/siModal/utils/useFileUpload.ts index c65a766..b16fa56 100644 --- a/src/features/modals/siModal/utils/useFileUpload.ts +++ b/src/features/modals/siModal/utils/useFileUpload.ts @@ -3,6 +3,10 @@ import { useState, useCallback, useRef } from 'react'; import { UploadedFile } from './tyeps'; import { countWordsFromFile, 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'; interface UseFileUploadReturn { documentName: string; @@ -28,6 +32,17 @@ export function useFileUpload(): UseFileUploadReturn { const [error, setError] = useState(null); const fileInputRef = useRef(null); + const wordCount = useMutation({ + mutationFn: (data: FormData) => apiRequest('POST', links.si_create, data), + onSuccess: (res) => { + console.log(res); + }, + onError: (err) => { + console.log(err instanceof Error ? err.message : 'Unknown error'); + toast.error(err instanceof Error ? err.message : 'Unknown error'); + }, + }); + const validateFile = (file: File): string | null => { const ext = '.' + file.name.split('.').pop()?.toLowerCase(); if (!SUPPORTED_EXTENSIONS.includes(ext)) { @@ -77,7 +92,14 @@ export function useFileUpload(): UseFileUploadReturn { wordCount: result.count, }); } - + console.log('running'); + if (!file) return; + console.log('running inner'); + const fd = new FormData(); + fd.append('title', file.name.replace(/\.[^/.]+$/, '')); + fd.append('file', file); + wordCount.mutate(fd); + console.log('stop'); setIsProcessing(false); }, []); diff --git a/src/shared/request/links.ts b/src/shared/request/links.ts index 921667d..8a705bb 100644 --- a/src/shared/request/links.ts +++ b/src/shared/request/links.ts @@ -11,4 +11,6 @@ export const links = { si_id: (si_id: number) => `/shared/ai_document/list/${si_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/', }; diff --git a/src/widgets/cabinet/ui/dashboard/CtaCards.tsx b/src/widgets/cabinet/ui/dashboard/CtaCards.tsx index c8cfcec..685e1dd 100644 --- a/src/widgets/cabinet/ui/dashboard/CtaCards.tsx +++ b/src/widgets/cabinet/ui/dashboard/CtaCards.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { FileSearch, ArrowRight } from 'lucide-react'; import Link from 'next/link'; -import SiCTACard from '@/features/modals/siModal/page'; +import { SiCTACard } from '@/features/modals/siModal/page'; export const CtaCards = () => ( <> diff --git a/src/widgets/cabinet/ui/tables/SiTable.tsx b/src/widgets/cabinet/ui/tables/SiTable.tsx index 94b7b07..b9be12a 100644 --- a/src/widgets/cabinet/ui/tables/SiTable.tsx +++ b/src/widgets/cabinet/ui/tables/SiTable.tsx @@ -8,6 +8,7 @@ import { apiRequest } from '@/shared/request/apiRequest'; import { links } from '@/shared/request/links'; import { toast } from 'react-toastify'; import type { SiDocument } from '../../lib/types'; +import { SiButton } from '@/features/modals/siModal/page'; // ─── State badge ─────────────────────────────────────────────────────────────── @@ -181,13 +182,14 @@ export const SiTable: React.FC = () => { return (
-
+

SI detektor

{isLoading ? '...' : `${items.length} ta tekshiruv`}

+
From df485b5797a9f16ec340786efe4b684edd1969ea Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Tue, 7 Apr 2026 12:46:37 +0500 Subject: [PATCH 13/29] payments history table connected to backend --- src/widgets/cabinet/ui/index.tsx | 4 +- .../cabinet/ui/profile/ProfileForm.tsx | 10 +- .../cabinet/ui/tables/PaymentsTable.tsx | 268 ++++++++++++------ src/widgets/history/ui/historyTableRow.tsx | 4 +- 4 files changed, 183 insertions(+), 103 deletions(-) diff --git a/src/widgets/cabinet/ui/index.tsx b/src/widgets/cabinet/ui/index.tsx index 1d51bd2..c24cd30 100644 --- a/src/widgets/cabinet/ui/index.tsx +++ b/src/widgets/cabinet/ui/index.tsx @@ -6,7 +6,7 @@ import { Sidebar } from './Sidebar'; import { CabinetNav } from './CabinetNav'; import { Dashboard } from './dashboard'; import { useCabinet } from '../lib/hooks/useCabinet'; -import { MOCK_USER, MOCK_STATS, MOCK_PAYMENTS } from '../lib/mock'; +import { MOCK_USER, MOCK_STATS } from '../lib/mock'; import type { CabinetSection } from '../lib/types'; // ─── Lazy sections (separate JS chunks) ─────────────────────────────────────── @@ -51,7 +51,7 @@ function SectionContent({ section }: { section: CabinetSection }) { case 'si': return ; case 'payments': - return ; + return ; case 'profile': return ; } diff --git a/src/widgets/cabinet/ui/profile/ProfileForm.tsx b/src/widgets/cabinet/ui/profile/ProfileForm.tsx index 35cb0dd..4737f65 100644 --- a/src/widgets/cabinet/ui/profile/ProfileForm.tsx +++ b/src/widgets/cabinet/ui/profile/ProfileForm.tsx @@ -1,6 +1,6 @@ 'use client'; import React from 'react'; -import { User, Mail, Phone, Lock, Save, CheckCircle } from 'lucide-react'; +import { User, Phone, Lock, Save, CheckCircle } from 'lucide-react'; import { useProfile } from '../../lib/hooks/useProfile'; import type { UserProfile } from '../../lib/types'; @@ -81,14 +81,6 @@ export const ProfileForm: React.FC = ({ initial }) => { icon={User} placeholder="Karimov" /> - handleChange('email', v)} - type="email" - icon={Mail} - placeholder="ali@example.com" - /> => + apiRequest('GET', links.pay_history).then( + (res) => res.data as Inspection[], + ), + }); -export const PaymentsTable: React.FC = ({ data }) => ( -
-
-

- To'lovlar tarixi -

-

- {data.length} ta to'lov -

-
+ const payment = useMutation({ + mutationKey: ['payload'], + mutationFn: ({ order_id }: { order_id: number }) => + apiRequest<{ payment_link: string }>('POST', links.payment(order_id)), + onSuccess: (res) => { + console.log('payment res: ', res); + window.open(res.data.payment_link, '_self'); + //route.push(`/${document_id}`); + setIsPaymentOpen(false); + }, + onError: (err) => { + const message = + err instanceof Error ? err.message : 'An unexpected error occurred.'; + toast.error(message); + setIsPaymentOpen(false); + }, + }); -
-
- - - - {['#', 'Xizmat', 'Summa', 'Chegirma', 'Sana', 'Holat'].map( - (h) => ( - - ), - )} - - - - {data.map((row) => { - const s = STATUS_MAP[row.status]; - const Icon = s.icon; - return ( - - - - - + + + {data.map((row) => { + const service_fee = row.total_price + row.discount; + return ( + + + + + + + + setIsPaymentOpen(false)} + price={{ + service_fee: Number(service_fee), + discount: Number(row.discount) || 0, + total_price: Number(row.total_price) || 0, + currency: 'UZS', + }} + onConfirmPayment={() => { + handleSubmit({ document_id: 0 }); + }} + isLoading={payment.isPending} + /> + + ); + })} + +
- {h} -
- {String(row.id).padStart(2, '0')} - - {row.service} - - {row.amount.toLocaleString()} UZS - - {row.discount > 0 ? ( - - -{row.discount.toLocaleString()} UZS - - ) : ( - + const handleSubmit = ({ document_id }: { document_id: number }) => { + if (document_id === 0) { + toast.error('Id not found'); + return; + } + payment.mutate({ order_id: document_id }); + }; + + return ( + <> +
+
+

+ To'lovlar tarixi +

+

+ {data?.length ?? 0} ta to'lov +

+
+ +
+ {isLoading ? ( +
+ + Yuklanmoqda... +
+ ) : !data || data.length === 0 ? ( +
+ +

To'lovlar tarixi mavjud emas

+
+ ) : ( +
+ + + + {['#', 'Xizmat', 'Summa', 'Chegirma', 'Sana', 'Holat'].map( + (h) => ( + + ), )} - - - - - ); - })} - -
+ {h} + - {row.date} - - - - {s.label} - -
+
+ {String(row.id).padStart(2, '0')} + + {row.turi} + + {formatPrice(row.total_price)} UZS + + {row.discount ? ( + + -{formatPrice(row.discount)} UZS + + ) : ( + + )} + + {formatDate(row.created_at)} + + {row.state ? ( + { + if (row.state === 'unpaid') { + setIsPaymentOpen(true); + } + }} + className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-medium text-emerald-600 bg-emerald-50" + > + + + ) : ( + + + Noma'lum + + )} +
+
+ )} +
-
-
-); + + ); +} diff --git a/src/widgets/history/ui/historyTableRow.tsx b/src/widgets/history/ui/historyTableRow.tsx index 94b574c..ea94eab 100644 --- a/src/widgets/history/ui/historyTableRow.tsx +++ b/src/widgets/history/ui/historyTableRow.tsx @@ -13,7 +13,9 @@ import { PaymentModal } from '@/features/modals/paymentModal/ui/Paymentmodal'; // ─── State badge ─────────────────────────────────────────────────────────────── -const StateBadge: React.FC<{ state: 'paid' | 'unpaid' }> = ({ state }) => { +export const StateBadge: React.FC<{ state: 'paid' | 'unpaid' }> = ({ + state, +}) => { const isPaid = state === 'paid'; return ( Date: Tue, 7 Apr 2026 13:11:23 +0500 Subject: [PATCH 14/29] dahsboard connected to backend --- src/shared/request/links.ts | 1 + .../cabinet/ui/dashboard/StatsCards.tsx | 72 +++++++++++++------ src/widgets/cabinet/ui/dashboard/index.tsx | 6 +- src/widgets/cabinet/ui/index.tsx | 2 +- 4 files changed, 53 insertions(+), 28 deletions(-) diff --git a/src/shared/request/links.ts b/src/shared/request/links.ts index 8a705bb..d83b629 100644 --- a/src/shared/request/links.ts +++ b/src/shared/request/links.ts @@ -13,4 +13,5 @@ export const links = { si_create: '/shared/ai_document/create/', document_types: '/shared/document_types/', pay_history: '/shared/orders/all/', + statistics: '/shared/statistics/', }; diff --git a/src/widgets/cabinet/ui/dashboard/StatsCards.tsx b/src/widgets/cabinet/ui/dashboard/StatsCards.tsx index a04f6df..68055f2 100644 --- a/src/widgets/cabinet/ui/dashboard/StatsCards.tsx +++ b/src/widgets/cabinet/ui/dashboard/StatsCards.tsx @@ -1,6 +1,16 @@ import React from 'react'; -import { TrendingUp, Calendar, Tag, Wallet } from 'lucide-react'; -import type { CabinetStats } from '../../lib/types'; +import { TrendingUp, Calendar, Wallet, Loader2 } from 'lucide-react'; +import { useQuery } from '@tanstack/react-query'; +import { apiRequest } from '@/shared/request/apiRequest'; +import { links } from '@/shared/request/links'; + +// ─── Types ───────────────────────────────────────────────────────────────────── + +type Stats = { + total_documents: number; + this_month_documents: number; + paid_price: number; +}; // ─── Single stat card ────────────────────────────────────────────────────────── @@ -33,46 +43,62 @@ const StatCard: React.FC = ({
); +const StatCardSkeleton = () => ( +
+
+
+
+
+); + // ─── Grid ────────────────────────────────────────────────────────────────────── -interface StatsCardsProps { - stats: CabinetStats; -} +export const StatsCards = () => { + const { data, isLoading } = useQuery({ + queryKey: ['statistics'], + queryFn: (): Promise => + apiRequest('GET', links.statistics).then((res) => res.data as Stats), + }); -export const StatsCards: React.FC = ({ stats }) => { - const discountPct = Math.round( - (stats.discountUsed / stats.discountTotal) * 100, - ); + if (isLoading) { + return ( +
+ + + +
+ ); + } + + if (!data) { + return ( +
+ + Ma'lumot topilmadi +
+ ); + } return ( -
+
- diff --git a/src/widgets/cabinet/ui/dashboard/index.tsx b/src/widgets/cabinet/ui/dashboard/index.tsx index 679bac5..6894ae8 100644 --- a/src/widgets/cabinet/ui/dashboard/index.tsx +++ b/src/widgets/cabinet/ui/dashboard/index.tsx @@ -2,14 +2,12 @@ import React from 'react'; import { CtaCards } from './CtaCards'; import { StatsCards } from './StatsCards'; import { ModulesSection } from './ModulesSection'; -import type { CabinetStats } from '../../lib/types'; interface DashboardProps { - stats: CabinetStats; userName: string; } -export const Dashboard: React.FC = ({ stats, userName }) => ( +export const Dashboard: React.FC = ({ userName }) => (

@@ -20,7 +18,7 @@ export const Dashboard: React.FC = ({ stats, userName }) => (

- +

diff --git a/src/widgets/cabinet/ui/index.tsx b/src/widgets/cabinet/ui/index.tsx index c24cd30..b80aa61 100644 --- a/src/widgets/cabinet/ui/index.tsx +++ b/src/widgets/cabinet/ui/index.tsx @@ -45,7 +45,7 @@ const ProfileSection = dynamic( function SectionContent({ section }: { section: CabinetSection }) { switch (section) { case 'dashboard': - return ; + return ; case 'plagiat': return ; case 'si': From 8f75349297c3e62975d8dc1fa41c781e0c2af38e Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Tue, 7 Apr 2026 16:40:28 +0500 Subject: [PATCH 15/29] SI detail page cretaed --- src/app/[locale]/si/[id]/page.tsx | 10 + src/widgets/cabinet/ui/tables/SiTable.tsx | 14 +- src/widgets/detail/SiDetailPage.tsx | 356 ++++++++++++++++++ src/widgets/fileUpload/lib/usePlagiraism.ts | 1 + .../fileUpload/ui/Plagiraismcheckform.tsx | 29 +- src/widgets/fileUpload/ui/documentsType.tsx | 53 +++ 6 files changed, 439 insertions(+), 24 deletions(-) create mode 100644 src/app/[locale]/si/[id]/page.tsx create mode 100644 src/widgets/detail/SiDetailPage.tsx create mode 100644 src/widgets/fileUpload/ui/documentsType.tsx diff --git a/src/app/[locale]/si/[id]/page.tsx b/src/app/[locale]/si/[id]/page.tsx new file mode 100644 index 0000000..7ec954b --- /dev/null +++ b/src/app/[locale]/si/[id]/page.tsx @@ -0,0 +1,10 @@ +import SiDetailPage from '@/widgets/detail/SiDetailPage'; + +interface Props { + params: Promise<{ id: string }>; +} + +export default async function SiDetail({ params }: Props) { + const { id } = await params; + return ; +} diff --git a/src/widgets/cabinet/ui/tables/SiTable.tsx b/src/widgets/cabinet/ui/tables/SiTable.tsx index b9be12a..1504db1 100644 --- a/src/widgets/cabinet/ui/tables/SiTable.tsx +++ b/src/widgets/cabinet/ui/tables/SiTable.tsx @@ -1,6 +1,6 @@ 'use client'; import React from 'react'; -import { Download, CreditCard } from 'lucide-react'; +import { Download, CreditCard, Eye } from 'lucide-react'; import { useMutation } from '@tanstack/react-query'; import { useSiHistory } from '../../lib/hooks/useSiHistory'; import { formatDate } from '@/widgets/history/lib/utils'; @@ -9,6 +9,7 @@ import { links } from '@/shared/request/links'; import { toast } from 'react-toastify'; import type { SiDocument } from '../../lib/types'; import { SiButton } from '@/features/modals/siModal/page'; +import { useRouter, useParams } from 'next/navigation'; // ─── State badge ─────────────────────────────────────────────────────────────── @@ -89,6 +90,9 @@ const SiRow: React.FC<{ item: SiDocument; index: number }> = ({ item, index, }) => { + const router = useRouter(); + const { locale } = useParams() as { locale: string }; + const pay = useMutation({ mutationKey: ['si-payment', item.id], mutationFn: () => @@ -168,7 +172,13 @@ const SiRow: React.FC<{ item: SiDocument; index: number }> = ({ {pay.isPending ? '...' : "To'lash"} ) : ( - + )} diff --git a/src/widgets/detail/SiDetailPage.tsx b/src/widgets/detail/SiDetailPage.tsx new file mode 100644 index 0000000..12b83cb --- /dev/null +++ b/src/widgets/detail/SiDetailPage.tsx @@ -0,0 +1,356 @@ +'use client'; + +import React, { useEffect } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { useParams } from 'next/navigation'; +import { apiRequest } from '@/shared/request/apiRequest'; +import { links } from '@/shared/request/links'; +import { Download, CloudDownload } from 'lucide-react'; + +// ── Types ──────────────────────────────────────────────────────────────────── + +type SiResult = { + original: number; + ai_possible: number; + ai: number; +}; + +type SiDetail = { + id: number; + title: string; + file: string; + created_at: string; + updated_at: string; + state: 'paid' | 'unpaid'; + total_words: number; + si_percantage: number | null; + result: SiResult | null; + file_size?: number; + file_extension?: string; + total_price?: number | string; + user?: { name: string; surname: string }; +}; + +// ── Helpers ─────────────────────────────────────────────────────────────────── + +function formatDate(iso: string): string { + return new Date(iso).toLocaleString('uz-UZ', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + }); +} + +function fileExtension(url: string): string { + const name = url.split('/').pop() ?? ''; + const ext = name.split('.').pop(); + return ext ? `.${ext}` : '—'; +} + +function fileName(url: string): string { + return url.split('/').pop() ?? '—'; +} + +function fileSizeMb(bytes?: number): string { + if (!bytes) return '—'; + return `${(bytes / 1024 / 1024).toFixed(2)} MB`; +} + +// ── Sub-components ──────────────────────────────────────────────────────────── + +function InfoRow({ label, value }: { label: string; value: React.ReactNode }) { + return ( +
+ {label} + + {value} + +
+ ); +} + +function SiBar({ + label, + value, + color, +}: { + label: string; + value: number; + color: string; +}) { + return ( +
+
+ {label} + {value}% +
+
+
+
+
+ ); +} + +// ── Skeleton ────────────────────────────────────────────────────────────────── + +function Skeleton({ className }: { className?: string }) { + return ( +
+ ); +} + +function LoadingSkeleton() { + return ( +
+
+
+ +
+ {Array.from({ length: 8 }).map((_, i) => ( +
+ + +
+ ))} +
+
+ {Array.from({ length: 3 }).map((_, i) => ( + + ))} +
+
+
+ ); +} + +// ── Main Page ───────────────────────────────────────────────────────────────── + +export default function SiDetailPage({ id }: { id: number }) { + const { locale } = useParams() as { locale: string }; + useEffect(() => { + console.log(locale); + }, []); + + const { + data: doc, + isLoading, + isError, + } = useQuery({ + queryKey: ['si-detail', id], + queryFn: (): Promise => + apiRequest('GET', links.si_id(id)).then((res) => res.data as SiDetail), + enabled: !!id, + staleTime: 1000 * 60 * 5, + }); + + if (isLoading) return ; + + if (isError || !doc) { + return ( +
+
+

+ Ma'lumot topilmadi +

+ +
+
+ ); + } + + // Derive SI percentages + const original = doc.result?.original ?? 100 - (doc.si_percantage ?? 0); + const aiPossible = doc.result?.ai_possible ?? 0; + const ai = doc.result?.ai ?? doc.si_percantage ?? 0; + + return ( +
+ {/* ── Header ── */} +
+
+ +

+ {doc.title || 'SI tekshiruv'} +

+
+
+ +
+ {/* ── Section 1: Asosiy ma'lumotlar ── */} +
+ {/* Section header */} +
+

+ Asosiy ma'lumotlar +

+
+
+ +
+ {/* Sub-header */} +

+ Hujjat haqida ma'lumotlar +

+ + + {doc.user && ( + + )} + + + 0 + ? doc.total_words.toLocaleString('uz-UZ') + : '—' + } + /> + + {doc.file_size !== undefined && ( + + )} + {doc.total_price !== undefined && ( + + )} + + {/* Download button */} + {doc.file && ( + + )} +
+
+ + {/* ── Section 2: SI detektor natijalari ── */} +
+ {/* Header row */} +
+
+ {/* PDF icon */} +
+ + + + PDF + + +
+
+

+ Hujjatning SI detektori natijalari +

+

+ Ushbu oynada foydalanuvchi tomonidan yuklangan matn + sun'iy intellekt (SI) yordamida yozilgan bo'lish + ehtimoli bo'yicha tahlil natijalari aks etirilgan. + Detektor matnning stilistik, grammatik va semantik + xususiyatlarini baholab, uning qanchalik darajada sun'iy + intellekt tomonidan generatsiya qilingan bo'lishi + mumkinligini foizlik ko'rinishida ko'rsatadi. +

+
+
+ + {doc.file && ( + + + Yuklab olish + + )} +
+ + {/* Bars */} +
+ + + +
+
+
+
+ ); +} diff --git a/src/widgets/fileUpload/lib/usePlagiraism.ts b/src/widgets/fileUpload/lib/usePlagiraism.ts index 02c8152..d520bbd 100644 --- a/src/widgets/fileUpload/lib/usePlagiraism.ts +++ b/src/widgets/fileUpload/lib/usePlagiraism.ts @@ -140,6 +140,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); checkdocumentRequest.mutate(fd); }, [form], diff --git a/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx b/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx index 21de95b..7e202b8 100644 --- a/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx +++ b/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx @@ -11,10 +11,10 @@ import { } from './Plagiraismui'; import { usePlagiarismForm } from '../lib/usePlagiraism'; import { useTranslations } from 'next-intl'; -import { DOCUMENT_TYPES } from '@/features/modals/sertificateModal/types'; import { PaymentModal } from '@/features/modals/paymentModal/ui/Paymentmodal'; +import DocumentsTypes from './documentsType'; -const inputCls = ` +export const inputCls = ` w-full px-3.5 py-3.5 text-[14px] text-slate-800 bg-blue-50 border border-blue-200 rounded-xl placeholder:text-blue-400 @@ -177,26 +177,11 @@ export function PlagiarismCheckForm() {
{/* Document type */} - - - + {/* Submit */} void; + disabled?: boolean; +} + +export default function DocumentsTypes({ + value, + onChange, + disabled, +}: DocumentsTypesProps) { + const { data, isLoading } = useQuery({ + queryKey: ['document_types'], + queryFn: (): Promise => + apiRequest('GET', links.document_types).then( + (res) => res.data as DocumentType[], + ), + }); + + return ( + + + + ); +} From 2baf9703feb259c1a36ac9af6cf8ef39ebc26ade Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Tue, 7 Apr 2026 19:02:03 +0500 Subject: [PATCH 16/29] plagiatcheck part complated base new request types --- src/app/[locale]/plagat/page.tsx | 2 +- src/features/modals/paymentModal/lib/types.ts | 1 - .../modals/paymentModal/ui/Pricesummary.tsx | 8 +- .../modals/siModal/ui/fileUploadModal.tsx | 17 +- src/features/modals/siModal/utils/pricing.ts | 8 - src/features/modals/siModal/utils/tyeps.ts | 3 +- .../modals/siModal/utils/useFileUpload.ts | 179 +++++++++++++----- src/shared/config/i18n/messages/en.json | 2 +- src/shared/config/i18n/messages/ru.json | 2 +- src/shared/config/i18n/messages/uz.d.json.ts | 2 +- src/shared/config/i18n/messages/uz.json | 2 +- src/shared/request/links.ts | 5 +- .../{fileUpload => plagiatCheck}/index.ts | 0 .../{fileUpload => plagiatCheck}/lib/types.ts | 7 + .../lib/usePlagiraism.ts | 24 ++- .../lib/userStore.ts | 0 .../lib/validation.ts | 0 .../ui/Plagiraismcheckform.tsx | 8 +- .../ui/Plagiraismui.tsx | 0 .../ui/documentsType.tsx | 0 20 files changed, 174 insertions(+), 96 deletions(-) rename src/widgets/{fileUpload => plagiatCheck}/index.ts (100%) rename src/widgets/{fileUpload => plagiatCheck}/lib/types.ts (88%) rename src/widgets/{fileUpload => plagiatCheck}/lib/usePlagiraism.ts (90%) rename src/widgets/{fileUpload => plagiatCheck}/lib/userStore.ts (100%) rename src/widgets/{fileUpload => plagiatCheck}/lib/validation.ts (100%) rename src/widgets/{fileUpload => plagiatCheck}/ui/Plagiraismcheckform.tsx (98%) rename src/widgets/{fileUpload => plagiatCheck}/ui/Plagiraismui.tsx (100%) rename src/widgets/{fileUpload => plagiatCheck}/ui/documentsType.tsx (100%) 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 From 0c262a8a97807a9bc9dc4705ed1fe8f033734c10 Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Tue, 7 Apr 2026 19:03:18 +0500 Subject: [PATCH 17/29] plagiatcheck part complated base new request types --- src/features/modals/siModal/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/modals/siModal/index.ts b/src/features/modals/siModal/index.ts index e31ca23..38c58e0 100644 --- a/src/features/modals/siModal/index.ts +++ b/src/features/modals/siModal/index.ts @@ -5,7 +5,7 @@ export { SUPPORTED_EXTENSIONS, SUPPORTED_MIME_TYPES, } from './utils/wordCount'; -export { calculatePrice, formatPrice, DEFAULT_PRICING } from './utils/pricing'; +export { formatPrice, DEFAULT_PRICING } from './utils/pricing'; export type { FileUploadModalProps, UploadedFile, From 7e534b1d6546de126fba7d7494ada2e05db6b562 Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Tue, 7 Apr 2026 19:04:52 +0500 Subject: [PATCH 18/29] plagiatcheck part complated base new request types --- src/shared/request/plagiarismapi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/request/plagiarismapi.ts b/src/shared/request/plagiarismapi.ts index b5ee827..e5869b8 100644 --- a/src/shared/request/plagiarismapi.ts +++ b/src/shared/request/plagiarismapi.ts @@ -3,7 +3,7 @@ import { PlagiarismSubmissionPayload, PlagiarismSubmissionResponse, -} from '@/widgets/fileUpload/lib/types'; +} from '@/widgets/plagiatCheck/lib/types'; const API_BASE_URL = process.env.VITE_API_BASE_URL ?? '/api'; const ENDPOINT = `${API_BASE_URL}/plagiarism/submit`; From 50a8d6dbd763232cc6fd6279f9e8f57fc137c427 Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Tue, 7 Apr 2026 19:10:44 +0500 Subject: [PATCH 19/29] plagiatcheck part complated base new request types --- src/widgets/cabinet/ui/tables/PaymentsTable.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/widgets/cabinet/ui/tables/PaymentsTable.tsx b/src/widgets/cabinet/ui/tables/PaymentsTable.tsx index df10c4a..8199d78 100644 --- a/src/widgets/cabinet/ui/tables/PaymentsTable.tsx +++ b/src/widgets/cabinet/ui/tables/PaymentsTable.tsx @@ -166,7 +166,6 @@ export function PaymentsTable() { service_fee: Number(service_fee), discount: Number(row.discount) || 0, total_price: Number(row.total_price) || 0, - currency: 'UZS', }} onConfirmPayment={() => { handleSubmit({ document_id: 0 }); From c61182adcff9b068c10325ca05274c0f5bd7bc72 Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Tue, 7 Apr 2026 21:30:27 +0500 Subject: [PATCH 20/29] profile page connected to backend and PATCH added, plagiatCheck updated and sertificate generate updated base backend types --- src/features/auth/login/ui/form.tsx | 1 - .../sertificateModal/sertificateModal.tsx | 38 ++---- .../modals/sertificateModal/sertifikat.tsx | 23 ---- src/features/modals/sertificateModal/types.ts | 13 +- .../sertificateModal/useSertificateModal.ts | 87 +++++++------ src/shared/request/apiRequest.ts | 2 +- src/widgets/cabinet/lib/hooks/useProfile.ts | 83 +++++++++---- src/widgets/cabinet/lib/mock.ts | 4 +- src/widgets/cabinet/lib/types.ts | 4 +- src/widgets/cabinet/ui/index.tsx | 6 +- .../cabinet/ui/profile/ProfileForm.tsx | 116 ++++++++++-------- src/widgets/cabinet/ui/profile/index.tsx | 10 +- src/widgets/plagiatCheck/lib/types.ts | 3 +- src/widgets/plagiatCheck/lib/usePlagiraism.ts | 14 +-- .../plagiatCheck/ui/Plagiraismcheckform.tsx | 2 +- 15 files changed, 208 insertions(+), 198 deletions(-) diff --git a/src/features/auth/login/ui/form.tsx b/src/features/auth/login/ui/form.tsx index 9b0b437..e06be18 100644 --- a/src/features/auth/login/ui/form.tsx +++ b/src/features/auth/login/ui/form.tsx @@ -122,7 +122,6 @@ export function LoginForm() { onChange={(e) => setPassword(e.target.value)} require={true} type="password" - maxLength={8} minLength={8} />
diff --git a/src/features/modals/sertificateModal/sertificateModal.tsx b/src/features/modals/sertificateModal/sertificateModal.tsx index 29cfcde..d32c6b9 100644 --- a/src/features/modals/sertificateModal/sertificateModal.tsx +++ b/src/features/modals/sertificateModal/sertificateModal.tsx @@ -6,14 +6,14 @@ import { User, FileText, BookOpen, - Layers, Loader2, CheckCircle2, } from 'lucide-react'; import { useCertificateModal } from './useSertificateModal'; import { Field, inputCls } from './modalField'; -import { DOCUMENT_TYPES, SertificateModalProps } from './types'; +import { SertificateModalProps } from './types'; +import DocumentsTypes from '@/widgets/plagiatCheck/ui/documentsType'; export default function SertificateModal({ document_id, @@ -138,35 +138,11 @@ export default function SertificateModal({ {/* Document type */} - - } - label="Hujjat turi" - > - - + updateField('document_type', val)} + disabled={loading || success} + /> {/* Document ID (read-only) */}
diff --git a/src/features/modals/sertificateModal/sertifikat.tsx b/src/features/modals/sertificateModal/sertifikat.tsx index a1fd43c..0f3062a 100644 --- a/src/features/modals/sertificateModal/sertifikat.tsx +++ b/src/features/modals/sertificateModal/sertifikat.tsx @@ -4,9 +4,6 @@ import { FileDown, Loader2 } from 'lucide-react'; import React, { useEffect, useState } from 'react'; import SertificateModal from './sertificateModal'; -// const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL; -// const baseUrl = 'https://api.anti-plagiat.uz/api/v1'; - export default function Sertifikat({ document_id }: { document_id: number }) { const t = useTranslations(); const [loading, setLoading] = useState(false); @@ -16,26 +13,6 @@ export default function Sertifikat({ document_id }: { document_id: number }) { console.log(loading); }, []); - // const handleClick = async () => { - // setLoading(true); - // try { - // const url = `${baseUrl}/shared/certificate/${document_id}/pdf/`; - // const res = await fetch(url); - // const blob = await res.blob(); - // const objectUrl = URL.createObjectURL(blob); - - // // ✅ window.open o'rniga tag bilan download - // const a = document.createElement('a'); - // a.href = objectUrl; - // a.download = `certificate-${document_id}.pdf`; - // a.click(); - - // URL.revokeObjectURL(objectUrl); - // } finally { - // setLoading(false); - // } - // }; - return ( <>

- handleChange('name', v)} - icon={User} - placeholder="Ali" - /> - handleChange('surname', v)} - icon={User} - placeholder="Karimov" - /> - handleChange('phone', v)} - icon={Phone} - placeholder="+998 90 123 45 67" - /> + {isLoading ? ( + <> + + + + + + ) : ( + <> + handleChange('first_name', v)} + icon={User} + placeholder="Ali" + /> + handleChange('last_name', v)} + icon={User} + placeholder="Karimov" + /> + handleChange('phone', v)} + icon={Phone} + placeholder="+998 90 123 45 67" + /> + {}} + icon={User} + placeholder="email@example.com" + disabled + /> + + )}
@@ -96,27 +130,11 @@ export const ProfileForm: React.FC = ({ initial }) => {

Parol o'zgartirish

-
- handleChange('currentPassword', v)} - type="password" - icon={Lock} - placeholder="••••••••" - /> +
handleChange('newPassword', v)} - type="password" - icon={Lock} - placeholder="••••••••" - /> - handleChange('confirmPassword', v)} + value={form.password} + onChange={(v) => handleChange('password', v)} type="password" icon={Lock} placeholder="••••••••" @@ -128,7 +146,7 @@ export const ProfileForm: React.FC = ({ initial }) => {
@@ -101,7 +102,7 @@ export default function SertificateModal({ icon={ } - label="Muallifning to'liq ismi" + label={t('authorName')} > updateField('fullname', e.target.value)} disabled={loading || success} - placeholder="Ismingizni kiriting..." + placeholder={t('namePlaceholder')} className={inputCls} /> @@ -124,7 +125,7 @@ export default function SertificateModal({ strokeWidth={2} /> } - label="Hujjat mavzusi" + label={t('documentTheme')} > updateField('document_theme', e.target.value)} disabled={loading || success} - placeholder="Mavzuni kiriting..." + placeholder={t('themePlaceholder')} className={inputCls} /> {/* Document type */} updateField('document_type', val)} + value={form.type} + onChange={(val) => updateField('type', val)} disabled={loading || success} /> @@ -151,7 +152,9 @@ export default function SertificateModal({ strokeWidth={1.8} />
- Hujjat ID + + {t('documentId')} + #{document_id} @@ -177,15 +180,15 @@ export default function SertificateModal({ {loading ? ( <> - Yaratilmoqda... + {t('creating')} ) : success ? ( <> - Sertifikat yaratildi! + {t('created')} ) : ( - Sertifikat yaratish + {t('create')} )}
diff --git a/src/features/modals/sertificateModal/types.ts b/src/features/modals/sertificateModal/types.ts index 2721456..0238e90 100644 --- a/src/features/modals/sertificateModal/types.ts +++ b/src/features/modals/sertificateModal/types.ts @@ -1,7 +1,7 @@ export interface CertificateFormData { fullname: string; document_theme: string; - document_type: string | number; + type: number; document_id: number; } diff --git a/src/features/modals/sertificateModal/useSertificateModal.ts b/src/features/modals/sertificateModal/useSertificateModal.ts index 6a2a432..751ee22 100644 --- a/src/features/modals/sertificateModal/useSertificateModal.ts +++ b/src/features/modals/sertificateModal/useSertificateModal.ts @@ -1,8 +1,10 @@ import React, { useState, useEffect, useRef } from 'react'; import { useMutation } from '@tanstack/react-query'; +import { AxiosError } from 'axios'; import { apiRequest } from '@/shared/request/apiRequest'; import { links } from '@/shared/request/links'; import { CertificateFormData } from './types'; +import { SIPaymentResponse } from '../siModal/utils/useFileUpload'; interface UseCertificateModalProps { document_id: number; @@ -24,13 +26,21 @@ export function useCertificateModal({ const [form, setForm] = useState({ fullname: '', document_theme: '', - document_type: '', + type: 0, document_id, }); const [success, setSuccess] = useState(false); const [visible, setVisible] = useState(false); const inputRef = useRef(null); + const payment = useMutation({ + mutationFn: (id: number) => + apiRequest('POST', links.demo_pay(id)), + onSuccess: (res) => { + window.open(res?.data?.payment_link, '_self'); + }, + }); + const certificateMutation = useMutation({ mutationFn: (payload: CertificatePayload) => apiRequest('POST', links.sertifikat(document_id), payload).then( @@ -53,13 +63,18 @@ export function useCertificateModal({ resetForm(); }, 1500); }, + onError: (error: AxiosError<{ code?: string }>) => { + if (error?.response?.data?.code === 'not_paid') { + payment.mutate(document_id); + } + }, }); const resetForm = () => { setForm({ fullname: '', document_theme: '', - document_type: '', + type: 0, document_id, }); }; @@ -90,16 +105,14 @@ export function useCertificateModal({ ) => setForm((prev) => ({ ...prev, [field]: value })); const isFormValid = - !!form.fullname.trim() && - !!form.document_theme.trim() && - !!form.document_type; + !!form.fullname.trim() && !!form.document_theme.trim() && !!form.type; const handleSubmit = () => { if (!isFormValid || certificateMutation.isPending) return; certificateMutation.mutate({ full_name: form.fullname, file_name: form.document_theme, - document_type: Number(form.document_type), + document_type: Number(form.type), }); }; diff --git a/src/features/modals/siModal/page.tsx b/src/features/modals/siModal/page.tsx index a74c03e..9e8b8af 100644 --- a/src/features/modals/siModal/page.tsx +++ b/src/features/modals/siModal/page.tsx @@ -3,9 +3,11 @@ import { useState } from 'react'; import { ArrowRight, BrainCircuit, Plus } from 'lucide-react'; import { FileUploadModal } from './ui/fileUploadModal'; +import { useTranslations } from 'next-intl'; export function SiCTACard() { const [isOpen, setIsOpen] = useState(false); + const t = useTranslations('Cabinet'); return ( <> @@ -17,12 +19,14 @@ export function SiCTACard() {
-

SI detektor

+

+ {t('siDetector')} +

- Matnni sun'iy intellekt uchun tekshiring + {t('siDesc')}

- Yuborish + {t('submit')} setIsOpen(false)} /> @@ -32,6 +36,7 @@ export function SiCTACard() { export function SiButton() { const [isOpen, setIsOpen] = useState(false); + const t = useTranslations('Cabinet'); return ( <> setIsOpen(false)} /> diff --git a/src/features/modals/siModal/utils/useFileUpload.ts b/src/features/modals/siModal/utils/useFileUpload.ts index b45e20c..40dd445 100644 --- a/src/features/modals/siModal/utils/useFileUpload.ts +++ b/src/features/modals/siModal/utils/useFileUpload.ts @@ -20,7 +20,7 @@ interface CreateSIOrderResponse { order_id: number; } -interface SIPaymentResponse { +export interface SIPaymentResponse { payment_link: string; } @@ -57,7 +57,7 @@ export function useFileUpload(): UseFileUploadReturn { const siPayment = useMutation({ mutationKey: ['si-payment'], mutationFn: (order_id: number) => - apiRequest('POST', links.si_payment(order_id)), + apiRequest('POST', links.demo_pay(order_id)), onSuccess: (res) => { window.open(res.data.payment_link, '_self'); }, diff --git a/src/shared/config/i18n/messages/en.json b/src/shared/config/i18n/messages/en.json index f1fce94..8527d8d 100644 --- a/src/shared/config/i18n/messages/en.json +++ b/src/shared/config/i18n/messages/en.json @@ -76,7 +76,8 @@ "resultClean": "Clean", "resultPlagiarismFound": "Plagiarism Found", "resultPending": "Pending", - "resultFailed": "Failed" + "resultFailed": "Failed", + "plagiatCheck": "Plagiarism Check" }, "DetailPage": { "id": "ID", @@ -126,7 +127,11 @@ "unknownError": "Unknown error", "words": "words", "aiProbabilityText": "Probability that the text was generated with AI has been detected", - "documentNumber": "Document subject" + "documentNumber": "Document subject", + "scoreAiContent": "Self-citation", + "scoreOriginality": "Originality", + "scorePlagiarism": "Plagiarism", + "scoreCitation": "Citation" }, "Hero": { "badge": "Academic Integrity Platform", @@ -240,5 +245,68 @@ }, "unknownUser": "Username not found", "file": "File", - "upload": "Download certificate" + "upload": "Download certificate", + "Cabinet": { + "plagiatCheck": "Plagiarism Check", + "checkDesc": "Check your document for originality", + "submit": "Submit", + "siDetector": "AI Detector", + "siDesc": "Check text for AI content", + "home": "Home", + "plagiat": "Plagiarism", + "siNav": "AI Detector", + "payments": "Payment History", + "profile": "Profile", + "close": "Close", + "personalCabinet": "Personal Cabinet", + "plagiatChecks": "Plagiarism Checks", + "dashboard": "Dashboard" + }, + "SiDetail": { + "siCheck": "AI Check", + "basicInfo": "Basic Information", + "documentInfo": "Document Information", + "documentName": "Document Name", + "checker": "Checker", + "uploadedAt": "Document Upload Time", + "originalFileName": "Original File Name", + "wordCount": "Word Count", + "fileExt": "File Extension", + "fileSize": "File Size", + "amountCharged": "Amount Charged", + "downloadOriginal": "Download Original Document", + "siResultsTitle": "Document AI Detector Results", + "siResultsDesc": "This window displays the analysis results of the text uploaded by the user regarding the probability of being written with the help of artificial intelligence. The detector evaluates the stylistic, grammatical and semantic features of the text and shows in percentage how likely it was generated by artificial intelligence.", + "download": "Download", + "originalText": "Original Text", + "possibleAi": "Possible AI", + "aiContent": "Artificial Intelligence" + }, + "PlagiatResult": { + "plagiarismLevel": "Plagiarism Level", + "aiWritten": "AI Written", + "originality": "Originality", + "citation": "Citation", + "plagiat": "Plagiarism", + "aiGeneration": "AI Generation", + "original": "Original", + "checked": "Checked" + }, + "CertificateModal": { + "title": "Create Certificate", + "close": "Close", + "authorName": "Author's Full Name", + "namePlaceholder": "Enter your name...", + "documentTheme": "Document Theme", + "themePlaceholder": "Enter the topic...", + "documentId": "Document ID", + "creating": "Creating...", + "created": "Certificate Created!", + "create": "Create Certificate" + }, + "DocumentTypes": { + "label": "Document Type", + "loading": "Loading...", + "placeholder": "Select document type..." + } } diff --git a/src/shared/config/i18n/messages/ru.json b/src/shared/config/i18n/messages/ru.json index d831516..5127112 100644 --- a/src/shared/config/i18n/messages/ru.json +++ b/src/shared/config/i18n/messages/ru.json @@ -75,7 +75,8 @@ "resultClean": "Чисто", "resultPlagiarismFound": "Обнаружен плагиат", "resultPending": "В ожидании", - "resultFailed": "Не удалось" + "resultFailed": "Не удалось", + "plagiatCheck": "Проверка на плагиат" }, "DetailPage": { "id": "ID", @@ -125,7 +126,11 @@ "unknownError": "Неизвестная ошибка", "words": "слов", "aiProbabilityText": "Обнаружена вероятность того, что текст создан с помощью ИИ", - "documentNumber": "Тема документа" + "documentNumber": "Тема документа", + "scoreAiContent": "Самоцитирование", + "scoreOriginality": "Оригинальность", + "scorePlagiarism": "Плагиат", + "scoreCitation": "Цитирование" }, "Hero": { "badge": "Платформа академической честности", @@ -239,5 +244,68 @@ }, "unknownUser": "Имя пользователя не найдено", "file": "Файл", - "upload": "Скачать сертификат" + "upload": "Скачать сертификат", + "Cabinet": { + "plagiatCheck": "Проверка на плагиат", + "checkDesc": "Проверьте ваш документ на оригинальность", + "submit": "Отправить", + "siDetector": "ИИ детектор", + "siDesc": "Проверьте текст на искусственный интеллект", + "home": "Главная", + "plagiat": "Плагиат", + "siNav": "ИИ детектор", + "payments": "История платежей", + "profile": "Профиль", + "close": "Закрыть", + "personalCabinet": "Личный кабинет", + "plagiatChecks": "Проверки на плагиат", + "dashboard": "Dashboard" + }, + "SiDetail": { + "siCheck": "Проверка ИИ", + "basicInfo": "Основная информация", + "documentInfo": "Информация о документе", + "documentName": "Название документа", + "checker": "Проверяющий", + "uploadedAt": "Дата загрузки документа", + "originalFileName": "Оригинальное имя файла", + "wordCount": "Количество слов", + "fileExt": "Расширение файла", + "fileSize": "Размер файла", + "amountCharged": "Списанная сумма", + "downloadOriginal": "Скачать оригинальный документ", + "siResultsTitle": "Результаты ИИ детектора документа", + "siResultsDesc": "В этом окне отображены результаты анализа текста, загруженного пользователем, на вероятность написания с помощью искусственного интеллекта. Детектор оценивает стилистические, грамматические и семантические особенности текста и показывает в процентах, насколько вероятно, что он был сгенерирован искусственным интеллектом.", + "download": "Скачать", + "originalText": "Оригинальный текст", + "possibleAi": "Возможный ИИ", + "aiContent": "Искусственный интеллект" + }, + "PlagiatResult": { + "plagiarismLevel": "Уровень плагиата", + "aiWritten": "Написано ИИ", + "originality": "Оригинальность", + "citation": "Цитирование", + "plagiat": "Плагиат", + "aiGeneration": "Генерация ИИ", + "original": "Оригинал", + "checked": "Проверено" + }, + "CertificateModal": { + "title": "Создать сертификат", + "close": "Закрыть", + "authorName": "Полное имя автора", + "namePlaceholder": "Введите ваше имя...", + "documentTheme": "Тема документа", + "themePlaceholder": "Введите тему...", + "documentId": "ID документа", + "creating": "Создание...", + "created": "Сертификат создан!", + "create": "Создать сертификат" + }, + "DocumentTypes": { + "label": "Тип документа", + "loading": "Загрузка...", + "placeholder": "Выберите тип документа..." + } } diff --git a/src/shared/config/i18n/messages/uz.d.json.ts b/src/shared/config/i18n/messages/uz.d.json.ts index 8f610e2..06b66f1 100644 --- a/src/shared/config/i18n/messages/uz.d.json.ts +++ b/src/shared/config/i18n/messages/uz.d.json.ts @@ -80,6 +80,7 @@ declare const messages: { resultPlagiarismFound: 'Plagiat topildi'; resultPending: 'Kutilmoqda'; resultFailed: 'Muvaffaqiyatsiz'; + plagiatCheck: 'Plagiat tekshiruvi'; }; DetailPage: { id: 'ID'; @@ -130,6 +131,10 @@ declare const messages: { words: "so'z"; aiProbabilityText: 'Ai yordamida yaratilganlik ehtimoli aniqlandi'; documentNumber: 'Dokument mavzusi'; + scoreAiContent: "O'zidan iqtibos keltirish"; + scoreOriginality: 'Originallik'; + scorePlagiarism: 'Plagiat'; + scoreCitation: 'Iqtibos'; }; Hero: { badge: 'Akademik halollik platformasi'; @@ -244,5 +249,68 @@ declare const messages: { unknownUser: 'Foydalanuvchi topilmadi'; file: 'Fayl'; upload: 'Sertifikatni yuklab olish'; + Cabinet: { + plagiatCheck: 'Plagiat tekshiruvi'; + checkDesc: 'Hujjatingizni originallik uchun tekshiring'; + submit: 'Yuborish'; + siDetector: 'SI detektor'; + siDesc: "Matnni sun'iy intellekt uchun tekshiring"; + home: 'Bosh sahifa'; + plagiat: 'Plagiat'; + siNav: 'SI detektor'; + payments: "To'lovlar tarixi"; + profile: 'Profil'; + close: 'Yopish'; + personalCabinet: 'Shaxsiy kabinet'; + plagiatChecks: 'Plagiat tekshiruvlar'; + dashboard: 'Dashboard'; + }; + SiDetail: { + siCheck: 'SI tekshiruv'; + basicInfo: "Asosiy ma'lumotlar"; + documentInfo: "Hujjat haqida ma'lumotlar"; + documentName: 'Hujjat nomi'; + checker: 'Tekshiruvchi'; + uploadedAt: 'Hujjat yuklangan vaqti'; + originalFileName: 'Hujjat faylining original nomi'; + wordCount: "So'zlar soni"; + fileExt: 'Hujjat fayli kenggaytmasi'; + fileSize: "Hujjat fayli o'lchami"; + amountCharged: 'Yechilgan summa'; + downloadOriginal: 'Original hujjatni yuklab olish'; + siResultsTitle: 'Hujjatning SI detektori natijalari'; + siResultsDesc: "Ushbu oynada foydalanuvchi tomonidan yuklangan matn sun'iy intellekt (SI) yordamida yozilgan bo'lish ehtimoli bo'yicha tahlil natijalari aks etirilgan. Detektor matnning stilistik, grammatik va semantik xususiyatlarini baholab, uning qanchalik darajada sun'iy intellekt tomonidan generatsiya qilingan bo'lishi mumkinligini foizlik ko'rinishida ko'rsatadi."; + download: 'Yuklab olish'; + originalText: 'Original matn'; + possibleAi: "Ehtimoliy Sun'iy Intellekt"; + aiContent: "Sun'iy Intellekt"; + }; + PlagiatResult: { + plagiarismLevel: 'Plagiat darajasi'; + aiWritten: 'AI yozgan'; + originality: 'Originallik'; + citation: 'Iqtibos'; + plagiat: 'Plagiat'; + aiGeneration: 'AI generatsiya'; + original: 'Original'; + checked: 'Tekshirilgan'; + }; + CertificateModal: { + title: 'Sertifikat yaratish'; + close: 'Yopish'; + authorName: "Muallifning to'liq ismi"; + namePlaceholder: 'Ismingizni kiriting...'; + documentTheme: 'Hujjat mavzusi'; + themePlaceholder: 'Mavzuni kiriting...'; + documentId: 'Hujjat ID'; + creating: 'Yaratilmoqda...'; + created: 'Sertifikat yaratildi!'; + create: 'Sertifikat yaratish'; + }; + DocumentTypes: { + label: 'Hujjat turi'; + loading: 'Yuklanmoqda...'; + placeholder: 'Hujjat turini tanlang...'; + }; }; export default messages; diff --git a/src/shared/config/i18n/messages/uz.json b/src/shared/config/i18n/messages/uz.json index 414ef3e..7ad8a85 100644 --- a/src/shared/config/i18n/messages/uz.json +++ b/src/shared/config/i18n/messages/uz.json @@ -76,7 +76,8 @@ "resultClean": "Toza", "resultPlagiarismFound": "Plagiat topildi", "resultPending": "Kutilmoqda", - "resultFailed": "Muvaffaqiyatsiz" + "resultFailed": "Muvaffaqiyatsiz", + "plagiatCheck": "Plagiat tekshiruvi" }, "DetailPage": { "id": "ID", @@ -126,7 +127,11 @@ "unknownError": "Noma'lum xato", "words": "so'z", "aiProbabilityText":"Ai yordamida yaratilganlik ehtimoli aniqlandi", - "documentNumber":"Dokument mavzusi" + "documentNumber":"Dokument mavzusi", + "scoreAiContent": "O'zidan iqtibos keltirish", + "scoreOriginality": "Originallik", + "scorePlagiarism": "Plagiat", + "scoreCitation": "Iqtibos" }, "Hero": { "badge": "Akademik halollik platformasi", @@ -240,5 +245,68 @@ }, "unknownUser": "Foydalanuvchi topilmadi", "file": "Fayl", - "upload": "Sertifikatni yuklab olish" + "upload": "Sertifikatni yuklab olish", + "Cabinet": { + "plagiatCheck": "Plagiat tekshiruvi", + "checkDesc": "Hujjatingizni originallik uchun tekshiring", + "submit": "Yuborish", + "siDetector": "SI detektor", + "siDesc": "Matnni sun'iy intellekt uchun tekshiring", + "home": "Bosh sahifa", + "plagiat": "Plagiat", + "siNav": "SI detektor", + "payments": "To'lovlar tarixi", + "profile": "Profil", + "close": "Yopish", + "personalCabinet": "Shaxsiy kabinet", + "plagiatChecks": "Plagiat tekshiruvlar", + "dashboard": "Dashboard" + }, + "SiDetail": { + "siCheck": "SI tekshiruv", + "basicInfo": "Asosiy ma'lumotlar", + "documentInfo": "Hujjat haqida ma'lumotlar", + "documentName": "Hujjat nomi", + "checker": "Tekshiruvchi", + "uploadedAt": "Hujjat yuklangan vaqti", + "originalFileName": "Hujjat faylining original nomi", + "wordCount": "So'zlar soni", + "fileExt": "Hujjat fayli kenggaytmasi", + "fileSize": "Hujjat fayli o'lchami", + "amountCharged": "Yechilgan summa", + "downloadOriginal": "Original hujjatni yuklab olish", + "siResultsTitle": "Hujjatning SI detektori natijalari", + "siResultsDesc": "Ushbu oynada foydalanuvchi tomonidan yuklangan matn sun'iy intellekt (SI) yordamida yozilgan bo'lish ehtimoli bo'yicha tahlil natijalari aks etirilgan. Detektor matnning stilistik, grammatik va semantik xususiyatlarini baholab, uning qanchalik darajada sun'iy intellekt tomonidan generatsiya qilingan bo'lishi mumkinligini foizlik ko'rinishida ko'rsatadi.", + "download": "Yuklab olish", + "originalText": "Original matn", + "possibleAi": "Ehtimoliy Sun'iy Intellekt", + "aiContent": "Sun'iy Intellekt" + }, + "PlagiatResult": { + "plagiarismLevel": "Plagiat darajasi", + "aiWritten": "AI yozgan", + "originality": "Originallik", + "citation": "Iqtibos", + "plagiat": "Plagiat", + "aiGeneration": "AI generatsiya", + "original": "Original", + "checked": "Tekshirilgan" + }, + "CertificateModal": { + "title": "Sertifikat yaratish", + "close": "Yopish", + "authorName": "Muallifning to'liq ismi", + "namePlaceholder": "Ismingizni kiriting...", + "documentTheme": "Hujjat mavzusi", + "themePlaceholder": "Mavzuni kiriting...", + "documentId": "Hujjat ID", + "creating": "Yaratilmoqda...", + "created": "Sertifikat yaratildi!", + "create": "Sertifikat yaratish" + }, + "DocumentTypes": { + "label": "Hujjat turi", + "loading": "Yuklanmoqda...", + "placeholder": "Hujjat turini tanlang..." + } } diff --git a/src/shared/request/apiRequest.ts b/src/shared/request/apiRequest.ts index 2d4fc3b..ad625c4 100644 --- a/src/shared/request/apiRequest.ts +++ b/src/shared/request/apiRequest.ts @@ -195,5 +195,7 @@ export const apiRequest = async ( }, }); + console.log('resposne: ', response); + return response; }; diff --git a/src/shared/request/links.ts b/src/shared/request/links.ts index 36cc386..b434082 100644 --- a/src/shared/request/links.ts +++ b/src/shared/request/links.ts @@ -6,15 +6,16 @@ export const links = { detail: (id: number) => `/shared/documents/${id}/`, payment: (order_id: number) => `/users/payme/link/${order_id}/`, sertifikat: (document_id: number) => - `/shared/certificate/${document_id}/pdf/`, + `/shared/certificate/${document_id}/download/`, si: '/shared/ai_document/list/', - si_id: (si_id: number) => `/shared/ai_document/list/${si_id}/`, + si_id: (si_id: number) => `/shared/ai_document/${si_id}/`, si_payment: (document_id: number) => `/shared/ai_document/pay/${document_id}/`, si_create: '/shared/ai_document/create/', - document_types: '/shared/document_types/', + document_types: '/shared/documents/types/', pay_history: '/shared/orders/all/', statistics: '/shared/statistics/', wordCount: '/shared/check_file/', users: '/users/profile/', + demo_pay: (order_id: number) => `/users/payme/link/${order_id}/`, }; diff --git a/src/widgets/cabinet/ui/CabinetNav.tsx b/src/widgets/cabinet/ui/CabinetNav.tsx index b4de3a6..29179c3 100644 --- a/src/widgets/cabinet/ui/CabinetNav.tsx +++ b/src/widgets/cabinet/ui/CabinetNav.tsx @@ -1,18 +1,9 @@ 'use client'; import React from 'react'; import { Menu } from 'lucide-react'; +import { useTranslations } from 'next-intl'; import type { CabinetSection } from '../lib/types'; -// ─── Labels ──────────────────────────────────────────────────────────────────── - -const SECTION_LABELS: Record = { - dashboard: 'Dashboard', - plagiat: 'Plagiat tekshiruvlar', - si: 'SI detektor', - payments: "To'lovlar tarixi", - profile: 'Profil', -}; - // ─── Props ───────────────────────────────────────────────────────────────────── interface CabinetNavProps { @@ -25,19 +16,31 @@ interface CabinetNavProps { export const CabinetNav: React.FC = ({ activeSection, onMenuClick, -}) => ( -
-
- -

- {SECTION_LABELS[activeSection]} -

-
-
-); +}) => { + const t = useTranslations('Cabinet'); + + const SECTION_LABELS: Record = { + dashboard: t('dashboard'), + plagiat: t('plagiatChecks'), + si: t('siNav'), + payments: t('payments'), + profile: t('profile'), + }; + + return ( +
+
+ +

+ {SECTION_LABELS[activeSection]} +

+
+
+ ); +}; diff --git a/src/widgets/cabinet/ui/Sidebar.tsx b/src/widgets/cabinet/ui/Sidebar.tsx index b10a062..1589e75 100644 --- a/src/widgets/cabinet/ui/Sidebar.tsx +++ b/src/widgets/cabinet/ui/Sidebar.tsx @@ -10,23 +10,9 @@ import { X, } from 'lucide-react'; import { Link } from '@/shared/config/i18n/navigation'; +import { useTranslations } from 'next-intl'; import type { CabinetSection } from '../lib/types'; -// ─── Nav items ───────────────────────────────────────────────────────────────── - -type NavItemDef = - | { id: CabinetSection; label: string; icon: React.ElementType; href?: never } - | { id: 'home'; label: string; icon: React.ElementType; href: string }; - -const NAV_ITEMS: NavItemDef[] = [ - { id: 'home', label: 'Bosh sahifa', icon: Home, href: '/' }, - { id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard }, - { id: 'plagiat', label: 'Plagiat', icon: FileSearch }, - { id: 'si', label: 'SI detektor', icon: BrainCircuit }, - { id: 'payments', label: "To'lovlar tarixi", icon: CreditCard }, - { id: 'profile', label: 'Profil', icon: User }, -]; - // ─── Props ───────────────────────────────────────────────────────────────────── interface SidebarProps { @@ -45,95 +31,118 @@ export const Sidebar: React.FC = ({ isOpen, onClose, userName, -}) => ( - <> - {/* Mobile backdrop */} - {isOpen && ( -
- )} +}) => { + const t = useTranslations('Cabinet'); - - -); + })} + + + + ); +}; diff --git a/src/widgets/cabinet/ui/dashboard/CtaCards.tsx b/src/widgets/cabinet/ui/dashboard/CtaCards.tsx index 685e1dd..5a0ba6b 100644 --- a/src/widgets/cabinet/ui/dashboard/CtaCards.tsx +++ b/src/widgets/cabinet/ui/dashboard/CtaCards.tsx @@ -1,33 +1,38 @@ +'use client'; import React from 'react'; import { FileSearch, ArrowRight } from 'lucide-react'; import Link from 'next/link'; import { SiCTACard } from '@/features/modals/siModal/page'; +import { useTranslations } from 'next-intl'; -export const CtaCards = () => ( - <> -
- {/* Plagiat */} - -
- -
- -

- Plagiat tekshiruvi -

-

- Hujjatingizni originallik uchun tekshiring -

- - Yuborish - - +export const CtaCards = () => { + const t = useTranslations('Cabinet'); + return ( + <> +
+ {/* Plagiat */} + +
+ +
+ +

+ {t('plagiatCheck')} +

+

+ {t('checkDesc')} +

+ + {t('submit')} + + - {/* SI */} - -
- -); + {/* SI */} + +
+ + ); +}; diff --git a/src/widgets/cabinet/ui/tables/SiTable.tsx b/src/widgets/cabinet/ui/tables/SiTable.tsx index 1504db1..4a3c567 100644 --- a/src/widgets/cabinet/ui/tables/SiTable.tsx +++ b/src/widgets/cabinet/ui/tables/SiTable.tsx @@ -96,7 +96,7 @@ const SiRow: React.FC<{ item: SiDocument; index: number }> = ({ const pay = useMutation({ mutationKey: ['si-payment', item.id], mutationFn: () => - apiRequest<{ payment_link: string }>('POST', links.si_payment(item.id)), + apiRequest<{ payment_link: string }>('POST', links.demo_pay(item.id)), onSuccess: (res) => { window.open(res.data.payment_link, '_self'); }, diff --git a/src/widgets/detail/SiDetailPage.tsx b/src/widgets/detail/SiDetailPage.tsx index 12b83cb..2a666ce 100644 --- a/src/widgets/detail/SiDetailPage.tsx +++ b/src/widgets/detail/SiDetailPage.tsx @@ -3,16 +3,35 @@ import React, { useEffect } from 'react'; import { useQuery } from '@tanstack/react-query'; import { useParams } from 'next/navigation'; +import { useTranslations } from 'next-intl'; import { apiRequest } from '@/shared/request/apiRequest'; import { links } from '@/shared/request/links'; -import { Download, CloudDownload } from 'lucide-react'; +import { Download } from 'lucide-react'; // ── Types ──────────────────────────────────────────────────────────────────── -type SiResult = { - original: number; - ai_possible: number; +type SiResultRes = { ai: number; + hash: string; + text: string; + citation: number; + plagiarism: number; + originality: number; +}; + +type SiResultData = { + ok: boolean; + res: SiResultRes; + error: string; + success: string; + text_res: string; + analyze_text?: Record; +}; + +type SiResult = { + id: number; + document: number; + result: SiResultData; }; type SiDetail = { @@ -134,6 +153,7 @@ function LoadingSkeleton() { // ── Main Page ───────────────────────────────────────────────────────────────── export default function SiDetailPage({ id }: { id: number }) { + const t = useTranslations('SiDetail'); const { locale } = useParams() as { locale: string }; useEffect(() => { console.log(locale); @@ -172,9 +192,10 @@ export default function SiDetailPage({ id }: { id: number }) { } // Derive SI percentages - const original = doc.result?.original ?? 100 - (doc.si_percantage ?? 0); - const aiPossible = doc.result?.ai_possible ?? 0; - const ai = doc.result?.ai ?? doc.si_percantage ?? 0; + const res = doc.result?.result?.res; + const original = res?.originality ?? 100 - (doc.si_percantage ?? 0); + const aiPossible = res?.plagiarism ?? 0; + const ai = res?.ai ?? doc.si_percantage ?? 0; return (
@@ -200,7 +221,7 @@ export default function SiDetailPage({ id }: { id: number }) {

- {doc.title || 'SI tekshiruv'} + {doc.title || t('siCheck')}

@@ -211,7 +232,7 @@ export default function SiDetailPage({ id }: { id: number }) { {/* Section header */}

- Asosiy ma'lumotlar + {t('basicInfo')}

@@ -219,48 +240,42 @@ export default function SiDetailPage({ id }: { id: number }) {
{/* Sub-header */}

- Hujjat haqida ma'lumotlar + {t('documentInfo')}

{doc.user && ( )} + - 0 ? doc.total_words.toLocaleString('uz-UZ') : '—' } /> - + {doc.file_size !== undefined && ( )} {doc.total_price !== undefined && ( )} @@ -275,7 +290,7 @@ export default function SiDetailPage({ id }: { id: number }) { className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl border border-cyan-500 text-cyan-600 text-sm font-semibold hover:bg-cyan-50 transition-colors" > - Original hujjatni yuklab olish + {t('downloadOriginal')}
)} @@ -308,21 +323,15 @@ export default function SiDetailPage({ id }: { id: number }) {

- Hujjatning SI detektori natijalari + {t('siResultsTitle')}

- Ushbu oynada foydalanuvchi tomonidan yuklangan matn - sun'iy intellekt (SI) yordamida yozilgan bo'lish - ehtimoli bo'yicha tahlil natijalari aks etirilgan. - Detektor matnning stilistik, grammatik va semantik - xususiyatlarini baholab, uning qanchalik darajada sun'iy - intellekt tomonidan generatsiya qilingan bo'lishi - mumkinligini foizlik ko'rinishida ko'rsatadi. + {t('siResultsDesc')}

- {doc.file && ( + {/* {doc.file && ( - Yuklab olish + {t('download')} - )} + )} */} {/* Bars */}
- +
diff --git a/src/widgets/detail/pageDetail.tsx b/src/widgets/detail/pageDetail.tsx index af0e291..062b678 100644 --- a/src/widgets/detail/pageDetail.tsx +++ b/src/widgets/detail/pageDetail.tsx @@ -10,7 +10,7 @@ import PaymentStatus from './paidStatus'; import Sertifikat from '@/features/modals/sertificateModal/sertifikat'; // ── Types ──────────────────────────────────────────────────────────────────── - +const baseUrl = 'https://dev-api.anti-plagiat.uz/api/v1'; interface AnalyzeText { [key: string]: number | string; } @@ -397,10 +397,10 @@ export default function DocumentDetailPage({ id }: { id: number }) {
- {doc.certificate && } + {doc.file && (
+ - +
diff --git a/src/widgets/detail/ui/components/Header.tsx b/src/widgets/detail/ui/components/Header.tsx index 98470b2..acc4d09 100644 --- a/src/widgets/detail/ui/components/Header.tsx +++ b/src/widgets/detail/ui/components/Header.tsx @@ -1,3 +1,5 @@ +'use client'; +import { useTranslations } from 'next-intl'; import { blue } from '../../lib/constant'; import { PlagiatData } from '../../lib/types'; @@ -21,6 +23,7 @@ export default function Header({ location, checkedAt, }: Props) { + const t = useTranslations('PlagiatResult'); return (
- {plagiarismPercent}% plagiat + {plagiarismPercent}% {t('plagiat').toLowerCase()}

@@ -50,7 +53,7 @@ export default function Header({

- Tekshirilgan + {t('checked')}

{checkedAt} diff --git a/src/widgets/detail/ui/components/TopMetrics.tsx b/src/widgets/detail/ui/components/TopMetrics.tsx index 49a1145..e58778a 100644 --- a/src/widgets/detail/ui/components/TopMetrics.tsx +++ b/src/widgets/detail/ui/components/TopMetrics.tsx @@ -1,3 +1,5 @@ +'use client'; +import { useTranslations } from 'next-intl'; import { PlagiatData } from '../../lib/types'; import MetricCard from './Metriccard'; @@ -12,12 +14,16 @@ export default function TopMetrics({ originalityPercent, citationPercent, }: Props) { + const t = useTranslations('PlagiatResult'); return (

- - - - + + + +
); } diff --git a/src/widgets/detail/ui/index.tsx b/src/widgets/detail/ui/index.tsx index 2d14174..62cfdb7 100644 --- a/src/widgets/detail/ui/index.tsx +++ b/src/widgets/detail/ui/index.tsx @@ -145,26 +145,6 @@ function Skeleton() { ); } -// ─── Error state ────────────────────────────────────────────────────────────── - -function ErrorState() { - return ( -
-
-

- Ma'lumot topilmadi -

-

- Ushbu tekshiruv mavjud emas yoki o'chirilgan -

-
-
- ); -} - // ─── Component ──────────────────────────────────────────────────────────────── export default function PlagiatResult({ id }: { id: number }) { @@ -186,7 +166,22 @@ export default function PlagiatResult({ id }: { id: number }) { }); if (isLoading) return ; - if (isError || !rawData) return ; + if (isError || !rawData) + return ( +
+
+

+ Ma'lumot topilmadi +

+

+ Ushbu tekshiruv mavjud emas yoki o'chirilgan +

+
+
+ ); const data: PlagiatData = transformResponse(rawData); diff --git a/src/widgets/history/ui/historyPage.tsx b/src/widgets/history/ui/historyPage.tsx index b486613..fd58cb9 100644 --- a/src/widgets/history/ui/historyPage.tsx +++ b/src/widgets/history/ui/historyPage.tsx @@ -30,7 +30,7 @@ const PageHeader: React.FC = () => { >

- Plagiat tekshiruvi + {t('plagiatCheck')}

diff --git a/src/widgets/home/index.tsx b/src/widgets/home/index.tsx index dc2bc1a..28316d4 100644 --- a/src/widgets/home/index.tsx +++ b/src/widgets/home/index.tsx @@ -1,10 +1,20 @@ 'use client'; +import { useEffect } from 'react'; import Hero from './components/Hero'; import InfoSection from './components/InfoSection'; import StepsSection from './components/StepsSection'; import Ticker from './components/Ticker'; +import { useRouter } from '@/shared/config/i18n/navigation'; const PlagiarismLanding = () => { + const route = useRouter(); + useEffect(() => { + const data = localStorage.getItem('user'); + + if (data) { + route.push('/plagat'); + } + }, []); return ( <> diff --git a/src/widgets/plagiatCheck/lib/types.ts b/src/widgets/plagiatCheck/lib/types.ts index c53f885..9d32cac 100644 --- a/src/widgets/plagiatCheck/lib/types.ts +++ b/src/widgets/plagiatCheck/lib/types.ts @@ -35,7 +35,7 @@ export interface PlagiarismFormState { file: File | null; certificate: boolean; text?: string; - type: string; + type: number; } export type PlagiarismFormErrors = Partial< diff --git a/src/widgets/plagiatCheck/lib/usePlagiraism.ts b/src/widgets/plagiatCheck/lib/usePlagiraism.ts index 8f665c3..b37d3b3 100644 --- a/src/widgets/plagiatCheck/lib/usePlagiraism.ts +++ b/src/widgets/plagiatCheck/lib/usePlagiraism.ts @@ -21,7 +21,7 @@ const INITIAL_FORM: PlagiarismFormState = { file: null, certificate: true, text: '', - type: 'boshqa', + type: 0, }; const PRICE: PriceCalculate = { @@ -71,6 +71,7 @@ export function usePlagiarismForm() { const priceInfo: PriceCalculate = { total_price: resdata?.total_price || 0, discount: resdata?.discount || 0, + certificate: resdata?.certificate || 0, service_fee: resdata?.service_fee || 0, }; setPrices(priceInfo); @@ -90,7 +91,7 @@ export function usePlagiarismForm() { const payment = useMutation({ mutationKey: ['payload'], mutationFn: ({ order_id }: { order_id: number }) => - apiRequest<{ payment_link: string }>('POST', links.payment(order_id)), + apiRequest<{ payment_link: string }>('POST', links.demo_pay(order_id)), onSuccess: (res) => { console.log('payment res: ', res); window.open(res.data.payment_link, '_self'); @@ -116,9 +117,9 @@ export function usePlagiarismForm() { setErrors((prev) => ({ ...prev, file: undefined })); }, []); - const setOption = useCallback((option: string) => { - setForm((prev) => ({ ...prev, document_type: option })); - setErrors((prev) => ({ ...prev, document_type: undefined })); + const setOption = useCallback((option: number) => { + setForm((prev) => ({ ...prev, type: option })); + setErrors((prev) => ({ ...prev, type: undefined })); }, []); const toggleCertificate = useCallback(() => { @@ -150,7 +151,7 @@ export function usePlagiarismForm() { fd.append('text', form.text || ''); fd.append('file', form.file!); fd.append('certificate', String(form.certificate)); - fd.append('type', form.type); + fd.append('type', String(form.type)); console.log('sended data: ', fd); checkdocumentRequest.mutate(fd); }, diff --git a/src/widgets/plagiatCheck/ui/documentsType.tsx b/src/widgets/plagiatCheck/ui/documentsType.tsx index 8954552..6e5e92a 100644 --- a/src/widgets/plagiatCheck/ui/documentsType.tsx +++ b/src/widgets/plagiatCheck/ui/documentsType.tsx @@ -1,5 +1,6 @@ 'use client'; import React from 'react'; +import { useTranslations } from 'next-intl'; import { FieldWrapper } from './Plagiraismui'; import { useQuery } from '@tanstack/react-query'; import { apiRequest } from '@/shared/request/apiRequest'; @@ -12,8 +13,8 @@ type DocumentType = { }; interface DocumentsTypesProps { - value: string; - onChange: (value: string) => void; + value: number; + onChange: (value: number) => void; disabled?: boolean; } @@ -22,6 +23,7 @@ export default function DocumentsTypes({ onChange, disabled, }: DocumentsTypesProps) { + const t = useTranslations('DocumentTypes'); const { data, isLoading } = useQuery({ queryKey: ['document_types'], queryFn: (): Promise => @@ -30,20 +32,24 @@ export default function DocumentsTypes({ ), }); + const selected = data?.find((item) => item.id === value); + return ( - + onChange(normalizeDigits(e.target.value))} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + placeholder="90 123 45 67" + className={` + w-full pl-24 pr-4 py-2.5 text-sm rounded-xl + border bg-white + text-slate-800 placeholder:text-slate-300 + focus:outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-400 + transition-all duration-150 + ${error ? 'border-rose-400 bg-rose-50' : 'border-slate-200'} + `} + /> +
+ {error &&

{error}

} +
+ ); +}; + // ─── Skeleton ────────────────────────────────────────────────────────────────── const FieldSkeleton = () => ( @@ -64,12 +122,14 @@ const FieldSkeleton = () => ( // ─── Form ────────────────────────────────────────────────────────────────────── export const ProfileForm: React.FC = () => { + const t = useTranslations('Cabinet'); const { form, profile, isLoading, isSaving, saved, + errors, handleChange, handleSave, } = useProfile(); @@ -79,7 +139,7 @@ export const ProfileForm: React.FC = () => { {/* Personal info */}

- Shaxsiy ma'lumotlar + {t('personalInfo')}

{isLoading ? ( @@ -92,28 +152,28 @@ export const ProfileForm: React.FC = () => { ) : ( <> handleChange('first_name', v)} icon={User} placeholder="Ali" + error={errors.first_name} /> handleChange('last_name', v)} icon={User} placeholder="Karimov" + error={errors.last_name} /> - handleChange('phone', v)} - icon={Phone} - placeholder="+998 90 123 45 67" + error={errors.phone} /> {}} icon={User} @@ -128,16 +188,17 @@ export const ProfileForm: React.FC = () => { {/* Password */}

- Parol o'zgartirish + {t('changePassword')}

handleChange('password', v)} type="password" icon={Lock} placeholder="••••••••" + error={errors.password} />
@@ -161,16 +222,16 @@ export const ProfileForm: React.FC = () => { > {saved ? ( <> - Saqlandi + {t('saved')} ) : isSaving ? ( <> - Saqlanmoqda… + {t('saving')} ) : ( <> - Saqlash + {t('save')} )} diff --git a/src/widgets/cabinet/ui/profile/index.tsx b/src/widgets/cabinet/ui/profile/index.tsx index 2b7aa79..b171889 100644 --- a/src/widgets/cabinet/ui/profile/index.tsx +++ b/src/widgets/cabinet/ui/profile/index.tsx @@ -1,5 +1,6 @@ +'use client'; import React from 'react'; -import { DiscountProgress } from './DiscountProgress'; +import { useTranslations } from 'next-intl'; import { ProfileForm } from './ProfileForm'; import type { CabinetStats } from '../../lib/types'; @@ -7,16 +8,18 @@ interface ProfileSectionProps { stats: CabinetStats; } -export const ProfileSection: React.FC = ({ stats }) => ( -
-
-

Profil

-

- Ma'lumotlaringizni boshqaring -

-
+export const ProfileSection: React.FC = ({ stats }) => { + const t = useTranslations('Cabinet'); + console.log(stats); + return ( +
+
+

{t('profile')}

+

{t('profileDesc')}

+
- - -
-); + {/* */} + +
+ ); +}; diff --git a/src/widgets/cabinet/ui/tables/PaymentsTable.tsx b/src/widgets/cabinet/ui/tables/PaymentsTable.tsx index 8199d78..c4ae8e4 100644 --- a/src/widgets/cabinet/ui/tables/PaymentsTable.tsx +++ b/src/widgets/cabinet/ui/tables/PaymentsTable.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { Clock, XCircle, ReceiptText } from 'lucide-react'; import { useMutation, useQuery } from '@tanstack/react-query'; +import { useTranslations } from 'next-intl'; import { apiRequest } from '@/shared/request/apiRequest'; import { links } from '@/shared/request/links'; import PaymentStatus from '@/widgets/detail/paidStatus'; @@ -36,6 +37,7 @@ function formatPrice(price: string) { // ─── Component ───────────────────────────────────────────────────────────────── export function PaymentsTable() { + const t = useTranslations('Cabinet'); const [isPaymentOpen, setIsPaymentOpen] = useState(false); const { data, isLoading } = useQuery({ queryKey: ['pay_history'], @@ -50,9 +52,7 @@ export function PaymentsTable() { mutationFn: ({ order_id }: { order_id: number }) => apiRequest<{ payment_link: string }>('POST', links.payment(order_id)), onSuccess: (res) => { - console.log('payment res: ', res); window.open(res.data.payment_link, '_self'); - //route.push(`/${document_id}`); setIsPaymentOpen(false); }, onError: (err) => { @@ -75,11 +75,9 @@ export function PaymentsTable() { <>
-

- To'lovlar tarixi -

+

{t('payments')}

- {data?.length ?? 0} ta to'lov + {t('paymentsCount', { count: data?.length ?? 0 })}

@@ -87,28 +85,33 @@ export function PaymentsTable() { {isLoading ? (
- Yuklanmoqda... + {t('loading')}
) : !data || data.length === 0 ? (
-

To'lovlar tarixi mavjud emas

+

{t('noPayments')}

) : (
- {['#', 'Xizmat', 'Summa', 'Chegirma', 'Sana', 'Holat'].map( - (h) => ( - - ), - )} + {[ + t('tableNum'), + t('service'), + t('amount'), + t('discount'), + t('date'), + t('status'), + ].map((h) => ( + + ))} @@ -155,7 +158,7 @@ export function PaymentsTable() { ) : ( - Noma'lum + {t('unknown')} )} diff --git a/src/widgets/cabinet/ui/tables/SiTable.tsx b/src/widgets/cabinet/ui/tables/SiTable.tsx index 4a3c567..1d8af46 100644 --- a/src/widgets/cabinet/ui/tables/SiTable.tsx +++ b/src/widgets/cabinet/ui/tables/SiTable.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Download, CreditCard, Eye } from 'lucide-react'; import { useMutation } from '@tanstack/react-query'; +import { useTranslations } from 'next-intl'; import { useSiHistory } from '../../lib/hooks/useSiHistory'; import { formatDate } from '@/widgets/history/lib/utils'; import { apiRequest } from '@/shared/request/apiRequest'; @@ -14,6 +15,7 @@ import { useRouter, useParams } from 'next/navigation'; // ─── State badge ─────────────────────────────────────────────────────────────── const StateBadge: React.FC<{ state: 'paid' | 'unpaid' }> = ({ state }) => { + const t = useTranslations('Cabinet'); const isPaid = state === 'paid'; return ( = ({ state }) => { isPaid ? 'bg-emerald-500' : 'bg-rose-500', ].join(' ')} /> - {isPaid ? "To'langan" : "To'lanmagan"} + {isPaid ? t('paid') : t('unpaid')} ); }; @@ -68,21 +70,27 @@ const SkeletonRow = () => ( // ─── Empty / Error states ────────────────────────────────────────────────────── -const EmptyState = () => ( - - - -); +const EmptyState = () => { + const t = useTranslations('Cabinet'); + return ( + + + + ); +}; -const ErrorState = () => ( - - - -); +const ErrorState = () => { + const t = useTranslations('Cabinet'); + return ( + + + + ); +}; // ─── Row ─────────────────────────────────────────────────────────────────────── @@ -90,6 +98,7 @@ const SiRow: React.FC<{ item: SiDocument; index: number }> = ({ item, index, }) => { + const t = useTranslations('Cabinet'); const router = useRouter(); const { locale } = useParams() as { locale: string }; @@ -107,19 +116,14 @@ const SiRow: React.FC<{ item: SiDocument; index: number }> = ({ return ( - {/* # */} - - {/* Sarlavha */} - - {/* Fayl */} - - {/* So'z */} - - {/* SI% */} - - {/* Sana */} - - {/* Holat */} - - {/* Amal */} @@ -188,15 +182,16 @@ const SiRow: React.FC<{ item: SiDocument; index: number }> = ({ // ─── SiTable ─────────────────────────────────────────────────────────────────── export const SiTable: React.FC = () => { + const t = useTranslations('Cabinet'); const { items, isLoading, isError } = useSiHistory(); return (
-

SI detektor

+

{t('siNav')}

- {isLoading ? '...' : `${items.length} ta tekshiruv`} + {isLoading ? '...' : t('checksCount', { count: items.length })}

@@ -208,14 +203,14 @@ export const SiTable: React.FC = () => {
{[ - '#', - 'Sarlavha', - 'Fayl', - "So'z", + t('tableNum'), + t('tableTitle'), + t('tableFile'), + t('words'), 'SI%', - 'Sana', - 'Holat', - 'Amal', + t('date'), + t('status'), + t('action'), ].map((h) => ( {data.map((row) => { - const service_fee = row.total_price + row.discount; + // const service_fee = row.total_price + row.discount; return ( - setIsPaymentOpen(false)} price={{ @@ -174,7 +167,7 @@ export function PaymentsTable() { handleSubmit({ document_id: 0 }); }} isLoading={payment.isPending} - /> + /> */} ); })} diff --git a/src/widgets/history/ui/historyTableRow.tsx b/src/widgets/history/ui/historyTableRow.tsx index a690576..8f9ec6c 100644 --- a/src/widgets/history/ui/historyTableRow.tsx +++ b/src/widgets/history/ui/historyTableRow.tsx @@ -10,6 +10,7 @@ import { apiRequest } from '@/shared/request/apiRequest'; import { links } from '@/shared/request/links'; import { toast } from 'react-toastify'; import { PaymentModal } from '@/features/modals/paymentModal/ui/Paymentmodal'; +import { PLAGIAT_SERVICE_FEE, SERTIFICATE_PRICE } from '@/shared/lib/metadata'; // ─── State badge ─────────────────────────────────────────────────────────────── @@ -60,8 +61,9 @@ export const HistoryTableRow: React.FC< }); const price = item.price_calculation ?? { - service_fee: 41200, + service_fee: item.state === 'unpaid' ? PLAGIAT_SERVICE_FEE : 0, discount: 0, + certificate: item.certificate ? SERTIFICATE_PRICE : 0, total_price: 41200, currency: 'UZS', }; @@ -142,6 +144,7 @@ export const HistoryTableRow: React.FC< payment.mutate({ order_id: Number(item.order_id) }) } isLoading={payment.isPending} + hasSertificate={item.certificate} /> ); diff --git a/src/widgets/plagiatCheck/lib/usePlagiraism.ts b/src/widgets/plagiatCheck/lib/usePlagiraism.ts index b37d3b3..aa54eda 100644 --- a/src/widgets/plagiatCheck/lib/usePlagiraism.ts +++ b/src/widgets/plagiatCheck/lib/usePlagiraism.ts @@ -13,6 +13,8 @@ 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'; +import { SERTIFICATE_PRICE, PLAGIAT_SERVICE_FEE } from '@/shared/lib/metadata'; +// import { fromTheme } from 'tailwind-merge'; // ─── Initial States ────────────────────────────────────────────────────────── @@ -25,7 +27,8 @@ const INITIAL_FORM: PlagiarismFormState = { }; const PRICE: PriceCalculate = { - service_fee: 0, + service_fee: PLAGIAT_SERVICE_FEE, + certificate: SERTIFICATE_PRICE, discount: 0, total_price: 0, }; @@ -71,8 +74,8 @@ export function usePlagiarismForm() { const priceInfo: PriceCalculate = { total_price: resdata?.total_price || 0, discount: resdata?.discount || 0, - certificate: resdata?.certificate || 0, - service_fee: resdata?.service_fee || 0, + certificate: form.certificate ? SERTIFICATE_PRICE : 0, + service_fee: PLAGIAT_SERVICE_FEE, }; setPrices(priceInfo); console.log('order_id:', resdata.id); diff --git a/src/widgets/plagiatCheck/ui/Plagiraismcheckform.tsx b/src/widgets/plagiatCheck/ui/Plagiraismcheckform.tsx index 2bdb55c..afb9ae1 100644 --- a/src/widgets/plagiatCheck/ui/Plagiraismcheckform.tsx +++ b/src/widgets/plagiatCheck/ui/Plagiraismcheckform.tsx @@ -209,6 +209,7 @@ export function PlagiarismCheckForm() { price={prices} onConfirmPayment={handleSubmit} isLoading={isLoading} + hasSertificate={!!form.certificate} /> ); From 553db7194aea862cc9df17822e2b2d7259bc9170 Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Thu, 9 Apr 2026 20:04:24 +0500 Subject: [PATCH 25/29] payment link chnage to new link --- src/widgets/history/ui/historyTableRow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/history/ui/historyTableRow.tsx b/src/widgets/history/ui/historyTableRow.tsx index 8f9ec6c..5ac50b9 100644 --- a/src/widgets/history/ui/historyTableRow.tsx +++ b/src/widgets/history/ui/historyTableRow.tsx @@ -49,7 +49,7 @@ export const HistoryTableRow: React.FC< const payment = useMutation({ mutationKey: ['payment', item.order_id], mutationFn: ({ order_id }: { order_id: number }) => - apiRequest<{ payment_link: string }>('POST', links.payment(order_id)), + apiRequest<{ payment_link: string }>('POST', links.demo_pay(order_id)), onSuccess: (res) => { window.open(res.data.payment_link, '_self'); setIsPaymentOpen(false); From b4fd590edae14fcbf558fa3628c589f9c2bd151d Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Fri, 10 Apr 2026 16:53:29 +0500 Subject: [PATCH 26/29] fix base_api error --- .../sertificateModal/useSertificateModal.ts | 8 +- src/shared/request/apiRequest.ts | 2 +- src/shared/ui/navigation-menu.tsx | 2 +- src/shared/zustand/cabinetNav.ts | 12 ++ src/widgets/cabinet/lib/hooks/useCabinet.ts | 35 +++++- src/widgets/detail/pageDetail.tsx | 31 +++-- src/widgets/navbar/lib/model.ts | 1 + src/widgets/navbar/ui/SubMenuLink.tsx | 12 +- src/widgets/navbar/ui/authButtons.tsx | 106 +++++++++++------- 9 files changed, 151 insertions(+), 58 deletions(-) create mode 100644 src/shared/zustand/cabinetNav.ts diff --git a/src/features/modals/sertificateModal/useSertificateModal.ts b/src/features/modals/sertificateModal/useSertificateModal.ts index 751ee22..4d9cc3b 100644 --- a/src/features/modals/sertificateModal/useSertificateModal.ts +++ b/src/features/modals/sertificateModal/useSertificateModal.ts @@ -43,9 +43,9 @@ export function useCertificateModal({ const certificateMutation = useMutation({ mutationFn: (payload: CertificatePayload) => - apiRequest('POST', links.sertifikat(document_id), payload).then( - (res) => res.data as ArrayBuffer, - ), + apiRequest('POST', links.sertifikat(document_id), payload, { + responseType: 'arraybuffer', + }).then((res) => res.data), onSuccess: (data: ArrayBuffer) => { if (data) { const blob = new Blob([data], { type: 'application/pdf' }); @@ -54,7 +54,7 @@ export function useCertificateModal({ a.href = objectUrl; a.download = `certificate-${document_id}.pdf`; a.click(); - URL.revokeObjectURL(objectUrl); + setTimeout(() => URL.revokeObjectURL(objectUrl), 1000); } setSuccess(true); setTimeout(() => { diff --git a/src/shared/request/apiRequest.ts b/src/shared/request/apiRequest.ts index 7b29cff..8c06aa9 100644 --- a/src/shared/request/apiRequest.ts +++ b/src/shared/request/apiRequest.ts @@ -45,7 +45,7 @@ function extractErrorMessage(error: AxiosError): string { // ─── Constants ───────────────────────────────────────────────────────────────── // const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL; -const baseUrl = 'https://dev-api.anti-plagiat.uz/api/v1'; +const baseUrl = 'https://api.anti-plagiat.uz/api/v1'; const DEFAULT_LOCALE = 'uz'; // fallback locale for redirect // ─── Token helpers ───────────────────────────────────────────────────────────── diff --git a/src/shared/ui/navigation-menu.tsx b/src/shared/ui/navigation-menu.tsx index d242416..427bbdc 100644 --- a/src/shared/ui/navigation-menu.tsx +++ b/src/shared/ui/navigation-menu.tsx @@ -18,7 +18,7 @@ function NavigationMenu({ data-slot="navigation-menu" data-viewport={viewport} className={cn( - 'group/navigation-menu relative flex max-w-max flex-1 items-center justify-center', + ' relative flex max-w-max flex-1 items-center justify-center', className, )} {...props} diff --git a/src/shared/zustand/cabinetNav.ts b/src/shared/zustand/cabinetNav.ts new file mode 100644 index 0000000..2d35e71 --- /dev/null +++ b/src/shared/zustand/cabinetNav.ts @@ -0,0 +1,12 @@ +import { CabinetSection } from '@/widgets/cabinet/lib/types'; +import { create } from 'zustand'; + +type CabinetNavZustand = { + navItem: CabinetSection; + setNavItem: (item: string) => void; +}; + +export const useCabinetNav = create((set) => ({ + navItem: 'dashboard', + setNavItem: (item: string) => set({ navItem: item || 'dash' }), +})); diff --git a/src/widgets/cabinet/lib/hooks/useCabinet.ts b/src/widgets/cabinet/lib/hooks/useCabinet.ts index b928c0b..320dbac 100644 --- a/src/widgets/cabinet/lib/hooks/useCabinet.ts +++ b/src/widgets/cabinet/lib/hooks/useCabinet.ts @@ -1,14 +1,43 @@ 'use client'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; +import { useCabinetNav } from '@/shared/zustand/cabinetNav'; import type { CabinetSection } from '../types'; +const VALID_SECTIONS: CabinetSection[] = [ + 'dashboard', + 'plagiat', + 'si', + 'payments', + 'profile', +]; + export const useCabinet = () => { + const { navItem, setNavItem } = useCabinetNav(); + const navItemZustrand = useCabinetNav((state) => state.navItem); + const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [activeSection, setActiveSection] = useState('dashboard'); - const [isSidebarOpen, setIsSidebarOpen] = useState(false); + + useEffect(() => { + if (navItemZustrand) { + const activeSection: CabinetSection = VALID_SECTIONS.includes( + navItemZustrand as CabinetSection, + ) + ? (navItemZustrand as CabinetSection) + : 'dashboard'; + setActiveSection(activeSection); + } else { + const activeSection: CabinetSection = VALID_SECTIONS.includes( + navItem as CabinetSection, + ) + ? (navItem as CabinetSection) + : 'dashboard'; + setActiveSection(activeSection); + } + }, [navItemZustrand]); const navigate = (section: CabinetSection) => { - setActiveSection(section); + setNavItem(section); setIsSidebarOpen(false); }; diff --git a/src/widgets/detail/pageDetail.tsx b/src/widgets/detail/pageDetail.tsx index 062b678..2d96f9d 100644 --- a/src/widgets/detail/pageDetail.tsx +++ b/src/widgets/detail/pageDetail.tsx @@ -10,7 +10,7 @@ import PaymentStatus from './paidStatus'; import Sertifikat from '@/features/modals/sertificateModal/sertifikat'; // ── Types ──────────────────────────────────────────────────────────────────── -const baseUrl = 'https://dev-api.anti-plagiat.uz/api/v1'; +const baseUrl = 'https://api.anti-plagiat.uz/api/v1'; interface AnalyzeText { [key: string]: number | string; } @@ -399,10 +399,25 @@ export default function DocumentDetailPage({ id }: { id: number }) { {doc.file && ( - { + const url = `${baseUrl}/shared/documents/${doc.id}/download`; + const res = await apiRequest( + 'GET', + url, + undefined, + { responseType: 'arraybuffer', baseURL: baseUrl }, + ); + const blob = new Blob([res.data], { + type: 'application/pdf', + }); + const objectUrl = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = objectUrl; + a.download = `document-${id}.pdf`; + a.click(); + setTimeout(() => URL.revokeObjectURL(objectUrl), 1000); + }} className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-900 text-white text-xs font-semibold hover:bg-slate-700 transition-colors" > {t('downloadPdf')} - + )} @@ -486,7 +501,7 @@ export default function DocumentDetailPage({ id }: { id: number }) {
@@ -502,7 +517,7 @@ export default function DocumentDetailPage({ id }: { id: number }) { color="#6366f1" /> diff --git a/src/widgets/navbar/lib/model.ts b/src/widgets/navbar/lib/model.ts index fa3c803..514a679 100644 --- a/src/widgets/navbar/lib/model.ts +++ b/src/widgets/navbar/lib/model.ts @@ -4,4 +4,5 @@ export interface MenuItem { description?: string; icon?: React.ComponentType<{ className?: string }>; items?: MenuItem[]; + key: string; } diff --git a/src/widgets/navbar/ui/SubMenuLink.tsx b/src/widgets/navbar/ui/SubMenuLink.tsx index dbff994..102efb7 100644 --- a/src/widgets/navbar/ui/SubMenuLink.tsx +++ b/src/widgets/navbar/ui/SubMenuLink.tsx @@ -1,3 +1,5 @@ +'use client'; +import { usePathname } from 'next/navigation'; import { MenuItem } from '../lib/model'; const SubMenuLink = ({ @@ -7,11 +9,17 @@ const SubMenuLink = ({ item: MenuItem; logOut?: () => void; }) => { + const pathname = usePathname(); + const isCabinet = pathname.includes('/cabinet'); + return ( { + href={isCabinet && item.url === '/cabinet' ? undefined : item.url} + onClick={(e) => { + if (isCabinet && item.url === '/cabinet') { + e.preventDefault(); + } if (logOut) { logOut(); } diff --git a/src/widgets/navbar/ui/authButtons.tsx b/src/widgets/navbar/ui/authButtons.tsx index 5cd7de9..82fef5d 100644 --- a/src/widgets/navbar/ui/authButtons.tsx +++ b/src/widgets/navbar/ui/authButtons.tsx @@ -2,37 +2,70 @@ import { Link } from '@/shared/config/i18n/navigation'; import { Button } from '@/shared/ui/button'; import { - NavigationMenu, - NavigationMenuContent, - NavigationMenuItem, - NavigationMenuLink, - NavigationMenuList, - NavigationMenuTrigger, -} from '@/shared/ui/navigation-menu'; + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/shared/ui/dropdown-menu'; import SubMenuLink from './SubMenuLink'; import { ChangeLang } from './ChangeLang'; import { useLoginModal, useRegisterModal } from '@/shared/zustand/auth'; import { useTranslations } from 'next-intl'; import { useUserPlagiatStore } from '@/shared/zustand/user'; -import { LogOut, User } from 'lucide-react'; +import { + ChevronDown, + LogOut, + User, + LayoutDashboard, + FileSearch, + BrainCircuit, + CreditCard, +} from 'lucide-react'; import { useEffect, useState } from 'react'; +import { useCabinetNav } from '@/shared/zustand/cabinetNav'; function AuthButtons() { const t = useTranslations('Navbar'); + const t_cab = useTranslations('Cabinet'); + const setNavItem = useCabinetNav((state) => state.setNavItem); const [localUser, setLocalUser] = useState<{ id: number; name: string; surname: string; } | null>(null); - + const [open, setOpen] = useState(false); const auth = { login: { title: t('login'), url: '#' }, signup: { title: t('signup'), url: '#' }, }; const userItem = [ - { title: t('profile'), url: '/cabinet', icon: User }, - { title: t('logout'), url: '/', icon: LogOut }, + { title: t('profile'), url: '/cabinet', icon: User, key: 'profile' }, + { + url: '/cabinet', + title: t_cab('dashboard'), + icon: LayoutDashboard, + key: 'dashboard', + }, + { + url: '/cabinet', + title: t_cab('plagiat'), + icon: FileSearch, + key: 'plagiat', + }, + { + url: '/cabinet', + title: t_cab('siNav'), + icon: BrainCircuit, + key: 'si', + }, + { + url: '/cabinet', + title: t_cab('payments'), + icon: CreditCard, + key: 'payments', + }, + { title: t('logout'), url: '/', icon: LogOut, key: 'logout' }, ]; const toggleLoginModal = useLoginModal((state) => state.toggleLoginModal); @@ -51,7 +84,6 @@ function AuthButtons() { useEffect(() => { const data = localStorage.getItem('user'); - if (data) { setLocalUser(JSON.parse(data)); } else { @@ -65,33 +97,29 @@ function AuthButtons() {
- - - - - {localUser.name} - - - {userItem.map((subItem) => ( - - { - if (subItem.url !== '/cabinet') { - clearTokens(); - } - }} - item={subItem} - /> - - ))} - - - - + + + {localUser.name} + + + + {userItem.map((subItem) => ( + + { + setOpen(false); + if (subItem.url !== '/cabinet') { + clearTokens(); + } else { + setNavItem(subItem.key); + } + }} + item={subItem} + /> + + ))} + +
); } From be582a64745a22e7a56688eadc837c9bb176c27a Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Fri, 10 Apr 2026 16:59:51 +0500 Subject: [PATCH 27/29] authButton fixed --- src/shared/zustand/cabinetNav.ts | 4 ++-- src/widgets/navbar/ui/authButtons.tsx | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/shared/zustand/cabinetNav.ts b/src/shared/zustand/cabinetNav.ts index 2d35e71..33549e9 100644 --- a/src/shared/zustand/cabinetNav.ts +++ b/src/shared/zustand/cabinetNav.ts @@ -3,10 +3,10 @@ import { create } from 'zustand'; type CabinetNavZustand = { navItem: CabinetSection; - setNavItem: (item: string) => void; + setNavItem: (item: CabinetSection) => void; }; export const useCabinetNav = create((set) => ({ navItem: 'dashboard', - setNavItem: (item: string) => set({ navItem: item || 'dash' }), + setNavItem: (item: CabinetSection) => set({ navItem: item }), })); diff --git a/src/widgets/navbar/ui/authButtons.tsx b/src/widgets/navbar/ui/authButtons.tsx index 82fef5d..9ba7cf0 100644 --- a/src/widgets/navbar/ui/authButtons.tsx +++ b/src/widgets/navbar/ui/authButtons.tsx @@ -111,7 +111,9 @@ function AuthButtons() { if (subItem.url !== '/cabinet') { clearTokens(); } else { - setNavItem(subItem.key); + setNavItem( + subItem.key as import('@/widgets/cabinet/lib/types').CabinetSection, + ); } }} item={subItem} From a6a221faf5db3f923322cc6a1c2c6213d9cd0f2a Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Fri, 10 Apr 2026 17:02:52 +0500 Subject: [PATCH 28/29] fix --- src/widgets/navbar/lib/data.ts | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/widgets/navbar/lib/data.ts b/src/widgets/navbar/lib/data.ts index fb29a8e..3ec6d7e 100644 --- a/src/widgets/navbar/lib/data.ts +++ b/src/widgets/navbar/lib/data.ts @@ -1,26 +1,5 @@ -import { MenuItem } from './model'; import { LanguageRoutes } from '@/shared/config/i18n/types'; -const getMenu = (t: (key: string) => string): MenuItem[] => [ - { title: t('aboutSite'), url: '/about' }, - // { - // title: 'Products', - // url: '#', - // items: [ - // { - // title: 'Blog', - // description: 'The latest industry news, updates, and info', - // icon: Book, - // url: '#', - // }, - // ], - // }, - { - title: t('contact'), - url: '/contact', - }, -]; - const languages: { name: string; key: LanguageRoutes }[] = [ { name: "O'zbekcha", @@ -36,4 +15,4 @@ const languages: { name: string; key: LanguageRoutes }[] = [ }, ]; -export { getMenu, languages }; +export { languages }; From 18724e135b8ee7a403204ed5328475a8c5683e81 Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Fri, 10 Apr 2026 17:05:51 +0500 Subject: [PATCH 29/29] fix --- src/widgets/navbar/ui/index.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/widgets/navbar/ui/index.tsx b/src/widgets/navbar/ui/index.tsx index 3e33edb..a24da44 100644 --- a/src/widgets/navbar/ui/index.tsx +++ b/src/widgets/navbar/ui/index.tsx @@ -1,4 +1,3 @@ -import { Accordion } from '@/shared/ui/accordion'; import { Button } from '@/shared/ui/button'; import { Sheet, @@ -8,8 +7,6 @@ import { SheetTrigger, } from '@/shared/ui/sheet'; import { Menu } from 'lucide-react'; -import { getMenu } from '../lib/data'; -import RenderMobileMenuItem from './RenderMobileMenuItem'; import { ChangeLang } from './ChangeLang'; import Link from 'next/link'; import { AuthButtons } from './authButtons'; @@ -19,7 +16,6 @@ import Image from 'next/image'; const Navbar = () => { const t = useTranslations('Navbar'); - const menu = getMenu(t); return (
@@ -72,13 +68,6 @@ const Navbar = () => {
- - {menu.map((item) => RenderMobileMenuItem(item))} -
- {h} - + {h} +
- Hozircha SI tekshiruvlar yo'q -
+ {t('noSiChecks')} +
- Ma'lumotlarni yuklashda xatolik yuz berdi -
+ {t('loadError')} +
{String(index).padStart(2, '0')} {item.title || '—'} {item.file ? ( = ({ className="inline-flex items-center gap-1.5 text-slate-500 hover:text-blue-600 transition-colors" > - Fayl + {t('tableFile')} ) : ( )} {item.total_words > 0 ? item.total_words.toLocaleString() : '—'} {item.si_percantage != null && item.result != null ? ( @@ -149,18 +149,12 @@ const SiRow: React.FC<{ item: SiDocument; index: number }> = ({ )} {formatDate(item.created_at)} {item.state === 'unpaid' ? ( ) : ( )}
{

{t('description')}

= ({ state, }) => { const isPaid = state === 'paid'; + const t = useTranslations(); return ( = ({ isPaid ? 'bg-emerald-500' : 'bg-rose-500', ].join(' ')} /> - {isPaid ? "To'langan" : "To'lanmagan"} + {isPaid ? t('Cabinet.paid') : t('Cabinet.unpaid')} ); }; @@ -127,7 +128,7 @@ export const HistoryTableRow: React.FC< }} className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium text-slate-600 bg-white border border-slate-200 hover:border-slate-300 hover:text-slate-900 hover:bg-slate-50 active:scale-95 transition-all duration-150" > - Ko'rish + {tUnknown('HistoryPage.view')} diff --git a/src/widgets/home/index.tsx b/src/widgets/home/index.tsx index 28316d4..441e3c8 100644 --- a/src/widgets/home/index.tsx +++ b/src/widgets/home/index.tsx @@ -12,7 +12,7 @@ const PlagiarismLanding = () => { const data = localStorage.getItem('user'); if (data) { - route.push('/plagat'); + route.push('/plagiat'); } }, []); return ( diff --git a/src/widgets/plagiatCheck/lib/validation.ts b/src/widgets/plagiatCheck/lib/validation.ts index 6912b86..9390ee2 100644 --- a/src/widgets/plagiatCheck/lib/validation.ts +++ b/src/widgets/plagiatCheck/lib/validation.ts @@ -42,6 +42,11 @@ export function validatePlagiarismForm( } } + // docuemnt - type validation + if (!state.type) { + errors.type = 'Type is required!'; + } + return errors; } diff --git a/src/widgets/plagiatCheck/ui/Plagiraismcheckform.tsx b/src/widgets/plagiatCheck/ui/Plagiraismcheckform.tsx index d0ba1d3..2bdb55c 100644 --- a/src/widgets/plagiatCheck/ui/Plagiraismcheckform.tsx +++ b/src/widgets/plagiatCheck/ui/Plagiraismcheckform.tsx @@ -182,6 +182,8 @@ export function PlagiarismCheckForm() { value={form.type} onChange={setOption} disabled={submission.status === 'success'} + hasError={!!errors.type} + error={errors.type} /> {/* Submit */} diff --git a/src/widgets/plagiatCheck/ui/documentsType.tsx b/src/widgets/plagiatCheck/ui/documentsType.tsx index 6e5e92a..f070cc7 100644 --- a/src/widgets/plagiatCheck/ui/documentsType.tsx +++ b/src/widgets/plagiatCheck/ui/documentsType.tsx @@ -1,5 +1,5 @@ 'use client'; -import React from 'react'; +import React, { useEffect } from 'react'; import { useTranslations } from 'next-intl'; import { FieldWrapper } from './Plagiraismui'; import { useQuery } from '@tanstack/react-query'; @@ -16,12 +16,16 @@ interface DocumentsTypesProps { value: number; onChange: (value: number) => void; disabled?: boolean; + hasError?: boolean; + error?: string; } export default function DocumentsTypes({ value, onChange, disabled, + hasError, + error, }: DocumentsTypesProps) { const t = useTranslations('DocumentTypes'); const { data, isLoading } = useQuery({ @@ -32,18 +36,29 @@ export default function DocumentsTypes({ ), }); - const selected = data?.find((item) => item.id === value); + useEffect(() => { + console.log({ value }); + }, [value]); return ( - +
{row.state ? ( - { - if (row.state === 'unpaid') { - setIsPaymentOpen(true); - } - }} - className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-medium text-emerald-600 bg-emerald-50" - > + ) : ( @@ -162,7 +155,7 @@ export function PaymentsTable() { )}