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 (
+
+ );
+}
+
+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 */}
-