Fix share assets, shared photo UI, and live show expiry
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-02-06 07:30:30 +01:00
parent 18b4f36fcf
commit b14435df8b
12 changed files with 352 additions and 85 deletions

View File

@@ -3,7 +3,7 @@ import { useParams } from 'react-router-dom';
import { YStack, XStack } from '@tamagui/stacks';
import { SizableText as Text } from '@tamagui/text';
import { Button } from '@tamagui/button';
import { AlertCircle, Download } from 'lucide-react';
import { AlertCircle, Download, Maximize2, X } from 'lucide-react';
import StandaloneShell from '../components/StandaloneShell';
import SurfaceCard from '../components/SurfaceCard';
import EventLogo from '../components/EventLogo';
@@ -14,6 +14,7 @@ import { EventBrandingProvider } from '@/guest/context/EventBrandingContext';
import { mapEventBranding } from '../lib/eventBranding';
import { BrandingTheme } from '../lib/brandingTheme';
import { useGuestThemeVariant } from '../lib/guestTheme';
import { getBentoSurfaceTokens } from '../lib/bento';
interface ShareResponse {
slug: string;
@@ -38,6 +39,7 @@ export default function SharedPhotoScreen() {
error: null,
data: null,
});
const [fullScreenOpen, setFullScreenOpen] = React.useState(false);
const branding = React.useMemo(() => {
if (!state.data?.branding) {
return null;
@@ -46,6 +48,7 @@ export default function SharedPhotoScreen() {
}, [state.data]);
const { isDark } = useGuestThemeVariant(branding);
const mutedText = isDark ? 'rgba(248, 250, 252, 0.7)' : 'rgba(15, 23, 42, 0.65)';
const bento = getBentoSurfaceTokens(isDark);
React.useEffect(() => {
let active = true;
@@ -105,66 +108,182 @@ export default function SharedPhotoScreen() {
const chips = buildChips(data, t);
const content = (
<StandaloneShell>
<SurfaceCard glow>
<XStack alignItems="center" gap="$3">
<EventLogo name={data.event?.name ?? t('share.defaultEvent', 'Ein besonderer Moment')} size="s" />
<YStack gap="$1">
<Text fontSize="$2" letterSpacing={2} textTransform="uppercase" color={mutedText}>
{t('share.title', 'Geteiltes Foto')}
</Text>
<Text fontSize="$6" fontWeight="$8">
{data.event?.name ?? t('share.defaultEvent', 'Ein besonderer Moment')}
</Text>
</YStack>
</XStack>
{data.photo.title ? (
<Text fontSize="$3" color={mutedText} marginTop="$1">
{data.photo.title}
</Text>
) : null}
</SurfaceCard>
<SurfaceCard padding={0} overflow="hidden">
<StandaloneShell compact>
<YStack gap="$3" style={{ paddingBottom: 'calc(var(--space-6) + 50px)' }}>
<YStack
height={360}
padding="$4"
borderRadius="$card"
borderWidth={1}
borderBottomWidth={2}
borderColor={bento.borderColor}
borderBottomColor={bento.borderBottomColor}
backgroundColor={bento.backgroundColor}
style={{ boxShadow: bento.shadow }}
>
<XStack alignItems="center" gap="$3" flexWrap="wrap">
<EventLogo name={data.event?.name ?? t('share.defaultEvent', 'Ein besonderer Moment')} size="s" />
<YStack gap="$1" flex={1} minWidth={180}>
<Text fontSize="$2" letterSpacing={2} textTransform="uppercase" color={mutedText}>
{t('share.title', 'Geteiltes Foto')}
</Text>
<Text fontSize="$6" fontWeight="$8">
{data.event?.name ?? t('share.defaultEvent', 'Ein besonderer Moment')}
</Text>
</YStack>
</XStack>
{data.photo.title ? (
<Text fontSize="$3" color={mutedText} marginTop="$2" numberOfLines={2}>
{data.photo.title}
</Text>
) : null}
</YStack>
<YStack
padding="$2"
borderRadius="$card"
borderWidth={1}
borderBottomWidth={2}
borderColor={bento.borderColor}
borderBottomColor={bento.borderBottomColor}
backgroundColor={bento.backgroundColor}
style={{ boxShadow: bento.shadow }}
>
<YStack
borderRadius="$card"
overflow="hidden"
position="relative"
style={{
height: 'min(45vh, 320px)',
backgroundColor: isDark ? 'rgba(15, 23, 42, 0.4)' : 'rgba(15, 23, 42, 0.06)',
backgroundImage: `url(${data.photo.image_urls.full})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
>
<Button
size="$3"
circular
position="absolute"
top="$2"
right="$2"
backgroundColor={isDark ? 'rgba(15, 23, 42, 0.6)' : 'rgba(255, 255, 255, 0.85)'}
borderWidth={1}
borderColor={bento.borderColor}
onPress={() => setFullScreenOpen(true)}
aria-label={t('share.fullscreen', 'Fullscreen')}
>
<Maximize2 size={16} color={isDark ? '#F8FAFF' : '#0F172A'} />
</Button>
</YStack>
</YStack>
{chips.length > 0 ? (
<XStack gap="$2" flexWrap="wrap" justifyContent="center">
{chips.map((chip) => (
<SurfaceCard key={chip.id} padding="$2" borderRadius="$pill" minWidth={120}>
<XStack alignItems="center" gap="$2" flexWrap="wrap">
{chip.icon ? <Text fontSize="$3">{chip.icon}</Text> : null}
<Text fontSize="$2" color={mutedText}>
{chip.label}
</Text>
<Text fontSize="$2" fontWeight="$7" numberOfLines={2} style={{ maxWidth: 220 }}>
{chip.value}
</Text>
</XStack>
</SurfaceCard>
))}
</XStack>
) : null}
<YStack style={{ position: 'sticky', bottom: 12 }}>
<Button
size="$4"
borderRadius="$pill"
backgroundColor="$primary"
onPress={() => window.open(data.photo.image_urls.full, '_blank')}
justifyContent="center"
>
<XStack alignItems="center" justifyContent="center" gap="$2" width="100%">
<Download size={18} color="white" />
<Text fontSize="$3" fontWeight="$7" color="white">
{t('galleryPublic.download', 'Download')}
</Text>
</XStack>
</Button>
</YStack>
</YStack>
{fullScreenOpen ? (
<YStack
position="fixed"
top={0}
left={0}
right={0}
bottom={0}
zIndex={3000}
alignItems="center"
justifyContent="center"
style={{
backgroundImage: `url(${data.photo.image_urls.full})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundColor: isDark ? 'rgba(2, 6, 23, 0.8)' : 'rgba(15, 23, 42, 0.45)',
backdropFilter: 'blur(14px)',
}}
/>
</SurfaceCard>
{chips.length > 0 ? (
<XStack gap="$2" flexWrap="wrap" justifyContent="center">
{chips.map((chip) => (
<SurfaceCard key={chip.id} padding="$2" borderRadius="$pill">
<XStack alignItems="center" gap="$2">
{chip.icon ? <Text fontSize="$3">{chip.icon}</Text> : null}
<Text fontSize="$2" color={mutedText}>
{chip.label}
</Text>
<Text fontSize="$2" fontWeight="$7">
{chip.value}
</Text>
</XStack>
</SurfaceCard>
))}
</XStack>
>
<Button
unstyled
onPress={() => setFullScreenOpen(false)}
style={{ position: 'absolute', inset: 0 }}
aria-label={t('common.actions.close', 'Close')}
/>
<YStack
width="92vw"
height="82vh"
maxWidth={920}
maxHeight={720}
borderRadius="$bentoLg"
overflow="hidden"
borderWidth={1}
borderColor={bento.borderColor}
style={{ position: 'relative', boxShadow: bento.shadow }}
>
<YStack
position="absolute"
top={0}
left={0}
right={0}
bottom={0}
style={{
backgroundImage: `url(${data.photo.image_urls.full})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
filter: 'blur(26px)',
transform: 'scale(1.08)',
opacity: 0.6,
}}
/>
<YStack flex={1} alignItems="center" justifyContent="center" padding="$4" style={{ zIndex: 1 }}>
<img
src={data.photo.image_urls.full}
alt={t('galleryPage.photo.alt', { id: data.photo.id, suffix: '' }, `Foto ${data.photo.id}`)}
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }}
/>
</YStack>
<Button
size="$3"
circular
position="absolute"
top="$3"
right="$3"
backgroundColor={isDark ? 'rgba(15, 23, 42, 0.6)' : 'rgba(255, 255, 255, 0.85)'}
borderWidth={1}
borderColor={bento.borderColor}
onPress={() => setFullScreenOpen(false)}
aria-label={t('common.actions.close', 'Close')}
style={{ zIndex: 2 }}
>
<X size={16} color={isDark ? '#F8FAFF' : '#0F172A'} />
</Button>
</YStack>
</YStack>
) : null}
<Button
size="$4"
borderRadius="$pill"
backgroundColor="$primary"
onPress={() => window.open(data.photo.image_urls.full, '_blank')}
>
<Download size={18} color="white" />
<Text fontSize="$3" fontWeight="$7" color="white">
{t('galleryPublic.download', 'Download')}
</Text>
</Button>
</StandaloneShell>
);