fix: standardize imports, tighten types, and align

mock data with actual API contracts

- Normalize all `cn` imports to `@/lib/utils` across
  30+ UI and component files
- Replace relative imports with `@/` alias in auth,
  roles, users, stats, and layout components
- Remove redundant `as` type assertions in playlist
  routes (Zod parse already returns typed data)
- Replace `any` with proper types in FallbackCache,
  mock handlers, and validateIcons
- Use `type` import for type-only imports
- Add type-safe `.filter(Boolean)` with type guard
  in Sidebar nav item arrays
- Update MSW mock data to match actual API shape:
  numeric IDs, `role` field, HTTP-only cookie auth,
  paginated response format, and settings schema
- Add JSDoc to AuthTokens clarifying cookie-based
  token storage
- Replace console.* calls with logger in icon
  validation
- Ignore SQLite WAL files in .gitignore
This commit is contained in:
Darkraise
2026-02-10 20:42:56 +07:00
parent b95f058a66
commit 3e35799de8
58 changed files with 118 additions and 109 deletions
+3 -1
View File
@@ -139,4 +139,6 @@ vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.claude/**/*.*
user.db
user.db
*.db-shm
*.db-wal
+3 -9
View File
@@ -57,7 +57,7 @@ router.post(
asyncHandler(async (req, res) => {
const data = createPlaylistSchema.parse(req.body);
const playlist = await playlistService.createPlaylist(data as CreatePlaylistDto);
const playlist = await playlistService.createPlaylist(data);
res.status(201).json(playlist);
})
);
@@ -70,10 +70,7 @@ router.post(
asyncHandler(async (req, res) => {
const data = addGamesToPlaylistSchema.parse(req.body);
const playlist = await playlistService.addGamesToPlaylist(
req.params.id,
data as AddGamesToPlaylistDto
);
const playlist = await playlistService.addGamesToPlaylist(req.params.id, data);
if (!playlist) {
throw new AppError(404, 'Playlist not found');
@@ -91,10 +88,7 @@ router.delete(
asyncHandler(async (req, res) => {
const data = addGamesToPlaylistSchema.parse(req.body);
const playlist = await playlistService.removeGamesFromPlaylist(
req.params.id,
data as AddGamesToPlaylistDto
);
const playlist = await playlistService.removeGamesFromPlaylist(req.params.id, data);
if (!playlist) {
throw new AppError(404, 'Playlist not found');
+1 -1
View File
@@ -2,7 +2,7 @@ import axios from 'axios';
import fs from 'fs';
import path from 'path';
import { logger } from '../utils/logger';
import { GameDataSource } from './PreferencesService';
import type { GameDataSource } from './PreferencesService';
import { HashValidator } from './HashValidator';
import { FileImporter } from './FileImporter';
import { GameDatabaseUpdater, GameData } from './GameDatabaseUpdater';
+1 -1
View File
@@ -7,7 +7,7 @@ import { logger } from '../utils/logger';
* Stores successful responses with extended TTL (1 hour) to serve
* when the primary data source (Flashpoint database) is unavailable
*/
export class FallbackCache<T = any> {
export class FallbackCache<T = unknown> {
private cache: LRUCache<string, { data: T; cachedAt: number }>;
private readonly FALLBACK_TTL = 60 * 60 * 1000; // 1 hour
@@ -1,10 +1,10 @@
import { useState, memo, useMemo, useCallback, useTransition } from 'react';
import { ColumnDef, SortingState } from '@tanstack/react-table';
import { ChevronDown, AlertCircle } from 'lucide-react';
import { useActivities } from '../../hooks/useActivities';
import { useActivityFilters } from '../../hooks/useActivityFilters';
import { useDebounce } from '../../hooks/useDebounce';
import { ActivityFilters } from '../../types/auth';
import { useActivities } from '@/hooks/useActivities';
import { useActivityFilters } from '@/hooks/useActivityFilters';
import { useDebounce } from '@/hooks/useDebounce';
import { ActivityFilters } from '@/types/auth';
import { getErrorMessage } from '@/types/api-error';
import { getActionBadgeVariant } from '@/utils/activityUtils';
import { DataTable } from '../ui/data-table';
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react';
import { useLocation, Link } from 'react-router-dom';
import { useAuth } from '../../contexts/AuthContext';
import { useAuth } from '@/contexts/AuthContext';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
@@ -1,7 +1,7 @@
import { Navigate, useLocation, useParams } from 'react-router-dom';
import { useAuthStore } from '../../store/auth';
import { useFeatureFlags } from '../../hooks/useFeatureFlags';
import { useSharedPlaylistAccess } from '../../hooks/useSharedPlaylistAccess';
import { useAuthStore } from '@/store/auth';
import { useFeatureFlags } from '@/hooks/useFeatureFlags';
import { useSharedPlaylistAccess } from '@/hooks/useSharedPlaylistAccess';
import { ReactNode } from 'react';
interface ProtectedRouteProps {
@@ -1,7 +1,7 @@
import { useNavigate, Link } from 'react-router-dom';
import { useRegister } from '../../hooks/useAuth';
import { useRegister } from '@/hooks/useAuth';
import { useQuery } from '@tanstack/react-query';
import { authSettingsApi, systemSettingsApi } from '../../lib/api';
import { authSettingsApi, systemSettingsApi } from '@/lib/api';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
@@ -1,6 +1,6 @@
import { Link, useNavigate } from 'react-router-dom';
import { ChevronRight, Home, ArrowLeft } from 'lucide-react';
import { cn } from '@/lib';
import { cn } from '@/lib/utils';
export interface BreadcrumbContext {
label: string;
@@ -2,7 +2,7 @@ import { useEffect, useState, useRef } from 'react';
import { WifiOff, Wifi, X } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
type NetworkStatus = 'online' | 'offline' | 'slow';
+1 -1
View File
@@ -1,5 +1,5 @@
import { ReactNode } from 'react';
import { useAuthStore } from '../../store/auth';
import { useAuthStore } from '@/store/auth';
interface RoleGuardProps {
children: ReactNode;
+4 -4
View File
@@ -20,7 +20,7 @@ import { RoleGuard } from '../common/RoleGuard';
import { useEffect } from 'react';
import { useSwipeGesture } from '@/hooks/useSwipeGesture';
import { ScrollArea } from '@/components/ui/scroll-area';
import { cn } from '@/lib';
import { cn } from '@/lib/utils';
import { SidebarItem } from './SidebarItem';
import { SidebarSection } from './SidebarSection';
import { useFeatureFlags } from '@/hooks/useFeatureFlags';
@@ -81,7 +81,7 @@ export function Sidebar({ isOpen }: SidebarProps) {
icon: ListIcon,
label: 'Flashpoint Playlists',
},
].filter(Boolean) as NavItem[];
].filter((item): item is Exclude<typeof item, false> => Boolean(item));
const managementNavItems: NavItem[] = [
{ path: '/users', icon: Users, label: 'Users', permission: 'users.read' },
@@ -92,13 +92,13 @@ export function Sidebar({ isOpen }: SidebarProps) {
label: 'Activity Logs',
permission: 'activities.read',
},
].filter(Boolean) as NavItem[];
].filter((item): item is Exclude<typeof item, false> => Boolean(item));
const bottomNavItems: NavItem[] = [
enableStatistics && { path: '/dashboard', icon: BarChart3, label: 'Dashboard' },
{ path: '/jobs', icon: Clock, label: 'Jobs', permission: 'settings.update' },
{ path: '/settings', icon: Settings, label: 'Settings' },
].filter(Boolean) as NavItem[];
].filter((item): item is Exclude<typeof item, false> => Boolean(item));
useEffect(() => {
if (isOpen && window.innerWidth < 1024) {
@@ -1,6 +1,6 @@
import { Link, useLocation } from 'react-router-dom';
import { LucideIcon } from 'lucide-react';
import { cn } from '@/lib';
import { cn } from '@/lib/utils';
interface SidebarItemProps {
path: string;
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react';
import { usePermissions, useUpdateRolePermissions } from '../../hooks/useRoles';
import { Role } from '../../types/auth';
import { usePermissions, useUpdateRolePermissions } from '@/hooks/useRoles';
import { Role } from '@/types/auth';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Alert, AlertDescription } from '@/components/ui/alert';
+2 -2
View File
@@ -1,6 +1,6 @@
import { useEffect } from 'react';
import { useCreateRole, useUpdateRole, usePermissions } from '../../hooks/useRoles';
import { Role, CreateRoleData, UpdateRoleData } from '../../types/auth';
import { useCreateRole, useUpdateRole, usePermissions } from '@/hooks/useRoles';
import { Role, CreateRoleData, UpdateRoleData } from '@/types/auth';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
+2 -2
View File
@@ -2,8 +2,8 @@ import { useState } from 'react';
import { ColumnDef } from '@tanstack/react-table';
import { MoreHorizontal } from 'lucide-react';
import { toast } from 'sonner';
import { useRoles, useDeleteRole } from '../../hooks/useRoles';
import { Role } from '../../types/auth';
import { useRoles, useDeleteRole } from '@/hooks/useRoles';
import { Role } from '@/types/auth';
import { RoleGuard } from '../common/RoleGuard';
import { ConfirmDialog } from '../common/ConfirmDialog';
import { DataTable } from '../ui/data-table';
@@ -1,7 +1,7 @@
import { X } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib';
import { cn } from '@/lib/utils';
export interface FilterChip {
id: string;
@@ -1,5 +1,5 @@
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend } from 'recharts';
import { useGamesDistribution } from '../../hooks/usePlayTracking';
import { useGamesDistribution } from '@/hooks/usePlayTracking';
import type { CustomTooltipProps, CustomLegendProps } from '@/types/chart';
import { CHART_COLORS, truncateTitle, formatPlaytimeCompact } from './chart-utils';
@@ -8,8 +8,8 @@ import {
ResponsiveContainer,
Legend,
} from 'recharts';
import { usePlayActivityOverTime } from '../../hooks/usePlayTracking';
import { useDateTimeFormat } from '../../hooks/useDateTimeFormat';
import { usePlayActivityOverTime } from '@/hooks/usePlayTracking';
import { useDateTimeFormat } from '@/hooks/useDateTimeFormat';
import type { CustomTooltipProps } from '@/types/chart';
import { formatPlaytimeCompact, getCSSVar } from './chart-utils';
@@ -8,7 +8,7 @@ import {
ResponsiveContainer,
Cell,
} from 'recharts';
import { useTopGames } from '../../hooks/usePlayTracking';
import { useTopGames } from '@/hooks/usePlayTracking';
import type { CustomTooltipProps } from '@/types/chart';
import { CHART_COLORS, truncateTitle, formatPlaytimeCompact, getCSSVar } from './chart-utils';
@@ -1,7 +1,7 @@
import { lazy, Suspense } from 'react';
import { Clock, GamepadIcon, TrendingUp, BarChart3 } from 'lucide-react';
import { useUserStats } from '../../hooks/usePlayTracking';
import { useDateTimeFormat } from '../../hooks/useDateTimeFormat';
import { useUserStats } from '@/hooks/usePlayTracking';
import { useDateTimeFormat } from '@/hooks/useDateTimeFormat';
import { ChartErrorBoundary } from './ChartErrorBoundary';
import { formatDuration } from '@/lib/cron-utils';
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from 'react';
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
import { buttonVariants } from '@/components/ui/button';
const AlertDialog = AlertDialogPrimitive.Root;
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
const alertVariants = cva(
'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
+1 -1
View File
@@ -3,7 +3,7 @@
import * as React from 'react';
import * as AvatarPrimitive from '@radix-ui/react-avatar';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib';
import { cn } from '@/lib/utils';
const badgeVariants = cva(
'inline-flex items-center rounded px-2 py-1 text-xs font-medium transition-colors focus:outline-none focus:ring-1 focus:ring-ring focus:ring-offset-1',
+1 -1
View File
@@ -2,7 +2,7 @@ import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 data-[state=open]:ring-0 data-[state=open]:focus-visible:ring-0',
+1 -1
View File
@@ -2,7 +2,7 @@ import * as React from 'react';
import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
import { DayButton, DayPicker, getDefaultClassNames } from 'react-day-picker';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
import { Button, buttonVariants } from '@/components/ui/button';
function Calendar({
+1 -1
View File
@@ -1,6 +1,6 @@
import * as React from 'react';
import { cn } from '@/lib';
import { cn } from '@/lib/utils';
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
+1 -1
View File
@@ -2,7 +2,7 @@ import * as React from 'react';
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import { Check } from 'lucide-react';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
+1 -1
View File
@@ -2,7 +2,7 @@ import * as React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { X } from 'lucide-react';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
const Dialog = DialogPrimitive.Root;
+1 -1
View File
@@ -2,7 +2,7 @@ import * as React from 'react';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { Check, ChevronRight, Circle } from 'lucide-react';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
const DropdownMenu = DropdownMenuPrimitive.Root;
+1 -1
View File
@@ -12,7 +12,7 @@ import {
type FieldValues,
} from 'react-hook-form';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
import { Label } from '@/components/ui/label';
const Form = FormProvider;
+1 -1
View File
@@ -1,6 +1,6 @@
import * as React from 'react';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
({ className, type, ...props }, ref) => {
+1 -1
View File
@@ -2,7 +2,7 @@ import * as React from 'react';
import * as LabelPrimitive from '@radix-ui/react-label';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
const labelVariants = cva(
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
+1 -1
View File
@@ -1,6 +1,6 @@
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
import { Button } from './button';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
export interface PaginationProps {
currentPage: number;
+1 -1
View File
@@ -1,6 +1,6 @@
import { useState } from 'react';
import { Badge } from '@/components/ui/badge';
import { cn } from '@/lib';
import { cn } from '@/lib/utils';
interface PlatformIconProps {
platformName: string;
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from 'react';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
const Popover = PopoverPrimitive.Root;
+1 -1
View File
@@ -2,7 +2,7 @@ import * as React from 'react';
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
import { Circle } from 'lucide-react';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
+1 -1
View File
@@ -3,7 +3,7 @@
import * as React from 'react';
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
+1 -1
View File
@@ -4,7 +4,7 @@ import * as React from 'react';
import * as SelectPrimitive from '@radix-ui/react-select';
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
import { cn } from '@/lib';
import { cn } from '@/lib/utils';
const Select = SelectPrimitive.Root;
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from 'react';
import * as SeparatorPrimitive from '@radix-ui/react-separator';
import { cn } from '@/lib';
import { cn } from '@/lib/utils';
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
+1 -1
View File
@@ -3,7 +3,7 @@ import * as SheetPrimitive from '@radix-ui/react-dialog';
import { cva, type VariantProps } from 'class-variance-authority';
import { X } from 'lucide-react';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
const Sheet = SheetPrimitive.Root;
+1 -1
View File
@@ -1,4 +1,4 @@
import { cn } from '@/lib';
import { cn } from '@/lib/utils';
function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return <div className={cn('animate-pulse rounded-md bg-primary/10', className)} {...props} />;
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from 'react';
import * as SwitchPrimitives from '@radix-ui/react-switch';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
+1 -1
View File
@@ -1,6 +1,6 @@
import * as React from 'react';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
({ className, ...props }, ref) => (
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from 'react';
import * as TabsPrimitive from '@radix-ui/react-tabs';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
const Tabs = TabsPrimitive.Root;
+1 -1
View File
@@ -1,6 +1,6 @@
import * as React from 'react';
import { cn } from '@/lib';
import { cn } from '@/lib/utils';
const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<'textarea'>>(
({ className, ...props }, ref) => {
+1 -1
View File
@@ -5,7 +5,7 @@ import * as ToastPrimitives from '@radix-ui/react-toast';
import { cva, type VariantProps } from 'class-variance-authority';
import { X } from 'lucide-react';
import { cn } from '@/lib';
import { cn } from '@/lib/utils';
const ToastProvider = ToastPrimitives.Provider;
+1 -1
View File
@@ -2,7 +2,7 @@ import * as React from 'react';
import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
import { type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
import { toggleVariants } from './toggle';
const ToggleGroupContext = React.createContext<VariantProps<typeof toggleVariants>>({
+1 -1
View File
@@ -2,7 +2,7 @@ import * as React from 'react';
import * as TogglePrimitive from '@radix-ui/react-toggle';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
const toggleVariants = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2',
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from 'react';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import { cn } from '@/lib/index';
import { cn } from '@/lib/utils';
const TooltipProvider = TooltipPrimitive.Provider;
@@ -1,5 +1,5 @@
import { useChangePassword } from '../../hooks/useUsers';
import { UserDetails } from '../../types/auth';
import { useChangePassword } from '@/hooks/useUsers';
import { UserDetails } from '@/types/auth';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
+3 -3
View File
@@ -1,7 +1,7 @@
import { useEffect } from 'react';
import { useCreateUser, useUpdateUser } from '../../hooks/useUsers';
import { useRoles } from '../../hooks/useRoles';
import { UserDetails, CreateUserData, UpdateUserData } from '../../types/auth';
import { useCreateUser, useUpdateUser } from '@/hooks/useUsers';
import { useRoles } from '@/hooks/useRoles';
import { UserDetails, CreateUserData, UpdateUserData } from '@/types/auth';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
+2 -2
View File
@@ -1,8 +1,8 @@
import { useState } from 'react';
import { ColumnDef } from '@tanstack/react-table';
import { MoreHorizontal } from 'lucide-react';
import { useUsers, useDeleteUser } from '../../hooks/useUsers';
import { UserDetails } from '../../types/auth';
import { useUsers, useDeleteUser } from '@/hooks/useUsers';
import { UserDetails } from '@/types/auth';
import { RoleGuard } from '../common/RoleGuard';
import { ConfirmDialog } from '../common/ConfirmDialog';
import { DataTable } from '../ui/data-table';
+13 -16
View File
@@ -1,4 +1,5 @@
import { PLAYLIST_ICONS, type PlaylistIconName } from './playlistIcons';
import { logger } from '@/lib/logger';
export function validateIconsNoDuplicates(): {
isValid: boolean;
@@ -55,28 +56,24 @@ export function logIconValidation(): void {
const result = validateIconsNoDuplicates();
console.group('🔍 Icon Validation Results');
console.log(`Total Icons: ${result.totalIcons}`);
console.log(`Valid: ${result.isValid ? '✅' : '❌'}`);
logger.info('🔍 Icon Validation Results');
logger.info(`Total Icons: ${result.totalIcons}`);
logger.info(`Valid: ${result.isValid ? '✅' : '❌'}`);
if (result.warnings.length > 0) {
console.group('⚠️ Warnings');
result.warnings.forEach((warning) => console.warn(warning));
console.groupEnd();
logger.warn('⚠️ Warnings:');
result.warnings.forEach((warning) => logger.warn(warning));
}
if (result.duplicates.length > 0) {
console.group('🔴 Duplicate Icons Found');
logger.warn('🔴 Duplicate Icons Found:');
result.duplicates.forEach(({ componentName, iconKeys }) => {
console.log(`\n${componentName}:`);
iconKeys.forEach((key) => console.log(` - ${key}`));
logger.warn(`\n${componentName}:`);
iconKeys.forEach((key) => logger.warn(` - ${key}`));
});
console.groupEnd();
} else {
console.log('✅ No duplicates found');
logger.info('✅ No duplicates found');
}
console.groupEnd();
}
export function isValidIconName(iconName: string): iconName is PlaylistIconName {
@@ -86,11 +83,11 @@ export function isValidIconName(iconName: string): iconName is PlaylistIconName
if (import.meta.env.DEV) {
const result = validateIconsNoDuplicates();
if (!result.isValid) {
console.warn(
logger.warn(
`⚠️ Icon validation failed: ${result.duplicates.length} duplicates, ${result.warnings.length} warnings`
);
console.warn('Run logIconValidation() for details');
logger.warn('Run logIconValidation() for details');
} else {
console.log(`✅ Icon validation passed: ${result.totalIcons} unique icons`);
logger.info(`✅ Icon validation passed: ${result.totalIcons} unique icons`);
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { User } from '../types/auth';
import { User } from '@/types/auth';
import { useThemeStore } from './theme';
import { logger } from '@/lib/logger';
+25 -12
View File
@@ -4,18 +4,18 @@ import { http, HttpResponse } from 'msw';
// Mock data
export const mockUser = {
id: 'user-1',
id: 1,
username: 'testuser',
email: 'test@example.com',
roleName: 'user',
role: 'user',
permissions: ['games.play', 'playlists.create', 'playlists.update'],
};
export const mockAdminUser = {
id: 'admin-1',
id: 2,
username: 'adminuser',
email: 'admin@example.com',
roleName: 'admin',
role: 'admin',
permissions: ['games.play', 'users.manage', 'settings.update', 'playlists.create'],
};
@@ -45,13 +45,12 @@ export const mockPlaylist = {
export const handlers = [
// Auth endpoints
http.post(`/api/auth/login`, async ({ request }) => {
const body = (await request.json()) as any;
const body = (await request.json()) as { username: string; password: string };
if (body.username === 'testuser' && body.password === 'password123') {
return HttpResponse.json({
user: mockUser,
tokens: {
accessToken: 'mock-access-token',
expiresIn: 3600,
},
});
@@ -66,7 +65,6 @@ export const handlers = [
http.post(`/api/auth/refresh`, () => {
return HttpResponse.json({
accessToken: 'new-mock-access-token',
expiresIn: 3600,
});
}),
@@ -78,10 +76,11 @@ export const handlers = [
// Games endpoints
http.get(`/api/games`, () => {
return HttpResponse.json({
games: [mockGame],
data: [mockGame],
total: 1,
page: 1,
limit: 20,
totalPages: 1,
});
}),
@@ -104,6 +103,8 @@ export const handlers = [
platform: 'Flash',
launchCommand: 'test.swf',
contentUrl: `/game-proxy/http://example.com/test.swf`,
applicationPath: '',
playMode: 'Single Player',
canPlayInBrowser: true,
downloading: false,
});
@@ -115,9 +116,13 @@ export const handlers = [
}),
http.post(`/api/user-playlists`, async ({ request }) => {
const body = (await request.json()) as any;
const body = (await request.json()) as {
title: string;
description?: string;
icon?: string;
};
return HttpResponse.json({
id: 'new-playlist-id',
id: 1,
title: body.title,
description: body.description,
icon: body.icon,
@@ -137,6 +142,14 @@ export const handlers = [
app: {
maintenance_mode: false,
allow_registration: true,
homeRecentHours: 24,
},
metadata: {
flashpointEdition: 'infinity',
flashpointVersion: '',
},
domains: {
defaultDomain: 'http://localhost:5173',
},
});
}),
@@ -144,8 +157,8 @@ export const handlers = [
// User settings endpoints
http.get(`/api/users/me/settings`, () => {
return HttpResponse.json({
theme: 'dark',
preferences: {},
theme_mode: 'dark',
primary_color: 'blue',
});
}),
];
+3
View File
@@ -18,6 +18,9 @@ export interface UserDetails {
lastLoginAt: string | null;
}
/**
* Client-side token metadata. Actual access/refresh tokens are stored in HTTP-only cookies.
*/
export interface AuthTokens {
expiresIn: number;
}