upgrade to tamagui v2 and guest pwa overhaul
This commit is contained in:
174
resources/js/guest-v2/screens/AchievementsScreen.tsx
Normal file
174
resources/js/guest-v2/screens/AchievementsScreen.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
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 { useAppearance } from '@/hooks/use-appearance';
|
||||
|
||||
export default function AchievementsScreen() {
|
||||
const { token } = useEventData();
|
||||
const identity = useOptionalGuestIdentity();
|
||||
const { t } = useTranslation();
|
||||
const { locale } = useLocale();
|
||||
const { resolved } = useAppearance();
|
||||
const isDark = resolved === 'dark';
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user