diff --git a/public/favicon.png b/public/favicon.png deleted file mode 100644 index 5b09d10..0000000 Binary files a/public/favicon.png and /dev/null differ diff --git a/src/app/[locale]/[detail]/page.tsx b/src/app/[locale]/[detail]/page.tsx index 1871be4..9aa3d63 100644 --- a/src/app/[locale]/[detail]/page.tsx +++ b/src/app/[locale]/[detail]/page.tsx @@ -1,4 +1,4 @@ -import { PlagiarismDetailPage } from '@/widgets/detail/ui/detailPage'; +import PlagiatResult from '@/widgets/detail/ui/PlagiatResult'; interface Props { params: Promise<{ detail: string }>; @@ -6,5 +6,6 @@ interface Props { export default async function DetailPage({ params }: Props) { const { detail } = await params; - return ; + console.log(detail); + return ; } diff --git a/src/image/index.ts b/src/image/index.ts new file mode 100644 index 0000000..03d49f7 --- /dev/null +++ b/src/image/index.ts @@ -0,0 +1 @@ +export { default as Logo_image } from './logo.png'; diff --git a/src/image/logo.png b/src/image/logo.png new file mode 100644 index 0000000..ff38c1d Binary files /dev/null and b/src/image/logo.png differ diff --git a/src/shared/request/links.ts b/src/shared/request/links.ts index 88ab53f..4a6839c 100644 --- a/src/shared/request/links.ts +++ b/src/shared/request/links.ts @@ -1,6 +1,6 @@ export const links = { login: '/users/login/', register: '/users/register/', - plagiarismCheck: '/plagiarism/check/', + plagiarismCheck: '/shared/documents/', history: '/shared/documents/list/', }; diff --git a/src/widgets/detail/ui/PlagiatResult.tsx b/src/widgets/detail/ui/PlagiatResult.tsx new file mode 100644 index 0000000..cdcc58e --- /dev/null +++ b/src/widgets/detail/ui/PlagiatResult.tsx @@ -0,0 +1,460 @@ +'use client'; +import { useState } from 'react'; + +interface Source { + url: string; + title: string; + matchPercentage: number; + matchedWords: number; +} + +interface CheckResult { + ai: number; + plagiarism: number; + originality: number; + citation: number; + checkedWords: number; + uniqueWords: number; + lexicalUniqueness: number; + sentences: number; + avgWordsPerSentence: number; + lines: number; + sources: Source[]; +} + +interface Certificate { + id: string; + issuedAt: string; + expiresAt: string; + verificationCode: string; + issuerName: string; + downloadUrl: string; +} + +interface PlagiatResultProps { + name: string; + initials: string; + email: string; + location: string; + fileName: string; + checkedAt: string; + result: CheckResult; + certificate: Certificate; +} + +// Blue palette +const blue = { + 50: '#E6F1FB', + 100: '#B5D4F4', + 200: '#85B7EB', + 400: '#378ADD', + 600: '#185FA5', + 800: '#0C447C', + 900: '#042C53', +}; + +const mockData: PlagiatResultProps = { + name: 'Sokhibjon Orzikulov', + initials: 'SO', + email: 'sakhib@orzklv.uz', + location: 'Tashkent, Uzbekistan', + fileName: 'resume_sokhibjon.pdf', + checkedAt: '2026-04-02', + result: { + ai: 86, + plagiarism: 92, + originality: 5, + citation: 3, + checkedWords: 1477, + uniqueWords: 593, + lexicalUniqueness: 40, + sentences: 105, + avgWordsPerSentence: 14.1, + lines: 191, + sources: [ + { + url: 'https://arxiv.org/abs/1706.03762', + title: 'arxiv.org — Attention Is All You Need', + matchPercentage: 9, + matchedWords: 937, + }, + { + url: 'https://en.wikipedia.org/wiki/Machine_learning', + title: 'Wikipedia — Machine Learning', + matchPercentage: 7, + matchedWords: 730, + }, + { + url: 'https://towardsdatascience.com/introduction-to-neural-networks', + title: 'Towards Data Science — Introduction to Neural Networks', + matchPercentage: 6, + matchedWords: 625, + }, + ], + }, + certificate: { + id: 'cert-9001', + issuedAt: '2026-03-30', + expiresAt: '2027-03-30', + verificationCode: 'PLAG-9001-VERIFY', + issuerName: 'Global Plagiarism Checker', + downloadUrl: '/certificates/cert-9001.pdf', + }, +}; + +// ─── Sub-components ─────────────────────────────────────────────────────────── + +function CircleGauge({ value }: { value: number }) { + const r = 50; + const circ = 2 * Math.PI * r; + const offset = circ - (value / 100) * circ; + + return ( + + + + + {value}% + + + plagiat + + + ); +} + +function BarRow({ + label, + value, + color, +}: { + label: string; + value: number; + color: string; +}) { + return ( +
+
+ + + {label} + + + {value}% + +
+
+
+
+
+ ); +} + +function MetricCard({ label, value }: { label: string; value: string }) { + return ( +
+

+ {label} +

+

+ {value} +

+
+ ); +} + +function SourceItem({ source, index }: { source: Source; index: number }) { + const colors = [blue[900], blue[600], blue[400]]; + const color = colors[index] ?? blue[400]; + + return ( +
+
+ + {source.title} + + + {source.matchPercentage}% + +
+

+ {source.url} +

+
+
+
+
+ ); +} + +// ─── Main component ─────────────────────────────────────────────────────────── + +export default function PlagiatResult({ + data = mockData, +}: { + data?: PlagiatResultProps; +}) { + const [downloading, setDownloading] = useState(false); + const { result, certificate } = data; + + const handleDownload = () => { + setDownloading(true); + setTimeout(() => setDownloading(false), 1500); + }; + + const divider = ( +
+ ); + + return ( +
+
+ {/* ── Header ── */} +
+
+ {data.initials} +
+
+
+ + {data.name} + + + {result.plagiarism}% plagiat + +
+

+ {data.fileName} · {data.email} · {data.location} +

+
+
+

+ Tekshirilgan +

+

+ {data.checkedAt} +

+
+
+ + {divider} + + {/* ── Top metrics ── */} +
+ + + + +
+ + {/* ── Gauge + bars ── */} +
+ +
+ + + + +
+
+ + {divider} + + {/* ── Text analysis ── */} +

+ Matn tahlili +

+
+ + + + + + +
+ + {divider} + + {/* ── Sources ── */} +

+ Manba yo‘nalishlari +

+
+ {result.sources.map((source, i) => ( + + ))} +
+ + {divider} + + {/* ── Certificate ── */} +

+ Sertifikat +

+
+
+ +
+
+

+ {certificate.issuerName} sertifikati +

+

+ {certificate.verificationCode} · Amal qilish:{' '} + {certificate.expiresAt} +

+
+ +
+
+
+ ); +} diff --git a/src/widgets/fileUpload/lib/types.ts b/src/widgets/fileUpload/lib/types.ts index a966a7c..f515e91 100644 --- a/src/widgets/fileUpload/lib/types.ts +++ b/src/widgets/fileUpload/lib/types.ts @@ -24,9 +24,11 @@ export interface PlagiarismSubmissionResponse { // ─── Form State Types ──────────────────────────────────────────────────────── export interface PlagiarismFormState { - topic: string; + title: string; file: File | null; - withCertificate: boolean; + certificate: boolean; + text?: string; + total_price: number; } export type PlagiarismFormErrors = Partial< @@ -39,6 +41,5 @@ export type SubmissionStatus = 'idle' | 'loading' | 'success' | 'error'; export interface SubmissionState { status: SubmissionStatus; - response: PlagiarismSubmissionResponse | null; error: string | null; } diff --git a/src/widgets/fileUpload/lib/usePlagiraism.ts b/src/widgets/fileUpload/lib/usePlagiraism.ts index 8a588a8..c1010a1 100644 --- a/src/widgets/fileUpload/lib/usePlagiraism.ts +++ b/src/widgets/fileUpload/lib/usePlagiraism.ts @@ -5,30 +5,31 @@ import { PlagiarismFormState, SubmissionState, } from './types'; -import { selectFullName, useUserStore } from './userStore'; import { isFormValid, validatePlagiarismForm } from './validation'; -import { submitPlagiarismCheck } from '@/shared/request/plagiarismapi'; import { toast } from 'react-toastify'; import { useUserPlagiatStore } from '@/shared/zustand/user'; +import { useMutation } from '@tanstack/react-query'; +import { links } from '@/shared/request/links'; +import { apiRequest } from '@/shared/request/apiRequest'; // ─── Initial States ────────────────────────────────────────────────────────── const INITIAL_FORM: PlagiarismFormState = { - topic: '', + title: '', file: null, - withCertificate: false, + certificate: true, + text: '', + total_price: 41200, }; const INITIAL_SUBMISSION: SubmissionState = { status: 'idle', - response: null, error: null, }; // ─── Hook ──────────────────────────────────────────────────────────────────── export function usePlagiarismForm() { - const senderFullName = useUserStore(selectFullName); const user = useUserPlagiatStore((state) => state.user); const [form, setForm] = useState(INITIAL_FORM); const [errors, setErrors] = useState({}); @@ -36,15 +37,28 @@ export function usePlagiarismForm() { const [submission, setSubmission] = useState(INITIAL_SUBMISSION); - // const checkdocumentRequest = useMutation({ - // mutationFn: (data:any) => apiRequest("POST",links.plagiarismCheck, data) - // }) + const checkdocumentRequest = useMutation({ + mutationKey: ['plagiarismCheck'], + mutationFn: (data: FormData) => + apiRequest('POST', links.plagiarismCheck, data), + onSuccess: () => { + setSubmission({ status: 'success', error: null }); + setForm(INITIAL_FORM); + setIsPaymentOpen(false); + }, + onError: (err) => { + const message = + err instanceof Error ? err.message : 'An unexpected error occurred.'; + setSubmission({ status: 'error', error: message }); + setIsPaymentOpen(false); + }, + }); // ── Field updaters ─────────────────────────────────────────────────────── const setTopic = useCallback((topic: string) => { - setForm((prev) => ({ ...prev, topic })); - setErrors((prev) => ({ ...prev, topic: undefined })); + setForm((prev) => ({ ...prev, title: topic })); + setErrors((prev) => ({ ...prev, title: undefined })); }, []); const setFile = useCallback((file: File | null) => { @@ -53,7 +67,7 @@ export function usePlagiarismForm() { }, []); const toggleCertificate = useCallback(() => { - setForm((prev) => ({ ...prev, withCertificate: !prev.withCertificate })); + setForm((prev) => ({ ...prev, certificate: !prev.certificate })); }, []); // ── Submission ─────────────────────────────────────────────────────────── @@ -82,23 +96,15 @@ export function usePlagiarismForm() { ); const handleSubmit = useCallback(async () => { - setSubmission({ status: 'loading', response: null, error: null }); - try { - const response = await submitPlagiarismCheck({ - topic: form.topic.trim(), - senderFullName, - file: form.file!, - withCertificate: form.withCertificate, - }); - setSubmission({ status: 'success', response, error: null }); - setForm(INITIAL_FORM); - setIsPaymentOpen(false); // Close modal on success - } catch (err) { - const message = - err instanceof Error ? err.message : 'An unexpected error occurred.'; - setSubmission({ status: 'error', response: null, error: message }); - } - }, [form, senderFullName]); + setSubmission({ status: 'loading', error: null }); + const fd = new FormData(); + fd.append('title', form.title.trim()); + fd.append('text', `${user?.name} ${user?.surname}` || ''); + fd.append('file', form.file!); // File object — multipart/form-data + fd.append('certificate', String(form.certificate)); + fd.append('total_price', '41200'); + checkdocumentRequest.mutate(fd); + }, [form, user]); const resetSubmission = useCallback(() => { setSubmission(INITIAL_SUBMISSION); @@ -112,7 +118,7 @@ export function usePlagiarismForm() { form, errors, submission, - senderFullName, + senderFullName: user ? `${user?.name} ${user?.surname}` : null, isLoading, setTopic, setFile, diff --git a/src/widgets/fileUpload/lib/validation.ts b/src/widgets/fileUpload/lib/validation.ts index ab6b4fd..6912b86 100644 --- a/src/widgets/fileUpload/lib/validation.ts +++ b/src/widgets/fileUpload/lib/validation.ts @@ -22,13 +22,13 @@ export function validatePlagiarismForm( const errors: PlagiarismFormErrors = {}; // Topic validation - const trimmedTopic = state.topic.trim(); + const trimmedTopic = state.title.trim(); if (!trimmedTopic) { - errors.topic = 'Topic is required.'; + errors.title = 'Title is required.'; } else if (trimmedTopic.length < 3) { - errors.topic = 'Topic must be at least 3 characters.'; + errors.title = 'Title must be at least 3 characters.'; } else if (trimmedTopic.length > 200) { - errors.topic = 'Topic must not exceed 200 characters.'; + errors.title = 'Title must not exceed 200 characters.'; } // File validation diff --git a/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx b/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx index 0d690f8..23ead3c 100644 --- a/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx +++ b/src/widgets/fileUpload/ui/Plagiraismcheckform.tsx @@ -83,10 +83,10 @@ export function PlagiarismCheckForm() { className="p-7 flex md:flex-row flex-col gap-6" > {/* Status banners */} - {submission.status === 'success' && submission.response && ( + {submission.status === 'success' && ( @@ -105,17 +105,17 @@ export function PlagiarismCheckForm() { {/* Topic */} setTopic(e.target.value)} - hasError={!!errors.topic} + hasError={!!errors.title} maxLength={200} disabled={isLoading} /> @@ -136,7 +136,7 @@ export function PlagiarismCheckForm() { {t('certificateOption')}

setIsPaymentOpen(false)} - hasCertificate={form.withCertificate} + hasCertificate={form.certificate} onConfirmPayment={handleSubmit} isLoading={isLoading} /> diff --git a/src/widgets/history/lib/useHistory.ts b/src/widgets/history/lib/useHistory.ts index 3c810b8..27f205b 100644 --- a/src/widgets/history/lib/useHistory.ts +++ b/src/widgets/history/lib/useHistory.ts @@ -3,6 +3,9 @@ import { useState, useEffect, useCallback } from 'react'; import { DEFAULT_PAGE_SIZE } from './constants'; import { HistoryState } from './types'; import { DEFAULT_HISTORY_ITEMS } from './mock'; +import { useQuery } from '@tanstack/react-query'; +import { links } from '@/shared/request/links'; +import { apiRequest } from '@/shared/request/apiRequest'; interface UseHistoryReturn extends HistoryState { refetch: () => void; @@ -20,27 +23,37 @@ export const useHistory = (pageSize = DEFAULT_PAGE_SIZE): UseHistoryReturn => { const [currentPage, setCurrentPage] = useState(1); const [total, setTotal] = useState(0); - const loadHistory = useCallback( - async (page: number) => { - setState((prev) => ({ ...prev, status: 'loading', error: null })); - setTotal(0); - console.log(page); + const { data, refetch } = useQuery({ + queryKey: ['history'], + queryFn: () => apiRequest('GET', links.history), + select: (response) => { + const { results, total } = response.data as { + results: []; + total: number; + }; + return { results, total }; }, - [pageSize], - ); + }); useEffect(() => { - loadHistory(currentPage); - }, [currentPage, loadHistory]); + if (data) { + setState({ + items: data?.results || [], + status: 'success', + error: null, + }); + setTotal(data?.total || 0); + } + }, [data]); + + useEffect(() => { + refetch(); + }, [currentPage]); const goToPage = useCallback((page: number) => { setCurrentPage(page); }, []); - const refetch = useCallback(() => { - loadHistory(currentPage); - }, [currentPage, loadHistory]); - const totalPages = Math.ceil(total / pageSize); return { diff --git a/src/widgets/history/ui/historyPage.tsx b/src/widgets/history/ui/historyPage.tsx index e42fc11..6423a44 100644 --- a/src/widgets/history/ui/historyPage.tsx +++ b/src/widgets/history/ui/historyPage.tsx @@ -4,19 +4,11 @@ import { useTranslations } from 'next-intl'; import { useHistory } from '../lib/useHistory'; import { HistoryTable } from './historyTable'; import { Pagination } from './pagination'; -import { useQuery } from '@tanstack/react-query'; -import { apiRequest } from '@/shared/request/apiRequest'; -import { links } from '@/shared/request/links'; // ─── Page Header ─────────────────────────────────────────────────────────────── const PageHeader: React.FC = () => { const t = useTranslations('HistoryPage'); - const { data } = useQuery({ - queryKey: ['history'], - queryFn: () => apiRequest('GET', links.history), - }); - console.log('History data:', data); // Debugging log return (
diff --git a/src/widgets/navbar/ui/index.tsx b/src/widgets/navbar/ui/index.tsx index 2fedfdb..d4b9b68 100644 --- a/src/widgets/navbar/ui/index.tsx +++ b/src/widgets/navbar/ui/index.tsx @@ -14,6 +14,8 @@ import { ChangeLang } from './ChangeLang'; import Link from 'next/link'; import { AuthButtons } from './authButtons'; import { useTranslations } from 'next-intl'; +import { Logo_image } from '@/image'; +import Image from 'next/image'; const Navbar = () => { const t = useTranslations('Navbar'); @@ -23,16 +25,22 @@ const Navbar = () => {
{/* Desktop Menu */} -