Guest PWA vollständig lokalisiert
This commit is contained in:
@@ -10,6 +10,7 @@ import { useEventStats } from '../context/EventStatsContext';
|
||||
import { useEventData } from '../hooks/useEventData';
|
||||
import { useGuestTaskProgress } from '../hooks/useGuestTaskProgress';
|
||||
import { Sparkles, UploadCloud, Images, CheckCircle2, Users, TimerReset } from 'lucide-react';
|
||||
import { useTranslation, type TranslateFn } from '../i18n/useTranslation';
|
||||
|
||||
export default function HomePage() {
|
||||
const { token } = useParams<{ token: string }>();
|
||||
@@ -17,63 +18,76 @@ export default function HomePage() {
|
||||
const stats = useEventStats();
|
||||
const { event } = useEventData();
|
||||
const { completedCount } = useGuestTaskProgress(token);
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!token) return null;
|
||||
|
||||
const displayName = hydrated && name ? name : 'Gast';
|
||||
const latestUploadText = formatLatestUpload(stats.latestPhotoAt);
|
||||
const displayName = hydrated && name ? name : t('home.fallbackGuestName');
|
||||
const eventNameDisplay = event?.name ?? t('home.hero.defaultEventName');
|
||||
const latestUploadText = formatLatestUpload(stats.latestPhotoAt, t);
|
||||
|
||||
const primaryActions: Array<{ to: string; label: string; description: string; icon: React.ReactNode }> = [
|
||||
{
|
||||
to: 'tasks',
|
||||
label: 'Aufgabe ziehen',
|
||||
description: 'Hol dir deine naechste Challenge',
|
||||
icon: <Sparkles className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
to: 'upload',
|
||||
label: 'Direkt hochladen',
|
||||
description: 'Teile deine neuesten Fotos',
|
||||
icon: <UploadCloud className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
to: 'gallery',
|
||||
label: 'Galerie ansehen',
|
||||
description: 'Lass dich von anderen inspirieren',
|
||||
icon: <Images className="h-5 w-5" />,
|
||||
},
|
||||
];
|
||||
const primaryActions = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
to: 'tasks',
|
||||
label: t('home.actions.items.tasks.label'),
|
||||
description: t('home.actions.items.tasks.description'),
|
||||
icon: <Sparkles className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
to: 'upload',
|
||||
label: t('home.actions.items.upload.label'),
|
||||
description: t('home.actions.items.upload.description'),
|
||||
icon: <UploadCloud className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
to: 'gallery',
|
||||
label: t('home.actions.items.gallery.label'),
|
||||
description: t('home.actions.items.gallery.description'),
|
||||
icon: <Images className="h-5 w-5" />,
|
||||
},
|
||||
],
|
||||
[t],
|
||||
);
|
||||
|
||||
const checklistItems = [
|
||||
'Aufgabe auswaehlen oder starten',
|
||||
'Emotion festhalten und Foto schiessen',
|
||||
'Bild hochladen und Credits sammeln',
|
||||
];
|
||||
const checklistItems = React.useMemo(
|
||||
() => [
|
||||
t('home.checklist.steps.first'),
|
||||
t('home.checklist.steps.second'),
|
||||
t('home.checklist.steps.third'),
|
||||
],
|
||||
[t],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 pb-24">
|
||||
<HeroCard name={displayName} eventName={event?.name ?? 'Dein Event'} tasksCompleted={completedCount} />
|
||||
<HeroCard
|
||||
name={displayName}
|
||||
eventName={eventNameDisplay}
|
||||
tasksCompleted={completedCount}
|
||||
t={t}
|
||||
/>
|
||||
|
||||
<Card>
|
||||
<CardContent className="grid grid-cols-1 gap-4 py-4 sm:grid-cols-4">
|
||||
<StatTile
|
||||
icon={<Users className="h-4 w-4" />}
|
||||
label="Gleichzeitig online"
|
||||
label={t('home.stats.online')}
|
||||
value={`${stats.onlineGuests}`}
|
||||
/>
|
||||
<StatTile
|
||||
icon={<Sparkles className="h-4 w-4" />}
|
||||
label="Aufgaben gelöst"
|
||||
label={t('home.stats.tasksSolved')}
|
||||
value={`${stats.tasksSolved}`}
|
||||
/>
|
||||
<StatTile
|
||||
icon={<TimerReset className="h-4 w-4" />}
|
||||
label="Letzter Upload"
|
||||
label={t('home.stats.lastUpload')}
|
||||
value={latestUploadText}
|
||||
/>
|
||||
<StatTile
|
||||
icon={<CheckCircle2 className="h-4 w-4" />}
|
||||
label="Deine erledigten Aufgaben"
|
||||
label={t('home.stats.completedTasks')}
|
||||
value={`${completedCount}`}
|
||||
/>
|
||||
</CardContent>
|
||||
@@ -81,8 +95,10 @@ export default function HomePage() {
|
||||
|
||||
<section className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">Deine Aktionen</h2>
|
||||
<span className="text-xs text-muted-foreground">Waehle aus, womit du starten willst</span>
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
{t('home.actions.title')}
|
||||
</h2>
|
||||
<span className="text-xs text-muted-foreground">{t('home.actions.subtitle')}</span>
|
||||
</div>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{primaryActions.map((action) => (
|
||||
@@ -102,14 +118,14 @@ export default function HomePage() {
|
||||
))}
|
||||
</div>
|
||||
<Button variant="outline" asChild className="w-full">
|
||||
<Link to="queue">Uploads in Warteschlange ansehen</Link>
|
||||
<Link to="queue">{t('home.actions.queueButton')}</Link>
|
||||
</Button>
|
||||
</section>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Dein Fortschritt</CardTitle>
|
||||
<CardDescription>Halte dich an diese drei kurzen Schritte fuer die besten Ergebnisse.</CardDescription>
|
||||
<CardTitle>{t('home.checklist.title')}</CardTitle>
|
||||
<CardDescription>{t('home.checklist.description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{checklistItems.map((item) => (
|
||||
@@ -130,17 +146,29 @@ export default function HomePage() {
|
||||
);
|
||||
}
|
||||
|
||||
function HeroCard({ name, eventName, tasksCompleted }: { name: string; eventName: string; tasksCompleted: number }) {
|
||||
function HeroCard({
|
||||
name,
|
||||
eventName,
|
||||
tasksCompleted,
|
||||
t,
|
||||
}: {
|
||||
name: string;
|
||||
eventName: string;
|
||||
tasksCompleted: number;
|
||||
t: TranslateFn;
|
||||
}) {
|
||||
const heroTitle = t('home.hero.title').replace('{name}', name);
|
||||
const heroDescription = t('home.hero.description').replace('{eventName}', eventName);
|
||||
const progressMessage = tasksCompleted > 0
|
||||
? `Schon ${tasksCompleted} Aufgaben erledigt - weiter so!`
|
||||
: 'Starte mit deiner ersten Aufgabe - wir zählen auf dich!';
|
||||
? t('home.hero.progress.some').replace('{count}', `${tasksCompleted}`)
|
||||
: t('home.hero.progress.none');
|
||||
|
||||
return (
|
||||
<Card className="overflow-hidden border-0 bg-gradient-to-r from-pink-500 via-fuchsia-500 to-purple-500 text-white shadow-md">
|
||||
<CardHeader className="space-y-1">
|
||||
<CardDescription className="text-sm text-white/80">Willkommen zur Party</CardDescription>
|
||||
<CardTitle className="text-2xl font-bold">Hey {name}!</CardTitle>
|
||||
<p className="text-sm text-white/80">Du bist bereit für "{eventName}". Fang die Highlights des Events ein und teile sie mit allen Gästen.</p>
|
||||
<CardDescription className="text-sm text-white/80">{t('home.hero.subtitle')}</CardDescription>
|
||||
<CardTitle className="text-2xl font-bold">{heroTitle}</CardTitle>
|
||||
<p className="text-sm text-white/80">{heroDescription}</p>
|
||||
<p className="text-sm font-medium text-white/90">{progressMessage}</p>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
@@ -161,26 +189,26 @@ function StatTile({ icon, label, value }: { icon: React.ReactNode; label: string
|
||||
);
|
||||
}
|
||||
|
||||
function formatLatestUpload(isoDate: string | null) {
|
||||
function formatLatestUpload(isoDate: string | null, t: TranslateFn) {
|
||||
if (!isoDate) {
|
||||
return 'Noch kein Upload';
|
||||
return t('home.latestUpload.none');
|
||||
}
|
||||
const date = new Date(isoDate);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return 'Noch kein Upload';
|
||||
return t('home.latestUpload.invalid');
|
||||
}
|
||||
const diffMs = Date.now() - date.getTime();
|
||||
const diffMinutes = Math.round(diffMs / 60000);
|
||||
if (diffMinutes < 1) {
|
||||
return 'Gerade eben';
|
||||
return t('home.latestUpload.justNow');
|
||||
}
|
||||
if (diffMinutes < 60) {
|
||||
return `vor ${diffMinutes} Min`;
|
||||
return t('home.latestUpload.minutes').replace('{count}', `${diffMinutes}`);
|
||||
}
|
||||
const diffHours = Math.round(diffMinutes / 60);
|
||||
if (diffHours < 24) {
|
||||
return `vor ${diffHours} Std`;
|
||||
return t('home.latestUpload.hours').replace('{count}', `${diffHours}`);
|
||||
}
|
||||
const diffDays = Math.round(diffHours / 24);
|
||||
return `vor ${diffDays} Tagen`;
|
||||
return t('home.latestUpload.days').replace('{count}', `${diffDays}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user