enhancements of the homepage in the guest pwa

This commit is contained in:
Codex Agent
2025-12-15 19:05:27 +01:00
parent 763af12617
commit f2473c6f6d
14 changed files with 568 additions and 207 deletions

View File

@@ -6,6 +6,7 @@ import { getDeviceId } from '../lib/device';
import { usePollGalleryDelta } from '../polling/usePollGalleryDelta';
import { Heart } from 'lucide-react';
import { useTranslation } from '../i18n/useTranslation';
import { useEventBranding } from '../context/EventBrandingContext';
type Props = { token: string };
@@ -27,10 +28,15 @@ type PreviewPhoto = {
export default function GalleryPreview({ token }: Props) {
const { locale } = useTranslation();
const { branding } = useEventBranding();
const { photos, loading } = usePollGalleryDelta(token, locale);
const [mode, setMode] = React.useState<PreviewFilter>('latest');
const typedPhotos = React.useMemo(() => photos as PreviewPhoto[], [photos]);
const hasPhotobooth = React.useMemo(() => typedPhotos.some((p) => p.ingest_source === 'photobooth'), [typedPhotos]);
const radius = branding.buttons?.radius ?? 12;
const linkColor = branding.buttons?.linkColor ?? branding.secondaryColor;
const headingFont = branding.typography?.heading ?? branding.fontFamily ?? undefined;
const bodyFont = branding.typography?.body ?? branding.fontFamily ?? undefined;
const items = React.useMemo(() => {
let arr = typedPhotos.slice();
@@ -84,64 +90,82 @@ export default function GalleryPreview({ token }: Props) {
];
return (
<section className="space-y-4">
<div className="flex items-center justify-between">
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">Live-Galerie</p>
<h3 className="text-lg font-semibold text-foreground">Alle Uploads auf einen Blick</h3>
<Card className="border border-muted/30 shadow-sm" style={{ borderRadius: radius, background: 'var(--guest-surface)', fontFamily: bodyFont }}>
<CardContent className="space-y-3 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground" style={headingFont ? { fontFamily: headingFont } : undefined}>Live-Galerie</p>
<h3 className="text-lg font-semibold text-foreground" style={headingFont ? { fontFamily: headingFont } : undefined}>Alle Uploads auf einen Blick</h3>
</div>
<Link
to={`/e/${encodeURIComponent(token)}/gallery?mode=${mode}`}
className="text-sm font-semibold transition"
style={{ color: linkColor }}
>
Alle ansehen
</Link>
</div>
<Link
to={`/e/${encodeURIComponent(token)}/gallery?mode=${mode}`}
className="text-sm font-semibold text-pink-600 hover:text-pink-700"
>
Alle ansehen
</Link>
</div>
<div className="flex gap-2 overflow-x-auto pb-1 text-sm font-medium [-ms-overflow-style:none] [scrollbar-width:none]">
<div className="flex gap-2 overflow-x-auto pb-1 text-sm font-medium [-ms-overflow-style:none] [scrollbar-width:none]">
{filters.map((filter) => (
<button
key={filter.value}
type="button"
onClick={() => setMode(filter.value)}
className={`rounded-full border px-4 py-1 transition ${
mode === filter.value
? 'border-pink-500 bg-pink-500 text-white shadow'
: 'border-transparent bg-white/70 text-muted-foreground hover:border-pink-200'
}`}
style={{
borderRadius: radius,
border: mode === filter.value ? `1px solid ${branding.primaryColor}` : `1px solid ${branding.primaryColor}22`,
background: mode === filter.value ? branding.primaryColor : 'var(--guest-surface)',
color: mode === filter.value ? '#ffffff' : 'var(--foreground)',
boxShadow: mode === filter.value ? `0 8px 18px ${branding.primaryColor}33` : 'none',
}}
className="px-4 py-1 transition"
>
{filter.label}
</button>
))}
</div>
{loading && <p className="text-sm text-muted-foreground">Lädt</p>}
{!loading && items.length === 0 && (
<Card>
<CardContent className="p-3 text-sm text-muted-foreground">
{loading && <p className="text-sm text-muted-foreground">Lädt</p>}
{!loading && items.length === 0 && (
<div className="flex items-center gap-3 rounded-xl border border-muted/30 bg-[var(--guest-surface)] p-3 text-sm text-muted-foreground">
<Heart className="h-4 w-4" style={{ color: branding.secondaryColor }} aria-hidden />
Noch keine Fotos. Starte mit deinem ersten Upload!
</CardContent>
</Card>
)}
</div>
)}
<div className="grid gap-3 sm:grid-cols-2">
<div className="grid gap-2 sm:grid-cols-2">
{items.map((p: PreviewPhoto) => (
<Link
key={p.id}
to={`/e/${encodeURIComponent(token)}/gallery?photoId=${p.id}`}
className="group relative block overflow-hidden rounded-3xl border border-white/30 bg-gray-900 text-white shadow-lg"
className="group relative block overflow-hidden text-foreground"
style={{
borderRadius: radius,
border: `1px solid ${branding.primaryColor}22`,
background: 'var(--guest-surface)',
boxShadow: `0 12px 26px ${branding.primaryColor}22`,
}}
>
<img
src={p.thumbnail_path || p.file_path}
alt={p.title || 'Foto'}
className="h-48 w-full object-cover transition duration-300 group-hover:scale-105"
className="h-40 w-full object-cover transition duration-300 group-hover:scale-105"
loading="lazy"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/10 to-transparent" aria-hidden />
<div className="absolute bottom-0 left-0 right-0 p-4">
<p className="text-sm font-semibold leading-tight line-clamp-2">{p.title || getPhotoTitle(p)}</p>
<div className="mt-2 flex items-center gap-1 text-xs text-white/80">
<Heart className="h-4 w-4 fill-current" aria-hidden />
<div
className="absolute inset-0"
style={{
background: `linear-gradient(180deg, transparent 50%, ${branding.primaryColor}33 100%)`,
}}
aria-hidden
/>
<div className="absolute bottom-0 left-0 right-0 space-y-1 p-3">
<p className="text-sm font-semibold leading-tight line-clamp-2" style={headingFont ? { fontFamily: headingFont } : undefined}>
{p.title || getPhotoTitle(p)}
</p>
<div className="flex items-center gap-1 text-xs text-foreground/80">
<Heart className="h-4 w-4" style={{ color: branding.primaryColor }} aria-hidden />
{p.likes_count ?? 0}
</div>
</div>
@@ -149,12 +173,13 @@ export default function GalleryPreview({ token }: Props) {
))}
</div>
<p className="text-center text-sm text-muted-foreground">
Lust auf mehr?{' '}
<Link to={`/e/${encodeURIComponent(token)}/gallery`} className="font-semibold text-pink-600 hover:text-pink-700">
Zur Galerie
</Link>
</p>
</section>
<p className="text-center text-sm text-muted-foreground">
Lust auf mehr?{' '}
<Link to={`/e/${encodeURIComponent(token)}/gallery`} className="font-semibold transition" style={{ color: linkColor }}>
Zur Galerie
</Link>
</p>
</CardContent>
</Card>
);
}