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