From d70360841aa2da8fc37b0164fbd699401485e788 Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Mon, 6 Apr 2026 09:50:48 +0500 Subject: [PATCH] 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 */}