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:
+3
-1
@@ -139,4 +139,6 @@ vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
.claude/**/*.*
|
||||
|
||||
user.db
|
||||
user.db
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,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,5 +1,5 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { useAuthStore } from '../../store/auth';
|
||||
import { useAuthStore } from '@/store/auth';
|
||||
|
||||
interface RoleGuardProps {
|
||||
children: ReactNode;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,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,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,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',
|
||||
|
||||
@@ -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,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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,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) => (
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,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) => {
|
||||
|
||||
@@ -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,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,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,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;
|
||||
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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,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>,
|
||||
|
||||
@@ -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,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,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,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,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,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) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>>({
|
||||
|
||||
@@ -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,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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,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';
|
||||
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user