From d3b6c6c029267b7af6e1d736098df08a28341d05 Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Sun, 28 Dec 2025 19:58:27 +0100 Subject: [PATCH] =?UTF-8?q?=20Onboarding=20guard/resume=20is=20now=20in=20?= =?UTF-8?q?place=20and=20respects=20=E2=80=9Cno=20package=E2=80=9D=20deep?= =?UTF-8?q?=20links=20to=20billing.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/mobile/lib/onboardingGuard.test.ts | 93 +++++++++++++++++++ .../js/admin/mobile/lib/onboardingGuard.ts | 44 +++++++++ resources/js/admin/router.tsx | 44 +++++++++ 3 files changed, 181 insertions(+) create mode 100644 resources/js/admin/mobile/lib/onboardingGuard.test.ts create mode 100644 resources/js/admin/mobile/lib/onboardingGuard.ts diff --git a/resources/js/admin/mobile/lib/onboardingGuard.test.ts b/resources/js/admin/mobile/lib/onboardingGuard.test.ts new file mode 100644 index 0000000..ea07121 --- /dev/null +++ b/resources/js/admin/mobile/lib/onboardingGuard.test.ts @@ -0,0 +1,93 @@ +import { describe, expect, it } from 'vitest'; +import { resolveOnboardingRedirect } from './onboardingGuard'; +import { + ADMIN_WELCOME_EVENT_PATH, + ADMIN_WELCOME_PACKAGES_PATH, + ADMIN_WELCOME_SUMMARY_PATH, +} from '../../constants'; + +describe('resolveOnboardingRedirect', () => { + it('returns null when events exist', () => { + const result = resolveOnboardingRedirect({ + hasEvents: true, + hasActivePackage: false, + selectedPackageId: null, + pathname: '/event-admin/mobile/dashboard', + isWelcomePath: false, + isBillingPath: false, + }); + expect(result).toBeNull(); + }); + + it('returns null for welcome paths', () => { + const result = resolveOnboardingRedirect({ + hasEvents: false, + hasActivePackage: false, + selectedPackageId: null, + pathname: ADMIN_WELCOME_PACKAGES_PATH, + isWelcomePath: true, + isBillingPath: false, + }); + expect(result).toBeNull(); + }); + + it('returns null for billing paths', () => { + const result = resolveOnboardingRedirect({ + hasEvents: false, + hasActivePackage: false, + selectedPackageId: null, + pathname: '/event-admin/mobile/billing', + isWelcomePath: false, + isBillingPath: true, + }); + expect(result).toBeNull(); + }); + + it('redirects to event setup when package active', () => { + const result = resolveOnboardingRedirect({ + hasEvents: false, + hasActivePackage: true, + selectedPackageId: null, + pathname: '/event-admin/mobile/dashboard', + isWelcomePath: false, + isBillingPath: false, + }); + expect(result).toBe(ADMIN_WELCOME_EVENT_PATH); + }); + + it('redirects to summary when selection exists', () => { + const result = resolveOnboardingRedirect({ + hasEvents: false, + hasActivePackage: false, + selectedPackageId: 5, + pathname: '/event-admin/mobile/dashboard', + isWelcomePath: false, + isBillingPath: false, + }); + expect(result).toBe(ADMIN_WELCOME_SUMMARY_PATH); + }); + + it('redirects to packages when no selection exists', () => { + const result = resolveOnboardingRedirect({ + hasEvents: false, + hasActivePackage: false, + selectedPackageId: null, + pathname: '/event-admin/mobile/dashboard', + isWelcomePath: false, + isBillingPath: false, + }); + expect(result).toBe(ADMIN_WELCOME_PACKAGES_PATH); + }); + + it('does not redirect when already on target', () => { + const result = resolveOnboardingRedirect({ + hasEvents: false, + hasActivePackage: false, + selectedPackageId: null, + pathname: ADMIN_WELCOME_PACKAGES_PATH, + isWelcomePath: false, + isBillingPath: false, + }); + expect(result).toBeNull(); + }); +}); diff --git a/resources/js/admin/mobile/lib/onboardingGuard.ts b/resources/js/admin/mobile/lib/onboardingGuard.ts new file mode 100644 index 0000000..3f4c440 --- /dev/null +++ b/resources/js/admin/mobile/lib/onboardingGuard.ts @@ -0,0 +1,44 @@ +import { + ADMIN_WELCOME_EVENT_PATH, + ADMIN_WELCOME_PACKAGES_PATH, + ADMIN_WELCOME_SUMMARY_PATH, +} from '../../constants'; + +type OnboardingRedirectInput = { + hasEvents: boolean; + hasActivePackage: boolean; + selectedPackageId?: number | null; + pathname: string; + isWelcomePath: boolean; + isBillingPath: boolean; +}; + +export function resolveOnboardingRedirect({ + hasEvents, + hasActivePackage, + selectedPackageId, + pathname, + isWelcomePath, + isBillingPath, +}: OnboardingRedirectInput): string | null { + if (hasEvents) { + return null; + } + + if (isWelcomePath || isBillingPath) { + return null; + } + + const shouldContinueSummary = Boolean(selectedPackageId && selectedPackageId > 0); + const target = hasActivePackage + ? ADMIN_WELCOME_EVENT_PATH + : shouldContinueSummary + ? ADMIN_WELCOME_SUMMARY_PATH + : ADMIN_WELCOME_PACKAGES_PATH; + + if (pathname === target) { + return null; + } + + return target; +} diff --git a/resources/js/admin/router.tsx b/resources/js/admin/router.tsx index f8ebf69..851773b 100644 --- a/resources/js/admin/router.tsx +++ b/resources/js/admin/router.tsx @@ -1,15 +1,22 @@ import React from 'react'; import { createBrowserRouter, Outlet, Navigate, useLocation, useParams } from 'react-router-dom'; +import { useQuery } from '@tanstack/react-query'; import RouteErrorElement from '@/components/RouteErrorElement'; import { useAuth } from './auth/context'; +import { useEventContext } from './context/EventContext'; import { ADMIN_BASE_PATH, + ADMIN_BILLING_PATH, ADMIN_DEFAULT_AFTER_LOGIN_PATH, ADMIN_EVENTS_PATH, ADMIN_LOGIN_PATH, ADMIN_LOGIN_START_PATH, ADMIN_PUBLIC_LANDING_PATH, + ADMIN_WELCOME_BASE_PATH, } from './constants'; +import { getTenantPackagesOverview } from './api'; +import { getSelectedPackageId } from './mobile/lib/onboardingSelection'; +import { resolveOnboardingRedirect } from './mobile/lib/onboardingGuard'; const AuthCallbackPage = React.lazy(() => import('./mobile/AuthCallbackPage')); const LoginStartPage = React.lazy(() => import('./mobile/LoginStartPage')); const LogoutPage = React.lazy(() => import('./mobile/LogoutPage')); @@ -42,6 +49,31 @@ const MobileWelcomeEventPage = React.lazy(() => import('./mobile/welcome/Welcome function RequireAuth() { const { status } = useAuth(); const location = useLocation(); + const { hasEvents, isLoading: eventsLoading } = useEventContext(); + const selectedPackageId = getSelectedPackageId(); + const isWelcomePath = location.pathname.startsWith(ADMIN_WELCOME_BASE_PATH); + const isBillingPath = location.pathname.startsWith(ADMIN_BILLING_PATH); + const shouldCheckPackages = + status === 'authenticated' && !eventsLoading && !hasEvents && !isWelcomePath && !isBillingPath; + + const { data: packagesData, isLoading: packagesLoading } = useQuery({ + queryKey: ['mobile', 'onboarding', 'packages-overview'], + queryFn: () => getTenantPackagesOverview({ force: true }), + enabled: shouldCheckPackages, + staleTime: 60_000, + }); + + const hasActivePackage = + Boolean(packagesData?.activePackage) || Boolean(packagesData?.packages?.some((pkg) => pkg.active)); + + const redirectTarget = resolveOnboardingRedirect({ + hasEvents, + hasActivePackage, + selectedPackageId, + pathname: location.pathname, + isWelcomePath, + isBillingPath, + }); if (status === 'loading') { return ( @@ -55,6 +87,18 @@ function RequireAuth() { return ; } + if (!isWelcomePath && !isBillingPath && (eventsLoading || packagesLoading)) { + return ( +
+ Bitte warten ... +
+ ); + } + + if (redirectTarget) { + return ; + } + return ( }>