Files
fotospiel-app/resources/js/guest-v2/screens/ShareScreen.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

225 lines
7.6 KiB
TypeScript

import React from 'react';
import { YStack, XStack } from '@tamagui/stacks';
import { SizableText as Text } from '@tamagui/text';
import { Button } from '@tamagui/button';
import { Share2, QrCode, Link, Users } from 'lucide-react';
import AppShell from '../components/AppShell';
import { useEventData } from '../context/EventDataContext';
import { buildEventShareLink } from '../services/eventLink';
import { usePollStats } from '../hooks/usePollStats';
import { fetchEventQrCode } from '../services/qrApi';
import { useGuestThemeVariant } from '../lib/guestTheme';
import { useTranslation } from '@/guest/i18n/useTranslation';
export default function ShareScreen() {
const { event, token } = useEventData();
const { t } = useTranslation();
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 [copyState, setCopyState] = React.useState<'idle' | 'copied' | 'failed'>('idle');
const { stats } = usePollStats(token ?? null);
const [qrCodeDataUrl, setQrCodeDataUrl] = React.useState('');
const [qrLoading, setQrLoading] = React.useState(false);
const shareUrl = buildEventShareLink(event, token);
const handleCopy = React.useCallback(async () => {
if (!shareUrl) {
return;
}
try {
await navigator.clipboard?.writeText(shareUrl);
setCopyState('copied');
} catch (error) {
console.error('Copy failed', error);
setCopyState('failed');
} finally {
window.setTimeout(() => setCopyState('idle'), 2000);
}
}, [shareUrl]);
const handleShare = React.useCallback(async () => {
if (!shareUrl) return;
const title = event?.name ?? t('share.defaultEvent', 'Fotospiel');
const data: ShareData = { title, text: title, url: shareUrl };
if (navigator.share && (!navigator.canShare || navigator.canShare(data))) {
try {
await navigator.share(data);
} catch (error) {
// user dismissed
}
} else {
await handleCopy();
}
}, [event?.name, handleCopy, shareUrl]);
React.useEffect(() => {
if (!token) {
setQrCodeDataUrl('');
setQrLoading(false);
return;
}
let active = true;
setQrLoading(true);
fetchEventQrCode(token, 240)
.then((payload) => {
if (!active) {
return;
}
setQrCodeDataUrl(payload.qr_code_data_url ?? '');
})
.catch((error) => {
if (!active) {
return;
}
console.error('Failed to load QR code', error);
setQrCodeDataUrl('');
})
.finally(() => {
if (active) {
setQrLoading(false);
}
});
return () => {
active = false;
};
}, [token]);
const guestCountLabel = stats.onlineGuests.toString();
const inviteDisabled = !shareUrl;
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">
<Share2 size={18} color={isDark ? '#F8FAFF' : '#0F172A'} />
<Text fontSize="$4" fontWeight="$7">
{t('share.invite.title', 'Invite guests')}
</Text>
</XStack>
<Text fontSize="$2" color="$color" opacity={0.7}>
{t('share.invite.description', 'Share the event link or show the QR code to join.')}
</Text>
</YStack>
<XStack gap="$3">
<YStack
flex={1}
height={180}
borderRadius="$card"
backgroundColor="$muted"
borderWidth={1}
borderColor={cardBorder}
alignItems="center"
justifyContent="center"
gap="$2"
style={{
backgroundImage: isDark
? 'radial-gradient(circle at 30% 30%, rgba(255, 79, 216, 0.2), transparent 55%)'
: 'radial-gradient(circle at 30% 30%, color-mix(in oklab, var(--guest-primary, #FF5A5F) 18%, white), transparent 60%)',
}}
>
{qrCodeDataUrl ? (
<img
src={qrCodeDataUrl}
alt={t('share.invite.qrAlt', 'Event QR code')}
style={{ width: 120, height: 120, borderRadius: 16 }}
/>
) : (
<QrCode size={qrLoading ? 22 : 28} color={isDark ? '#F8FAFF' : '#0F172A'} />
)}
<Text fontSize="$3" fontWeight="$7">
{t('share.invite.qrLabel', 'Show QR')}
</Text>
</YStack>
<YStack
flex={1}
height={180}
borderRadius="$card"
backgroundColor="$surface"
borderWidth={1}
borderColor={cardBorder}
alignItems="center"
justifyContent="center"
gap="$2"
style={{
boxShadow: isDark ? '0 16px 30px rgba(2, 6, 23, 0.35)' : '0 14px 24px rgba(15, 23, 42, 0.12)',
}}
>
<Link size={24} color={isDark ? '#F8FAFF' : '#0F172A'} />
<Text fontSize="$3" fontWeight="$7">
{copyState === 'copied'
? t('share.copySuccess', 'Copied')
: copyState === 'failed'
? t('share.copyError', 'Copy failed')
: t('share.invite.copyLabel', 'Copy link')}
</Text>
<Button size="$2" backgroundColor="$primary" borderRadius="$pill" onPress={handleCopy} disabled={!shareUrl}>
{t('share.copyLink', 'Copy link')}
</Button>
</YStack>
</XStack>
<YStack
padding="$4"
borderRadius="$card"
backgroundColor="$surface"
borderWidth={1}
borderColor={cardBorder}
gap="$3"
style={{
backgroundImage: isDark
? 'linear-gradient(135deg, rgba(79, 209, 255, 0.12), rgba(255, 79, 216, 0.18))'
: 'linear-gradient(135deg, color-mix(in oklab, var(--guest-secondary, #F43F5E) 8%, white), color-mix(in oklab, var(--guest-primary, #FF5A5F) 12%, white))',
boxShadow: isDark ? '0 22px 44px rgba(2, 6, 23, 0.45)' : '0 18px 32px rgba(15, 23, 42, 0.12)',
}}
>
<XStack alignItems="center" gap="$2">
<Users size={18} color={isDark ? '#F8FAFF' : '#0F172A'} />
<Text fontSize="$4" fontWeight="$7">
{t('share.invite.guestsTitle', 'Guests joined')}
</Text>
</XStack>
<YStack gap="$1">
<Text fontSize="$6" fontWeight="$8">
{guestCountLabel}
</Text>
<Text fontSize="$2" color="$color" opacity={0.7}>
{t('home.stats.online', 'Guests online')}
</Text>
</YStack>
<Text fontSize="$2" color="$color" opacity={0.7}>
{event?.name
? t('share.invite.guestsSubtitleEvent', 'Share {event} with your guests.', { event: event.name })
: t('share.invite.guestsSubtitle', 'Share the event with your guests.')}
</Text>
<Button
size="$3"
backgroundColor="$primary"
borderRadius="$pill"
alignSelf="flex-start"
onPress={handleShare}
disabled={inviteDisabled}
>
{t('share.invite.send', 'Send invite')}
</Button>
</YStack>
</YStack>
</AppShell>
);
}