update navbar , create pages
This commit is contained in:
BIN
public/lanka.jpg
Normal file
BIN
public/lanka.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
26
src/app/[locale]/dashboard/page.tsx
Normal file
26
src/app/[locale]/dashboard/page.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
'use client';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Dashboard } from '@/widgets/cabinet/ui/dashboard';
|
||||||
|
import { MOCK_USER } from '@/widgets/cabinet/lib/mock';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const [userName, setUserName] = useState(MOCK_USER.first_name);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const data = localStorage.getItem('user');
|
||||||
|
if (data) {
|
||||||
|
try {
|
||||||
|
const user = JSON.parse(data);
|
||||||
|
setUserName(user.name || MOCK_USER.first_name);
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-6xl mx-auto w-full pt-20 px-4 pb-10">
|
||||||
|
<Dashboard userName={userName} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
9
src/app/[locale]/payments/page.tsx
Normal file
9
src/app/[locale]/payments/page.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { PaymentsTable } from '@/widgets/cabinet/ui/tables/PaymentsTable';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<div className="max-w-6xl mx-auto w-full pt-20 px-4 pb-10">
|
||||||
|
<PaymentsTable />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
src/app/[locale]/profile/page.tsx
Normal file
10
src/app/[locale]/profile/page.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { ProfileSection } from '@/widgets/cabinet/ui/profile';
|
||||||
|
import { MOCK_STATS } from '@/widgets/cabinet/lib/mock';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<div className="max-w-6xl mx-auto w-full pt-20 px-4 pb-10">
|
||||||
|
<ProfileSection stats={MOCK_STATS} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
12
src/app/[locale]/si/page.tsx
Normal file
12
src/app/[locale]/si/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { SiTable } from '@/widgets/cabinet/ui/tables/SiTable';
|
||||||
|
import { SiUploadSection } from '@/features/modals/siModal/ui/SiUploadSection';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<div className="max-w-6xl mx-auto w-full pt-20 px-4">
|
||||||
|
<SiUploadSection />
|
||||||
|
<SiTable />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -52,7 +52,7 @@ export function useLoginForm() {
|
|||||||
console.log('Login successful:', data);
|
console.log('Login successful:', data);
|
||||||
toggleLoginModal();
|
toggleLoginModal();
|
||||||
toast.success('Kirish muvaffaqiyatli!');
|
toast.success('Kirish muvaffaqiyatli!');
|
||||||
route.push('/plagiat');
|
route.push('/dashboard');
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
console.log('Login failed:', err);
|
console.log('Login failed:', err);
|
||||||
|
|||||||
@@ -1,54 +1,30 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { ArrowRight, BrainCircuit } from 'lucide-react';
|
||||||
import { ArrowRight, BrainCircuit, Plus } from 'lucide-react';
|
|
||||||
import { FileUploadModal } from './ui/fileUploadModal';
|
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
export function SiCTACard() {
|
export function SiCTACard() {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const t = useTranslations('Cabinet');
|
const t = useTranslations('Cabinet');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Link
|
||||||
<button
|
href="/si"
|
||||||
onClick={() => setIsOpen(true)}
|
className="group relative overflow-hidden rounded-2xl bg-linear-to-br from-violet-500 to-violet-600 p-6 text-left shadow-md hover:shadow-xl transition-all duration-200 hover:-translate-y-0.5 active:translate-y-0 active:shadow-md"
|
||||||
className="group relative overflow-hidden rounded-2xl bg-linear-to-br from-violet-500 to-violet-600 p-6 text-left shadow-md hover:shadow-xl transition-all duration-200 hover:-translate-y-0.5 active:translate-y-0 active:shadow-md"
|
>
|
||||||
>
|
<div className="absolute right-3 top-3 opacity-10 pointer-events-none">
|
||||||
<div className="absolute right-3 top-3 opacity-10 pointer-events-none">
|
<BrainCircuit size={72} className="text-white" />
|
||||||
<BrainCircuit size={72} className="text-white" />
|
</div>
|
||||||
</div>
|
<BrainCircuit size={26} className="text-white mb-4" />
|
||||||
<BrainCircuit size={26} className="text-white mb-4" />
|
<h3 className="text-white font-semibold text-base mb-1">
|
||||||
<h3 className="text-white font-semibold text-base mb-1">
|
{t('siDetector')}
|
||||||
{t('siDetector')}
|
</h3>
|
||||||
</h3>
|
<p className="text-violet-100 text-sm mb-4 leading-relaxed">
|
||||||
<p className="text-violet-100 text-sm mb-4 leading-relaxed">
|
{t('siDesc')}
|
||||||
{t('siDesc')}
|
</p>
|
||||||
</p>
|
<span className="inline-flex items-center gap-1.5 text-white text-xs font-medium bg-white/20 rounded-lg px-3 py-1.5 group-hover:bg-white/30 transition-colors">
|
||||||
<span className="inline-flex items-center gap-1.5 text-white text-xs font-medium bg-white/20 rounded-lg px-3 py-1.5 group-hover:bg-white/30 transition-colors">
|
{t('submit')} <ArrowRight size={12} />
|
||||||
{t('submit')} <ArrowRight size={12} />
|
</span>
|
||||||
</span>
|
</Link>
|
||||||
</button>
|
|
||||||
<FileUploadModal isOpen={isOpen} onClose={() => setIsOpen(false)} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SiButton() {
|
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
|
||||||
const t = useTranslations('Cabinet');
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
onClick={() => setIsOpen(true)}
|
|
||||||
className="flex items-center gap-2 py-1 px-2 group relative overflow-hidden rounded-sm bg-linear-to-br from-violet-500 to-violet-600 text-left shadow-md hover:shadow-xl transition-all duration-200 hover:-translate-y-0.5 active:translate-y-0 active:shadow-md"
|
|
||||||
>
|
|
||||||
<Plus size={15} className="text-white" />
|
|
||||||
<h3 className="text-white font-semibold text-base">
|
|
||||||
{t('siDetector')}
|
|
||||||
</h3>
|
|
||||||
</button>
|
|
||||||
<FileUploadModal isOpen={isOpen} onClose={() => setIsOpen(false)} />
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
117
src/features/modals/siModal/ui/SiUploadSection.tsx
Normal file
117
src/features/modals/siModal/ui/SiUploadSection.tsx
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { DEFAULT_PRICING, formatPrice } from '../utils/pricing';
|
||||||
|
import { PricingConfig } from '../utils/tyeps';
|
||||||
|
import { useFileUpload } from '../utils/useFileUpload';
|
||||||
|
import { SUPPORTED_EXTENSIONS } from '../utils/wordCount';
|
||||||
|
import { ErrorBanner, FileChip, PricingInfo, Spinner } from './modalParts';
|
||||||
|
import { DropZone } from './dropZone';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
interface SiUploadSectionProps {
|
||||||
|
pricing?: PricingConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SiUploadSection({
|
||||||
|
pricing = DEFAULT_PRICING,
|
||||||
|
}: SiUploadSectionProps) {
|
||||||
|
const t = useTranslations('Cabinet');
|
||||||
|
const {
|
||||||
|
documentName,
|
||||||
|
setDocumentName,
|
||||||
|
uploadedFile,
|
||||||
|
isDragging,
|
||||||
|
isProcessing,
|
||||||
|
error,
|
||||||
|
fileInputRef,
|
||||||
|
handleFileSelect,
|
||||||
|
handleDrop,
|
||||||
|
handleDragOver,
|
||||||
|
handleDragLeave,
|
||||||
|
handleRemoveFile,
|
||||||
|
openFilePicker,
|
||||||
|
canSubmit,
|
||||||
|
handleSubmit,
|
||||||
|
} = useFileUpload();
|
||||||
|
|
||||||
|
const wordCount = uploadedFile?.word_count ?? 0;
|
||||||
|
const totalPrice = uploadedFile?.total_price ?? 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full rounded-2xl bg-white border border-slate-100 shadow-sm flex flex-col gap-5 p-6 mb-8">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-semibold text-slate-800 tracking-tight">
|
||||||
|
{t('siDetector')}
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-slate-500 mt-0.5">{t('siDesc')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
<label
|
||||||
|
htmlFor="doc-name"
|
||||||
|
className="text-sm font-medium text-slate-700"
|
||||||
|
>
|
||||||
|
Document name{' '}
|
||||||
|
<span className="text-red-500" aria-hidden>
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="doc-name"
|
||||||
|
type="text"
|
||||||
|
value={documentName}
|
||||||
|
onChange={(e) => setDocumentName(e.target.value)}
|
||||||
|
placeholder="Enter document name…"
|
||||||
|
className="w-full rounded-xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-800 placeholder:text-slate-400 outline-none transition-all duration-150 focus:border-blue-400 focus:ring-4 focus:ring-blue-400/10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
type="file"
|
||||||
|
accept={SUPPORTED_EXTENSIONS.join(',')}
|
||||||
|
className="hidden"
|
||||||
|
aria-hidden="true"
|
||||||
|
onChange={(e) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (file) handleFileSelect(file);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{uploadedFile ? (
|
||||||
|
<div className="animate-in fade-in slide-in-from-top-1 duration-200">
|
||||||
|
<FileChip file={uploadedFile} onRemove={handleRemoveFile} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<DropZone
|
||||||
|
isDragging={isDragging}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
onDragLeave={handleDragLeave}
|
||||||
|
onClick={openFilePicker}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && <ErrorBanner message={error} />}
|
||||||
|
|
||||||
|
{uploadedFile?.status === 'done' && wordCount > 0 && (
|
||||||
|
<PricingInfo
|
||||||
|
wordCount={wordCount}
|
||||||
|
minimumPrice={formatPrice(pricing.minimumPayment)}
|
||||||
|
totalPrice={formatPrice(totalPrice)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={!canSubmit}
|
||||||
|
className="relative flex items-center gap-2 rounded-xl bg-blue-600 px-6 py-3 text-sm font-semibold text-white shadow-lg shadow-blue-500/25 transition-all duration-150 hover:bg-blue-700 hover:shadow-blue-500/40 active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed disabled:shadow-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-2"
|
||||||
|
>
|
||||||
|
{isProcessing && <Spinner />}
|
||||||
|
Check
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -10,7 +10,9 @@
|
|||||||
"login": "Login",
|
"login": "Login",
|
||||||
"signup": "Sign up",
|
"signup": "Sign up",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
"logout": "Logout"
|
"logout": "Logout",
|
||||||
|
"aboutPlagiat": "About Plagiarism",
|
||||||
|
"howItWorks": "How It Works"
|
||||||
},
|
},
|
||||||
"Footer": {
|
"Footer": {
|
||||||
"product": "Product",
|
"product": "Product",
|
||||||
|
|||||||
@@ -10,7 +10,9 @@
|
|||||||
"login": "Войти",
|
"login": "Войти",
|
||||||
"signup": "Регистрация",
|
"signup": "Регистрация",
|
||||||
"profile": "Профиль",
|
"profile": "Профиль",
|
||||||
"logout": "Выйти"
|
"logout": "Выйти",
|
||||||
|
"aboutPlagiat": "О плагиате",
|
||||||
|
"howItWorks": "Как это работает"
|
||||||
},
|
},
|
||||||
"Footer": {
|
"Footer": {
|
||||||
"product": "Продукт",
|
"product": "Продукт",
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ declare const messages: {
|
|||||||
signup: "Ro'yxatdan o'tish";
|
signup: "Ro'yxatdan o'tish";
|
||||||
profile: 'Profil';
|
profile: 'Profil';
|
||||||
logout: 'Chiqish';
|
logout: 'Chiqish';
|
||||||
|
aboutPlagiat: 'Plagiat haqida';
|
||||||
|
howItWorks: 'Bu qanday ishlaydi';
|
||||||
};
|
};
|
||||||
Footer: {
|
Footer: {
|
||||||
product: 'Mahsulot';
|
product: 'Mahsulot';
|
||||||
|
|||||||
@@ -10,7 +10,9 @@
|
|||||||
"login": "Kirish",
|
"login": "Kirish",
|
||||||
"signup": "Ro'yxatdan o'tish",
|
"signup": "Ro'yxatdan o'tish",
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"logout": "Chiqish"
|
"logout": "Chiqish",
|
||||||
|
"aboutPlagiat": "Plagiat haqida",
|
||||||
|
"howItWorks": "Bu qanday ishlaydi"
|
||||||
},
|
},
|
||||||
"Footer": {
|
"Footer": {
|
||||||
"product": "Mahsulot",
|
"product": "Mahsulot",
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { Sidebar } from './Sidebar';
|
|
||||||
import { CabinetNav } from './CabinetNav';
|
|
||||||
import { Dashboard } from './dashboard';
|
import { Dashboard } from './dashboard';
|
||||||
import { useCabinet } from '../lib/hooks/useCabinet';
|
import { useCabinet } from '../lib/hooks/useCabinet';
|
||||||
import { MOCK_USER, MOCK_STATS } from '../lib/mock';
|
import { MOCK_USER, MOCK_STATS } from '../lib/mock';
|
||||||
@@ -69,31 +67,15 @@ const FADE = {
|
|||||||
// ─── CabinetLayout ────────────────────────────────────────────────────────────
|
// ─── CabinetLayout ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export const CabinetLayout: React.FC = () => {
|
export const CabinetLayout: React.FC = () => {
|
||||||
const { activeSection, navigate, isSidebarOpen, toggleSidebar } =
|
const { activeSection } = useCabinet();
|
||||||
useCabinet();
|
|
||||||
const fullName = `${MOCK_USER.first_name} ${MOCK_USER.last_name}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex bg-slate-50 min-h-screen">
|
<main className="flex-1 p-4 md:p-6 lg:p-8 max-w-6xl mx-auto w-full pt-20">
|
||||||
<Sidebar
|
<AnimatePresence mode="wait">
|
||||||
active={activeSection}
|
<motion.div key={activeSection} {...FADE}>
|
||||||
onNavigate={navigate}
|
<SectionContent section={activeSection} />
|
||||||
isOpen={isSidebarOpen}
|
</motion.div>
|
||||||
onClose={toggleSidebar}
|
</AnimatePresence>
|
||||||
userName={fullName}
|
</main>
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex-1 flex flex-col min-w-0">
|
|
||||||
<CabinetNav activeSection={activeSection} onMenuClick={toggleSidebar} />
|
|
||||||
|
|
||||||
<main className="flex-1 p-4 md:p-6 lg:p-8 max-w-6xl mx-auto w-full">
|
|
||||||
<AnimatePresence mode="wait">
|
|
||||||
<motion.div key={activeSection} {...FADE}>
|
|
||||||
<SectionContent section={activeSection} />
|
|
||||||
</motion.div>
|
|
||||||
</AnimatePresence>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { apiRequest } from '@/shared/request/apiRequest';
|
|||||||
import { links } from '@/shared/request/links';
|
import { links } from '@/shared/request/links';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import type { SiDocument } from '../../lib/types';
|
import type { SiDocument } from '../../lib/types';
|
||||||
import { SiButton } from '@/features/modals/siModal/page';
|
|
||||||
import { useRouter, useParams } from 'next/navigation';
|
import { useRouter, useParams } from 'next/navigation';
|
||||||
|
|
||||||
// ─── State badge ───────────────────────────────────────────────────────────────
|
// ─── State badge ───────────────────────────────────────────────────────────────
|
||||||
@@ -194,7 +193,6 @@ export const SiTable: React.FC = () => {
|
|||||||
{isLoading ? '...' : t('checksCount', { count: items.length })}
|
{isLoading ? '...' : t('checksCount', { count: items.length })}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<SiButton />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm overflow-hidden">
|
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm overflow-hidden">
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const InfoSection: FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ background: C.surfaceWarm }}>
|
<div id="info-section" style={{ background: C.surfaceWarm }}>
|
||||||
<Section style={{ paddingTop: 96, paddingBottom: 96 }}>
|
<Section style={{ paddingTop: 96, paddingBottom: 96 }}>
|
||||||
{/* Heading */}
|
{/* Heading */}
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const StepsSection = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
id="steps-section"
|
||||||
style={{
|
style={{
|
||||||
background: C.surface,
|
background: C.surface,
|
||||||
borderTop: `1px solid ${C.border}`,
|
borderTop: `1px solid ${C.border}`,
|
||||||
|
|||||||
@@ -7,66 +7,21 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/shared/ui/dropdown-menu';
|
} from '@/shared/ui/dropdown-menu';
|
||||||
import SubMenuLink from './SubMenuLink';
|
|
||||||
import { ChangeLang } from './ChangeLang';
|
import { ChangeLang } from './ChangeLang';
|
||||||
import { useLoginModal, useRegisterModal } from '@/shared/zustand/auth';
|
import { useLoginModal, useRegisterModal } from '@/shared/zustand/auth';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { useUserPlagiatStore } from '@/shared/zustand/user';
|
import { useUserPlagiatStore } from '@/shared/zustand/user';
|
||||||
import {
|
import { ChevronDown, LogOut } from 'lucide-react';
|
||||||
ChevronDown,
|
|
||||||
LogOut,
|
|
||||||
User,
|
|
||||||
LayoutDashboard,
|
|
||||||
FileSearch,
|
|
||||||
BrainCircuit,
|
|
||||||
CreditCard,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useCabinetNav } from '@/shared/zustand/cabinetNav';
|
|
||||||
|
|
||||||
function AuthButtons() {
|
function AuthButtons() {
|
||||||
const t = useTranslations('Navbar');
|
const t = useTranslations('Navbar');
|
||||||
const t_cab = useTranslations('Cabinet');
|
|
||||||
const setNavItem = useCabinetNav((state) => state.setNavItem);
|
|
||||||
const [localUser, setLocalUser] = useState<{
|
const [localUser, setLocalUser] = useState<{
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
surname: string;
|
surname: string;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const [open, setOpen] = useState(false);
|
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, 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);
|
const toggleLoginModal = useLoginModal((state) => state.toggleLoginModal);
|
||||||
const toggleRegisterModal = useRegisterModal(
|
const toggleRegisterModal = useRegisterModal(
|
||||||
@@ -74,13 +29,13 @@ function AuthButtons() {
|
|||||||
);
|
);
|
||||||
const user = useUserPlagiatStore((state) => state.user);
|
const user = useUserPlagiatStore((state) => state.user);
|
||||||
const clearUser = useUserPlagiatStore((state) => state.clearUser);
|
const clearUser = useUserPlagiatStore((state) => state.clearUser);
|
||||||
|
|
||||||
const clearTokens = () => {
|
const clearTokens = () => {
|
||||||
localStorage.removeItem('access');
|
localStorage.removeItem('access');
|
||||||
localStorage.removeItem('refresh');
|
localStorage.removeItem('refresh');
|
||||||
localStorage.removeItem('user');
|
localStorage.removeItem('user');
|
||||||
clearUser();
|
clearUser();
|
||||||
};
|
};
|
||||||
console.log('Current user:', user);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const data = localStorage.getItem('user');
|
const data = localStorage.getItem('user');
|
||||||
@@ -93,7 +48,7 @@ function AuthButtons() {
|
|||||||
|
|
||||||
if (localUser) {
|
if (localUser) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row max-sm:items-center max-sm:justify-around gap-3">
|
<div className="flex flex-row max-sm:items-center max-sm:justify-around gap-3 items-center">
|
||||||
<div className="sm:flex hidden">
|
<div className="sm:flex hidden">
|
||||||
<ChangeLang />
|
<ChangeLang />
|
||||||
</div>
|
</div>
|
||||||
@@ -102,24 +57,21 @@ function AuthButtons() {
|
|||||||
{localUser.name}
|
{localUser.name}
|
||||||
<ChevronDown className="size-4" />
|
<ChevronDown className="size-4" />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="">
|
<DropdownMenuContent>
|
||||||
{userItem.map((subItem) => (
|
<DropdownMenuItem
|
||||||
<DropdownMenuItem key={subItem.title} asChild>
|
onClick={() => {
|
||||||
<SubMenuLink
|
clearTokens();
|
||||||
logOut={() => {
|
setOpen(false);
|
||||||
setOpen(false);
|
}}
|
||||||
if (subItem.url !== '/cabinet') {
|
>
|
||||||
clearTokens();
|
<Link
|
||||||
} else {
|
className="flex flex-row gap-4 rounded-md p-3 cursor-pointer"
|
||||||
setNavItem(
|
href={'/'}
|
||||||
subItem.key as import('@/widgets/cabinet/lib/types').CabinetSection,
|
>
|
||||||
);
|
<LogOut className="size-5 shrink-0 text-foreground" />
|
||||||
}
|
<span className="text-sm font-semibold">{t('logout')}</span>
|
||||||
}}
|
</Link>
|
||||||
item={subItem}
|
</DropdownMenuItem>
|
||||||
/>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
))}
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
@@ -127,16 +79,14 @@ function AuthButtons() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row max-sm:items-center max-sm:justify-around gap-3">
|
<div className="flex flex-row max-sm:items-center max-sm:justify-around gap-3 items-center">
|
||||||
<div className="sm:flex hidden">
|
<div className="sm:flex hidden">
|
||||||
<ChangeLang />
|
<ChangeLang />
|
||||||
</div>
|
</div>
|
||||||
<Button variant="outline" onClick={() => toggleLoginModal()}>
|
<Button variant="outline" onClick={() => toggleLoginModal()}>
|
||||||
<Link href={auth.login.url}>{auth.login.title}</Link>
|
{t('login')}
|
||||||
</Button>
|
|
||||||
<Button onClick={() => toggleRegisterModal()}>
|
|
||||||
<Link href={auth.signup.url}>{auth.signup.title}</Link>
|
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => toggleRegisterModal()}>{t('signup')}</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
'use client';
|
||||||
import { Button } from '@/shared/ui/button';
|
import { Button } from '@/shared/ui/button';
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
@@ -13,16 +14,47 @@ import { AuthButtons } from './authButtons';
|
|||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { Logo_image } from '@/image';
|
import { Logo_image } from '@/image';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useUserPlagiatStore } from '@/shared/zustand/user';
|
||||||
|
|
||||||
const Navbar = () => {
|
const Navbar = () => {
|
||||||
const t = useTranslations('Navbar');
|
const t = useTranslations('Navbar');
|
||||||
|
const t_cab = useTranslations('Cabinet');
|
||||||
|
const [localUser, setLocalUser] = useState<{
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
surname: string;
|
||||||
|
} | null>(null);
|
||||||
|
const user = useUserPlagiatStore((state) => state.user);
|
||||||
|
|
||||||
|
const scrollTo = (id: string) => {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const data = localStorage.getItem('user');
|
||||||
|
if (data) {
|
||||||
|
setLocalUser(JSON.parse(data));
|
||||||
|
} else {
|
||||||
|
setLocalUser(null);
|
||||||
|
}
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ title: t_cab('dashboard'), href: '/dashboard' as const },
|
||||||
|
{ title: t_cab('plagiat'), href: '/plagiat' as const },
|
||||||
|
{ title: t_cab('siNav'), href: '/si' as const },
|
||||||
|
{ title: t_cab('payments'), href: '/payments' as const },
|
||||||
|
{ title: t('profile'), href: '/profile' as const },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="py-1 flex items-center justify-center w-full ">
|
<section className="py-1 flex items-center justify-center w-full ">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 w-full">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 w-full">
|
||||||
{/* Desktop Menu */}
|
{/* Desktop Menu */}
|
||||||
<nav className="justify-between items-center flex max-sm:flex-col gap-5">
|
<nav className="justify-between items-center flex max-sm:flex-col gap-5">
|
||||||
<div className="flex items-center justify-between gap-6">
|
<div className="flex items-center justify-around gap-2 w-full">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<Link
|
<Link
|
||||||
href={'/'}
|
href={'/'}
|
||||||
@@ -36,6 +68,36 @@ const Navbar = () => {
|
|||||||
height={10}
|
height={10}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
{localUser ? (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<Link
|
||||||
|
key={item.title}
|
||||||
|
href={item.href}
|
||||||
|
className="flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium rounded-md hover:bg-muted transition-colors"
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<nav className="hidden sm:flex items-center gap-1">
|
||||||
|
<button
|
||||||
|
onClick={() => scrollTo('info-section')}
|
||||||
|
className="px-3 py-1.5 text-sm font-medium rounded-md hover:bg-muted transition-colors"
|
||||||
|
>
|
||||||
|
{t('aboutPlagiat')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => scrollTo('steps-section')}
|
||||||
|
className="px-3 py-1.5 text-sm font-medium rounded-md hover:bg-muted transition-colors"
|
||||||
|
>
|
||||||
|
{t('howItWorks')}
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex sm:hidden items-center justify-center">
|
<div className="flex sm:hidden items-center justify-center">
|
||||||
<ChangeLang />
|
<ChangeLang />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { SERTIFICATE_PRICE, PLAGIAT_SERVICE_FEE } from '@/shared/lib/metadata';
|
|||||||
const INITIAL_FORM: PlagiarismFormState = {
|
const INITIAL_FORM: PlagiarismFormState = {
|
||||||
title: '',
|
title: '',
|
||||||
file: null,
|
file: null,
|
||||||
certificate: true,
|
certificate: false,
|
||||||
text: '',
|
text: '',
|
||||||
type: 0,
|
type: 0,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user