Navigation now feels more “app‑like” with
stateful tabs and reliable back behavior, and a full onboarding flow is wired in with conditional package selection
(skips when an active package exists).
What changed
- Added per‑tab history + back navigation fallback to make tab switching/Back feel native (resources/js/admin/mobile/
lib/tabHistory.ts, resources/js/admin/mobile/hooks/useBackNavigation.ts, resources/js/admin/mobile/hooks/
useMobileNav.ts, resources/js/admin/mobile/components/MobileShell.tsx + updates across mobile pages).
- Implemented onboarding flow pages + shared shell, and wired new routes/prefetch (resources/js/admin/mobile/welcome/
WelcomeLandingPage.tsx, resources/js/admin/mobile/welcome/WelcomePackagesPage.tsx, resources/js/admin/mobile/
welcome/WelcomeSummaryPage.tsx, resources/js/admin/mobile/welcome/WelcomeEventPage.tsx, resources/js/admin/mobile/
components/OnboardingShell.tsx, resources/js/admin/router.tsx, resources/js/admin/mobile/prefetch.ts).
- Conditional package step: packages page redirects to event setup if activePackage exists; selection stored locally
for summary (resources/js/admin/mobile/lib/onboardingSelection.ts, resources/js/admin/mobile/welcome/
WelcomePackagesPage.tsx).
- Added a “Start welcome journey” CTA in the empty dashboard state (resources/js/admin/mobile/DashboardPage.tsx).
- Added translations for onboarding shell + selected package + dashboard CTA (resources/js/admin/i18n/locales/en/
onboarding.json, resources/js/admin/i18n/locales/de/onboarding.json, resources/js/admin/i18n/locales/en/
management.json, resources/js/admin/i18n/locales/de/management.json).
- Tests for new helpers/hooks (resources/js/admin/mobile/lib/tabHistory.test.ts, resources/js/admin/mobile/lib/
onboardingSelection.test.ts, resources/js/admin/mobile/hooks/useBackNavigation.test.tsx).
This commit is contained in:
47
resources/js/admin/mobile/hooks/useBackNavigation.test.tsx
Normal file
47
resources/js/admin/mobile/hooks/useBackNavigation.test.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { useBackNavigation } from './useBackNavigation';
|
||||
|
||||
const navigateMock = vi.fn();
|
||||
|
||||
vi.mock('react-router-dom', () => ({
|
||||
useNavigate: () => navigateMock,
|
||||
}));
|
||||
|
||||
function TestComponent({ fallback }: { fallback?: string }) {
|
||||
const back = useBackNavigation(fallback);
|
||||
return (
|
||||
<button type="button" onClick={back}>
|
||||
Back
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
describe('useBackNavigation', () => {
|
||||
beforeEach(() => {
|
||||
navigateMock.mockReset();
|
||||
});
|
||||
|
||||
it('navigates back when history is available', () => {
|
||||
Object.defineProperty(window.history, 'length', {
|
||||
configurable: true,
|
||||
get: () => 3,
|
||||
});
|
||||
|
||||
const { getByText } = render(<TestComponent />);
|
||||
fireEvent.click(getByText('Back'));
|
||||
expect(navigateMock).toHaveBeenCalledWith(-1);
|
||||
});
|
||||
|
||||
it('navigates to fallback when history is empty', () => {
|
||||
Object.defineProperty(window.history, 'length', {
|
||||
configurable: true,
|
||||
get: () => 1,
|
||||
});
|
||||
|
||||
const { getByText } = render(<TestComponent fallback="/event-admin/mobile/dashboard" />);
|
||||
fireEvent.click(getByText('Back'));
|
||||
expect(navigateMock).toHaveBeenCalledWith('/event-admin/mobile/dashboard', { replace: true });
|
||||
});
|
||||
});
|
||||
17
resources/js/admin/mobile/hooks/useBackNavigation.ts
Normal file
17
resources/js/admin/mobile/hooks/useBackNavigation.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ADMIN_HOME_PATH } from '../../constants';
|
||||
|
||||
export function useBackNavigation(fallback?: string) {
|
||||
const navigate = useNavigate();
|
||||
const fallbackTarget = fallback ?? ADMIN_HOME_PATH;
|
||||
|
||||
return React.useCallback(() => {
|
||||
if (typeof window !== 'undefined' && window.history.length > 1) {
|
||||
navigate(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(fallbackTarget, { replace: true });
|
||||
}, [fallbackTarget, navigate]);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { adminPath } from '../../constants';
|
||||
import { useEventContext } from '../../context/EventContext';
|
||||
import { NavKey } from '../components/BottomNav';
|
||||
import { resolveTabTarget } from '../lib/tabHistory';
|
||||
|
||||
export function useMobileNav(currentSlug?: string | null) {
|
||||
const navigate = useNavigate();
|
||||
@@ -11,29 +11,8 @@ export function useMobileNav(currentSlug?: string | null) {
|
||||
|
||||
const go = React.useCallback(
|
||||
(key: NavKey) => {
|
||||
if (key === 'tasks') {
|
||||
if (slug) {
|
||||
navigate(adminPath(`/mobile/events/${slug}/tasks`));
|
||||
} else {
|
||||
navigate(adminPath('/mobile/tasks'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (key === 'uploads') {
|
||||
if (slug) {
|
||||
navigate(adminPath(`/mobile/events/${slug}/photos`));
|
||||
} else {
|
||||
navigate(adminPath('/mobile/uploads'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (key === 'home') {
|
||||
navigate(adminPath('/mobile/dashboard'));
|
||||
return;
|
||||
}
|
||||
if (key === 'profile') {
|
||||
navigate(adminPath('/mobile/profile'));
|
||||
}
|
||||
const target = resolveTabTarget(key, slug);
|
||||
navigate(target);
|
||||
},
|
||||
[navigate, slug]
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user