Files
fotospiel-app/resources/js/guest-v2/screens/AchievementsScreen.tsx
Codex Agent 298a8375b6
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
Update guest v2 branding and theming
2026-02-03 15:18:44 +01:00

174 lines
5.9 KiB
TypeScript

import React from 'react';
import { YStack, XStack } from '@tamagui/stacks';
import { SizableText as Text } from '@tamagui/text';
import { Trophy, Star } from 'lucide-react';
import AppShell from '../components/AppShell';
import { useEventData } from '../context/EventDataContext';
import { useOptionalGuestIdentity } from '../context/GuestIdentityContext';
import { fetchAchievements, type AchievementsPayload } from '../services/achievementsApi';
import { useTranslation } from '@/guest/i18n/useTranslation';
import { useLocale } from '@/guest/i18n/LocaleContext';
import { useGuestThemeVariant } from '../lib/guestTheme';
export default function AchievementsScreen() {
const { token } = useEventData();
const identity = useOptionalGuestIdentity();
const { t } = useTranslation();
const { locale } = useLocale();
const { isDark } = useGuestThemeVariant();
const cardBorder = isDark ? 'rgba(255, 255, 255, 0.12)' : 'rgba(15, 23, 42, 0.12)';
const cardShadow = isDark ? '0 18px 40px rgba(2, 6, 23, 0.4)' : '0 16px 30px rgba(15, 23, 42, 0.12)';
const [payload, setPayload] = React.useState<AchievementsPayload | null>(null);
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<string | null>(null);
React.useEffect(() => {
if (!token) {
setPayload(null);
return;
}
let active = true;
setLoading(true);
setError(null);
fetchAchievements(token, { guestName: identity?.name ?? undefined, locale })
.then((data) => {
if (!active) return;
setPayload(data);
})
.catch((err) => {
console.error('Failed to load achievements', err);
if (active) {
setError(t('achievements.error', 'Achievements could not be loaded.'));
}
})
.finally(() => {
if (active) {
setLoading(false);
}
});
return () => {
active = false;
};
}, [token, identity?.name, locale, t]);
const topPhoto = payload?.highlights?.topPhoto ?? null;
const totalPhotos = payload?.summary?.totalPhotos ?? 0;
const totalTasks = payload?.summary?.tasksSolved ?? 0;
const totalLikes = payload?.summary?.likesTotal ?? 0;
return (
<AppShell>
<YStack gap="$4">
<YStack
padding="$4"
borderRadius="$card"
backgroundColor="$surface"
borderWidth={1}
borderColor={cardBorder}
gap="$2"
style={{
boxShadow: cardShadow,
}}
>
<XStack alignItems="center" gap="$2">
<Trophy size={18} color="#FDE047" />
<Text fontSize="$4" fontWeight="$7">
{t('achievements.page.title', 'Achievements')}
</Text>
</XStack>
<Text fontSize="$2" color="$color" opacity={0.7}>
{loading
? t('common.actions.loading', 'Loading...')
: t('achievements.page.subtitle', 'Track your milestones and highlight streaks.')}
</Text>
</YStack>
{error ? (
<YStack
padding="$3"
borderRadius="$card"
backgroundColor="rgba(248, 113, 113, 0.12)"
borderWidth={1}
borderColor="rgba(248, 113, 113, 0.4)"
>
<Text fontSize="$2" color="#FEE2E2">
{error ?? t('achievements.page.loadError', 'Achievements could not be loaded.')}
</Text>
</YStack>
) : null}
<YStack
padding="$4"
borderRadius="$card"
backgroundColor="$surface"
borderWidth={1}
borderColor={cardBorder}
gap="$2"
style={{
backgroundImage: isDark
? 'linear-gradient(135deg, rgba(255, 79, 216, 0.18), rgba(79, 209, 255, 0.12))'
: 'linear-gradient(135deg, color-mix(in oklab, var(--guest-primary, #FF5A5F) 12%, white), color-mix(in oklab, var(--guest-secondary, #F43F5E) 10%, white))',
}}
>
<XStack alignItems="center" gap="$2">
<Star size={16} color={isDark ? '#F8FAFF' : '#0F172A'} />
<Text fontSize="$3" fontWeight="$7">
{topPhoto
? t('achievements.highlights.topTitle', 'Top photo')
: t('achievements.summary.topContributor', 'Top contributor')}
</Text>
</XStack>
<Text fontSize="$2" color="$color" opacity={0.7}>
{topPhoto
? t('achievements.highlights.likesAmount', { count: topPhoto.likes }, '{count} Likes')
: t('achievements.summary.placeholder', 'Keep sharing to unlock highlights.')}
</Text>
</YStack>
<XStack gap="$3">
{[1, 2].map((card) => (
<YStack
key={card}
flex={1}
padding="$3"
borderRadius="$card"
backgroundColor="$surface"
borderWidth={1}
borderColor={cardBorder}
gap="$1"
>
<Text fontSize="$4" fontWeight="$8">
{card === 1 ? totalTasks : totalPhotos}
</Text>
<Text fontSize="$2" color="$color" opacity={0.7}>
{card === 1
? t('achievements.summary.tasksCompleted', 'Tasks completed')
: t('achievements.summary.photosShared', 'Photos shared')}
</Text>
</YStack>
))}
</XStack>
<YStack
padding="$4"
borderRadius="$card"
backgroundColor="$surface"
borderWidth={1}
borderColor={cardBorder}
gap="$1"
>
<Text fontSize="$4" fontWeight="$7">
{totalLikes}
</Text>
<Text fontSize="$2" color="$color" opacity={0.7}>
{t('achievements.summary.likesCollected', 'Likes collected')}
</Text>
</YStack>
</YStack>
</AppShell>
);
}