diff --git a/resources/js/guest/components/BottomNav.tsx b/resources/js/guest/components/BottomNav.tsx index 40d3db4..919d2d3 100644 --- a/resources/js/guest/components/BottomNav.tsx +++ b/resources/js/guest/components/BottomNav.tsx @@ -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 (
diff --git a/resources/js/guest/components/GalleryPreview.tsx b/resources/js/guest/components/GalleryPreview.tsx new file mode 100644 index 0000000..a82d646 --- /dev/null +++ b/resources/js/guest/components/GalleryPreview.tsx @@ -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 ( +
+
+
+ + +
+
+ +
+ + {loading &&

Lädt…

} + {!loading && items.length === 0 && ( + + + Noch keine Fotos. Starte mit deinem ersten Upload! + + + )} +
+ {items.map((p: any) => ( + + Foto + + ))} +
+
+ ); +} + diff --git a/resources/js/guest/components/Header.tsx b/resources/js/guest/components/Header.tsx index c1d3ded..721b617 100644 --- a/resources/js/guest/components/Header.tsx +++ b/resources/js/guest/components/Header.tsx @@ -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() { Einstellungen
-
-
Darstellung
-
Hell, Dunkel oder System
-
- + +
+
Cache
+ + +
-
-
-
Cache
- -
-
-
Rechtliches
- -
+ +
+ +
+
+ + + +
+
Rechtliches
+ + + +
+ +
    +
  • Impressum
  • +
  • Datenschutz
  • +
  • AGB
  • +
+
+
diff --git a/resources/js/guest/pages/HomePage.tsx b/resources/js/guest/pages/HomePage.tsx index ae2c1d6..30365bf 100644 --- a/resources/js/guest/pages/HomePage.tsx +++ b/resources/js/guest/pages/HomePage.tsx @@ -4,6 +4,7 @@ import { useParams, Link } from 'react-router-dom'; import { usePollStats } from '../polling/usePollStats'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; +import GalleryPreview from '../components/GalleryPreview'; export default function HomePage() { const { slug } = useParams(); @@ -27,7 +28,7 @@ export default function HomePage() {
- Zur Galerie + ); } diff --git a/resources/js/guest/pages/LegalPage.tsx b/resources/js/guest/pages/LegalPage.tsx index 2c420b7..275f828 100644 --- a/resources/js/guest/pages/LegalPage.tsx +++ b/resources/js/guest/pages/LegalPage.tsx @@ -4,10 +4,47 @@ import { useParams } from 'react-router-dom'; export default function LegalPage() { const { page } = useParams(); + const [loading, setLoading] = React.useState(true); + const [title, setTitle] = React.useState(''); + const [body, setBody] = React.useState(''); + + React.useEffect(() => { + async function load() { + setLoading(true); + const res = await fetch(`/api/v1/legal/${encodeURIComponent(page || '')}?lang=de`, { headers: { 'Cache-Control': 'no-store' }}); + if (res.ok) { + const j = await res.json(); + setTitle(j.title || ''); + setBody(j.body_markdown || ''); + } + setLoading(false); + } + if (page) load(); + }, [page]); + return ( - -

Impressum / Datenschutz / AGB

+ + {loading ?

Lädt…

: }
); } +function Markdown({ md }: { md: string }) { + // Tiny, safe Markdown: paragraphs + basic bold/italic + links; no external dependency + const html = React.useMemo(() => { + let s = md + .replace(/&/g, '&') + .replace(//g, '>'); + // bold **text** + s = s.replace(/\*\*(.+?)\*\*/g, '$1'); + // italic *text* + s = s.replace(/(?$1'); + // links [text](url) + s = s.replace(/\[(.+?)\]\((https?:[^\s)]+)\)/g, '$1<\/a>'); + // paragraphs + s = s.split(/\n{2,}/).map(p => `

${p.replace(/\n/g, '
')}<\/p>`).join('\n'); + return s; + }, [md]); + return

; +}