photobooth funktionen im event admin verlinkt, gäste pwa zeigt photobooth nur noch an, wenn diese aktiviert ist. kontaktformular optimiert. teilen-link mit iMessage und whatsapp erweitert.

This commit is contained in:
Codex Agent
2025-11-23 22:22:06 +01:00
parent 3d9eaa1194
commit df414a31cd
32 changed files with 809 additions and 280 deletions

View File

@@ -5,15 +5,27 @@ import { useTranslation } from '../i18n/useTranslation';
export type GalleryFilter = 'latest' | 'popular' | 'mine' | 'photobooth';
const filterConfig: Array<{ value: GalleryFilter; labelKey: string; icon: React.ReactNode }> = [
type FilterConfig = Array<{ value: GalleryFilter; labelKey: string; icon: React.ReactNode }>;
const baseFilters: FilterConfig = [
{ value: 'latest', labelKey: 'galleryPage.filters.latest', icon: <Sparkles className="h-4 w-4" aria-hidden /> },
{ value: 'popular', labelKey: 'galleryPage.filters.popular', icon: <Flame className="h-4 w-4" aria-hidden /> },
{ value: 'mine', labelKey: 'galleryPage.filters.mine', icon: <UserRound className="h-4 w-4" aria-hidden /> },
{ value: 'photobooth', labelKey: 'galleryPage.filters.photobooth', icon: <Camera className="h-4 w-4" aria-hidden /> },
];
export default function FiltersBar({ value, onChange, className }: { value: GalleryFilter; onChange: (v: GalleryFilter) => void; className?: string }) {
export default function FiltersBar({
value,
onChange,
className,
showPhotobooth = true,
}: { value: GalleryFilter; onChange: (v: GalleryFilter) => void; className?: string; showPhotobooth?: boolean }) {
const { t } = useTranslation();
const filters: FilterConfig = React.useMemo(
() => (showPhotobooth
? [...baseFilters, { value: 'photobooth', labelKey: 'galleryPage.filters.photobooth', icon: <Camera className="h-4 w-4" aria-hidden /> }]
: baseFilters),
[showPhotobooth],
);
return (
<div
@@ -22,7 +34,7 @@ export default function FiltersBar({ value, onChange, className }: { value: Gall
className,
)}
>
{filterConfig.map((filter) => (
{filters.map((filter) => (
<button
key={filter.value}
type="button"

View File

@@ -28,10 +28,11 @@ export default function GalleryPreview({ token }: Props) {
const { locale } = useTranslation();
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 items = React.useMemo(() => {
const typed = photos as PreviewPhoto[];
let arr = typed.slice();
let arr = typedPhotos.slice();
// MyPhotos filter (requires session_id matching)
if (mode === 'mine') {
@@ -49,7 +50,13 @@ export default function GalleryPreview({ token }: Props) {
}
return arr.slice(0, 4); // 2x2 = 4 items
}, [photos, mode]);
}, [typedPhotos, mode]);
React.useEffect(() => {
if (mode === 'photobooth' && !hasPhotobooth) {
setMode('latest');
}
}, [mode, hasPhotobooth]);
// Helper function to generate photo title (must be before return)
function getPhotoTitle(photo: PreviewPhoto): string {
@@ -72,7 +79,7 @@ export default function GalleryPreview({ token }: Props) {
{ value: 'latest', label: 'Newest' },
{ value: 'popular', label: 'Popular' },
{ value: 'mine', label: 'My Photos' },
{ value: 'photobooth', label: 'Fotobox' },
...(hasPhotobooth ? [{ value: 'photobooth', label: 'Fotobox' } as const] : []),
];
return (