Adopt Tamagui defaults for tabs and filters
This commit is contained in:
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { CheckCircle2, Lock, MailWarning, User } from 'lucide-react';
|
||||
import { YStack, XStack } from '@tamagui/stacks';
|
||||
import { SizableText as Text } from '@tamagui/text';
|
||||
import { Pressable } from '@tamagui/react-native-web-lite';
|
||||
import { Tabs } from 'tamagui';
|
||||
import toast from 'react-hot-toast';
|
||||
import { MobileShell } from './components/MobileShell';
|
||||
import { MobileCard, CTAButton, PillBadge } from './components/Primitives';
|
||||
@@ -300,6 +300,275 @@ export default function MobileProfileAccountPage() {
|
||||
form.password.trim().length > 0 &&
|
||||
form.passwordConfirmation.trim().length > 0;
|
||||
const brandingDisabled = brandingLoading || brandingSaving;
|
||||
const brandingContent = (
|
||||
<>
|
||||
{brandingError ? (
|
||||
<MobileCard>
|
||||
<Text fontWeight="700" color={danger}>
|
||||
{brandingError}
|
||||
</Text>
|
||||
</MobileCard>
|
||||
) : null}
|
||||
|
||||
<MobileCard gap="$3">
|
||||
<Text fontSize="$md" fontWeight="800" color={text}>
|
||||
{t('profile.branding.title', 'Standard-Branding')}
|
||||
</Text>
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{t('profile.branding.hint', 'Diese Werte dienen als Vorschlag für neue Events und beschleunigen die Einrichtung.')}
|
||||
</Text>
|
||||
</MobileCard>
|
||||
|
||||
<MobileCard gap="$3">
|
||||
<Text fontSize="$md" fontWeight="800" color={text}>
|
||||
{t('profile.branding.theme', 'Theme')}
|
||||
</Text>
|
||||
<YStack gap="$3">
|
||||
<MobileField label={t('events.branding.mode', 'Theme')}>
|
||||
<MobileSelect
|
||||
value={brandingForm.mode}
|
||||
onChange={(event) => setBrandingForm((prev) => ({ ...prev, mode: event.target.value as BrandingFormValues['mode'] }))}
|
||||
disabled={brandingDisabled}
|
||||
>
|
||||
<option value="light">{t('events.branding.modeLight', 'Light')}</option>
|
||||
<option value="auto">{t('events.branding.modeAuto', 'Auto')}</option>
|
||||
<option value="dark">{t('events.branding.modeDark', 'Dark')}</option>
|
||||
</MobileSelect>
|
||||
</MobileField>
|
||||
<MobileField label={t('events.branding.fontSize', 'Font Size')}>
|
||||
<MobileSelect
|
||||
value={brandingForm.fontSize}
|
||||
onChange={(event) => setBrandingForm((prev) => ({ ...prev, fontSize: event.target.value as BrandingFormValues['fontSize'] }))}
|
||||
disabled={brandingDisabled}
|
||||
>
|
||||
<option value="s">{t('events.branding.fontSizeSmall', 'S')}</option>
|
||||
<option value="m">{t('events.branding.fontSizeMedium', 'M')}</option>
|
||||
<option value="l">{t('events.branding.fontSizeLarge', 'L')}</option>
|
||||
</MobileSelect>
|
||||
</MobileField>
|
||||
</YStack>
|
||||
</MobileCard>
|
||||
|
||||
<MobileCard gap="$3">
|
||||
<Text fontSize="$md" fontWeight="800" color={text}>
|
||||
{t('events.branding.colors', 'Colors')}
|
||||
</Text>
|
||||
<YStack gap="$3">
|
||||
<ColorField
|
||||
label={t('events.branding.primary', 'Primary Color')}
|
||||
value={brandingForm.primary}
|
||||
onChange={(value) => setBrandingForm((prev) => ({ ...prev, primary: value }))}
|
||||
disabled={brandingDisabled}
|
||||
/>
|
||||
<ColorField
|
||||
label={t('events.branding.accent', 'Accent Color')}
|
||||
value={brandingForm.accent}
|
||||
onChange={(value) => setBrandingForm((prev) => ({ ...prev, accent: value }))}
|
||||
disabled={brandingDisabled}
|
||||
/>
|
||||
<ColorField
|
||||
label={t('events.branding.backgroundColor', 'Background Color')}
|
||||
value={brandingForm.background}
|
||||
onChange={(value) => setBrandingForm((prev) => ({ ...prev, background: value }))}
|
||||
disabled={brandingDisabled}
|
||||
/>
|
||||
<ColorField
|
||||
label={t('events.branding.surfaceColor', 'Surface Color')}
|
||||
value={brandingForm.surface}
|
||||
onChange={(value) => setBrandingForm((prev) => ({ ...prev, surface: value }))}
|
||||
disabled={brandingDisabled}
|
||||
/>
|
||||
</YStack>
|
||||
</MobileCard>
|
||||
|
||||
<MobileCard gap="$3">
|
||||
<Text fontSize="$md" fontWeight="800" color={text}>
|
||||
{t('events.branding.fonts', 'Fonts')}
|
||||
</Text>
|
||||
<YStack gap="$3">
|
||||
<MobileField label={t('events.branding.headingFont', 'Headline Font')}>
|
||||
<MobileInput
|
||||
value={brandingForm.headingFont}
|
||||
onChange={(event) => setBrandingForm((prev) => ({ ...prev, headingFont: event.target.value }))}
|
||||
placeholder={t('events.branding.headingFontPlaceholder', 'Playfair Display')}
|
||||
hasError={false}
|
||||
disabled={brandingDisabled}
|
||||
/>
|
||||
</MobileField>
|
||||
<MobileField label={t('events.branding.bodyFont', 'Body Font')}>
|
||||
<MobileInput
|
||||
value={brandingForm.bodyFont}
|
||||
onChange={(event) => setBrandingForm((prev) => ({ ...prev, bodyFont: event.target.value }))}
|
||||
placeholder={t('events.branding.bodyFontPlaceholder', 'Montserrat')}
|
||||
hasError={false}
|
||||
disabled={brandingDisabled}
|
||||
/>
|
||||
</MobileField>
|
||||
</YStack>
|
||||
</MobileCard>
|
||||
|
||||
<CTAButton
|
||||
label={brandingSaving ? t('profile.branding.saving', 'Saving...') : t('profile.branding.save', 'Save defaults')}
|
||||
onPress={handleBrandingSave}
|
||||
disabled={brandingDisabled}
|
||||
loading={brandingSaving}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
const accountContent = (
|
||||
<>
|
||||
{error ? (
|
||||
<MobileCard>
|
||||
<Text fontWeight="700" color={danger}>
|
||||
{error}
|
||||
</Text>
|
||||
</MobileCard>
|
||||
) : null}
|
||||
|
||||
<MobileCard gap="$3">
|
||||
<XStack alignItems="center" gap="$3">
|
||||
<XStack
|
||||
width={48}
|
||||
height={48}
|
||||
borderRadius={16}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
backgroundColor={accentSoft}
|
||||
>
|
||||
<User size={20} color={primary} />
|
||||
</XStack>
|
||||
<YStack gap="$1">
|
||||
<Text fontSize="$md" fontWeight="800" color={text}>
|
||||
{form.name || profile?.email || t('profile.title', 'Profil')}
|
||||
</Text>
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{form.email || profile?.email || '—'}
|
||||
</Text>
|
||||
</YStack>
|
||||
</XStack>
|
||||
<XStack alignItems="center" gap="$2" flexWrap="wrap">
|
||||
{profile?.email_verified ? (
|
||||
<CheckCircle2 size={14} color={subtle} />
|
||||
) : (
|
||||
<MailWarning size={14} color={subtle} />
|
||||
)}
|
||||
<PillBadge tone={profile?.email_verified ? 'success' : 'warning'}>
|
||||
{emailStatusLabel}
|
||||
</PillBadge>
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
{emailHint}
|
||||
</Text>
|
||||
</XStack>
|
||||
</MobileCard>
|
||||
|
||||
<MobileCard gap="$3">
|
||||
<XStack alignItems="center" gap="$2">
|
||||
<User size={16} color={text} />
|
||||
<Text fontSize="$md" fontWeight="800" color={text}>
|
||||
{t('profile.sections.account.heading', 'Account-Informationen')}
|
||||
</Text>
|
||||
</XStack>
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{t('profile.sections.account.description', 'Passe Anzeigename, E-Mail-Adresse und Sprache der Admin-Oberfläche an.')}
|
||||
</Text>
|
||||
{loading ? (
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{t('profile.loading', 'Lädt ...')}
|
||||
</Text>
|
||||
) : (
|
||||
<YStack gap="$3">
|
||||
<MobileField label={t('profile.fields.name', 'Anzeigename')}>
|
||||
<MobileInput
|
||||
value={form.name}
|
||||
onChange={(event) => setForm((prev) => ({ ...prev, name: event.target.value }))}
|
||||
placeholder={t('profile.placeholders.name', 'z. B. Hochzeitsplanung Schmidt')}
|
||||
hasError={false}
|
||||
/>
|
||||
</MobileField>
|
||||
<MobileField label={t('profile.fields.email', 'E-Mail-Adresse')}>
|
||||
<MobileInput
|
||||
value={form.email}
|
||||
onChange={(event) => setForm((prev) => ({ ...prev, email: event.target.value }))}
|
||||
placeholder="mail@beispiel.de"
|
||||
type="email"
|
||||
hasError={false}
|
||||
/>
|
||||
</MobileField>
|
||||
<MobileField label={t('profile.fields.locale', 'Bevorzugte Sprache')}>
|
||||
<MobileSelect
|
||||
value={form.preferredLocale}
|
||||
onChange={(event) => setForm((prev) => ({ ...prev, preferredLocale: event.target.value }))}
|
||||
>
|
||||
{LOCALE_OPTIONS.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label ?? t(option.labelKey, option.fallback)}
|
||||
</option>
|
||||
))}
|
||||
</MobileSelect>
|
||||
</MobileField>
|
||||
<CTAButton
|
||||
label={t('profile.actions.save', 'Speichern')}
|
||||
onPress={handleAccountSave}
|
||||
disabled={savingAccount || loading}
|
||||
loading={savingAccount}
|
||||
/>
|
||||
</YStack>
|
||||
)}
|
||||
</MobileCard>
|
||||
|
||||
<MobileCard gap="$3">
|
||||
<XStack alignItems="center" gap="$2">
|
||||
<Lock size={16} color={text} />
|
||||
<Text fontSize="$md" fontWeight="800" color={text}>
|
||||
{t('profile.sections.password.heading', 'Passwort ändern')}
|
||||
</Text>
|
||||
</XStack>
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{t('profile.sections.password.description', 'Wähle ein sicheres Passwort, um deinen Admin-Zugang zu schützen.')}
|
||||
</Text>
|
||||
<YStack gap="$3">
|
||||
<MobileField label={t('profile.fields.currentPassword', 'Aktuelles Passwort')}>
|
||||
<MobileInput
|
||||
value={form.currentPassword}
|
||||
onChange={(event) => setForm((prev) => ({ ...prev, currentPassword: event.target.value }))}
|
||||
placeholder="••••••••"
|
||||
type="password"
|
||||
hasError={false}
|
||||
/>
|
||||
</MobileField>
|
||||
<MobileField
|
||||
label={t('profile.fields.newPassword', 'Neues Passwort')}
|
||||
hint={t('profile.sections.password.hint', 'Nutze mindestens 8 Zeichen und kombiniere Buchstaben sowie Zahlen für mehr Sicherheit.')}
|
||||
>
|
||||
<MobileInput
|
||||
value={form.password}
|
||||
onChange={(event) => setForm((prev) => ({ ...prev, password: event.target.value }))}
|
||||
placeholder="••••••••"
|
||||
type="password"
|
||||
hasError={false}
|
||||
/>
|
||||
</MobileField>
|
||||
<MobileField label={t('profile.fields.passwordConfirmation', 'Passwort bestätigen')}>
|
||||
<MobileInput
|
||||
value={form.passwordConfirmation}
|
||||
onChange={(event) => setForm((prev) => ({ ...prev, passwordConfirmation: event.target.value }))}
|
||||
placeholder="••••••••"
|
||||
type="password"
|
||||
hasError={false}
|
||||
/>
|
||||
</MobileField>
|
||||
<CTAButton
|
||||
label={t('profile.actions.updatePassword', 'Passwort aktualisieren')}
|
||||
onPress={handlePasswordSave}
|
||||
disabled={!passwordReady || savingPassword || loading}
|
||||
loading={savingPassword}
|
||||
tone="ghost"
|
||||
/>
|
||||
</YStack>
|
||||
</MobileCard>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<MobileShell
|
||||
@@ -308,312 +577,38 @@ export default function MobileProfileAccountPage() {
|
||||
onBack={back}
|
||||
>
|
||||
{brandingTabEnabled ? (
|
||||
<XStack gap="$2">
|
||||
<TabButton
|
||||
label={t('profile.tabs.account', 'Account')}
|
||||
active={activeTab === 'account'}
|
||||
onPress={() => setActiveTab('account')}
|
||||
/>
|
||||
<TabButton
|
||||
label={t('profile.tabs.branding', 'Standard-Branding')}
|
||||
active={activeTab === 'branding'}
|
||||
onPress={() => setActiveTab('branding')}
|
||||
/>
|
||||
</XStack>
|
||||
) : null}
|
||||
|
||||
{activeTab === 'branding' && brandingTabEnabled ? (
|
||||
<>
|
||||
{brandingError ? (
|
||||
<MobileCard>
|
||||
<Text fontWeight="700" color={danger}>
|
||||
{brandingError}
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(value) => setActiveTab(value as TabKey)}
|
||||
orientation="horizontal"
|
||||
flexDirection="column"
|
||||
>
|
||||
<Tabs.List gap="$2">
|
||||
<Tabs.Tab value="account" flex={1} paddingVertical="$2.5" paddingHorizontal="$3" borderRadius="$4">
|
||||
<Text fontSize="$sm" fontWeight="600" textAlign="center">
|
||||
{t('profile.tabs.account', 'Account')}
|
||||
</Text>
|
||||
</MobileCard>
|
||||
) : null}
|
||||
|
||||
<MobileCard gap="$3">
|
||||
<Text fontSize="$md" fontWeight="800" color={text}>
|
||||
{t('profile.branding.title', 'Standard-Branding')}
|
||||
</Text>
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{t('profile.branding.hint', 'Diese Werte dienen als Vorschlag für neue Events und beschleunigen die Einrichtung.')}
|
||||
</Text>
|
||||
</MobileCard>
|
||||
|
||||
<MobileCard gap="$3">
|
||||
<Text fontSize="$md" fontWeight="800" color={text}>
|
||||
{t('profile.branding.theme', 'Theme')}
|
||||
</Text>
|
||||
<YStack gap="$3">
|
||||
<MobileField label={t('events.branding.mode', 'Theme')}>
|
||||
<MobileSelect
|
||||
value={brandingForm.mode}
|
||||
onChange={(event) => setBrandingForm((prev) => ({ ...prev, mode: event.target.value as BrandingFormValues['mode'] }))}
|
||||
disabled={brandingDisabled}
|
||||
>
|
||||
<option value="light">{t('events.branding.modeLight', 'Light')}</option>
|
||||
<option value="auto">{t('events.branding.modeAuto', 'Auto')}</option>
|
||||
<option value="dark">{t('events.branding.modeDark', 'Dark')}</option>
|
||||
</MobileSelect>
|
||||
</MobileField>
|
||||
<MobileField label={t('events.branding.fontSize', 'Font Size')}>
|
||||
<MobileSelect
|
||||
value={brandingForm.fontSize}
|
||||
onChange={(event) => setBrandingForm((prev) => ({ ...prev, fontSize: event.target.value as BrandingFormValues['fontSize'] }))}
|
||||
disabled={brandingDisabled}
|
||||
>
|
||||
<option value="s">{t('events.branding.fontSizeSmall', 'S')}</option>
|
||||
<option value="m">{t('events.branding.fontSizeMedium', 'M')}</option>
|
||||
<option value="l">{t('events.branding.fontSizeLarge', 'L')}</option>
|
||||
</MobileSelect>
|
||||
</MobileField>
|
||||
</YStack>
|
||||
</MobileCard>
|
||||
|
||||
<MobileCard gap="$3">
|
||||
<Text fontSize="$md" fontWeight="800" color={text}>
|
||||
{t('events.branding.colors', 'Colors')}
|
||||
</Text>
|
||||
<YStack gap="$3">
|
||||
<ColorField
|
||||
label={t('events.branding.primary', 'Primary Color')}
|
||||
value={brandingForm.primary}
|
||||
onChange={(value) => setBrandingForm((prev) => ({ ...prev, primary: value }))}
|
||||
disabled={brandingDisabled}
|
||||
/>
|
||||
<ColorField
|
||||
label={t('events.branding.accent', 'Accent Color')}
|
||||
value={brandingForm.accent}
|
||||
onChange={(value) => setBrandingForm((prev) => ({ ...prev, accent: value }))}
|
||||
disabled={brandingDisabled}
|
||||
/>
|
||||
<ColorField
|
||||
label={t('events.branding.backgroundColor', 'Background Color')}
|
||||
value={brandingForm.background}
|
||||
onChange={(value) => setBrandingForm((prev) => ({ ...prev, background: value }))}
|
||||
disabled={brandingDisabled}
|
||||
/>
|
||||
<ColorField
|
||||
label={t('events.branding.surfaceColor', 'Surface Color')}
|
||||
value={brandingForm.surface}
|
||||
onChange={(value) => setBrandingForm((prev) => ({ ...prev, surface: value }))}
|
||||
disabled={brandingDisabled}
|
||||
/>
|
||||
</YStack>
|
||||
</MobileCard>
|
||||
|
||||
<MobileCard gap="$3">
|
||||
<Text fontSize="$md" fontWeight="800" color={text}>
|
||||
{t('events.branding.fonts', 'Fonts')}
|
||||
</Text>
|
||||
<YStack gap="$3">
|
||||
<MobileField label={t('events.branding.headingFont', 'Headline Font')}>
|
||||
<MobileInput
|
||||
value={brandingForm.headingFont}
|
||||
onChange={(event) => setBrandingForm((prev) => ({ ...prev, headingFont: event.target.value }))}
|
||||
placeholder={t('events.branding.headingFontPlaceholder', 'Playfair Display')}
|
||||
hasError={false}
|
||||
disabled={brandingDisabled}
|
||||
/>
|
||||
</MobileField>
|
||||
<MobileField label={t('events.branding.bodyFont', 'Body Font')}>
|
||||
<MobileInput
|
||||
value={brandingForm.bodyFont}
|
||||
onChange={(event) => setBrandingForm((prev) => ({ ...prev, bodyFont: event.target.value }))}
|
||||
placeholder={t('events.branding.bodyFontPlaceholder', 'Montserrat')}
|
||||
hasError={false}
|
||||
disabled={brandingDisabled}
|
||||
/>
|
||||
</MobileField>
|
||||
</YStack>
|
||||
</MobileCard>
|
||||
|
||||
<CTAButton
|
||||
label={brandingSaving ? t('profile.branding.saving', 'Saving...') : t('profile.branding.save', 'Save defaults')}
|
||||
onPress={handleBrandingSave}
|
||||
disabled={brandingDisabled}
|
||||
loading={brandingSaving}
|
||||
/>
|
||||
</>
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab value="branding" flex={1} paddingVertical="$2.5" paddingHorizontal="$3" borderRadius="$4">
|
||||
<Text fontSize="$sm" fontWeight="600" textAlign="center">
|
||||
{t('profile.tabs.branding', 'Standard-Branding')}
|
||||
</Text>
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Content value="account" paddingTop="$2">
|
||||
{accountContent}
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="branding" paddingTop="$2">
|
||||
{brandingContent}
|
||||
</Tabs.Content>
|
||||
</Tabs>
|
||||
) : (
|
||||
<>
|
||||
{error ? (
|
||||
<MobileCard>
|
||||
<Text fontWeight="700" color={danger}>
|
||||
{error}
|
||||
</Text>
|
||||
</MobileCard>
|
||||
) : null}
|
||||
|
||||
<MobileCard gap="$3">
|
||||
<XStack alignItems="center" gap="$3">
|
||||
<XStack
|
||||
width={48}
|
||||
height={48}
|
||||
borderRadius={16}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
backgroundColor={accentSoft}
|
||||
>
|
||||
<User size={20} color={primary} />
|
||||
</XStack>
|
||||
<YStack gap="$1">
|
||||
<Text fontSize="$md" fontWeight="800" color={text}>
|
||||
{form.name || profile?.email || t('profile.title', 'Profil')}
|
||||
</Text>
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{form.email || profile?.email || '—'}
|
||||
</Text>
|
||||
</YStack>
|
||||
</XStack>
|
||||
<XStack alignItems="center" gap="$2" flexWrap="wrap">
|
||||
{profile?.email_verified ? (
|
||||
<CheckCircle2 size={14} color={subtle} />
|
||||
) : (
|
||||
<MailWarning size={14} color={subtle} />
|
||||
)}
|
||||
<PillBadge tone={profile?.email_verified ? 'success' : 'warning'}>
|
||||
{emailStatusLabel}
|
||||
</PillBadge>
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
{emailHint}
|
||||
</Text>
|
||||
</XStack>
|
||||
</MobileCard>
|
||||
|
||||
<MobileCard gap="$3">
|
||||
<XStack alignItems="center" gap="$2">
|
||||
<User size={16} color={text} />
|
||||
<Text fontSize="$md" fontWeight="800" color={text}>
|
||||
{t('profile.sections.account.heading', 'Account-Informationen')}
|
||||
</Text>
|
||||
</XStack>
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{t('profile.sections.account.description', 'Passe Anzeigename, E-Mail-Adresse und Sprache der Admin-Oberfläche an.')}
|
||||
</Text>
|
||||
{loading ? (
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{t('profile.loading', 'Lädt ...')}
|
||||
</Text>
|
||||
) : (
|
||||
<YStack gap="$3">
|
||||
<MobileField label={t('profile.fields.name', 'Anzeigename')}>
|
||||
<MobileInput
|
||||
value={form.name}
|
||||
onChange={(event) => setForm((prev) => ({ ...prev, name: event.target.value }))}
|
||||
placeholder={t('profile.placeholders.name', 'z. B. Hochzeitsplanung Schmidt')}
|
||||
hasError={false}
|
||||
/>
|
||||
</MobileField>
|
||||
<MobileField label={t('profile.fields.email', 'E-Mail-Adresse')}>
|
||||
<MobileInput
|
||||
value={form.email}
|
||||
onChange={(event) => setForm((prev) => ({ ...prev, email: event.target.value }))}
|
||||
placeholder="mail@beispiel.de"
|
||||
type="email"
|
||||
hasError={false}
|
||||
/>
|
||||
</MobileField>
|
||||
<MobileField label={t('profile.fields.locale', 'Bevorzugte Sprache')}>
|
||||
<MobileSelect
|
||||
value={form.preferredLocale}
|
||||
onChange={(event) => setForm((prev) => ({ ...prev, preferredLocale: event.target.value }))}
|
||||
>
|
||||
{LOCALE_OPTIONS.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label ?? t(option.labelKey, option.fallback)}
|
||||
</option>
|
||||
))}
|
||||
</MobileSelect>
|
||||
</MobileField>
|
||||
<CTAButton
|
||||
label={t('profile.actions.save', 'Speichern')}
|
||||
onPress={handleAccountSave}
|
||||
disabled={savingAccount || loading}
|
||||
loading={savingAccount}
|
||||
/>
|
||||
</YStack>
|
||||
)}
|
||||
</MobileCard>
|
||||
|
||||
<MobileCard gap="$3">
|
||||
<XStack alignItems="center" gap="$2">
|
||||
<Lock size={16} color={text} />
|
||||
<Text fontSize="$md" fontWeight="800" color={text}>
|
||||
{t('profile.sections.password.heading', 'Passwort ändern')}
|
||||
</Text>
|
||||
</XStack>
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{t('profile.sections.password.description', 'Wähle ein sicheres Passwort, um deinen Admin-Zugang zu schützen.')}
|
||||
</Text>
|
||||
<YStack gap="$3">
|
||||
<MobileField label={t('profile.fields.currentPassword', 'Aktuelles Passwort')}>
|
||||
<MobileInput
|
||||
value={form.currentPassword}
|
||||
onChange={(event) => setForm((prev) => ({ ...prev, currentPassword: event.target.value }))}
|
||||
placeholder="••••••••"
|
||||
type="password"
|
||||
hasError={false}
|
||||
/>
|
||||
</MobileField>
|
||||
<MobileField
|
||||
label={t('profile.fields.newPassword', 'Neues Passwort')}
|
||||
hint={t('profile.sections.password.hint', 'Nutze mindestens 8 Zeichen und kombiniere Buchstaben sowie Zahlen für mehr Sicherheit.')}
|
||||
>
|
||||
<MobileInput
|
||||
value={form.password}
|
||||
onChange={(event) => setForm((prev) => ({ ...prev, password: event.target.value }))}
|
||||
placeholder="••••••••"
|
||||
type="password"
|
||||
hasError={false}
|
||||
/>
|
||||
</MobileField>
|
||||
<MobileField label={t('profile.fields.passwordConfirmation', 'Passwort bestätigen')}>
|
||||
<MobileInput
|
||||
value={form.passwordConfirmation}
|
||||
onChange={(event) => setForm((prev) => ({ ...prev, passwordConfirmation: event.target.value }))}
|
||||
placeholder="••••••••"
|
||||
type="password"
|
||||
hasError={false}
|
||||
/>
|
||||
</MobileField>
|
||||
<CTAButton
|
||||
label={t('profile.actions.updatePassword', 'Passwort aktualisieren')}
|
||||
onPress={handlePasswordSave}
|
||||
disabled={!passwordReady || savingPassword || loading}
|
||||
loading={savingPassword}
|
||||
tone="ghost"
|
||||
/>
|
||||
</YStack>
|
||||
</MobileCard>
|
||||
</>
|
||||
accountContent
|
||||
)}
|
||||
</MobileShell>
|
||||
);
|
||||
}
|
||||
|
||||
function TabButton({ label, active, onPress }: { label: string; active: boolean; onPress: () => void }) {
|
||||
const { primary, surfaceMuted, border, surface, text } = useAdminTheme();
|
||||
return (
|
||||
<Pressable onPress={onPress} style={{ flex: 1 }}>
|
||||
<XStack
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
paddingVertical="$2.5"
|
||||
borderRadius={12}
|
||||
backgroundColor={active ? primary : surfaceMuted}
|
||||
borderWidth={1}
|
||||
borderColor={active ? primary : border}
|
||||
>
|
||||
<Text fontSize="$sm" color={active ? surface : text} fontWeight="700">
|
||||
{label}
|
||||
</Text>
|
||||
</XStack>
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
function ColorField({
|
||||
label,
|
||||
value,
|
||||
|
||||
Reference in New Issue
Block a user