Public Access
The name field takes its value from the page's own load, not from the user context: the context can still be empty when the form mounts.
82 lines
4.5 KiB
React
82 lines
4.5 KiB
React
import {useTranslation} from 'react-i18next';
|
|
import {ImageOff, Palette, RotateCcw} from 'lucide-react';
|
|
|
|
import {Button} from '@/components/ui/button';
|
|
import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card';
|
|
import {Label} from '@/components/ui/label';
|
|
import {Switch} from '@/components/ui/switch';
|
|
import {AccentPicker, CornersPicker} from './StylePickers.jsx';
|
|
import {usePreferences} from '../context/PreferencesContext.jsx';
|
|
import useSaveAppearance from '../hooks/useSaveAppearance.js';
|
|
import {coverUrl} from '../utils/games.js';
|
|
|
|
export default function AppearanceCard() {
|
|
const {t} = useTranslation();
|
|
const {appearance} = usePreferences();
|
|
const saveAppearance = useSaveAppearance();
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2"><Palette className="size-4 text-primary"/>{t('SETTINGS.APPEARANCE')}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
<AccentPicker id="accent" value={appearance.accent} onChange={(accent) => saveAppearance({accent})}/>
|
|
<CornersPicker id="corners" value={appearance.corners} onChange={(corners) => saveAppearance({corners})}/>
|
|
<div className="flex items-center justify-between gap-4">
|
|
<div className="space-y-1">
|
|
<Label htmlFor="wideLayout">{t('SETTINGS.WIDE_LAYOUT')}</Label>
|
|
<p className="text-xs text-muted-foreground">{t('SETTINGS.WIDE_LAYOUT_HINT')}</p>
|
|
</div>
|
|
<Switch id="wideLayout" checked={appearance.wideLayout}
|
|
onCheckedChange={(wideLayout) => saveAppearance({wideLayout})}/>
|
|
</div>
|
|
<div className="flex items-center justify-between gap-4">
|
|
<div className="space-y-1">
|
|
<Label id="background-label">{t('SETTINGS.BACKGROUND')}</Label>
|
|
{!appearance.backgroundAppid && (
|
|
<p className="text-xs text-muted-foreground">{t('SETTINGS.BACKGROUND_HINT')}</p>
|
|
)}
|
|
</div>
|
|
{appearance.backgroundAppid && (
|
|
<div className="flex items-center gap-2">
|
|
<img src={coverUrl(appearance.backgroundAppid)} alt=""
|
|
onError={(event) => { event.currentTarget.hidden = true; }}
|
|
className="h-10 w-20 rounded-md object-cover"/>
|
|
<Button type="button" variant="outline" size="sm"
|
|
aria-describedby="background-label"
|
|
onClick={() => saveAppearance({backgroundAppid: null})}>
|
|
<ImageOff/>{t('SETTINGS.BACKGROUND_REMOVE')}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="space-y-3">
|
|
<div className="flex items-center justify-between gap-4">
|
|
<div className="space-y-1">
|
|
<Label id="banner-label" htmlFor="showBanner">{t('SETTINGS.BANNER')}</Label>
|
|
{appearance.showBanner && !appearance.bannerAppid && (
|
|
<p className="text-xs text-muted-foreground">{t('SETTINGS.BANNER_HINT')}</p>
|
|
)}
|
|
</div>
|
|
<Switch id="showBanner" checked={appearance.showBanner}
|
|
onCheckedChange={(showBanner) => saveAppearance({showBanner})}/>
|
|
</div>
|
|
{appearance.showBanner && appearance.bannerAppid && (
|
|
<div className="flex items-center justify-end gap-2">
|
|
<img src={coverUrl(appearance.bannerAppid)} alt=""
|
|
onError={(event) => { event.currentTarget.hidden = true; }}
|
|
className="h-10 w-20 rounded-md object-cover"/>
|
|
<Button type="button" variant="outline" size="sm"
|
|
aria-describedby="banner-label"
|
|
onClick={() => saveAppearance({bannerAppid: null})}>
|
|
<RotateCcw/>{t('SETTINGS.BANNER_AUTO')}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|