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: 120, 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' },
})