feat(ui): modern shadcn/ui theme with violet accent across all pages
Build, Test & Push Frontend / quality-check (push) Successful in 8s
Build, Test & Push Frontend / docker (push) Successful in 26s

Add shadcn/ui (Radix + Tailwind 4) components, zinc/violet theme tokens and self-hosted Inter. New header with user menu, Steam cover art, skeletons and toasts. Replace @mdi/font with lucide-react. Fix: top-owned list on the landing page only showed with top-played entries; numbers use the app language.
This commit is contained in:
lobo committed 2026-09-24 10:07:32 +02:00
1 parent 76a9ac3e6f
commit e480369a98
43 files changed
+3804 -979

No files matched your search

+98
View File
@@ -0,0 +1,98 @@
import {Link, NavLink} from 'react-router-dom';
import {useTranslation} from 'react-i18next';
import {Gamepad2, Languages, LayoutDashboard, Library, LogOut, Moon, Settings, Sun, UserRound} from 'lucide-react';
import {Button} from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {usePreferences} from '@/context/PreferencesContext.jsx';
import {cn} from '@/lib/utils';
const NAV_ITEMS = [
{to: '/dashboard', labelKey: 'NAV.DASHBOARD', Icon: LayoutDashboard},
{to: '/library', labelKey: 'NAV.LIBRARY', Icon: Library},
];
const navLinkClass = ({isActive}) => cn(
'flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
isActive
? 'bg-accent text-accent-foreground'
: 'text-muted-foreground hover:bg-muted hover:text-foreground',
);
export default function AppHeader({isAuthenticated, onLogout}) {
const {t, i18n} = useTranslation();
const {isDarkMode, setDarkMode, setLanguage} = usePreferences();
const toggleLanguage = () => setLanguage(i18n.language === 'de' ? 'en' : 'de');
const toggleTheme = () => setDarkMode(!isDarkMode);
const ThemeIcon = isDarkMode ? Sun : Moon;
return (
<header className="sticky top-0 z-50 border-b bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="mx-auto flex h-16 max-w-5xl items-center gap-2 px-4 md:px-8">
<Link to={isAuthenticated ? '/dashboard' : '/'} className="mr-2 flex items-center gap-2 font-bold tracking-tight whitespace-nowrap">
<span className="flex size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
<Gamepad2 className="size-5"/>
</span>
<span className="text-lg">Game Log</span>
</Link>
{isAuthenticated && (
<nav className="flex items-center gap-1">
{NAV_ITEMS.map(({to, labelKey, Icon}) => (
<NavLink key={to} to={to} className={navLinkClass} title={t(labelKey)}>
<Icon className="size-4"/>
<span className="hidden sm:inline">{t(labelKey)}</span>
</NavLink>
))}
</nav>
)}
<div className="ml-auto flex items-center gap-1">
{isAuthenticated ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" aria-label={t('NAV.MENU')}>
<UserRound className="size-5"/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52">
<DropdownMenuItem asChild>
<Link to="/settings"><Settings/>{t('NAV.SETTINGS')}</Link>
</DropdownMenuItem>
<DropdownMenuItem onSelect={toggleLanguage}>
<Languages/>{t('NAV.LANGUAGE')}: {i18n.language.toUpperCase()}
</DropdownMenuItem>
<DropdownMenuItem onSelect={toggleTheme}>
<ThemeIcon/>{t(isDarkMode ? 'NAV.LIGHT_MODE' : 'NAV.DARK_MODE')}
</DropdownMenuItem>
<DropdownMenuSeparator/>
<DropdownMenuItem variant="destructive" onSelect={onLogout}>
<LogOut/>{t('MAIN.LOGOUT')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : (
<>
<Button variant="ghost" size="sm" onClick={toggleLanguage}>
{i18n.language.toUpperCase()}
</Button>
<Button variant="ghost" size="icon" onClick={toggleTheme} aria-label={t(isDarkMode ? 'NAV.LIGHT_MODE' : 'NAV.DARK_MODE')}>
<ThemeIcon className="size-5"/>
</Button>
<Button asChild className="ml-1">
<Link to="/login">{t('MAIN.LOGIN')}</Link>
</Button>
</>
)}
</div>
</div>
</header>
);
}