174 lines
5.9 KiB
TypeScript
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>
|
|
);
|
|
}
|