feat: Enhance Guest Frontend with new features and UI improvements

This commit is contained in:
2025-09-09 21:22:44 +02:00
parent e3423a7da5
commit 81958899a6
5 changed files with 141 additions and 23 deletions

View File

@@ -13,7 +13,8 @@ function TabLink({ to, children }: { to: string; children: React.ReactNode }) {
export default function BottomNav() {
const { slug } = useParams();
const base = `/e/${encodeURIComponent(slug ?? 'demo')}`;
if (!slug) return null; // Only show bottom nav within event context
const base = `/e/${encodeURIComponent(slug)}`;
return (
<div className="fixed inset-x-0 bottom-0 z-20 border-t bg-white/90 px-3 py-2 backdrop-blur dark:bg-black/40">
<div className="mx-auto flex max-w-md items-center justify-between">

View File

@@ -0,0 +1,63 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { usePollGalleryDelta } from '../polling/usePollGalleryDelta';
type Props = { slug: string };
export default function GalleryPreview({ slug }: Props) {
const { photos, loading } = usePollGalleryDelta(slug);
const [mode, setMode] = React.useState<'latest' | 'popular'>('latest');
const items = React.useMemo(() => {
const arr = photos.slice();
if (mode === 'popular') {
arr.sort((a: any, b: any) => (b.likes_count ?? 0) - (a.likes_count ?? 0));
} else {
arr.sort((a: any, b: any) => new Date(b.created_at ?? 0).getTime() - new Date(a.created_at ?? 0).getTime());
}
return arr.slice(0, 6);
}, [photos, mode]);
return (
<div>
<div className="mb-2 flex items-center gap-2">
<div className="inline-flex rounded-md border p-1 text-xs">
<button
onClick={() => setMode('latest')}
className={`px-2 py-1 ${mode === 'latest' ? 'rounded-sm bg-muted font-medium' : ''}`}
>Neueste</button>
<button
onClick={() => setMode('popular')}
className={`px-2 py-1 ${mode === 'popular' ? 'rounded-sm bg-muted font-medium' : ''}`}
>Beliebt</button>
</div>
<div className="grow" />
<Link to={`../gallery`}><Button variant="link" className="px-0">Alle ansehen </Button></Link>
</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">
Noch keine Fotos. Starte mit deinem ersten Upload!
</CardContent>
</Card>
)}
<div className="grid grid-cols-3 gap-2">
{items.map((p: any) => (
<Link key={p.id} to={`../photo/${p.id}`} state={{ photo: p }}>
<img
src={p.thumbnail_path || p.file_path}
alt="Foto"
className="aspect-square w-full rounded object-cover"
loading="lazy"
/>
</Link>
))}
</div>
</div>
);
}

View File

@@ -1,8 +1,10 @@
import React from 'react';
import { Button } from '@/components/ui/button';
import { Link } from 'react-router-dom';
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet';
import AppearanceToggleDropdown from '@/components/appearance-dropdown';
import { Settings } from 'lucide-react';
import { Settings, ChevronDown } from 'lucide-react';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
export default function Header({ title = '' }: { title?: string }) {
return (
@@ -30,25 +32,39 @@ function SettingsSheet() {
<SheetTitle>Einstellungen</SheetTitle>
</SheetHeader>
<div className="mt-4 space-y-4">
<div>
<div className="text-sm font-medium">Darstellung</div>
<div className="text-sm text-muted-foreground">Hell, Dunkel oder System</div>
<div className="mt-2">
<AppearanceToggleDropdown />
<Collapsible defaultOpen>
<div className="flex items-center justify-between">
<div className="text-sm font-medium">Cache</div>
<CollapsibleTrigger asChild>
<Button variant="ghost" size="sm" className="h-7 px-2">
<ChevronDown className="h-4 w-4" />
</Button>
</CollapsibleTrigger>
</div>
</div>
<div>
<div className="text-sm font-medium">Cache</div>
<ClearCacheButton />
</div>
<div>
<div className="text-sm font-medium">Rechtliches</div>
<ul className="mt-2 list-disc pl-5 text-sm">
<li><a href="/legal/imprint" className="underline">Impressum</a></li>
<li><a href="/legal/privacy" className="underline">Datenschutz</a></li>
<li><a href="/legal/terms" className="underline">AGB</a></li>
</ul>
</div>
<CollapsibleContent>
<div className="mt-2">
<ClearCacheButton />
</div>
</CollapsibleContent>
</Collapsible>
<Collapsible defaultOpen>
<div className="flex items-center justify-between">
<div className="text-sm font-medium">Rechtliches</div>
<CollapsibleTrigger asChild>
<Button variant="ghost" size="sm" className="h-7 px-2">
<ChevronDown className="h-4 w-4" />
</Button>
</CollapsibleTrigger>
</div>
<CollapsibleContent>
<ul className="mt-2 list-disc pl-5 text-sm">
<li><Link to="/legal/impressum" className="underline">Impressum</Link></li>
<li><Link to="/legal/datenschutz" className="underline">Datenschutz</Link></li>
<li><Link to="/legal/agb" className="underline">AGB</Link></li>
</ul>
</CollapsibleContent>
</Collapsible>
</div>
</SheetContent>
</Sheet>