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 (
+
+
+
+ setMode('latest')}
+ className={`px-2 py-1 ${mode === 'latest' ? 'rounded-sm bg-muted font-medium' : ''}`}
+ >Neueste
+ setMode('popular')}
+ className={`px-2 py-1 ${mode === 'popular' ? 'rounded-sm bg-muted font-medium' : ''}`}
+ >Beliebt
+
+
+
Alle ansehen →
+
+
+ {loading &&
Lädt…
}
+ {!loading && items.length === 0 && (
+
+
+ Noch keine Fotos. Starte mit deinem ersten Upload!
+
+
+ )}
+
+ {items.map((p: any) => (
+
+
+
+ ))}
+
+
+ );
+}
+
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
-
-
-
+
+
+
+
+
+
+
+
+
+
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() {
Einfach ein Foto machen
-
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
;
+}