feat: Implement comprehensive member management with user accounts, roles, and password handling for admin and mobile applications.

This commit is contained in:
Timo Knuth
2026-02-27 18:50:17 +01:00
parent 253c3c1c6d
commit 4863d032d9
12 changed files with 148 additions and 115 deletions

View File

@@ -57,7 +57,6 @@ function ChatTabIcon({ color, focused }: { color: string; focused: boolean }) {
function ForcePasswordChangeScreen() {
const { setSession, signOut } = useAuthStore()
const [current, setCurrent] = useState('')
const [next, setNext] = useState('')
const [confirm, setConfirm] = useState('')
const [loading, setLoading] = useState(false)
@@ -65,32 +64,39 @@ function ForcePasswordChangeScreen() {
async function handleSubmit() {
setError('')
if (!current) { setError('Bitte temporäres Passwort eingeben.'); return }
if (next.length < 8) { setError('Das neue Passwort muss mindestens 8 Zeichen haben.'); return }
if (next !== confirm) { setError('Die Passwörter stimmen nicht überein.'); return }
setLoading(true)
const result = await authClient.changePassword({ currentPassword: current, newPassword: next })
setLoading(false)
if (result.error) {
setError(result.error.message ?? 'Passwort konnte nicht geändert werden.')
return
}
// Refresh session — mustChangePassword is now false
const sessionResult = await authClient.getSession()
if (sessionResult?.data?.user) {
const u = sessionResult.data.user as any
await setSession({
user: {
id: u.id,
email: u.email,
name: u.name,
mustChangePassword: false,
// Set password directly via tRPC (no old password needed — user is already authenticated)
try {
const apiUrl = process.env.EXPO_PUBLIC_API_URL ?? 'http://localhost:3032'
const sessionResult = await authClient.getSession()
const token = (sessionResult?.data as any)?.session?.token
const res = await fetch(`${apiUrl}/api/auth/force-set-password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
credentials: 'include',
body: JSON.stringify({ newPassword: next }),
})
const data = await res.json()
if (!res.ok || data.error) {
setError(data.error ?? 'Passwort konnte nicht geändert werden.')
setLoading(false)
return
}
// Update local session state
if (sessionResult?.data?.user) {
const u = sessionResult.data.user as any
await setSession({ user: { id: u.id, email: u.email, name: u.name, mustChangePassword: false } })
}
} catch (e) {
setError('Verbindungsfehler. Bitte erneut versuchen.')
}
setLoading(false)
}
return (
@@ -100,23 +106,11 @@ function ForcePasswordChangeScreen() {
<View style={fpc.iconWrap}>
<Ionicons name="lock-closed-outline" size={32} color="#003B7E" />
</View>
<Text style={fpc.title}>Passwort ändern</Text>
<Text style={fpc.title}>Passwort festlegen</Text>
<Text style={fpc.subtitle}>
Ihr Administrator hat ein temporäres Passwort vergeben. Bitte legen Sie jetzt Ihr persönliches Passwort fest.
Bitte legen Sie jetzt Ihr persönliches Passwort fest.
</Text>
<View style={fpc.field}>
<Text style={fpc.label}>Temporäres Passwort</Text>
<TextInput
style={fpc.input}
value={current}
onChangeText={setCurrent}
secureTextEntry
placeholder="••••••••"
placeholderTextColor="#CBD5E1"
autoCapitalize="none"
/>
</View>
<View style={fpc.field}>
<Text style={fpc.label}>Neues Passwort</Text>
<TextInput
@@ -166,7 +160,7 @@ function ForcePasswordChangeScreen() {
const fpc = StyleSheet.create({
safe: { flex: 1, backgroundColor: '#F8FAFC' },
content: { flex: 1, justifyContent: 'center', padding: 24 },
content: { flexGrow: 1, justifyContent: 'center', padding: 24, paddingBottom: 40 },
card: {
backgroundColor: '#FFFFFF', borderRadius: 20,
borderWidth: 1, borderColor: '#E2E8F0',