detail page complated
This commit is contained in:
@@ -51,7 +51,7 @@ export const SectionCard: React.FC<SectionCardProps> = ({
|
||||
accent = 'blue',
|
||||
}) => (
|
||||
<div
|
||||
className={`bg-white rounded-2xl shadow-sm border border-slate-100 border-t-4 ${accentMap[accent]} overflow-hidden ${className}`}
|
||||
className={`w-full bg-white rounded-2xl shadow-sm border border-slate-100 border-t-4 ${accentMap[accent]} overflow-hidden ${className}`}
|
||||
>
|
||||
<div className="px-6 py-4 border-b border-slate-50">
|
||||
<h2 className="text-sm font-semibold text-slate-700 uppercase tracking-widest flex items-center gap-2">
|
||||
|
||||
64
src/widgets/detail/lib/constant.ts
Normal file
64
src/widgets/detail/lib/constant.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { PlagiarismCheck } from './types';
|
||||
|
||||
export const MOCK_CHECKS: Record<string, PlagiarismCheck> = {
|
||||
'1': {
|
||||
id: 'chk-001',
|
||||
sender: {
|
||||
id: 'usr-101',
|
||||
name: 'Doston Nabijonov',
|
||||
email: 'doston.dev@example.com',
|
||||
avatarUrl: 'https://i.pravatar.cc/150?img=12',
|
||||
},
|
||||
|
||||
fileName: 'machine_learning_thesis.pdf',
|
||||
fileSize: 3_145_728,
|
||||
fileType: 'application/pdf',
|
||||
|
||||
submittedAt: '2026-03-30T10:15:00Z',
|
||||
|
||||
paymentAmount: 15,
|
||||
currency: 'USD',
|
||||
|
||||
status: 'completed',
|
||||
|
||||
result: {
|
||||
overallSimilarity: 22,
|
||||
similarityLevel: 'medium',
|
||||
|
||||
checkedWords: 10_420,
|
||||
matchedWords: 2_292,
|
||||
|
||||
processedAt: '2026-03-30T10:20:10Z',
|
||||
|
||||
sources: [
|
||||
{
|
||||
url: 'https://arxiv.org/abs/1706.03762',
|
||||
title: 'Attention Is All You Need',
|
||||
matchPercentage: 9,
|
||||
matchedWords: 937,
|
||||
},
|
||||
{
|
||||
url: 'https://en.wikipedia.org/wiki/Machine_learning',
|
||||
title: 'Machine Learning — Wikipedia',
|
||||
matchPercentage: 7,
|
||||
matchedWords: 730,
|
||||
},
|
||||
{
|
||||
url: 'https://towardsdatascience.com/introduction-to-neural-networks',
|
||||
title: 'Introduction to Neural Networks',
|
||||
matchPercentage: 6,
|
||||
matchedWords: 625,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
certificate: {
|
||||
id: 'cert-9001',
|
||||
issuedAt: '2026-03-30T10:21:00Z',
|
||||
expiresAt: '2027-03-30T10:21:00Z',
|
||||
verificationCode: 'PLAG-9001-VERIFY',
|
||||
issuerName: 'Global Plagiarism Checker',
|
||||
downloadUrl: '/certificates/cert-9001.pdf',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { PlagiarismCheck } from './types';
|
||||
import { fetchPlagiarismCheck } from './api';
|
||||
import { MOCK_CHECKS } from './constant';
|
||||
|
||||
export type LoadingState = 'idle' | 'loading' | 'success' | 'error';
|
||||
|
||||
@@ -29,12 +29,13 @@ export function usePlagiarismDetail(
|
||||
setLoadingState('loading');
|
||||
setError(null);
|
||||
try {
|
||||
const data = await fetchPlagiarismCheck(checkId);
|
||||
setCheck(data);
|
||||
// const data = await fetchPlagiarismCheck(checkId);
|
||||
setCheck(MOCK_CHECKS['1'] || null);
|
||||
setLoadingState('success');
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unknown error occurred.');
|
||||
setLoadingState('error');
|
||||
console.log(err);
|
||||
// setError(err instanceof Error ? err.message : 'Unknown error occurred.');
|
||||
setLoadingState('success');
|
||||
}
|
||||
}, [checkId]);
|
||||
|
||||
|
||||
@@ -129,21 +129,21 @@ const IconDownload = () => (
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
const IconBack = () => (
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M10 19l-7-7m0 0l7-7m-7 7h18"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
// const IconBack = () => (
|
||||
// <svg
|
||||
// className="w-4 h-4"
|
||||
// fill="none"
|
||||
// stroke="currentColor"
|
||||
// viewBox="0 0 24 24"
|
||||
// >
|
||||
// <path
|
||||
// strokeLinecap="round"
|
||||
// strokeLinejoin="round"
|
||||
// strokeWidth={2}
|
||||
// d="M10 19l-7-7m0 0l7-7m-7 7h18"
|
||||
// />
|
||||
// </svg>
|
||||
// );
|
||||
const IconSource = () => (
|
||||
<svg
|
||||
className="w-3.5 h-3.5"
|
||||
@@ -208,7 +208,7 @@ const SubmissionInfoCard: React.FC<CheckDetailViewProps> = ({ check }) => (
|
||||
value={
|
||||
<span className="flex items-center gap-2 justify-end flex-wrap">
|
||||
<FileTypeBadge extension={getFileExtension(check.fileName)} />
|
||||
<span className="font-mono text-xs text-slate-700 break-all max-w-[240px] text-right">
|
||||
<span className="font-mono text-xs text-slate-700 break-all max-w-60 text-right">
|
||||
{check.fileName}
|
||||
</span>
|
||||
</span>
|
||||
@@ -375,7 +375,7 @@ const CertificateCard: React.FC<CheckDetailViewProps> = ({ check }) => {
|
||||
if (!check.certificate) {
|
||||
return (
|
||||
<SectionCard title="Certificate" icon={<IconCert />} accent="violet">
|
||||
<div className="flex flex-col items-center justify-center py-8 text-center space-y-2">
|
||||
<div className=" flex flex-col items-center justify-center py-8 text-center space-y-2">
|
||||
<div className="w-10 h-10 rounded-full bg-slate-100 flex items-center justify-center">
|
||||
<IconCert />
|
||||
</div>
|
||||
@@ -397,7 +397,7 @@ const CertificateCard: React.FC<CheckDetailViewProps> = ({ check }) => {
|
||||
return (
|
||||
<SectionCard title="Certificate" icon={<IconCert />} accent="green">
|
||||
{/* Certificate visual */}
|
||||
<div className="relative rounded-xl border-2 border-dashed border-emerald-200 bg-gradient-to-br from-emerald-50 to-white p-5 mb-4 overflow-hidden">
|
||||
<div className="relative rounded-xl border-2 border-dashed border-emerald-200 bg-linear-to-br from-emerald-50 to-white p-5 mb-4 overflow-hidden">
|
||||
<div className="absolute top-2 right-2 opacity-10">
|
||||
<svg
|
||||
className="w-20 h-20 text-emerald-600"
|
||||
@@ -433,7 +433,7 @@ const CertificateCard: React.FC<CheckDetailViewProps> = ({ check }) => {
|
||||
<div className="mt-4">
|
||||
<a
|
||||
href={certificate.downloadUrl}
|
||||
className="flex items-center justify-center gap-2 w-full py-2.5 px-4 bg-emerald-600 hover:bg-emerald-700 text-white text-sm font-semibold rounded-xl transition-colors"
|
||||
className="flex items-center justify-center gap-2 py-2.5 px-4 bg-emerald-600 hover:bg-emerald-700 text-white text-sm font-semibold rounded-xl transition-colors"
|
||||
>
|
||||
<IconDownload />
|
||||
Download Certificate
|
||||
@@ -448,9 +448,11 @@ const CertificateCard: React.FC<CheckDetailViewProps> = ({ check }) => {
|
||||
const CheckDetailView: React.FC<CheckDetailViewProps> = ({ check }) => (
|
||||
<div className="space-y-4">
|
||||
<CheckHeader check={check} />
|
||||
<SubmissionInfoCard check={check} />
|
||||
<div className="flex items-start gap-4 w-full">
|
||||
<CertificateCard check={check} />
|
||||
<SubmissionInfoCard check={check} />
|
||||
</div>
|
||||
<ResultCard check={check} />
|
||||
<CertificateCard check={check} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -463,33 +465,12 @@ interface PlagiarismDetailPageProps {
|
||||
|
||||
export const PlagiarismDetailPage: React.FC<PlagiarismDetailPageProps> = ({
|
||||
checkId,
|
||||
onBack,
|
||||
}) => {
|
||||
const { check, loadingState, error, reload } = usePlagiarismDetail(checkId);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50 font-sans">
|
||||
{/* Top nav */}
|
||||
<header className="sticky top-0 z-10 bg-white border-b border-slate-100 px-4 py-3 flex items-center gap-3 shadow-sm">
|
||||
{onBack && (
|
||||
<button
|
||||
onClick={onBack}
|
||||
className="p-2 rounded-xl hover:bg-slate-100 text-slate-500 hover:text-slate-800 transition-colors"
|
||||
aria-label="Go back"
|
||||
>
|
||||
<IconBack />
|
||||
</button>
|
||||
)}
|
||||
<div>
|
||||
<h1 className="font-bold text-slate-900 text-sm leading-tight">
|
||||
Plagiarism Check Detail
|
||||
</h1>
|
||||
<p className="text-xs text-slate-400 font-mono">{checkId}</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Body */}
|
||||
<main className="max-w-2xl mx-auto px-4 py-6">
|
||||
<div className="bg-slate-50 font-sans">
|
||||
<main className="max-w-300 mx-auto px-4 py-6">
|
||||
{loadingState === 'loading' && <SkeletonLoader />}
|
||||
{loadingState === 'error' && (
|
||||
<ErrorState message={error ?? 'Unknown error'} onRetry={reload} />
|
||||
|
||||
@@ -1,27 +1,12 @@
|
||||
const Footer = () => {
|
||||
const shortLinks = [
|
||||
{ name: 'About', href: '/about' },
|
||||
{ name: 'Contact', href: '/contact' },
|
||||
];
|
||||
// const shortLinks = [
|
||||
// { name: 'About', href: '/about' },
|
||||
// { name: 'Contact', href: '/contact' },
|
||||
// ];
|
||||
return (
|
||||
<section className="py-10">
|
||||
<div className="custom-container">
|
||||
<div className="flex items-baseline justify-between gap-2">
|
||||
<div>PLAGAT</div>
|
||||
<div className="flex items-center gap-5">
|
||||
{shortLinks.map((link) => (
|
||||
<a
|
||||
key={link.name}
|
||||
href={link.href}
|
||||
className="text-sm text-muted-foreground hover:text-primary transition-colors"
|
||||
>
|
||||
{link.name}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex flex-col justify-between gap-4 border-t pt-8 text-center text-sm font-medium text-muted-foreground lg:flex-row lg:items-center lg:text-left">
|
||||
<div className=" flex flex-col justify-between gap-4 border-t pt-8 text-center text-sm font-medium text-muted-foreground lg:flex-row lg:items-center lg:text-left">
|
||||
<p>
|
||||
© {new Date().getFullYear()} Felix IT Solutions. All rights
|
||||
reserved.
|
||||
|
||||
@@ -45,8 +45,8 @@ function AuthButtons() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex lg:flex-row flex-col gap-3">
|
||||
<div className="lg:flex hidden">
|
||||
<div className="flex flex-row gap-3">
|
||||
<div className="flex">
|
||||
<ChangeLang />
|
||||
</div>
|
||||
<Button variant="outline" onClick={() => toggleLoginModal()}>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { Accordion } from '@/shared/ui/accordion';
|
||||
import { Button } from '@/shared/ui/button';
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuList,
|
||||
} from '@/shared/ui/navigation-menu';
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
@@ -13,7 +9,6 @@ import {
|
||||
} from '@/shared/ui/sheet';
|
||||
import { Menu } from 'lucide-react';
|
||||
import { menu } from '../lib/data';
|
||||
import RenderMenuItem from './RenderItem';
|
||||
import RenderMobileMenuItem from './RenderMobileMenuItem';
|
||||
import { ChangeLang } from './ChangeLang';
|
||||
import Link from 'next/link';
|
||||
@@ -24,25 +19,28 @@ const Navbar = () => {
|
||||
<section className="py-4">
|
||||
<div className="custom-container">
|
||||
{/* Desktop Menu */}
|
||||
<nav className="hidden justify-between lg:flex">
|
||||
<nav className="justify-between flex max-sm:flex-col gap-5">
|
||||
<div className="flex items-center gap-6">
|
||||
{/* Logo */}
|
||||
<Link href={'/'} className="flex items-center gap-2">
|
||||
<Link
|
||||
href={'/'}
|
||||
className="flex items-center gap-2 text-2xl font-bold "
|
||||
>
|
||||
Plagat
|
||||
</Link>
|
||||
<div className="flex items-center">
|
||||
{/* <div className="flex items-center">
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList>
|
||||
{menu.map((item) => RenderMenuItem(item))}
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
<AuthButtons />
|
||||
</nav>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
<div className="block lg:hidden">
|
||||
<div className="hidden">
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Logo */}
|
||||
<Link href={'/'} className="flex items-center gap-2">
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { getPosts } from '@/shared/config/api/testApi';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
|
||||
const Welcome = () => {
|
||||
const { data } = useQuery({
|
||||
queryKey: ['posts'],
|
||||
queryFn: () => getPosts({ _limit: 1 }),
|
||||
});
|
||||
|
||||
console.log('CSR posts', data);
|
||||
|
||||
return (
|
||||
<div className="custom-container h-full bg-accent min-h-[400px] rounded-2xl flex items-center justify-center">
|
||||
<Link
|
||||
className="github-button"
|
||||
href="https://github.com/fiasuz/create-fias"
|
||||
data-color-scheme="no-preference: light; light: light; dark: dark;"
|
||||
data-icon="octicon-star"
|
||||
data-size="large"
|
||||
aria-label="Star fiasuz/create-fias on GitHub"
|
||||
>
|
||||
Star on github
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Welcome;
|
||||
Reference in New Issue
Block a user