diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index fcc8f80..e078803 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -1,4 +1,3 @@
-{"id":"--stealth-d39","title":"Superadmin control surface spec and access matrix","description":"Define the minimal superadmin control surface, permissions, and mapping to tenant/guest responsibilities. Document scope and non-goals.","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-01-01T14:16:06.994379577+01:00","updated_at":"2026-01-01T17:23:28.230936323+01:00","close_reason":"Duplicate of fotospiel-app-ihd after beads re-init","deleted_at":"2026-01-01T17:23:28.230936323+01:00","deleted_by":"soeren","delete_reason":"Remove stray stealth issue id","original_type":"task"}
{"id":"fotospiel-app-097","title":"Tenant announcements / release notes","description":"Broadcast announcements to tenants/admins with targeting and scheduling.","status":"closed","priority":3,"issue_type":"feature","created_at":"2026-01-01T14:20:21.68206312+01:00","updated_at":"2026-01-02T14:18:31.676816348+01:00","closed_at":"2026-01-02T14:18:31.676816348+01:00","close_reason":"Closed"}
{"id":"fotospiel-app-0h0","title":"SEC-BILL-02 Signature freshness + retry policies for Paddle webhooks","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-01T15:53:37.618780852+01:00","created_by":"soeren","updated_at":"2026-01-01T15:53:37.618780852+01:00"}
{"id":"fotospiel-app-0rb","title":"Tenant admin onboarding: inline checkout integration in welcome flow","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T16:08:22.434997456+01:00","created_by":"soeren","updated_at":"2026-01-01T16:08:28.026795975+01:00","closed_at":"2026-01-01T16:08:28.026795975+01:00","close_reason":"Completed in codebase (verified)"}
@@ -17,6 +16,7 @@
{"id":"fotospiel-app-38f","title":"Paddle catalog sync: surface last sync error/log context in admin","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T15:59:14.865414785+01:00","created_by":"soeren","updated_at":"2026-01-02T21:16:09.109922491+01:00","closed_at":"2026-01-02T21:16:09.109922491+01:00","close_reason":"Completed"}
{"id":"fotospiel-app-3ut","title":"SEC-API-03 Synthetic monitoring + alert config","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-01T15:52:46.793875724+01:00","created_by":"soeren","updated_at":"2026-01-01T15:52:46.793875724+01:00"}
{"id":"fotospiel-app-3xa","title":"Security review: event admin code audit (policies, PKCE, file handling)","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-01T16:05:20.115675149+01:00","created_by":"soeren","updated_at":"2026-01-01T16:05:20.115675149+01:00"}
+{"id":"fotospiel-app-43mp","title":"Help-System für Event Admin PWA planen","status":"open","priority":2,"issue_type":"task","owner":"codex-agent@example.com","created_at":"2026-01-23T08:21:47.812129626+01:00","created_by":"Codex Agent","updated_at":"2026-01-23T08:21:47.812129626+01:00"}
{"id":"fotospiel-app-4ar","title":"SEC-BILL-03 Failed capture notifications + ledger hook","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-01T15:54:33.266516715+01:00","created_by":"soeren","updated_at":"2026-01-01T15:54:33.266516715+01:00"}
{"id":"fotospiel-app-4en","title":"Add translations for Mobile Package Shop","description":"The new MobilePackageShopPage.tsx uses translation keys like 'shop.title', 'shop.legal.agb', etc. Ensure these are added to the management.json files for de and en.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T18:05:50.469751088+01:00","created_by":"soeren","updated_at":"2026-01-06T18:14:19.984343737+01:00","closed_at":"2026-01-06T18:14:19.984346372+01:00"}
{"id":"fotospiel-app-4i4","title":"Security review: map roles/data","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T16:02:58.370301875+01:00","created_by":"soeren","updated_at":"2026-01-01T16:03:03.997327414+01:00","closed_at":"2026-01-01T16:03:03.997327414+01:00","close_reason":"Completed in codebase (verified)"}
diff --git a/.beads/last-touched b/.beads/last-touched
index fa12790..9ce75b2 100644
--- a/.beads/last-touched
+++ b/.beads/last-touched
@@ -1 +1 @@
-fotospiel-app-5veo
+fotospiel-app-43mp
diff --git a/resources/js/admin/mobile/HelpArticlePage.tsx b/resources/js/admin/mobile/HelpArticlePage.tsx
new file mode 100644
index 0000000..7a94221
--- /dev/null
+++ b/resources/js/admin/mobile/HelpArticlePage.tsx
@@ -0,0 +1,114 @@
+import React from 'react';
+import { useNavigate, useParams } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
+import { useQuery } from '@tanstack/react-query';
+import { ChevronRight } from 'lucide-react';
+import { YStack } from '@tamagui/stacks';
+import { SizableText as Text } from '@tamagui/text';
+import { YGroup } from '@tamagui/group';
+import { ListItem } from '@tamagui/list-item';
+
+import { MobileShell } from './components/MobileShell';
+import { MobileCard, CTAButton, SkeletonCard } from './components/Primitives';
+import { useAdminTheme } from './theme';
+import { useBackNavigation } from './hooks/useBackNavigation';
+import { adminPath, ADMIN_FAQ_PATH } from '../constants';
+import { fetchHelpCenterArticle, type HelpCenterArticle } from '../api';
+
+export default function MobileHelpArticlePage() {
+ const { slug } = useParams<{ slug: string }>();
+ const { t, i18n } = useTranslation(['management', 'dashboard']);
+ const theme = useAdminTheme();
+ const back = useBackNavigation(ADMIN_FAQ_PATH);
+ const navigate = useNavigate();
+ const locale = i18n.language;
+
+ const { data, isLoading, isError, refetch } = useQuery({
+ queryKey: ['mobile', 'help-article', slug, locale],
+ enabled: Boolean(slug),
+ queryFn: () => fetchHelpCenterArticle(slug ?? '', locale),
+ });
+
+ const article: HelpCenterArticle | null = data ?? null;
+
+ return (
+
+ {isLoading ? (
+
+
+
+
+ ) : null}
+
+ {isError ? (
+
+
+
+ {t('dashboard:help.error', 'Help could not be loaded.')}
+
+ refetch()}
+ fullWidth={false}
+ />
+
+
+ ) : null}
+
+ {!isLoading && article ? (
+
+
+
+
+ {article.title}
+
+ {article.updated_at ? (
+
+ {t('help.article.updated', 'Aktualisiert')}:{' '}
+ {new Date(article.updated_at).toLocaleDateString(locale, {
+ day: '2-digit',
+ month: 'short',
+ year: 'numeric',
+ })}
+
+ ) : null}
+
+
+
+
+ {article.related && article.related.length > 0 ? (
+
+
+
+ {t('help.article.relatedTitle', 'Weitere Artikel')}
+
+
+ {article.related.map((rel) => (
+
+ navigate(adminPath(`/mobile/help/${encodeURIComponent(rel.slug)}`))}
+ title={
+
+ {rel.title ?? rel.slug}
+
+ }
+ iconAfter={}
+ />
+
+ ))}
+
+
+
+ ) : null}
+
+ ) : null}
+
+ );
+}
diff --git a/resources/js/admin/mobile/HelpCenterPage.tsx b/resources/js/admin/mobile/HelpCenterPage.tsx
new file mode 100644
index 0000000..828408e
--- /dev/null
+++ b/resources/js/admin/mobile/HelpCenterPage.tsx
@@ -0,0 +1,158 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
+import { useQuery } from '@tanstack/react-query';
+import { ChevronRight, HelpCircle } from 'lucide-react';
+import { YStack, XStack } from '@tamagui/stacks';
+import { SizableText as Text } from '@tamagui/text';
+import { YGroup } from '@tamagui/group';
+import { ListItem } from '@tamagui/list-item';
+
+import { MobileShell } from './components/MobileShell';
+import { MobileCard, CTAButton, SkeletonCard } from './components/Primitives';
+import { useAdminTheme } from './theme';
+import { useBackNavigation } from './hooks/useBackNavigation';
+import { adminPath, ADMIN_PROFILE_PATH } from '../constants';
+import { fetchHelpCenterArticles, type HelpCenterArticleSummary } from '../api';
+
+const FAQ_SLUGS = new Set(['admin-issue-resolution']);
+
+function isFaqArticle(article: HelpCenterArticleSummary): boolean {
+ const title = article.title?.toLowerCase() ?? '';
+ return FAQ_SLUGS.has(article.slug) || article.slug.startsWith('faq-') || title.includes('faq');
+}
+
+export default function MobileHelpCenterPage() {
+ const navigate = useNavigate();
+ const { t, i18n } = useTranslation(['management', 'dashboard']);
+ const theme = useAdminTheme();
+ const back = useBackNavigation(ADMIN_PROFILE_PATH);
+ const locale = i18n.language;
+
+ const { data, isLoading, isError, refetch } = useQuery({
+ queryKey: ['mobile', 'help-center', locale],
+ queryFn: () => fetchHelpCenterArticles(locale),
+ });
+
+ const articles = Array.isArray(data) ? data : [];
+ const faqArticles = articles.filter(isFaqArticle);
+ const guideArticles = articles.filter((article) => !isFaqArticle(article));
+
+ return (
+
+ {isLoading ? (
+
+
+
+
+ ) : null}
+
+ {isError ? (
+
+
+
+ {t('dashboard:help.error', 'Help could not be loaded.')}
+
+ refetch()}
+ fullWidth={false}
+ />
+
+
+ ) : null}
+
+ {!isLoading && !isError ? (
+
+ navigate(adminPath(`/mobile/help/${encodeURIComponent(slug)}`))}
+ />
+ navigate(adminPath(`/mobile/help/${encodeURIComponent(slug)}`))}
+ />
+
+ ) : null}
+
+ );
+}
+
+function HelpSection({
+ title,
+ items,
+ emptyLabel,
+ icon: IconCmp,
+ onSelect,
+}: {
+ title: string;
+ items: HelpCenterArticleSummary[];
+ emptyLabel: string;
+ icon?: React.ComponentType<{ size?: number; color?: string }>;
+ onSelect: (slug: string) => void;
+}) {
+ const theme = useAdminTheme();
+
+ return (
+
+
+
+ {IconCmp ? (
+
+
+
+ ) : null}
+
+ {title}
+
+
+ {items.length === 0 ? (
+
+ {emptyLabel}
+
+ ) : (
+
+ {items.map((item) => (
+
+ onSelect(item.slug)}
+ title={
+
+ {item.title}
+
+ }
+ subTitle={
+ item.summary ? (
+
+ {item.summary}
+
+ ) : undefined
+ }
+ iconAfter={}
+ />
+
+ ))}
+
+ )}
+
+
+ );
+}
diff --git a/resources/js/admin/mobile/ProfilePage.tsx b/resources/js/admin/mobile/ProfilePage.tsx
index 8d3b775..82c14f0 100644
--- a/resources/js/admin/mobile/ProfilePage.tsx
+++ b/resources/js/admin/mobile/ProfilePage.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
-import { User, Settings, Globe, Moon, Download, LogOut } from 'lucide-react';
+import { User, Settings, Globe, Moon, Download, LogOut, HelpCircle } from 'lucide-react';
import { Avatar } from '@tamagui/avatar';
import { Card } from '@tamagui/card';
import { YStack, XStack } from '@tamagui/stacks';
@@ -13,7 +13,7 @@ import { CTAButton } from './components/Primitives';
import { MobileSelect } from './components/FormControls';
import { useAuth } from '../auth/context';
import { fetchTenantProfile } from '../api';
-import { adminPath, ADMIN_DATA_EXPORTS_PATH, ADMIN_PROFILE_ACCOUNT_PATH } from '../constants';
+import { adminPath, ADMIN_DATA_EXPORTS_PATH, ADMIN_PROFILE_ACCOUNT_PATH, ADMIN_FAQ_PATH } from '../constants';
import i18n from '../i18n';
import { useAppearance } from '@/hooks/use-appearance';
import { useBackNavigation } from './hooks/useBackNavigation';
@@ -180,6 +180,21 @@ export default function MobileProfilePage() {
>
) : null}
+
+
+ {t('common.help', 'Help')}
+
+ }
+ iconAfter={}
+ onPress={() => navigate(ADMIN_FAQ_PATH)}
+ />
+
diff --git a/resources/js/admin/mobile/__tests__/HelpArticlePage.test.tsx b/resources/js/admin/mobile/__tests__/HelpArticlePage.test.tsx
new file mode 100644
index 0000000..81044c6
--- /dev/null
+++ b/resources/js/admin/mobile/__tests__/HelpArticlePage.test.tsx
@@ -0,0 +1,86 @@
+import React from 'react';
+import { describe, expect, it, vi } from 'vitest';
+import { render, screen } from '@testing-library/react';
+
+const article = {
+ slug: 'tenant-dashboard-overview',
+ title: 'Dashboard overview',
+ summary: 'Learn the dashboard.',
+ body_html: '
Welcome
',
+ related: [],
+};
+
+vi.mock('@tanstack/react-query', () => ({
+ useQuery: () => ({
+ data: article,
+ isLoading: false,
+ isError: false,
+ refetch: vi.fn(),
+ }),
+}));
+
+vi.mock('react-router-dom', () => ({
+ useParams: () => ({ slug: 'tenant-dashboard-overview' }),
+ useNavigate: () => vi.fn(),
+}));
+
+vi.mock('react-i18next', () => ({
+ useTranslation: () => ({
+ t: (_key: string, fallback?: string) => fallback ?? _key,
+ i18n: { language: 'de' },
+ }),
+ initReactI18next: {
+ type: '3rdParty',
+ init: () => undefined,
+ },
+}));
+
+vi.mock('../components/MobileShell', () => ({
+ MobileShell: ({ children }: { children: React.ReactNode }) => {children}
,
+}));
+
+vi.mock('../hooks/useBackNavigation', () => ({
+ useBackNavigation: () => vi.fn(),
+}));
+
+vi.mock('../theme', () => ({
+ useAdminTheme: () => ({
+ textStrong: '#111827',
+ muted: '#6b7280',
+ border: '#e5e7eb',
+ }),
+}));
+
+vi.mock('@tamagui/stacks', () => ({
+ YStack: ({ children }: { children: React.ReactNode }) => {children}
,
+}));
+
+vi.mock('@tamagui/text', () => ({
+ SizableText: ({ children }: { children: React.ReactNode }) => {children},
+}));
+
+vi.mock('@tamagui/group', () => ({
+ YGroup: Object.assign(({ children }: { children: React.ReactNode }) => {children}
, {
+ Item: ({ children }: { children: React.ReactNode }) => {children}
,
+ }),
+}));
+
+vi.mock('@tamagui/list-item', () => ({
+ ListItem: ({ title }: { title?: React.ReactNode }) => {title}
,
+}));
+
+vi.mock('../components/Primitives', () => ({
+ MobileCard: ({ children }: { children: React.ReactNode }) => {children}
,
+ CTAButton: ({ label }: { label: string }) => ,
+ SkeletonCard: () => Loading...
,
+}));
+
+import MobileHelpArticlePage from '../HelpArticlePage';
+
+describe('MobileHelpArticlePage', () => {
+ it('renders article title', async () => {
+ render();
+
+ expect(screen.getByText('Dashboard overview')).toBeInTheDocument();
+ });
+});
diff --git a/resources/js/admin/mobile/__tests__/HelpCenterPage.test.tsx b/resources/js/admin/mobile/__tests__/HelpCenterPage.test.tsx
new file mode 100644
index 0000000..8f5aa13
--- /dev/null
+++ b/resources/js/admin/mobile/__tests__/HelpCenterPage.test.tsx
@@ -0,0 +1,95 @@
+import React from 'react';
+import { describe, expect, it, vi } from 'vitest';
+import { render, screen } from '@testing-library/react';
+
+const articles = [
+ { slug: 'admin-issue-resolution', title: 'FAQ & Troubleshooting', summary: 'Common issues.' },
+ { slug: 'tenant-dashboard-overview', title: 'Dashboard overview', summary: 'Learn the dashboard.' },
+];
+
+vi.mock('@tanstack/react-query', () => ({
+ useQuery: () => ({
+ data: articles,
+ isLoading: false,
+ isError: false,
+ refetch: vi.fn(),
+ }),
+}));
+
+vi.mock('react-router-dom', () => ({
+ useNavigate: () => vi.fn(),
+}));
+
+vi.mock('react-i18next', () => ({
+ useTranslation: () => ({
+ t: (_key: string, fallback?: string) => fallback ?? _key,
+ i18n: { language: 'de' },
+ }),
+ initReactI18next: {
+ type: '3rdParty',
+ init: () => undefined,
+ },
+}));
+
+vi.mock('../components/MobileShell', () => ({
+ MobileShell: ({ children }: { children: React.ReactNode }) => {children}
,
+}));
+
+vi.mock('../hooks/useBackNavigation', () => ({
+ useBackNavigation: () => vi.fn(),
+}));
+
+vi.mock('../theme', () => ({
+ useAdminTheme: () => ({
+ textStrong: '#111827',
+ muted: '#6b7280',
+ border: '#e5e7eb',
+ surfaceMuted: '#f3f4f6',
+ shadow: 'rgba(0,0,0,0.12)',
+ }),
+}));
+
+vi.mock('@tamagui/stacks', () => ({
+ YStack: ({ children }: { children: React.ReactNode }) => {children}
,
+ XStack: ({ children }: { children: React.ReactNode }) => {children}
,
+}));
+
+vi.mock('@tamagui/text', () => ({
+ SizableText: ({ children }: { children: React.ReactNode }) => {children},
+}));
+
+vi.mock('@tamagui/group', () => ({
+ YGroup: Object.assign(({ children }: { children: React.ReactNode }) => {children}
, {
+ Item: ({ children }: { children: React.ReactNode }) => {children}
,
+ }),
+}));
+
+vi.mock('@tamagui/list-item', () => ({
+ ListItem: ({ title, subTitle }: { title?: React.ReactNode; subTitle?: React.ReactNode }) => (
+
+ {title}
+ {subTitle}
+
+ ),
+}));
+
+vi.mock('tamagui', () => ({
+ Separator: ({ children }: { children?: React.ReactNode }) => {children}
,
+}));
+
+vi.mock('../components/Primitives', () => ({
+ MobileCard: ({ children }: { children: React.ReactNode }) => {children}
,
+ CTAButton: ({ label }: { label: string }) => ,
+ SkeletonCard: () => Loading...
,
+}));
+
+import MobileHelpCenterPage from '../HelpCenterPage';
+
+describe('MobileHelpCenterPage', () => {
+ it('renders FAQ and guide articles', async () => {
+ render();
+
+ expect(screen.getByText('FAQ & Troubleshooting')).toBeInTheDocument();
+ expect(screen.getByText('Dashboard overview')).toBeInTheDocument();
+ });
+});
diff --git a/resources/js/admin/mobile/components/MobileShell.tsx b/resources/js/admin/mobile/components/MobileShell.tsx
index bdac2bb..5b2c6f6 100644
--- a/resources/js/admin/mobile/components/MobileShell.tsx
+++ b/resources/js/admin/mobile/components/MobileShell.tsx
@@ -21,6 +21,7 @@ import { countQueuedPhotoActions } from '../lib/queueStatus';
import { useAdminTheme } from '../theme';
import { useAuth } from '../../auth/context';
import { EventSwitcherSheet } from './EventSwitcherSheet';
+import { UserMenuSheet } from './UserMenuSheet';
type MobileShellProps = {
title?: string;
@@ -55,6 +56,7 @@ export function MobileShell({ title, children, activeTab, onBack, headerActions
const [attemptedFetch, setAttemptedFetch] = React.useState(false);
const [queuedPhotoCount, setQueuedPhotoCount] = React.useState(0);
const [switcherOpen, setSwitcherOpen] = React.useState(false);
+ const [userMenuOpen, setUserMenuOpen] = React.useState(false);
const effectiveEvents = events.length ? events : fallbackEvents;
const effectiveActive = activeEvent ?? (effectiveEvents.length === 1 ? effectiveEvents[0] : null);
@@ -267,7 +269,7 @@ export function MobileShell({ title, children, activeTab, onBack, headerActions
{/* User Avatar */}
- navigate(adminPath('/mobile/profile'))}>
+ setUserMenuOpen(true)} aria-label={t('mobileProfile.title', 'Profile')}>
+ setUserMenuOpen(false)}
+ user={user}
+ isMember={isMember}
+ navigate={navigate}
+ />
);
}
diff --git a/resources/js/admin/mobile/components/UserMenuSheet.tsx b/resources/js/admin/mobile/components/UserMenuSheet.tsx
new file mode 100644
index 0000000..b184a17
--- /dev/null
+++ b/resources/js/admin/mobile/components/UserMenuSheet.tsx
@@ -0,0 +1,266 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { ChevronRight, CreditCard, FileText, HelpCircle, User, X } from 'lucide-react';
+import { XStack, YStack } from '@tamagui/stacks';
+import { SizableText as Text } from '@tamagui/text';
+import { Pressable } from '@tamagui/react-native-web-lite';
+import { ListItem } from '@tamagui/list-item';
+import { YGroup } from '@tamagui/group';
+import { Switch } from '@tamagui/switch';
+import { Separator } from 'tamagui';
+
+import { useAppearance } from '@/hooks/use-appearance';
+import { ADMIN_BILLING_PATH, ADMIN_DATA_EXPORTS_PATH, ADMIN_FAQ_PATH, ADMIN_PROFILE_ACCOUNT_PATH, adminPath } from '../../constants';
+import { useAdminTheme } from '../theme';
+import { MobileSelect } from './FormControls';
+
+type UserMenuSheetProps = {
+ open: boolean;
+ onClose: () => void;
+ user?: { name?: string | null; email?: string | null; avatar_url?: string | null };
+ isMember: boolean;
+ navigate: (path: string) => void;
+};
+
+const MENU_WIDTH = 320;
+
+export function UserMenuSheet({ open, onClose, user, isMember, navigate }: UserMenuSheetProps) {
+ const { t, i18n } = useTranslation('management');
+ const theme = useAdminTheme();
+ const { appearance, resolved, updateAppearance } = useAppearance();
+ const [language, setLanguage] = React.useState(() => (i18n.language?.startsWith('en') ? 'en' : 'de'));
+
+ React.useEffect(() => {
+ setLanguage(i18n.language?.startsWith('en') ? 'en' : 'de');
+ }, [i18n.language]);
+
+ const isDark = resolved === 'dark';
+
+ const handleNavigate = (path: string) => {
+ onClose();
+ navigate(path);
+ };
+
+ const menuItems = [
+ {
+ label: t('mobileProfile.account', 'Account bearbeiten'),
+ icon: User,
+ path: ADMIN_PROFILE_ACCOUNT_PATH,
+ show: true,
+ },
+ {
+ label: t('billing.sections.packages.title', 'Pakete'),
+ icon: CreditCard,
+ path: adminPath('/mobile/billing#packages'),
+ show: !isMember,
+ },
+ {
+ label: t('billing.sections.invoices.title', 'Rechnungen & Zahlungen'),
+ icon: CreditCard,
+ path: adminPath('/mobile/billing#invoices'),
+ show: !isMember,
+ },
+ {
+ label: t('dataExports.title', 'Datenexporte'),
+ icon: FileText,
+ path: ADMIN_DATA_EXPORTS_PATH,
+ show: !isMember,
+ },
+ {
+ label: t('common.help', 'Help'),
+ icon: HelpCircle,
+ path: ADMIN_FAQ_PATH,
+ show: true,
+ },
+ ].filter((item) => item.show);
+
+ return (
+
+
+
+
+
+
+ {t('mobileProfile.title', 'Profil')}
+
+
+
+
+
+
+
+
+
+
+
+ {user?.name?.charAt(0)?.toUpperCase() ?? 'U'}
+
+
+
+
+ {user?.name ?? t('events.members.roles.guest', 'Guest')}
+
+
+ {user?.email ?? ''}
+
+
+
+
+
+
+
+
+ {t('mobileProfile.settings', 'Einstellungen')}
+
+
+ {menuItems.map((item) => (
+
+ handleNavigate(item.path)}
+ title={
+
+
+
+
+
+ {item.label}
+
+
+ }
+ iconAfter={}
+ />
+
+ ))}
+
+
+
+
+
+ {t('settings.appearance.title', 'Darstellung')}
+
+
+
+
+
+ {t('mobileProfile.language', 'Sprache')}
+
+
+ }
+ iconAfter={
+ {
+ const lng = event.target.value;
+ setLanguage(lng);
+ void i18n.changeLanguage(lng);
+ }}
+ compact
+ style={{ minWidth: 120 }}
+ >
+
+
+
+ }
+ />
+
+
+
+
+ {t('mobileProfile.theme', 'Dark Mode')}
+
+
+ }
+ iconAfter={
+ updateAppearance(next ? 'dark' : 'light')}
+ aria-label={t('mobileProfile.theme', 'Dark Mode')}
+ >
+
+
+ }
+ />
+
+
+ {appearance === 'system' ? (
+
+ {t('mobileProfile.themeSystem', 'System')}
+
+ ) : null}
+
+
+
+ );
+}
diff --git a/resources/js/admin/mobile/components/__tests__/MobileShell.test.tsx b/resources/js/admin/mobile/components/__tests__/MobileShell.test.tsx
index 8bb8e5a..1055066 100644
--- a/resources/js/admin/mobile/components/__tests__/MobileShell.test.tsx
+++ b/resources/js/admin/mobile/components/__tests__/MobileShell.test.tsx
@@ -37,11 +37,24 @@ vi.mock('@tamagui/react-native-web-lite', () => ({
),
}));
+vi.mock('tamagui', () => ({
+ Separator: ({ children }: { children?: React.ReactNode }) => {children}
,
+ Tabs: Object.assign(({ children }: { children: React.ReactNode }) => {children}
, {
+ List: ({ children }: { children: React.ReactNode }) => {children}
,
+ Tab: ({ children }: { children: React.ReactNode }) => {children}
,
+ Content: ({ children }: { children: React.ReactNode }) => {children}
,
+ }),
+}));
+
vi.mock('../BottomNav', () => ({
BottomNav: () => ,
NavKey: {},
}));
+vi.mock('../UserMenuSheet', () => ({
+ UserMenuSheet: () => ,
+}));
+
const eventContext = {
events: [],
activeEvent: { slug: 'event-1', name: 'Test Event', event_date: '2024-01-01', status: 'active', settings: {} },
diff --git a/resources/js/admin/router.tsx b/resources/js/admin/router.tsx
index 83f3d7e..64fd4a1 100644
--- a/resources/js/admin/router.tsx
+++ b/resources/js/admin/router.tsx
@@ -41,6 +41,8 @@ const MobileSettingsPage = React.lazy(() => import('./mobile/SettingsPage'));
const MobileDataExportsPage = React.lazy(() => import('./mobile/DataExportsPage'));
const MobileLoginPage = React.lazy(() => import('./mobile/LoginPage'));
const MobilePublicHelpPage = React.lazy(() => import('./mobile/PublicHelpPage'));
+const MobileHelpCenterPage = React.lazy(() => import('./mobile/HelpCenterPage'));
+const MobileHelpArticlePage = React.lazy(() => import('./mobile/HelpArticlePage'));
const MobileForgotPasswordPage = React.lazy(() => import('./mobile/ForgotPasswordPage'));
const MobileResetPasswordPage = React.lazy(() => import('./mobile/ResetPasswordPage'));
const MobileDashboardPage = React.lazy(() => import('./mobile/DashboardPage'));
@@ -214,6 +216,8 @@ export const router = createBrowserRouter([
{ path: 'mobile/notifications/:notificationId', element: },
{ path: 'mobile/profile', element: },
{ path: 'mobile/profile/account', element: },
+ { path: 'mobile/help', element: },
+ { path: 'mobile/help/:slug', element: },
{ path: 'mobile/billing', element: },
{ path: 'mobile/billing/shop', element: },
{ path: 'mobile/settings', element: },