This commit is contained in:
Timo Knuth
2026-02-27 15:19:24 +01:00
parent b7f8221095
commit 253c3c1c6d
134 changed files with 11188 additions and 1871 deletions

View File

@@ -1,54 +1,140 @@
import { View, Text, ScrollView, TouchableOpacity, Alert, StyleSheet } from 'react-native'
import { View, Text, ScrollView, TouchableOpacity, StyleSheet, TextInput, ActivityIndicator, Alert } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { Ionicons } from '@expo/vector-icons'
import { useState } from 'react'
import { useRouter } from 'expo-router'
import { useAuth } from '@/hooks/useAuth'
import { trpc } from '@/lib/trpc'
import { authClient } from '@/lib/auth-client'
type Item = {
label: string
icon: React.ComponentProps<typeof Ionicons>['name']
badge?: string
const AVATAR_PALETTES = [
{ bg: '#003B7E', text: '#FFFFFF' },
{ bg: '#1D4ED8', text: '#FFFFFF' },
{ bg: '#059669', text: '#FFFFFF' },
{ bg: '#4338CA', text: '#FFFFFF' },
{ bg: '#B45309', text: '#FFFFFF' },
{ bg: '#0F766E', text: '#FFFFFF' },
]
function getInitials(name: string) {
return name.split(' ').slice(0, 2).map((w) => w[0]?.toUpperCase() ?? '').join('')
}
function getPalette(name: string) {
if (!name) return AVATAR_PALETTES[0]
return AVATAR_PALETTES[name.charCodeAt(0) % AVATAR_PALETTES.length]
}
const MENU_ITEMS: Item[] = [
{ label: 'Persoenliche Daten', icon: 'person-outline' },
{ label: 'Betriebsdaten', icon: 'business-outline', badge: 'Aktiv' },
{ label: 'Mitteilungen', icon: 'notifications-outline', badge: '2' },
{ label: 'Sicherheit & Login', icon: 'shield-checkmark-outline' },
{ label: 'Hilfe & Support', icon: 'help-circle-outline' },
]
function InfoRow({ label, value }: { label: string; value?: string | null }) {
if (!value) return null
return (
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>{label}</Text>
<Text style={styles.infoValue}>{value}</Text>
</View>
)
}
export default function ProfilScreen() {
const { signOut } = useAuth()
const router = useRouter()
const utils = trpc.useUtils()
const { data: me } = trpc.members.me.useQuery()
const name = me?.name ?? ''
const { data: unread } = trpc.messages.unreadCount.useQuery(undefined, { refetchInterval: 15_000 })
const [showPersonal, setShowPersonal] = useState(false)
const [showBetrieb, setShowBetrieb] = useState(false)
const [showSicherheit, setShowSicherheit] = useState(false)
const [showMitteilungen, setShowMitteilungen] = useState(false)
const initials = name
.split(' ')
.slice(0, 2)
.map((chunk) => chunk[0]?.toUpperCase() ?? '')
.join('')
const [isEditing, setIsEditing] = useState(false)
const [isEditingBetrieb, setIsEditingBetrieb] = useState(false)
const [isChangingPassword, setIsChangingPassword] = useState(false)
const [passwordForm, setPasswordForm] = useState({ current: '', next: '', confirm: '' })
const [passwordLoading, setPasswordLoading] = useState(false)
const [passwordError, setPasswordError] = useState('')
const [editForm, setEditForm] = useState({ name: '', email: '', telefon: '', ort: '' })
const [editBetriebForm, setEditBetriebForm] = useState({ betrieb: '', sparte: '', istAusbildungsbetrieb: false })
const openPlaceholder = () => {
Alert.alert('Hinweis', 'Dieser Bereich folgt in einer naechsten Version.')
const { mutate: updateMe, isPending: isUpdating } = trpc.members.updateMe.useMutation({
onSuccess: () => {
utils.members.me.invalidate()
setIsEditing(false)
Alert.alert('Erfolg', 'Profil wurde aktualisiert.')
},
onError: () => {
Alert.alert('Fehler', 'Profil konnte nicht aktualisiert werden.')
},
})
const handleEditPersonal = () => {
if (!me) return
setEditForm({
name: me.name || '',
email: me.email || '',
telefon: me.telefon || '',
ort: me.ort || '',
})
setIsEditing(true)
setShowPersonal(true)
}
const handleEditBetrieb = () => {
if (!me) return
setEditBetriebForm({
betrieb: me.betrieb || '',
sparte: me.sparte || '',
istAusbildungsbetrieb: me.istAusbildungsbetrieb ?? false,
})
setIsEditingBetrieb(true)
setShowBetrieb(true)
}
const handleSavePersonal = () => {
updateMe({
name: editForm.name,
email: editForm.email,
telefon: editForm.telefon,
ort: editForm.ort,
})
}
const handleSaveBetrieb = () => {
updateMe({
betrieb: editBetriebForm.betrieb,
sparte: editBetriebForm.sparte,
istAusbildungsbetrieb: editBetriebForm.istAusbildungsbetrieb,
})
setIsEditingBetrieb(false)
}
const name = me?.name ?? ''
const initials = getInitials(name)
const palette = getPalette(name)
const role = me?.org?.name ? 'Mitglied' : 'Mitglied'
const unreadCount = unread?.count ?? 0
return (
<SafeAreaView style={styles.safeArea} edges={['top']}>
<ScrollView contentContainerStyle={styles.content} showsVerticalScrollIndicator={false}>
{/* HERO */}
<View style={styles.hero}>
<View style={styles.avatarWrap}>
<Text style={styles.avatarText}>{initials}</Text>
<TouchableOpacity style={styles.settingsBtn} activeOpacity={0.8} onPress={openPlaceholder}>
<Ionicons name="settings-outline" size={15} color="#64748B" />
</TouchableOpacity>
</View>
<Text style={styles.name}>{name}</Text>
<Text style={styles.role}>Innungsgeschaeftsfuehrer</Text>
<View style={styles.badgesRow}>
<View style={styles.statusBadge}>
<Text style={styles.statusBadgeText}>Admin-Status</Text>
{/* Avatar — View+Text statt nur Text, damit Initials wirklich mittig */}
<View style={[styles.avatarCircle, { backgroundColor: palette.bg }]}>
<Text style={[styles.avatarText, { color: palette.text }]}>{initials}</Text>
</View>
<View style={styles.settingsBtn}>
<Ionicons name="settings-outline" size={15} color="#64748B" />
</View>
</View>
<Text style={styles.name}>{name || ''}</Text>
<Text style={styles.role}>{me?.org?.name ?? 'Innung'}</Text>
<View style={styles.badgesRow}>
{me?.status === 'aktiv' && (
<View style={styles.statusBadge}>
<Text style={styles.statusBadgeText}>Aktiv</Text>
</View>
)}
<View style={[styles.statusBadge, styles.verifyBadge]}>
<Text style={[styles.statusBadgeText, styles.verifyBadgeText]}>Verifiziert</Text>
</View>
@@ -57,43 +143,332 @@ export default function ProfilScreen() {
<Text style={styles.sectionTitle}>Mein Account</Text>
<View style={styles.menuCard}>
{MENU_ITEMS.map((item, index) => (
<TouchableOpacity
key={item.label}
style={[styles.menuRow, index < MENU_ITEMS.length - 1 && styles.menuRowBorder]}
activeOpacity={0.82}
onPress={openPlaceholder}
>
<View style={styles.menuLeft}>
<View style={styles.menuIcon}>
<Ionicons name={item.icon} size={18} color="#475569" />
</View>
<Text style={styles.menuLabel}>{item.label}</Text>
</View>
<View style={styles.menuRight}>
{item.badge ? (
<View style={[styles.rowBadge, item.badge === 'Aktiv' ? styles.rowBadgeActive : styles.rowBadgeAlert]}>
<Text style={styles.rowBadgeText}>{item.badge}</Text>
</View>
) : null}
<Ionicons name="chevron-forward" size={16} color="#94A3B8" />
</View>
</TouchableOpacity>
))}
</View>
<Text style={styles.sectionTitle}>Unterstuetzung</Text>
<View style={styles.supportCard}>
<Text style={styles.supportTitle}>Probleme oder Fragen?</Text>
<Text style={styles.supportText}>
Unser Support-Team hilft Ihnen gerne bei technischen Schwierigkeiten weiter.
</Text>
<TouchableOpacity style={styles.supportBtn} activeOpacity={0.84} onPress={openPlaceholder}>
<Text style={styles.supportBtnText}>Support kontaktieren</Text>
{/* PERSÖNLICHE DATEN */}
<TouchableOpacity
style={styles.menuRow}
activeOpacity={0.82}
onPress={() => {
if (showPersonal) {
setShowPersonal(false)
setIsEditing(false)
} else {
setShowPersonal(true)
}
}}
>
<View style={styles.menuLeft}>
<View style={styles.menuIcon}>
<Ionicons name="person-outline" size={18} color="#475569" />
</View>
<Text style={styles.menuLabel}>Persönliche Daten</Text>
</View>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
{showPersonal && !isEditing && (
<TouchableOpacity onPress={handleEditPersonal} style={{ marginRight: 12, padding: 4 }}>
<Ionicons name="create-outline" size={18} color="#2563EB" />
</TouchableOpacity>
)}
<Ionicons name={showPersonal ? 'chevron-up' : 'chevron-down'} size={16} color="#94A3B8" />
</View>
</TouchableOpacity>
<Ionicons name="help-circle-outline" size={70} color="rgba(255,255,255,0.12)" style={styles.supportIcon} />
{showPersonal && (
<View style={styles.expandedCard}>
{isEditing ? (
<>
<View style={styles.infoRowEdit}>
<Text style={styles.infoLabel}>Name</Text>
<TextInput style={styles.input} value={editForm.name} onChangeText={t => setEditForm(f => ({ ...f, name: t }))} />
</View>
<View style={styles.infoRowEdit}>
<Text style={styles.infoLabel}>E-Mail</Text>
<TextInput style={styles.input} value={editForm.email} keyboardType="email-address" autoCapitalize="none" onChangeText={t => setEditForm(f => ({ ...f, email: t }))} />
</View>
<View style={styles.infoRowEdit}>
<Text style={styles.infoLabel}>Telefon</Text>
<TextInput style={styles.input} value={editForm.telefon} keyboardType="phone-pad" onChangeText={t => setEditForm(f => ({ ...f, telefon: t }))} />
</View>
<View style={styles.infoRowEdit}>
<Text style={styles.infoLabel}>Ort</Text>
<TextInput style={styles.input} value={editForm.ort} onChangeText={t => setEditForm(f => ({ ...f, ort: t }))} />
</View>
<View style={styles.editActionRow}>
<TouchableOpacity style={styles.cancelBtn} onPress={() => setIsEditing(false)}>
<Text style={styles.cancelBtnText}>Abbrechen</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.saveBtn} onPress={handleSavePersonal} disabled={isUpdating}>
{isUpdating ? <ActivityIndicator color="#fff" size="small" /> : <Text style={styles.saveBtnText}>Speichern</Text>}
</TouchableOpacity>
</View>
</>
) : (
<>
<InfoRow label="Name" value={me?.name} />
<InfoRow label="E-Mail" value={me?.email} />
<InfoRow label="Telefon" value={me?.telefon} />
<InfoRow label="Ort" value={me?.ort} />
<InfoRow label="Mitglied seit" value={me?.seit ? String(me.seit) : null} />
{!me?.name && (
<Text style={styles.emptyHint}>Keine Daten vorhanden.</Text>
)}
</>
)}
</View>
)}
<View style={styles.menuRowBorderOnly} />
{/* BETRIEBSDATEN */}
<TouchableOpacity
style={styles.menuRow}
activeOpacity={0.82}
onPress={() => {
if (showBetrieb) {
setShowBetrieb(false)
setIsEditingBetrieb(false)
} else {
setShowBetrieb(true)
}
}}
>
<View style={styles.menuLeft}>
<View style={styles.menuIcon}>
<Ionicons name="business-outline" size={18} color="#475569" />
</View>
<Text style={styles.menuLabel}>Betriebsdaten</Text>
</View>
<View style={styles.menuRight}>
{showBetrieb && !isEditingBetrieb && (
<TouchableOpacity onPress={handleEditBetrieb} style={{ marginRight: 12, padding: 4 }}>
<Ionicons name="create-outline" size={18} color="#2563EB" />
</TouchableOpacity>
)}
<Ionicons name={showBetrieb ? 'chevron-up' : 'chevron-down'} size={16} color="#94A3B8" />
</View>
</TouchableOpacity>
{showBetrieb && (
<View style={styles.expandedCard}>
{isEditingBetrieb ? (
<>
<View style={styles.infoRowEdit}>
<Text style={styles.infoLabel}>Betrieb</Text>
<TextInput style={styles.input} value={editBetriebForm.betrieb} onChangeText={t => setEditBetriebForm(f => ({ ...f, betrieb: t }))} />
</View>
<View style={styles.infoRowEdit}>
<Text style={styles.infoLabel}>Sparte</Text>
<TextInput style={styles.input} value={editBetriebForm.sparte} onChangeText={t => setEditBetriebForm(f => ({ ...f, sparte: t }))} />
</View>
<TouchableOpacity
style={[styles.infoRowEdit, { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: 10 }]}
onPress={() => setEditBetriebForm(f => ({ ...f, istAusbildungsbetrieb: !f.istAusbildungsbetrieb }))}
>
<Text style={styles.infoLabel}>Ausbildungsbetrieb</Text>
<Ionicons name={editBetriebForm.istAusbildungsbetrieb ? 'checkbox' : 'square-outline'} size={20} color="#2563EB" />
</TouchableOpacity>
<View style={styles.editActionRow}>
<TouchableOpacity style={styles.cancelBtn} onPress={() => setIsEditingBetrieb(false)}>
<Text style={styles.cancelBtnText}>Abbrechen</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.saveBtn} onPress={handleSaveBetrieb} disabled={isUpdating}>
{isUpdating ? <ActivityIndicator color="#fff" size="small" /> : <Text style={styles.saveBtnText}>Speichern</Text>}
</TouchableOpacity>
</View>
</>
) : (
<>
<InfoRow label="Betrieb" value={me?.betrieb} />
<InfoRow label="Sparte" value={me?.sparte} />
<InfoRow label="Ausbildung" value={me?.istAusbildungsbetrieb ? 'Ja' : 'Nein'} />
{!me?.betrieb && !me?.sparte && (
<Text style={styles.emptyHint}>Keine Betriebsdaten vorhanden.</Text>
)}
</>
)}
</View>
)}
{!showBetrieb && (me?.betrieb || me?.sparte) && (
<View style={styles.subInfo}>
{me?.betrieb && <Text style={styles.subInfoText}>{me.betrieb}</Text>}
{me?.sparte && <Text style={styles.subInfoMuted}>{me.sparte}</Text>}
</View>
)}
<View style={styles.menuRowBorderOnly} />
{/* MITTEILUNGEN */}
<TouchableOpacity
style={styles.menuRow}
activeOpacity={0.82}
onPress={() => setShowMitteilungen((v) => !v)}
>
<View style={styles.menuLeft}>
<View style={styles.menuIcon}>
<Ionicons name="notifications-outline" size={18} color="#475569" />
</View>
<Text style={styles.menuLabel}>Mitteilungen</Text>
</View>
<View style={styles.menuRight}>
{unreadCount > 0 && (
<View style={styles.rowBadgeAlert}>
<Text style={[styles.rowBadgeText, { color: '#fff' }]}>{unreadCount}</Text>
</View>
)}
<Ionicons name={showMitteilungen ? 'chevron-up' : 'chevron-down'} size={16} color="#94A3B8" />
</View>
</TouchableOpacity>
{showMitteilungen && (
<View style={styles.expandedCard}>
{unreadCount > 0 ? (
<TouchableOpacity
style={styles.mitteilungenAction}
onPress={() => router.push('/(app)/chat')}
>
<Ionicons name="chatbubbles-outline" size={20} color="#2563EB" />
<View style={{ flex: 1 }}>
<Text style={styles.mitteilungenTitle}>
{unreadCount} ungelesene Nachricht{unreadCount > 1 ? 'en' : ''}
</Text>
<Text style={styles.mitteilungenSub}>Direkt zu den Nachrichten</Text>
</View>
<Ionicons name="chevron-forward" size={16} color="#94A3B8" />
</TouchableOpacity>
) : (
<Text style={styles.emptyHint}>Keine neuen Mitteilungen.</Text>
)}
</View>
)}
<View style={styles.menuRowBorderOnly} />
{/* SICHERHEIT */}
<TouchableOpacity
style={styles.menuRow}
activeOpacity={0.82}
onPress={() => setShowSicherheit(!showSicherheit)}
>
<View style={styles.menuLeft}>
<View style={styles.menuIcon}>
<Ionicons name="shield-checkmark-outline" size={18} color="#475569" />
</View>
<Text style={styles.menuLabel}>Sicherheit & Login</Text>
</View>
<Ionicons name={showSicherheit ? 'chevron-up' : 'chevron-down'} size={16} color="#94A3B8" />
</TouchableOpacity>
{showSicherheit && (
<View style={styles.expandedCard}>
<InfoRow label="Login-E-Mail" value={me?.email} />
<InfoRow label="Account Status" value={me?.status === 'aktiv' ? 'Aktiviert' : 'Inaktiv'} />
{!isChangingPassword ? (
<TouchableOpacity
style={[styles.saveBtn, { marginTop: 10, width: '100%' }]}
onPress={() => {
setPasswordForm({ current: '', next: '', confirm: '' })
setPasswordError('')
setIsChangingPassword(true)
}}
>
<Text style={styles.saveBtnText}>Passwort ändern</Text>
</TouchableOpacity>
) : (
<View style={{ marginTop: 10, gap: 8 }}>
<View style={styles.infoRowEdit}>
<Text style={styles.infoLabel}>Aktuelles Passwort</Text>
<TextInput
style={styles.input}
value={passwordForm.current}
onChangeText={t => setPasswordForm(f => ({ ...f, current: t }))}
secureTextEntry
placeholder="••••••••"
placeholderTextColor="#CBD5E1"
autoCapitalize="none"
/>
</View>
<View style={styles.infoRowEdit}>
<Text style={styles.infoLabel}>Neues Passwort</Text>
<TextInput
style={styles.input}
value={passwordForm.next}
onChangeText={t => setPasswordForm(f => ({ ...f, next: t }))}
secureTextEntry
placeholder="Mindestens 8 Zeichen"
placeholderTextColor="#CBD5E1"
autoCapitalize="none"
/>
</View>
<View style={styles.infoRowEdit}>
<Text style={styles.infoLabel}>Wiederholen</Text>
<TextInput
style={styles.input}
value={passwordForm.confirm}
onChangeText={t => setPasswordForm(f => ({ ...f, confirm: t }))}
secureTextEntry
placeholder="Neues Passwort wiederholen"
placeholderTextColor="#CBD5E1"
autoCapitalize="none"
/>
</View>
{!!passwordError && (
<Text style={styles.passwordError}>{passwordError}</Text>
)}
<View style={styles.editActionRow}>
<TouchableOpacity
style={styles.cancelBtn}
onPress={() => setIsChangingPassword(false)}
disabled={passwordLoading}
>
<Text style={styles.cancelBtnText}>Abbrechen</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.saveBtn}
disabled={passwordLoading}
onPress={async () => {
setPasswordError('')
if (!passwordForm.current) {
setPasswordError('Bitte aktuelles Passwort eingeben.')
return
}
if (passwordForm.next.length < 8) {
setPasswordError('Das neue Passwort muss mindestens 8 Zeichen haben.')
return
}
if (passwordForm.next !== passwordForm.confirm) {
setPasswordError('Die Passwörter stimmen nicht überein.')
return
}
setPasswordLoading(true)
const result = await authClient.changePassword({
currentPassword: passwordForm.current,
newPassword: passwordForm.next,
})
setPasswordLoading(false)
if (result.error) {
setPasswordError(result.error.message ?? 'Passwort konnte nicht geändert werden.')
return
}
setIsChangingPassword(false)
Alert.alert('Erfolg', 'Passwort wurde erfolgreich geändert.')
}}
>
{passwordLoading
? <ActivityIndicator color="#fff" size="small" />
: <Text style={styles.saveBtnText}>Speichern</Text>
}
</TouchableOpacity>
</View>
</View>
)}
</View>
)}
</View>
{/* LOGOUT */}
<TouchableOpacity style={styles.logoutBtn} activeOpacity={0.84} onPress={() => void signOut()}>
<Ionicons name="log-out-outline" size={20} color="#B91C1C" />
<Text style={styles.logoutText}>Abmelden</Text>
@@ -106,226 +481,91 @@ export default function ProfilScreen() {
}
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#F8FAFC',
},
content: {
paddingHorizontal: 18,
paddingBottom: 30,
gap: 14,
},
safeArea: { flex: 1, backgroundColor: '#F8FAFC' },
content: { paddingHorizontal: 18, paddingBottom: 30, gap: 14 },
// Hero
hero: {
backgroundColor: '#FFFFFF',
alignItems: 'center',
paddingTop: 24,
paddingBottom: 18,
borderRadius: 22,
borderWidth: 1,
borderColor: '#E2E8F0',
marginTop: 8,
backgroundColor: '#FFFFFF', alignItems: 'center',
paddingTop: 24, paddingBottom: 18, borderRadius: 22,
borderWidth: 1, borderColor: '#E2E8F0', marginTop: 8,
},
avatarWrap: {
position: 'relative',
},
avatarText: {
width: 94,
height: 94,
borderRadius: 47,
backgroundColor: '#DBEAFE',
borderWidth: 4,
borderColor: '#FFFFFF',
overflow: 'hidden',
textAlign: 'center',
textAlignVertical: 'center',
color: '#003B7E',
fontSize: 34,
fontWeight: '800',
includeFontPadding: false,
avatarWrap: { position: 'relative' },
avatarCircle: {
width: 94, height: 94, borderRadius: 47,
alignItems: 'center', justifyContent: 'center',
borderWidth: 4, borderColor: '#FFFFFF',
shadowColor: '#000', shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.12, shadowRadius: 8, elevation: 3,
},
avatarText: { fontSize: 34, fontWeight: '800', lineHeight: 40, includeFontPadding: false },
settingsBtn: {
position: 'absolute',
right: 0,
bottom: 2,
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: '#FFFFFF',
borderWidth: 1,
borderColor: '#E2E8F0',
alignItems: 'center',
justifyContent: 'center',
},
name: {
marginTop: 14,
fontSize: 24,
fontWeight: '800',
color: '#0F172A',
},
role: {
marginTop: 2,
fontSize: 12,
fontWeight: '700',
letterSpacing: 0.5,
color: '#64748B',
textTransform: 'uppercase',
},
badgesRow: {
marginTop: 10,
flexDirection: 'row',
gap: 8,
},
statusBadge: {
backgroundColor: '#DCFCE7',
borderRadius: 999,
paddingHorizontal: 10,
paddingVertical: 4,
},
statusBadgeText: {
color: '#166534',
fontSize: 11,
fontWeight: '700',
},
verifyBadge: {
backgroundColor: '#DBEAFE',
},
verifyBadgeText: {
color: '#1D4ED8',
position: 'absolute', right: 0, bottom: 2,
width: 30, height: 30, borderRadius: 15,
backgroundColor: '#FFFFFF', borderWidth: 1, borderColor: '#E2E8F0',
alignItems: 'center', justifyContent: 'center',
},
name: { marginTop: 14, fontSize: 24, fontWeight: '800', color: '#0F172A' },
role: { marginTop: 2, fontSize: 12, fontWeight: '700', letterSpacing: 0.5, color: '#64748B', textTransform: 'uppercase' },
badgesRow: { marginTop: 10, flexDirection: 'row', gap: 8 },
statusBadge: { backgroundColor: '#DCFCE7', borderRadius: 999, paddingHorizontal: 10, paddingVertical: 4 },
statusBadgeText: { color: '#166534', fontSize: 11, fontWeight: '700' },
verifyBadge: { backgroundColor: '#DBEAFE' },
verifyBadgeText: { color: '#1D4ED8' },
// Section
sectionTitle: {
marginTop: 2,
paddingLeft: 2,
fontSize: 11,
textTransform: 'uppercase',
letterSpacing: 1.1,
color: '#94A3B8',
fontWeight: '800',
},
menuCard: {
backgroundColor: '#FFFFFF',
borderRadius: 18,
borderWidth: 1,
borderColor: '#E2E8F0',
overflow: 'hidden',
},
menuRow: {
paddingHorizontal: 14,
paddingVertical: 12,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
menuRowBorder: {
borderBottomWidth: 1,
borderBottomColor: '#F1F5F9',
},
menuLeft: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
},
menuIcon: {
width: 36,
height: 36,
borderRadius: 11,
backgroundColor: '#F1F5F9',
alignItems: 'center',
justifyContent: 'center',
},
menuLabel: {
fontSize: 14,
fontWeight: '700',
color: '#1E293B',
},
menuRight: {
flexDirection: 'row',
alignItems: 'center',
gap: 7,
},
rowBadge: {
paddingHorizontal: 8,
paddingVertical: 3,
borderRadius: 999,
},
rowBadgeActive: {
backgroundColor: '#DCFCE7',
},
rowBadgeAlert: {
backgroundColor: '#EF4444',
},
rowBadgeText: {
fontSize: 10,
fontWeight: '700',
color: '#FFFFFF',
},
supportCard: {
borderRadius: 18,
backgroundColor: '#003B7E',
padding: 16,
overflow: 'hidden',
position: 'relative',
},
supportTitle: {
color: '#FFFFFF',
fontSize: 18,
fontWeight: '800',
marginBottom: 4,
maxWidth: 180,
},
supportText: {
color: '#BFDBFE',
fontSize: 12,
lineHeight: 18,
marginBottom: 12,
maxWidth: 240,
},
supportBtn: {
alignSelf: 'flex-start',
backgroundColor: 'rgba(255,255,255,0.15)',
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.25)',
borderRadius: 10,
paddingHorizontal: 12,
paddingVertical: 8,
},
supportBtnText: {
color: '#FFFFFF',
fontSize: 12,
fontWeight: '700',
textTransform: 'uppercase',
letterSpacing: 0.6,
},
supportIcon: {
position: 'absolute',
right: -10,
bottom: -12,
marginTop: 2, paddingLeft: 2, fontSize: 11,
textTransform: 'uppercase', letterSpacing: 1.1,
color: '#94A3B8', fontWeight: '800',
},
// Menu card
menuCard: { backgroundColor: '#FFFFFF', borderRadius: 18, borderWidth: 1, borderColor: '#E2E8F0', overflow: 'hidden' },
menuRow: { paddingHorizontal: 14, paddingVertical: 13, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
menuRowBorderOnly: { height: 1, backgroundColor: '#F1F5F9', marginHorizontal: 14 },
menuLeft: { flexDirection: 'row', alignItems: 'center', gap: 12 },
menuIcon: { width: 36, height: 36, borderRadius: 11, backgroundColor: '#F1F5F9', alignItems: 'center', justifyContent: 'center' },
menuLabel: { fontSize: 14, fontWeight: '700', color: '#1E293B' },
menuRight: { flexDirection: 'row', alignItems: 'center', gap: 7 },
rowBadgeActive: { backgroundColor: '#DCFCE7', paddingHorizontal: 8, paddingVertical: 3, borderRadius: 999 },
rowBadgeAlert: { backgroundColor: '#EF4444', paddingHorizontal: 8, paddingVertical: 3, borderRadius: 999 },
rowBadgeText: { fontSize: 10, fontWeight: '700' },
// Expanded card
expandedCard: { backgroundColor: '#F8FAFC', marginHorizontal: 14, marginBottom: 12, borderRadius: 12, padding: 12, gap: 6 },
infoRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 5, borderBottomWidth: 1, borderBottomColor: '#E2E8F0' },
infoLabel: { fontSize: 12, color: '#64748B', fontWeight: '600', width: 100 },
infoValue: { fontSize: 13, color: '#0F172A', fontWeight: '500', flex: 1, textAlign: 'right' },
emptyHint: { fontSize: 13, color: '#94A3B8', textAlign: 'center', paddingVertical: 8 },
passwordError: { fontSize: 12, color: '#B91C1C', backgroundColor: '#FEF2F2', borderRadius: 6, paddingHorizontal: 10, paddingVertical: 6 },
infoRowEdit: { paddingTop: 6, paddingBottom: 2, borderBottomWidth: 1, borderBottomColor: '#E2E8F0', marginTop: 2 },
input: { fontSize: 13, color: '#0F172A', fontWeight: '500', paddingVertical: 4, paddingHorizontal: 0, marginTop: 2 },
editActionRow: { flexDirection: 'row', justifyContent: 'flex-end', gap: 10, marginTop: 14 },
cancelBtn: { paddingHorizontal: 16, paddingVertical: 8, borderRadius: 8, backgroundColor: '#E2E8F0' },
cancelBtnText: { color: '#475569', fontWeight: '600', fontSize: 13 },
saveBtn: { paddingHorizontal: 16, paddingVertical: 8, borderRadius: 8, backgroundColor: '#2563EB', minWidth: 90, alignItems: 'center' },
saveBtnText: { color: '#FFFFFF', fontWeight: '600', fontSize: 13 },
// Sub info (Betrieb)
subInfo: { paddingHorizontal: 14, paddingBottom: 12, gap: 4 },
subInfoText: { fontSize: 13, fontWeight: '600', color: '#334155' },
subInfoMuted: { fontSize: 12, color: '#64748B' },
ausbildungPill: { flexDirection: 'row', alignItems: 'center', gap: 4, marginTop: 4 },
ausbildungText: { fontSize: 11, color: '#15803D', fontWeight: '600' },
// Mitteilungen action
mitteilungenAction: { flexDirection: 'row', alignItems: 'center', gap: 12, padding: 4 },
mitteilungenTitle: { fontSize: 13, fontWeight: '700', color: '#1E293B' },
mitteilungenSub: { fontSize: 11, color: '#64748B', marginTop: 1 },
// Logout
logoutBtn: {
marginTop: 4,
backgroundColor: '#FEF2F2',
borderRadius: 14,
borderWidth: 1,
borderColor: '#FECACA',
paddingVertical: 14,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: 8,
},
logoutText: {
color: '#B91C1C',
fontSize: 14,
fontWeight: '800',
textTransform: 'uppercase',
letterSpacing: 0.8,
},
footer: {
textAlign: 'center',
marginTop: 4,
fontSize: 10,
fontWeight: '700',
letterSpacing: 1,
color: '#94A3B8',
textTransform: 'uppercase',
marginTop: 4, backgroundColor: '#FEF2F2', borderRadius: 14,
borderWidth: 1, borderColor: '#FECACA', paddingVertical: 14,
flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8,
},
logoutText: { color: '#B91C1C', fontSize: 14, fontWeight: '800', textTransform: 'uppercase', letterSpacing: 0.8 },
footer: { textAlign: 'center', marginTop: 4, fontSize: 10, fontWeight: '700', letterSpacing: 1, color: '#94A3B8', textTransform: 'uppercase' },
})