push
This commit is contained in:
@@ -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' },
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user