Route tenant admin PWA via /event-admin
This commit is contained in:
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from '../auth/context';
|
||||
import { isAuthError } from '../auth/tokens';
|
||||
import { ADMIN_HOME_PATH } from '../constants';
|
||||
|
||||
export default function AuthCallbackPage() {
|
||||
const { completeLogin } = useAuth();
|
||||
@@ -12,7 +13,7 @@ export default function AuthCallbackPage() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
completeLogin(params)
|
||||
.then((redirectTo) => {
|
||||
navigate(redirectTo ?? '/admin', { replace: true });
|
||||
navigate(redirectTo ?? ADMIN_HOME_PATH, { replace: true });
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('[Auth] Callback processing failed', err);
|
||||
@@ -33,3 +34,4 @@ export default function AuthCallbackPage() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
|
||||
import { AdminLayout } from '../components/AdminLayout';
|
||||
import { createInviteLink, getEvent, getEventStats, TenantEvent, EventStats as TenantEventStats, toggleEvent } from '../api';
|
||||
import { isAuthError } from '../auth/tokens';
|
||||
import { adminPath } from '../constants';
|
||||
|
||||
interface State {
|
||||
event: TenantEvent | null;
|
||||
@@ -105,7 +106,7 @@ export default function EventDetailPage() {
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => navigate('/admin/events')}
|
||||
onClick={() => navigate(adminPath('/events'))}
|
||||
className="border-pink-200 text-pink-600 hover:bg-pink-50"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" /> Zurueck zur Liste
|
||||
@@ -113,7 +114,7 @@ export default function EventDetailPage() {
|
||||
{event && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => navigate(`/admin/events/edit?slug=${encodeURIComponent(event.slug)}`)}
|
||||
onClick={() => navigate(adminPath(`/events/edit?slug=${encodeURIComponent(event.slug)}`))}
|
||||
className="border-fuchsia-200 text-fuchsia-700 hover:bg-fuchsia-50"
|
||||
>
|
||||
Bearbeiten
|
||||
@@ -176,7 +177,7 @@ export default function EventDetailPage() {
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => navigate(`/admin/events/photos?slug=${encodeURIComponent(event.slug)}`)}
|
||||
onClick={() => navigate(adminPath(`/events/photos?slug=${encodeURIComponent(event.slug)}`))}
|
||||
className="border-sky-200 text-sky-700 hover:bg-sky-50"
|
||||
>
|
||||
<Camera className="h-4 w-4" /> Fotos moderieren
|
||||
@@ -280,3 +281,4 @@ function renderName(name: TenantEvent['name']): string {
|
||||
}
|
||||
return 'Unbenanntes Event';
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Di
|
||||
import { AdminLayout } from '../components/AdminLayout';
|
||||
import { createEvent, getEvent, updateEvent, getPackages } from '../api';
|
||||
import { isAuthError } from '../auth/tokens';
|
||||
import { adminPath } from '../constants';
|
||||
|
||||
interface EventFormState {
|
||||
name: string;
|
||||
@@ -129,10 +130,10 @@ export default function EventFormPage() {
|
||||
const targetSlug = originalSlug ?? slugParam!;
|
||||
const updated = await updateEvent(targetSlug, payload);
|
||||
setOriginalSlug(updated.slug);
|
||||
navigate(`/admin/events/view?slug=${encodeURIComponent(updated.slug)}`);
|
||||
navigate(adminPath(`/events/view?slug=${encodeURIComponent(updated.slug)}`));
|
||||
} else {
|
||||
const { event: created } = await createEvent(payload);
|
||||
navigate(`/admin/events/view?slug=${encodeURIComponent(created.slug)}`);
|
||||
navigate(adminPath(`/events/view?slug=${encodeURIComponent(created.slug)}`));
|
||||
}
|
||||
} catch (err) {
|
||||
if (!isAuthError(err)) {
|
||||
@@ -146,7 +147,7 @@ export default function EventFormPage() {
|
||||
const actions = (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => navigate('/admin/events')}
|
||||
onClick={() => navigate(adminPath('/events'))}
|
||||
className="border-pink-200 text-pink-600 hover:bg-pink-50"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" /> Zurueck zur Liste
|
||||
@@ -213,38 +214,38 @@ export default function EventFormPage() {
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="package_id">Package</Label>
|
||||
<Select value={form.package_id.toString()} onValueChange={(value) => setForm((prev) => ({ ...prev, package_id: parseInt(value) }))}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Wählen Sie ein Package" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{packagesLoading ? (
|
||||
<SelectItem value="">Laden...</SelectItem>
|
||||
) : (
|
||||
packages?.map((pkg) => (
|
||||
<SelectItem key={pkg.id} value={pkg.id.toString()}>
|
||||
{pkg.name} - {pkg.price} € ({pkg.max_photos} Fotos)
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Waehlen Sie ein Package" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{packagesLoading ? (
|
||||
<SelectItem value="">Laden...</SelectItem>
|
||||
) : (
|
||||
packages?.map((pkg) => (
|
||||
<SelectItem key={pkg.id} value={pkg.id.toString()}>
|
||||
{pkg.name} - {pkg.price} EUR ({pkg.max_photos} Fotos)
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="sm">Package-Details</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Package auswählen</DialogTitle>
|
||||
<DialogDescription>Wählen Sie das Package für Ihr Event. Höhere Packages bieten mehr Limits und Features.</DialogDescription>
|
||||
<DialogTitle>Package auswaehlen</DialogTitle>
|
||||
<DialogDescription>Waehlen Sie das Package fuer Ihr Event. Hoehere Packages bieten mehr Limits und Features.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-2">
|
||||
{packages?.map((pkg) => (
|
||||
<div key={pkg.id} className="p-4 border rounded">
|
||||
<h3 className="font-semibold">{pkg.name}</h3>
|
||||
<p>{pkg.price} €</p>
|
||||
<p>{pkg.price} EUR</p>
|
||||
<ul className="text-sm">
|
||||
<li>Max Fotos: {pkg.max_photos}</li>
|
||||
<li>Max Gäste: {pkg.max_guests}</li>
|
||||
<li>Max Gaeste: {pkg.max_guests}</li>
|
||||
<li>Galerie: {pkg.gallery_days} Tage</li>
|
||||
<li>Features: {Object.keys(pkg.features).filter(k => pkg.features[k]).join(', ')}</li>
|
||||
</ul>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
|
||||
import { AdminLayout } from '../components/AdminLayout';
|
||||
import { deletePhoto, featurePhoto, getEventPhotos, TenantPhoto, unfeaturePhoto } from '../api';
|
||||
import { isAuthError } from '../auth/tokens';
|
||||
import { adminPath } from '../constants';
|
||||
|
||||
export default function EventPhotosPage() {
|
||||
const [searchParams] = useSearchParams();
|
||||
@@ -81,7 +82,7 @@ export default function EventPhotosPage() {
|
||||
<Card className="border-0 bg-white/85 shadow-xl shadow-pink-100/60">
|
||||
<CardContent className="p-6 text-sm text-slate-600">
|
||||
Kein Slug in der URL gefunden. Kehre zur Event-Liste zurueck und waehle dort ein Event aus.
|
||||
<Button className="mt-4" onClick={() => navigate('/admin/events')}>
|
||||
<Button className="mt-4" onClick={() => navigate(adminPath('/events'))}>
|
||||
Zurueck zur Liste
|
||||
</Button>
|
||||
</CardContent>
|
||||
@@ -93,7 +94,7 @@ export default function EventPhotosPage() {
|
||||
const actions = (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => navigate(`/admin/events/view-slug=${encodeURIComponent(slug)}`)}
|
||||
onClick={() => navigate(adminPath(`/events/view?slug=${encodeURIComponent(slug)}`))}
|
||||
className="border-pink-200 text-pink-600 hover:bg-pink-50"
|
||||
>
|
||||
Zurueck zum Event
|
||||
@@ -197,3 +198,4 @@ function EmptyGallery() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
|
||||
import { AdminLayout } from '../components/AdminLayout';
|
||||
import { getEvents, TenantEvent, getPackages } from '../api';
|
||||
import { isAuthError } from '../auth/tokens';
|
||||
import { adminPath, ADMIN_SETTINGS_PATH } from '../constants';
|
||||
|
||||
export default function EventsPage() {
|
||||
const [rows, setRows] = React.useState<TenantEvent[]>([]);
|
||||
@@ -41,11 +42,11 @@ export default function EventsPage() {
|
||||
<>
|
||||
<Button
|
||||
className="bg-gradient-to-r from-pink-500 via-fuchsia-500 to-purple-500 text-white shadow-lg shadow-pink-500/20"
|
||||
onClick={() => navigate('/admin/events/new')}
|
||||
onClick={() => navigate(adminPath('/events/new'))}
|
||||
>
|
||||
<Plus className="h-4 w-4" /> Neues Event
|
||||
</Button>
|
||||
<Link to="/admin/settings">
|
||||
<Link to={ADMIN_SETTINGS_PATH}>
|
||||
<Button variant="outline" className="border-pink-200 text-pink-600 hover:bg-pink-50">
|
||||
<Settings className="h-4 w-4" /> Einstellungen
|
||||
</Button>
|
||||
@@ -111,7 +112,7 @@ export default function EventsPage() {
|
||||
{loading ? (
|
||||
<LoadingState />
|
||||
) : rows.length === 0 ? (
|
||||
<EmptyState onCreate={() => navigate('/admin/events/new')} />
|
||||
<EmptyState onCreate={() => navigate(adminPath('/events/new'))} />
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{rows.map((event) => (
|
||||
@@ -163,15 +164,15 @@ function EventCard({ event }: { event: TenantEvent }) {
|
||||
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
<Button asChild variant="outline" className="border-pink-200 text-pink-700 hover:bg-pink-50">
|
||||
<Link to={`/admin/events/view?slug=${encodeURIComponent(slug)}`}>
|
||||
<Link to={adminPath(`/events/view?slug=${encodeURIComponent(slug)}`)}>
|
||||
Details <ArrowRight className="ml-1 h-3.5 w-3.5" />
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="border-fuchsia-200 text-fuchsia-700 hover:bg-fuchsia-50">
|
||||
<Link to={`/admin/events/edit?slug=${encodeURIComponent(slug)}`}>Bearbeiten</Link>
|
||||
<Link to={adminPath(`/events/edit?slug=${encodeURIComponent(slug)}`)}>Bearbeiten</Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="border-sky-200 text-sky-700 hover:bg-sky-50">
|
||||
<Link to={`/admin/events/photos?slug=${encodeURIComponent(slug)}`}>Fotos moderieren</Link>
|
||||
<Link to={adminPath(`/events/photos?slug=${encodeURIComponent(slug)}`)}>Fotos moderieren</Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="border-amber-200 text-amber-600 hover:bg-amber-50">
|
||||
<a href={`/e/${slug}`} target="_blank" rel="noreferrer">
|
||||
@@ -238,3 +239,4 @@ function renderName(name: TenantEvent['name']): string {
|
||||
}
|
||||
return 'Unbenanntes Event';
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Location, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import AppearanceToggleDropdown from '@/components/appearance-dropdown';
|
||||
import { useAuth } from '../auth/context';
|
||||
import { ADMIN_HOME_PATH } from '../constants';
|
||||
|
||||
interface LocationState {
|
||||
from?: Location;
|
||||
@@ -17,7 +18,7 @@ export default function LoginPage() {
|
||||
|
||||
React.useEffect(() => {
|
||||
if (status === 'authenticated') {
|
||||
navigate('/admin', { replace: true });
|
||||
navigate(ADMIN_HOME_PATH, { replace: true });
|
||||
}
|
||||
}, [status, navigate]);
|
||||
|
||||
@@ -29,7 +30,7 @@ export default function LoginPage() {
|
||||
const hash = from.hash ?? '';
|
||||
return `${from.pathname}${search}${hash}`;
|
||||
}
|
||||
return '/admin';
|
||||
return ADMIN_HOME_PATH;
|
||||
}, [location.state]);
|
||||
|
||||
return (
|
||||
@@ -41,7 +42,7 @@ export default function LoginPage() {
|
||||
<div className="space-y-4 text-sm text-muted-foreground">
|
||||
<p>
|
||||
Melde dich mit deinem Fotospiel-Account an. Du wirst zur sicheren OAuth-Anmeldung weitergeleitet und danach
|
||||
wieder zur Admin-Oberfl<EFBFBD>che gebracht.
|
||||
wieder zur Admin-Oberflaeche gebracht.
|
||||
</p>
|
||||
{oauthError && (
|
||||
<div className="rounded border border-red-300 bg-red-50 p-2 text-sm text-red-700">
|
||||
@@ -53,9 +54,10 @@ export default function LoginPage() {
|
||||
disabled={status === 'loading'}
|
||||
onClick={() => login(redirectTarget)}
|
||||
>
|
||||
{status === 'loading' ? 'Bitte warten <EFBFBD>' : 'Mit Tenant-Account anmelden'}
|
||||
{status === 'loading' ? 'Bitte warten ...' : 'Mit Tenant-Account anmelden'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,19 +8,20 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
|
||||
|
||||
import { AdminLayout } from '../components/AdminLayout';
|
||||
import { useAuth } from '../auth/context';
|
||||
import { ADMIN_EVENTS_PATH, ADMIN_LOGIN_PATH } from '../constants';
|
||||
|
||||
export default function SettingsPage() {
|
||||
const navigate = useNavigate();
|
||||
const { user, logout } = useAuth();
|
||||
|
||||
function handleLogout() {
|
||||
logout({ redirect: '/admin/login' });
|
||||
logout({ redirect: ADMIN_LOGIN_PATH });
|
||||
}
|
||||
|
||||
const actions = (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => navigate('/admin/events')}
|
||||
onClick={() => navigate(ADMIN_EVENTS_PATH)}
|
||||
className="border-pink-200 text-pink-600 hover:bg-pink-50"
|
||||
>
|
||||
Zurueck zur Uebersicht
|
||||
@@ -77,3 +78,4 @@ export default function SettingsPage() {
|
||||
</AdminLayout>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user