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' 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] } function InfoRow({ label, value }: { label: string; value?: string | null }) { if (!value) return null return ( {label} {value} ) } export default function ProfilScreen() { const { signOut } = useAuth() const router = useRouter() const utils = trpc.useUtils() const { data: me } = trpc.members.me.useQuery() 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 [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 { 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 ( {/* HERO */} {/* Avatar — View+Text statt nur Text, damit Initials wirklich mittig */} {initials} {name || '–'} {me?.org?.name ?? 'Innung'} {me?.status === 'aktiv' && ( Aktiv )} Verifiziert Mein Account {/* PERSÖNLICHE DATEN */} { if (showPersonal) { setShowPersonal(false) setIsEditing(false) } else { setShowPersonal(true) } }} > Persönliche Daten {showPersonal && !isEditing && ( )} {showPersonal && ( {isEditing ? ( <> Name setEditForm(f => ({ ...f, name: t }))} /> E-Mail setEditForm(f => ({ ...f, email: t }))} /> Telefon setEditForm(f => ({ ...f, telefon: t }))} /> Ort setEditForm(f => ({ ...f, ort: t }))} /> setIsEditing(false)}> Abbrechen {isUpdating ? : Speichern} ) : ( <> {!me?.name && ( Keine Daten vorhanden. )} )} )} {/* BETRIEBSDATEN */} { if (showBetrieb) { setShowBetrieb(false) setIsEditingBetrieb(false) } else { setShowBetrieb(true) } }} > Betriebsdaten {showBetrieb && !isEditingBetrieb && ( )} {showBetrieb && ( {isEditingBetrieb ? ( <> Betrieb setEditBetriebForm(f => ({ ...f, betrieb: t }))} /> Sparte setEditBetriebForm(f => ({ ...f, sparte: t }))} /> setEditBetriebForm(f => ({ ...f, istAusbildungsbetrieb: !f.istAusbildungsbetrieb }))} > Ausbildungsbetrieb setIsEditingBetrieb(false)}> Abbrechen {isUpdating ? : Speichern} ) : ( <> {!me?.betrieb && !me?.sparte && ( Keine Betriebsdaten vorhanden. )} )} )} {!showBetrieb && (me?.betrieb || me?.sparte) && ( {me?.betrieb && {me.betrieb}} {me?.sparte && {me.sparte}} )} {/* MITTEILUNGEN */} setShowMitteilungen((v) => !v)} > Mitteilungen {unreadCount > 0 && ( {unreadCount} )} {showMitteilungen && ( {unreadCount > 0 ? ( router.push('/(app)/chat')} > {unreadCount} ungelesene Nachricht{unreadCount > 1 ? 'en' : ''} Direkt zu den Nachrichten ) : ( Keine neuen Mitteilungen. )} )} {/* SICHERHEIT */} setShowSicherheit(!showSicherheit)} > Sicherheit & Login {showSicherheit && ( {!isChangingPassword ? ( { setPasswordForm({ current: '', next: '', confirm: '' }) setPasswordError('') setIsChangingPassword(true) }} > Passwort ändern ) : ( Aktuelles Passwort setPasswordForm(f => ({ ...f, current: t }))} secureTextEntry placeholder="••••••••" placeholderTextColor="#CBD5E1" autoCapitalize="none" /> Neues Passwort setPasswordForm(f => ({ ...f, next: t }))} secureTextEntry placeholder="Mindestens 8 Zeichen" placeholderTextColor="#CBD5E1" autoCapitalize="none" /> Wiederholen setPasswordForm(f => ({ ...f, confirm: t }))} secureTextEntry placeholder="Neues Passwort wiederholen" placeholderTextColor="#CBD5E1" autoCapitalize="none" /> {!!passwordError && ( {passwordError} )} setIsChangingPassword(false)} disabled={passwordLoading} > Abbrechen { 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 ? : Speichern } )} )} {/* LOGOUT */} void signOut()}> Abmelden InnungsApp Version 2.4.0 ) } const styles = StyleSheet.create({ 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, }, 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' }, // Section sectionTitle: { 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' }, })