first implementation of tamagui mobile pages
This commit is contained in:
132
resources/js/admin/mobile/ProfilePage.tsx
Normal file
132
resources/js/admin/mobile/ProfilePage.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LogOut, User, Settings, Shield, Globe, Moon } from 'lucide-react';
|
||||
import { YStack, XStack } from '@tamagui/stacks';
|
||||
import { SizableText as Text } from '@tamagui/text';
|
||||
import { MobileScaffold } from './components/Scaffold';
|
||||
import { MobileCard, CTAButton } from './components/Primitives';
|
||||
import { BottomNav } from './components/BottomNav';
|
||||
import { useAuth } from '../auth/context';
|
||||
import { fetchTenantProfile } from '../api';
|
||||
import { useMobileNav } from './hooks/useMobileNav';
|
||||
import { adminPath } from '../constants';
|
||||
import i18n from '../i18n';
|
||||
|
||||
export default function MobileProfilePage() {
|
||||
const { user, logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation('management');
|
||||
const { go } = useMobileNav();
|
||||
|
||||
const [name, setName] = React.useState(user?.name ?? 'Guest');
|
||||
const [email, setEmail] = React.useState(user?.email ?? '');
|
||||
const [role, setRole] = React.useState<string>(user?.role ?? '');
|
||||
const [theme, setTheme] = React.useState<'light' | 'dark'>('light');
|
||||
const [language, setLanguage] = React.useState<string>(i18n.language || 'de');
|
||||
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const profile = await fetchTenantProfile();
|
||||
setName(profile.name ?? name);
|
||||
setEmail(profile.email ?? email);
|
||||
setRole((profile as any)?.role ?? role);
|
||||
} catch {
|
||||
// non-fatal for mobile profile view
|
||||
}
|
||||
})();
|
||||
}, [email, name, role]);
|
||||
|
||||
return (
|
||||
<MobileScaffold
|
||||
title={t('profile.title', 'Profile')}
|
||||
onBack={() => navigate(-1)}
|
||||
footer={
|
||||
<BottomNav active="profile" onNavigate={go} />
|
||||
}
|
||||
>
|
||||
<MobileCard space="$3" alignItems="center">
|
||||
<XStack
|
||||
width={64}
|
||||
height={64}
|
||||
borderRadius={20}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
backgroundColor="#e0f2fe"
|
||||
>
|
||||
<User size={28} color="#2563eb" />
|
||||
</XStack>
|
||||
<Text fontSize="$md" fontWeight="800" color="#111827">
|
||||
{name}
|
||||
</Text>
|
||||
<Text fontSize="$sm" color="#4b5563">
|
||||
{email}
|
||||
</Text>
|
||||
{role ? (
|
||||
<Text fontSize="$xs" color="#6b7280">
|
||||
{role}
|
||||
</Text>
|
||||
) : null}
|
||||
</MobileCard>
|
||||
|
||||
<MobileCard space="$3">
|
||||
<Text fontSize="$md" fontWeight="800" color="#111827">
|
||||
{t('profile.settings', 'Settings')}
|
||||
</Text>
|
||||
<Pressable onPress={() => navigate(adminPath('/settings'))}>
|
||||
<XStack alignItems="center" justifyContent="space-between" paddingVertical="$2" borderBottomWidth={1} borderColor="#e5e7eb">
|
||||
<Text fontSize="$sm" color="#111827">
|
||||
{t('profile.account', 'Account & Security')}
|
||||
</Text>
|
||||
<Settings size={18} color="#9ca3af" />
|
||||
</XStack>
|
||||
</Pressable>
|
||||
<XStack alignItems="center" justifyContent="space-between" paddingVertical="$2" borderBottomWidth={1} borderColor="#e5e7eb">
|
||||
<XStack space="$2" alignItems="center">
|
||||
<Globe size={16} color="#6b7280" />
|
||||
<Text fontSize="$sm" color="#111827">
|
||||
{t('profile.language', 'Language')}
|
||||
</Text>
|
||||
</XStack>
|
||||
<select
|
||||
value={language}
|
||||
onChange={(e) => {
|
||||
const lng = e.target.value;
|
||||
setLanguage(lng);
|
||||
void i18n.changeLanguage(lng);
|
||||
}}
|
||||
style={{ border: '1px solid #e5e7eb', borderRadius: 10, padding: '6px 10px', background: 'white', fontSize: 13 }}
|
||||
>
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="en">English</option>
|
||||
</select>
|
||||
</XStack>
|
||||
<XStack alignItems="center" justifyContent="space-between" paddingVertical="$2">
|
||||
<XStack space="$2" alignItems="center">
|
||||
<Moon size={16} color="#6b7280" />
|
||||
<Text fontSize="$sm" color="#111827">
|
||||
{t('profile.theme', 'Theme')}
|
||||
</Text>
|
||||
</XStack>
|
||||
<select
|
||||
value={theme}
|
||||
onChange={(e) => setTheme(e.target.value as 'light' | 'dark')}
|
||||
style={{ border: '1px solid #e5e7eb', borderRadius: 10, padding: '6px 10px', background: 'white', fontSize: 13 }}
|
||||
>
|
||||
<option value="light">{t('profile.themeLight', 'Light')}</option>
|
||||
<option value="dark">{t('profile.themeDark', 'Dark')}</option>
|
||||
</select>
|
||||
</XStack>
|
||||
</MobileCard>
|
||||
|
||||
<CTAButton
|
||||
label={t('profile.logout', 'Log out')}
|
||||
onPress={() => {
|
||||
logout();
|
||||
navigate(adminPath('/logout'));
|
||||
}}
|
||||
/>
|
||||
</MobileScaffold>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user