added a help system, replaced the words "tenant" and "Pwa" with better alternatives. corrected and implemented cron jobs. prepared going live on a coolify-powered system.

This commit is contained in:
Codex Agent
2025-11-10 16:23:09 +01:00
parent ba9e64dfcb
commit 447a90a742
123 changed files with 6398 additions and 153 deletions

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
export type GalleryFilter = 'latest' | 'popular' | 'mine';
export type GalleryFilter = 'latest' | 'popular' | 'mine' | 'photobooth';
export default function FiltersBar({ value, onChange }: { value: GalleryFilter; onChange: (v: GalleryFilter) => void }) {
return (
@@ -10,8 +10,8 @@ export default function FiltersBar({ value, onChange }: { value: GalleryFilter;
<ToggleGroupItem value="latest">Neueste</ToggleGroupItem>
<ToggleGroupItem value="popular">Beliebt</ToggleGroupItem>
<ToggleGroupItem value="mine">Meine</ToggleGroupItem>
<ToggleGroupItem value="photobooth">Fotobox</ToggleGroupItem>
</ToggleGroup>
</div>
);
}

View File

@@ -6,17 +6,21 @@ import { usePollGalleryDelta } from '../polling/usePollGalleryDelta';
type Props = { token: string };
type PreviewFilter = 'latest' | 'popular' | 'mine' | 'photobooth';
export default function GalleryPreview({ token }: Props) {
const { photos, loading } = usePollGalleryDelta(token);
const [mode, setMode] = React.useState<'latest' | 'popular' | 'myphotos'>('latest');
const [mode, setMode] = React.useState<PreviewFilter>('latest');
const items = React.useMemo(() => {
let arr = photos.slice();
// MyPhotos filter (requires session_id matching)
if (mode === 'myphotos') {
if (mode === 'mine') {
const deviceId = getDeviceId();
arr = arr.filter((photo: any) => photo.session_id === deviceId);
} else if (mode === 'photobooth') {
arr = arr.filter((photo: any) => photo.ingest_source === 'photobooth');
}
// Sorting
@@ -71,15 +75,25 @@ export default function GalleryPreview({ token }: Props) {
Popular
</button>
<button
onClick={() => setMode('myphotos')}
onClick={() => setMode('mine')}
className={`px-4 py-2 text-sm font-medium rounded-full transition-all duration-200 ${
mode === 'myphotos'
mode === 'mine'
? 'bg-gradient-to-r from-pink-500 to-pink-600 text-white shadow-lg scale-105'
: 'text-gray-600 hover:bg-pink-50 hover:text-pink-700'
}`}
>
My Photos
</button>
<button
onClick={() => setMode('photobooth')}
className={`px-4 py-2 text-sm font-medium rounded-full transition-all duration-200 ${
mode === 'photobooth'
? 'bg-gradient-to-r from-pink-500 to-pink-600 text-white shadow-lg scale-105'
: 'text-gray-600 hover:bg-pink-50 hover:text-pink-700'
}`}
>
Fotobox
</button>
</div>
<Link to={`/e/${encodeURIComponent(token)}/gallery?mode=${mode}`} className="text-sm text-pink-600 hover:text-pink-700 font-medium">
Alle ansehen

View File

@@ -1,4 +1,5 @@
import React from "react";
import { Link, useParams } from 'react-router-dom';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import {
@@ -13,7 +14,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Settings, ArrowLeft, FileText, RefreshCcw, ChevronRight, UserCircle } from 'lucide-react';
import { Settings, ArrowLeft, FileText, RefreshCcw, ChevronRight, UserCircle, LifeBuoy } from 'lucide-react';
import { useOptionalGuestIdentity } from '../context/GuestIdentityContext';
import { LegalMarkdown } from './legal-markdown';
import { useLocale, type LocaleContextValue } from '../i18n/LocaleContext';
@@ -48,11 +49,13 @@ export function SettingsSheet() {
const identity = useOptionalGuestIdentity();
const localeContext = useLocale();
const { t } = useTranslation();
const params = useParams<{ token?: string }>();
const [nameDraft, setNameDraft] = React.useState(identity?.name ?? '');
const [nameStatus, setNameStatus] = React.useState<NameStatus>('idle');
const [savingName, setSavingName] = React.useState(false);
const isLegal = view.mode === 'legal';
const legalDocument = useLegalDocument(isLegal ? view.slug : null, localeContext.locale);
const helpHref = params?.token ? `/e/${encodeURIComponent(params.token)}/help` : '/help';
React.useEffect(() => {
if (open && identity?.hydrated) {
@@ -170,6 +173,7 @@ export function SettingsSheet() {
nameStatus={nameStatus}
localeContext={localeContext}
onOpenLegal={handleOpenLegal}
helpHref={helpHref}
/>
)}
</main>
@@ -241,6 +245,7 @@ interface HomeViewProps {
slug: (typeof legalPages)[number]['slug'],
translationKey: (typeof legalPages)[number]['translationKey'],
) => void;
helpHref: string;
}
function HomeView({
@@ -254,6 +259,7 @@ function HomeView({
nameStatus,
localeContext,
onOpenLegal,
helpHref,
}: HomeViewProps) {
const { t } = useTranslation();
const legalLinks = React.useMemo(
@@ -374,6 +380,23 @@ function HomeView({
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle>
<div className="flex items-center gap-2">
<LifeBuoy className="h-4 w-4 text-pink-500" />
{t('settings.help.title')}
</div>
</CardTitle>
<CardDescription>{t('settings.help.description')}</CardDescription>
</CardHeader>
<CardContent>
<Button asChild className="w-full">
<Link to={helpHref}>{t('settings.help.cta')}</Link>
</Button>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>{t('settings.cache.title')}</CardTitle>