neuer demo tenant switcher + demo tenants mit eigenem artisan command. Event Admin überarbeitet, aber das ist nur ein Zwischenstand.

This commit is contained in:
Codex Agent
2025-11-25 09:47:39 +01:00
parent 8947a37261
commit fd788ef770
22 changed files with 1096 additions and 299 deletions

View File

@@ -40,6 +40,7 @@ import {
} from '../api';
import { isAuthError } from '../auth/tokens';
import { useAuth } from '../auth/context';
import { useEventContext } from '../context/EventContext';
import {
adminPath,
ADMIN_HOME_PATH,
@@ -82,6 +83,7 @@ export default function DashboardPage() {
const navigate = useNavigate();
const location = useLocation();
const { user } = useAuth();
const { events: ctxEvents, activeEvent: ctxActiveEvent } = useEventContext();
const { progress, markStep } = useOnboardingProgress();
const { t, i18n } = useTranslation('dashboard', { keyPrefix: 'dashboard' });
const { t: tc } = useTranslation('common');
@@ -132,7 +134,7 @@ export default function DashboardPage() {
try {
const [summary, events, packages] = await Promise.all([
getDashboardSummary().catch(() => null),
getEvents().catch(() => [] as TenantEvent[]),
getEvents({ force: true }).catch(() => [] as TenantEvent[]),
getTenantPackagesOverview().catch(() => ({ packages: [], activePackage: null })),
]);
@@ -141,11 +143,12 @@ export default function DashboardPage() {
}
const fallbackSummary = buildSummaryFallback(events, packages.activePackage);
const primaryEvent = events[0] ?? null;
const eventPool = events.length ? events : ctxEvents;
const primaryEvent = ctxActiveEvent ?? eventPool[0] ?? null;
const primaryEventName = primaryEvent ? resolveEventName(primaryEvent.name, primaryEvent.slug) : null;
setReadiness({
hasEvent: events.length > 0,
hasEvent: eventPool.length > 0,
hasTasks: primaryEvent ? (Number(primaryEvent.tasks_count ?? 0) > 0) : false,
hasQrInvites: primaryEvent
? Number(
@@ -162,7 +165,7 @@ export default function DashboardPage() {
setState({
summary: summary ?? fallbackSummary,
events,
events: eventPool,
activePackage: packages.activePackage,
loading: false,
errorKey: null,
@@ -217,12 +220,21 @@ export default function DashboardPage() {
const subtitle = translate('welcome.subtitle');
const errorMessage = errorKey ? translate(`errors.${errorKey}`) : null;
const dateLocale = i18n.language?.startsWith('en') ? 'en-GB' : 'de-DE';
const canCreateEvent = React.useMemo(() => {
if (!activePackage) {
return true;
}
if (activePackage.remaining_events === null || activePackage.remaining_events === undefined) {
return true;
}
return activePackage.remaining_events > 0;
}, [activePackage]);
const upcomingEvents = getUpcomingEvents(events);
const publishedEvents = events.filter((event) => event.status === 'published');
const primaryEvent = events[0] ?? null;
const upcomingEvents = getUpcomingEvents(ctxEvents.length ? ctxEvents : events);
const publishedEvents = (ctxEvents.length ? ctxEvents : events).filter((event) => event.status === 'published');
const primaryEvent = ctxActiveEvent ?? (ctxEvents[0] ?? events[0] ?? null);
const primaryEventName = primaryEvent ? resolveEventName(primaryEvent.name, primaryEvent.slug) : null;
const singleEvent = events.length === 1 ? events[0] : null;
const singleEvent = ctxEvents.length === 1 ? ctxEvents[0] : (events.length === 1 ? events[0] : null);
const singleEventName = singleEvent ? resolveEventName(singleEvent.name, singleEvent.slug) : null;
const singleEventDateLabel = singleEvent?.event_date ? formatDate(singleEvent.event_date, dateLocale) : null;
const primaryEventLimits = primaryEvent?.limits ?? null;
@@ -468,7 +480,15 @@ export default function DashboardPage() {
label: translate('quickActions.createEvent.label'),
description: translate('quickActions.createEvent.description'),
icon: <Plus className="h-5 w-5" />,
onClick: () => navigate(ADMIN_EVENT_CREATE_PATH),
onClick: () => {
if (!canCreateEvent) {
toast.error(tc('errors.eventLimit', 'Dein aktuelles Paket enthält keine freien Event-Slots mehr.'));
navigate(ADMIN_BILLING_PATH);
return;
}
navigate(ADMIN_EVENT_CREATE_PATH);
},
disabled: !canCreateEvent,
},
{
key: 'photos',
@@ -559,7 +579,7 @@ export default function DashboardPage() {
);
return (
<AdminLayout title={adminTitle} subtitle={adminSubtitle} actions={layoutActions} tabs={dashboardTabs} currentTabKey={currentDashboardTab}>
<AdminLayout title={adminTitle} subtitle={adminSubtitle} tabs={dashboardTabs} currentTabKey={currentDashboardTab}>
{errorMessage && (
<Alert variant="destructive">
<AlertTitle>{t('dashboard.alerts.errorTitle')}</AlertTitle>