fix base_api error
This commit is contained in:
@@ -43,9 +43,9 @@ export function useCertificateModal({
|
|||||||
|
|
||||||
const certificateMutation = useMutation({
|
const certificateMutation = useMutation({
|
||||||
mutationFn: (payload: CertificatePayload) =>
|
mutationFn: (payload: CertificatePayload) =>
|
||||||
apiRequest('POST', links.sertifikat(document_id), payload).then(
|
apiRequest<ArrayBuffer>('POST', links.sertifikat(document_id), payload, {
|
||||||
(res) => res.data as ArrayBuffer,
|
responseType: 'arraybuffer',
|
||||||
),
|
}).then((res) => res.data),
|
||||||
onSuccess: (data: ArrayBuffer) => {
|
onSuccess: (data: ArrayBuffer) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
const blob = new Blob([data], { type: 'application/pdf' });
|
const blob = new Blob([data], { type: 'application/pdf' });
|
||||||
@@ -54,7 +54,7 @@ export function useCertificateModal({
|
|||||||
a.href = objectUrl;
|
a.href = objectUrl;
|
||||||
a.download = `certificate-${document_id}.pdf`;
|
a.download = `certificate-${document_id}.pdf`;
|
||||||
a.click();
|
a.click();
|
||||||
URL.revokeObjectURL(objectUrl);
|
setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
|
||||||
}
|
}
|
||||||
setSuccess(true);
|
setSuccess(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ function extractErrorMessage(error: AxiosError): string {
|
|||||||
// ─── Constants ─────────────────────────────────────────────────────────────────
|
// ─── Constants ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL;
|
// const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||||
const baseUrl = 'https://dev-api.anti-plagiat.uz/api/v1';
|
const baseUrl = 'https://api.anti-plagiat.uz/api/v1';
|
||||||
const DEFAULT_LOCALE = 'uz'; // fallback locale for redirect
|
const DEFAULT_LOCALE = 'uz'; // fallback locale for redirect
|
||||||
|
|
||||||
// ─── Token helpers ─────────────────────────────────────────────────────────────
|
// ─── Token helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ function NavigationMenu({
|
|||||||
data-slot="navigation-menu"
|
data-slot="navigation-menu"
|
||||||
data-viewport={viewport}
|
data-viewport={viewport}
|
||||||
className={cn(
|
className={cn(
|
||||||
'group/navigation-menu relative flex max-w-max flex-1 items-center justify-center',
|
' relative flex max-w-max flex-1 items-center justify-center',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
12
src/shared/zustand/cabinetNav.ts
Normal file
12
src/shared/zustand/cabinetNav.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { CabinetSection } from '@/widgets/cabinet/lib/types';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
type CabinetNavZustand = {
|
||||||
|
navItem: CabinetSection;
|
||||||
|
setNavItem: (item: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCabinetNav = create<CabinetNavZustand>((set) => ({
|
||||||
|
navItem: 'dashboard',
|
||||||
|
setNavItem: (item: string) => set({ navItem: item || 'dash' }),
|
||||||
|
}));
|
||||||
@@ -1,14 +1,43 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useCabinetNav } from '@/shared/zustand/cabinetNav';
|
||||||
import type { CabinetSection } from '../types';
|
import type { CabinetSection } from '../types';
|
||||||
|
|
||||||
|
const VALID_SECTIONS: CabinetSection[] = [
|
||||||
|
'dashboard',
|
||||||
|
'plagiat',
|
||||||
|
'si',
|
||||||
|
'payments',
|
||||||
|
'profile',
|
||||||
|
];
|
||||||
|
|
||||||
export const useCabinet = () => {
|
export const useCabinet = () => {
|
||||||
|
const { navItem, setNavItem } = useCabinetNav();
|
||||||
|
const navItemZustrand = useCabinetNav((state) => state.navItem);
|
||||||
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
const [activeSection, setActiveSection] =
|
const [activeSection, setActiveSection] =
|
||||||
useState<CabinetSection>('dashboard');
|
useState<CabinetSection>('dashboard');
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (navItemZustrand) {
|
||||||
|
const activeSection: CabinetSection = VALID_SECTIONS.includes(
|
||||||
|
navItemZustrand as CabinetSection,
|
||||||
|
)
|
||||||
|
? (navItemZustrand as CabinetSection)
|
||||||
|
: 'dashboard';
|
||||||
|
setActiveSection(activeSection);
|
||||||
|
} else {
|
||||||
|
const activeSection: CabinetSection = VALID_SECTIONS.includes(
|
||||||
|
navItem as CabinetSection,
|
||||||
|
)
|
||||||
|
? (navItem as CabinetSection)
|
||||||
|
: 'dashboard';
|
||||||
|
setActiveSection(activeSection);
|
||||||
|
}
|
||||||
|
}, [navItemZustrand]);
|
||||||
|
|
||||||
const navigate = (section: CabinetSection) => {
|
const navigate = (section: CabinetSection) => {
|
||||||
setActiveSection(section);
|
setNavItem(section);
|
||||||
setIsSidebarOpen(false);
|
setIsSidebarOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import PaymentStatus from './paidStatus';
|
|||||||
import Sertifikat from '@/features/modals/sertificateModal/sertifikat';
|
import Sertifikat from '@/features/modals/sertificateModal/sertifikat';
|
||||||
|
|
||||||
// ── Types ────────────────────────────────────────────────────────────────────
|
// ── Types ────────────────────────────────────────────────────────────────────
|
||||||
const baseUrl = 'https://dev-api.anti-plagiat.uz/api/v1';
|
const baseUrl = 'https://api.anti-plagiat.uz/api/v1';
|
||||||
interface AnalyzeText {
|
interface AnalyzeText {
|
||||||
[key: string]: number | string;
|
[key: string]: number | string;
|
||||||
}
|
}
|
||||||
@@ -399,10 +399,25 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
|||||||
<PaymentStatus status={doc.state} />
|
<PaymentStatus status={doc.state} />
|
||||||
<Sertifikat document_id={Number(id)} />
|
<Sertifikat document_id={Number(id)} />
|
||||||
{doc.file && (
|
{doc.file && (
|
||||||
<a
|
<button
|
||||||
href={`${baseUrl}${doc.file}`}
|
onClick={async () => {
|
||||||
target="_blank"
|
const url = `${baseUrl}/shared/documents/${doc.id}/download`;
|
||||||
rel="noopener noreferrer"
|
const res = await apiRequest<ArrayBuffer>(
|
||||||
|
'GET',
|
||||||
|
url,
|
||||||
|
undefined,
|
||||||
|
{ responseType: 'arraybuffer', baseURL: baseUrl },
|
||||||
|
);
|
||||||
|
const blob = new Blob([res.data], {
|
||||||
|
type: 'application/pdf',
|
||||||
|
});
|
||||||
|
const objectUrl = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = objectUrl;
|
||||||
|
a.download = `document-${id}.pdf`;
|
||||||
|
a.click();
|
||||||
|
setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
|
||||||
|
}}
|
||||||
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-900 text-white text-xs font-semibold hover:bg-slate-700 transition-colors"
|
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-900 text-white text-xs font-semibold hover:bg-slate-700 transition-colors"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -419,7 +434,7 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{t('downloadPdf')}
|
{t('downloadPdf')}
|
||||||
</a>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -486,7 +501,7 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
|||||||
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm p-8">
|
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm p-8">
|
||||||
<div className="flex flex-wrap justify-around gap-8">
|
<div className="flex flex-wrap justify-around gap-8">
|
||||||
<ScoreRing
|
<ScoreRing
|
||||||
value={res.ai}
|
value={res.originality}
|
||||||
label={t('scoreOriginality')}
|
label={t('scoreOriginality')}
|
||||||
color="#10b981"
|
color="#10b981"
|
||||||
/>
|
/>
|
||||||
@@ -502,7 +517,7 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
|||||||
color="#6366f1"
|
color="#6366f1"
|
||||||
/>
|
/>
|
||||||
<ScoreRing
|
<ScoreRing
|
||||||
value={res.originality}
|
value={res.ai}
|
||||||
label={t('scoreAiContent')}
|
label={t('scoreAiContent')}
|
||||||
color="#ef4444"
|
color="#ef4444"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ export interface MenuItem {
|
|||||||
description?: string;
|
description?: string;
|
||||||
icon?: React.ComponentType<{ className?: string }>;
|
icon?: React.ComponentType<{ className?: string }>;
|
||||||
items?: MenuItem[];
|
items?: MenuItem[];
|
||||||
|
key: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
'use client';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
import { MenuItem } from '../lib/model';
|
import { MenuItem } from '../lib/model';
|
||||||
|
|
||||||
const SubMenuLink = ({
|
const SubMenuLink = ({
|
||||||
@@ -7,11 +9,17 @@ const SubMenuLink = ({
|
|||||||
item: MenuItem;
|
item: MenuItem;
|
||||||
logOut?: () => void;
|
logOut?: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const isCabinet = pathname.includes('/cabinet');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
className="flex flex-row gap-4 rounded-md p-3 leading-none no-underline transition-colors outline-none select-none hover:bg-muted hover:text-accent-foreground"
|
className="flex flex-row gap-4 rounded-md p-3 leading-none no-underline transition-colors outline-none select-none hover:bg-muted hover:text-accent-foreground"
|
||||||
href={item.url}
|
href={isCabinet && item.url === '/cabinet' ? undefined : item.url}
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
|
if (isCabinet && item.url === '/cabinet') {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
if (logOut) {
|
if (logOut) {
|
||||||
logOut();
|
logOut();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,37 +2,70 @@
|
|||||||
import { Link } from '@/shared/config/i18n/navigation';
|
import { Link } from '@/shared/config/i18n/navigation';
|
||||||
import { Button } from '@/shared/ui/button';
|
import { Button } from '@/shared/ui/button';
|
||||||
import {
|
import {
|
||||||
NavigationMenu,
|
DropdownMenu,
|
||||||
NavigationMenuContent,
|
DropdownMenuContent,
|
||||||
NavigationMenuItem,
|
DropdownMenuItem,
|
||||||
NavigationMenuLink,
|
DropdownMenuTrigger,
|
||||||
NavigationMenuList,
|
} from '@/shared/ui/dropdown-menu';
|
||||||
NavigationMenuTrigger,
|
|
||||||
} from '@/shared/ui/navigation-menu';
|
|
||||||
import SubMenuLink from './SubMenuLink';
|
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 { LogOut, User } from 'lucide-react';
|
import {
|
||||||
|
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 auth = {
|
const auth = {
|
||||||
login: { title: t('login'), url: '#' },
|
login: { title: t('login'), url: '#' },
|
||||||
signup: { title: t('signup'), url: '#' },
|
signup: { title: t('signup'), url: '#' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const userItem = [
|
const userItem = [
|
||||||
{ title: t('profile'), url: '/cabinet', icon: User },
|
{ title: t('profile'), url: '/cabinet', icon: User, key: 'profile' },
|
||||||
{ title: t('logout'), url: '/', icon: LogOut },
|
{
|
||||||
|
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);
|
||||||
@@ -51,7 +84,6 @@ function AuthButtons() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const data = localStorage.getItem('user');
|
const data = localStorage.getItem('user');
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
setLocalUser(JSON.parse(data));
|
setLocalUser(JSON.parse(data));
|
||||||
} else {
|
} else {
|
||||||
@@ -65,33 +97,29 @@ function AuthButtons() {
|
|||||||
<div className="sm:flex hidden">
|
<div className="sm:flex hidden">
|
||||||
<ChangeLang />
|
<ChangeLang />
|
||||||
</div>
|
</div>
|
||||||
<NavigationMenu viewport={true}>
|
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||||
<NavigationMenuList>
|
<DropdownMenuTrigger className="inline-flex items-center gap-1 text-lg font-medium outline-none">
|
||||||
<NavigationMenuItem>
|
{localUser.name}
|
||||||
<NavigationMenuTrigger className="text-lg">
|
<ChevronDown className="size-4" />
|
||||||
{localUser.name}
|
</DropdownMenuTrigger>
|
||||||
</NavigationMenuTrigger>
|
<DropdownMenuContent className="">
|
||||||
<NavigationMenuContent className="bg-popover text-popover-foreground">
|
{userItem.map((subItem) => (
|
||||||
{userItem.map((subItem) => (
|
<DropdownMenuItem key={subItem.title} asChild>
|
||||||
<NavigationMenuLink
|
<SubMenuLink
|
||||||
asChild
|
logOut={() => {
|
||||||
key={subItem.title}
|
setOpen(false);
|
||||||
className="w-80"
|
if (subItem.url !== '/cabinet') {
|
||||||
>
|
clearTokens();
|
||||||
<SubMenuLink
|
} else {
|
||||||
logOut={() => {
|
setNavItem(subItem.key);
|
||||||
if (subItem.url !== '/cabinet') {
|
}
|
||||||
clearTokens();
|
}}
|
||||||
}
|
item={subItem}
|
||||||
}}
|
/>
|
||||||
item={subItem}
|
</DropdownMenuItem>
|
||||||
/>
|
))}
|
||||||
</NavigationMenuLink>
|
</DropdownMenuContent>
|
||||||
))}
|
</DropdownMenu>
|
||||||
</NavigationMenuContent>
|
|
||||||
</NavigationMenuItem>
|
|
||||||
</NavigationMenuList>
|
|
||||||
</NavigationMenu>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user