Partner flow: QR scanner component (html5-qrcode) with camera permission/not-found handling, 3-step spend flow (scan → amount entry with auto-filled max → success/error result). Admin dashboard: stats overview, grant points with debounced student search, paginated transactions table with type filters, paginated students table. Tests: comprehensive unit tests for points and auth packages — service (all paths including error branches, RS256 wrong-method), handler (all HTTP status codes via httptest), JWT round-trip, repository constructors. Auth coverage: 72.9%, points service coverage: 100%. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
45 lines
1.4 KiB
TypeScript
45 lines
1.4 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import { usePathname } from 'next/navigation';
|
|
import { useAuthStore } from '@/lib/store';
|
|
|
|
const LINKS = [
|
|
{ href: '/admin/dashboard', label: 'Дашборд' },
|
|
{ href: '/admin/grant', label: 'Начислить' },
|
|
{ href: '/admin/transactions', label: 'Транзакции' },
|
|
{ href: '/admin/users', label: 'Студенты' },
|
|
] as const;
|
|
|
|
export function AdminNav() {
|
|
const pathname = usePathname();
|
|
const { logout } = useAuthStore();
|
|
|
|
return (
|
|
<nav className="border-b border-gray-700 bg-gray-900">
|
|
<div className="mx-auto flex max-w-5xl flex-wrap items-center gap-1 px-4 py-3">
|
|
<span className="mr-4 text-sm font-semibold text-white">CU Points Admin</span>
|
|
{LINKS.map((link) => (
|
|
<Link
|
|
key={link.href}
|
|
href={link.href}
|
|
className={`rounded-lg px-3 py-1.5 text-sm transition-colors ${
|
|
pathname === link.href
|
|
? 'bg-blue-600 text-white'
|
|
: 'text-gray-400 hover:bg-gray-700 hover:text-white'
|
|
}`}
|
|
>
|
|
{link.label}
|
|
</Link>
|
|
))}
|
|
<button
|
|
onClick={logout}
|
|
className="ml-auto rounded-lg px-3 py-1.5 text-sm text-gray-400 transition-colors hover:bg-gray-700 hover:text-white"
|
|
>
|
|
Выйти
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
);
|
|
}
|