Files
movieloop-frontend/src/components/ui/difficulty-badge.tsx
T
TehRiehlDeal 991082e65b Add Keep-Me-Signed-In, movie release dates, and lint cleanup
Features:
- "Keep me signed in" — Login.tsx adds a checkbox visible on both login
  and register tabs. authApi, auth-store, and the API contract pass a
  rememberMe flag through to the backend, which controls the JWT TTL.
- Movie release dates — DailyChallenge, GameHistoryEntry, VersusMatch,
  AsyncAttemptResponse, and AsyncLeaderboardResponse interfaces gain
  optional movieAReleaseDate / movieBReleaseDate. UI sites:
    * DailyChallenge.tsx — year on a muted line under each title
      (matches MovieCard convention)
    * GameReview.tsx — inline (YYYY) on the heading
    * ShareableResult.tsx + GameCompletionModal — inline (YYYY) in the
      copied/shared text
    * AsyncMatchLeaderboard.tsx — inline (YYYY) on the subtitle
  All sites guard on truthy date so legacy NULL rows render unchanged.

Lint cleanup (34 → 0 errors):
- New src/lib/error.ts (getErrorMessage / getErrorStatus) to replace
  `catch (err: any) { err.response?.data?.message }` patterns in
  auth-store, Profile, and GameNight.
- The two new react-hooks v6 rules (set-state-in-effect, purity) flag
  standard data-fetching patterns; downgraded to "warn" so CI doesn't
  fail while keeping them visible in the IDE.
- Typed JSON score blobs in VersusCompletionModal and GameNightResults
  with `{ totalScore?: number }`.
- Typed game-start socket payloads in VersusLobby and GameNightLobby.
- ShadCN convention: eslint-disable-next-line on badge, button, and
  difficulty-badge to allow CVA helpers colocated with components
  (matches upstream ShadCN pattern).
- Typed admin generateAllChallenges API response.
- Misc: prefer-const in Home.tsx, no-empty in storage.ts, underscore
  ignore-pattern for no-unused-vars.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 16:40:23 -07:00

46 lines
1.5 KiB
TypeScript

import { cn } from '@/lib/utils';
type Difficulty = 'easy' | 'medium' | 'hard';
const DIFFICULTY_STYLES: Record<Difficulty, string> = {
easy: 'bg-emerald-100 text-emerald-800 dark:bg-emerald-900/40 dark:text-emerald-300',
medium: 'bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-300',
hard: 'bg-rose-100 text-rose-800 dark:bg-rose-900/40 dark:text-rose-300',
};
const DIFFICULTY_BUTTON_ACTIVE: Record<Difficulty, string> = {
easy: 'border-emerald-500 bg-emerald-100 text-emerald-800 dark:border-emerald-500 dark:bg-emerald-900/40 dark:text-emerald-300',
medium: 'border-amber-500 bg-amber-100 text-amber-800 dark:border-amber-500 dark:bg-amber-900/40 dark:text-amber-300',
hard: 'border-rose-500 bg-rose-100 text-rose-800 dark:border-rose-500 dark:bg-rose-900/40 dark:text-rose-300',
};
// eslint-disable-next-line react-refresh/only-export-components
export function getDifficultyButtonClass(difficulty: Difficulty, isActive: boolean): string {
if (!isActive) return '';
return DIFFICULTY_BUTTON_ACTIVE[difficulty];
}
export function DifficultyBadge({
difficulty,
className,
}: {
difficulty: string;
className?: string;
}) {
const level = (difficulty as Difficulty) in DIFFICULTY_STYLES
? (difficulty as Difficulty)
: 'medium';
return (
<span
className={cn(
'inline-flex items-center rounded-full px-2 py-0.5 text-xs font-semibold capitalize',
DIFFICULTY_STYLES[level],
className,
)}
>
{difficulty}
</span>
);
}