Add admin help center entry points
This commit is contained in:
266
resources/js/admin/mobile/components/UserMenuSheet.tsx
Normal file
266
resources/js/admin/mobile/components/UserMenuSheet.tsx
Normal file
@@ -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<string>(() => (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 (
|
||||
<YStack
|
||||
position="fixed"
|
||||
top={0}
|
||||
bottom={0}
|
||||
left={0}
|
||||
right={0}
|
||||
zIndex={100000}
|
||||
pointerEvents={open ? 'auto' : 'none'}
|
||||
>
|
||||
<Pressable
|
||||
onPress={onClose}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
backgroundColor: theme.overlay,
|
||||
opacity: open ? 1 : 0,
|
||||
transition: 'opacity 180ms ease',
|
||||
}}
|
||||
/>
|
||||
|
||||
<YStack
|
||||
position="absolute"
|
||||
top={0}
|
||||
bottom={0}
|
||||
right={0}
|
||||
width="86vw"
|
||||
maxWidth={MENU_WIDTH}
|
||||
backgroundColor={theme.surface}
|
||||
borderLeftWidth={1}
|
||||
borderColor={theme.border}
|
||||
padding="$4"
|
||||
space="$3"
|
||||
style={{
|
||||
transform: open ? 'translateX(0)' : 'translateX(100%)',
|
||||
transition: 'transform 220ms ease',
|
||||
boxShadow: `-12px 0 24px ${theme.glassShadow ?? theme.shadow}`,
|
||||
}}
|
||||
>
|
||||
<XStack alignItems="center" justifyContent="space-between">
|
||||
<Text fontSize="$md" fontWeight="800" color={theme.textStrong}>
|
||||
{t('mobileProfile.title', 'Profil')}
|
||||
</Text>
|
||||
<Pressable onPress={onClose} aria-label={t('common.close', 'Close')}>
|
||||
<XStack
|
||||
width={32}
|
||||
height={32}
|
||||
borderRadius={10}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
backgroundColor={theme.surfaceMuted}
|
||||
borderWidth={1}
|
||||
borderColor={theme.border}
|
||||
>
|
||||
<X size={16} color={theme.muted} />
|
||||
</XStack>
|
||||
</Pressable>
|
||||
</XStack>
|
||||
|
||||
<XStack alignItems="center" space="$3">
|
||||
<XStack
|
||||
width={48}
|
||||
height={48}
|
||||
borderRadius={16}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
backgroundColor={theme.accentSoft}
|
||||
>
|
||||
<Text fontSize="$lg" fontWeight="800" color={theme.primary}>
|
||||
{user?.name?.charAt(0)?.toUpperCase() ?? 'U'}
|
||||
</Text>
|
||||
</XStack>
|
||||
<YStack>
|
||||
<Text fontSize="$sm" fontWeight="800" color={theme.textStrong}>
|
||||
{user?.name ?? t('events.members.roles.guest', 'Guest')}
|
||||
</Text>
|
||||
<Text fontSize="$xs" color={theme.muted}>
|
||||
{user?.email ?? ''}
|
||||
</Text>
|
||||
</YStack>
|
||||
</XStack>
|
||||
|
||||
<Separator backgroundColor={theme.border} opacity={0.6} />
|
||||
|
||||
<YStack space="$2">
|
||||
<Text fontSize="$xs" fontWeight="700" color={theme.muted} textTransform="uppercase" letterSpacing={1}>
|
||||
{t('mobileProfile.settings', 'Einstellungen')}
|
||||
</Text>
|
||||
<YGroup {...({ borderRadius: '$4', borderWidth: 1, borderColor: theme.border, overflow: 'hidden' } as any)}>
|
||||
{menuItems.map((item) => (
|
||||
<YGroup.Item key={item.label}>
|
||||
<ListItem
|
||||
hoverTheme
|
||||
pressTheme
|
||||
paddingVertical="$2"
|
||||
paddingHorizontal="$3"
|
||||
onPress={() => handleNavigate(item.path)}
|
||||
title={
|
||||
<XStack alignItems="center" space="$2">
|
||||
<XStack
|
||||
width={28}
|
||||
height={28}
|
||||
borderRadius={10}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
backgroundColor={theme.surfaceMuted}
|
||||
borderWidth={1}
|
||||
borderColor={theme.border}
|
||||
>
|
||||
<item.icon size={14} color={theme.textStrong} />
|
||||
</XStack>
|
||||
<Text fontSize="$sm" color={theme.textStrong}>
|
||||
{item.label}
|
||||
</Text>
|
||||
</XStack>
|
||||
}
|
||||
iconAfter={<ChevronRight size={16} color={theme.muted} />}
|
||||
/>
|
||||
</YGroup.Item>
|
||||
))}
|
||||
</YGroup>
|
||||
</YStack>
|
||||
|
||||
<YStack space="$2">
|
||||
<Text fontSize="$xs" fontWeight="700" color={theme.muted} textTransform="uppercase" letterSpacing={1}>
|
||||
{t('settings.appearance.title', 'Darstellung')}
|
||||
</Text>
|
||||
<YGroup {...({ borderRadius: '$4', borderWidth: 1, borderColor: theme.border, overflow: 'hidden' } as any)}>
|
||||
<YGroup.Item>
|
||||
<ListItem
|
||||
paddingVertical="$2"
|
||||
paddingHorizontal="$3"
|
||||
title={
|
||||
<XStack space="$2" alignItems="center">
|
||||
<Text fontSize="$sm" color={theme.textStrong}>
|
||||
{t('mobileProfile.language', 'Sprache')}
|
||||
</Text>
|
||||
</XStack>
|
||||
}
|
||||
iconAfter={
|
||||
<MobileSelect
|
||||
value={language}
|
||||
onChange={(event) => {
|
||||
const lng = event.target.value;
|
||||
setLanguage(lng);
|
||||
void i18n.changeLanguage(lng);
|
||||
}}
|
||||
compact
|
||||
style={{ minWidth: 120 }}
|
||||
>
|
||||
<option value="de">{t('mobileProfile.languageDe', 'Deutsch')}</option>
|
||||
<option value="en">{t('mobileProfile.languageEn', 'English')}</option>
|
||||
</MobileSelect>
|
||||
}
|
||||
/>
|
||||
</YGroup.Item>
|
||||
<YGroup.Item>
|
||||
<ListItem
|
||||
paddingVertical="$2"
|
||||
paddingHorizontal="$3"
|
||||
title={
|
||||
<XStack space="$2" alignItems="center">
|
||||
<Text fontSize="$sm" color={theme.textStrong}>
|
||||
{t('mobileProfile.theme', 'Dark Mode')}
|
||||
</Text>
|
||||
</XStack>
|
||||
}
|
||||
iconAfter={
|
||||
<Switch
|
||||
size="$2"
|
||||
checked={isDark}
|
||||
onCheckedChange={(next) => updateAppearance(next ? 'dark' : 'light')}
|
||||
aria-label={t('mobileProfile.theme', 'Dark Mode')}
|
||||
>
|
||||
<Switch.Thumb />
|
||||
</Switch>
|
||||
}
|
||||
/>
|
||||
</YGroup.Item>
|
||||
</YGroup>
|
||||
{appearance === 'system' ? (
|
||||
<Text fontSize="$xs" color={theme.muted}>
|
||||
{t('mobileProfile.themeSystem', 'System')}
|
||||
</Text>
|
||||
) : null}
|
||||
</YStack>
|
||||
</YStack>
|
||||
</YStack>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user