chahinebrini 7ad523f8ba feat(rebreak-native): phase 2 sheet standardisation — SheetFieldStack + FormSheet migrations
PostCommentsSheet:
- Fix Resize-Bug: PanResponder nur auf Grabber+Header, kein onStartShouldSetPanResponderCapture
  (das stahl Touch-Events von der FlatList und brach Drag-Resize)
- Height-Limits (MAX/MIN/INITIAL) als Refs in PanResponder-Closure, damit sie nicht
  auf den ersten-Render-Stand eingefroren werden
- Keyboard-Show/-Hide animiert currentHeight korrekt ohne den Resize-Referenzpunkt
  zu verlieren
- Avatar in CommentRow: resolveAvatar() wenn authorAvatar vorhanden, Initialen-Fallback
  sonst. Bereit sobald Backend authorAvatar in Comments-Response mitliefert.
- Alle Pressable durch TouchableOpacity ersetzt

SheetFieldStack (neu):
- Progressives Multi-Input-Pattern als FormSheet-Inhalt
- Ausgefüllte Felder werden als antippbare Chips (mit Stift-Icon) nach oben verschoben
- Aktives Feld: TextInput + →/✓-Button (letztes Feld = Checkmark)
- Validate + Normalize pro Feld, Fehleranzeige unter dem Input
- suffix-Slot für Eye-Toggle etc.
- Nach letztem Feld: Keyboard.dismiss() + children (Rest des Formulars) erscheint

Migriert auf FormSheet + SheetFieldStack:
- ConnectMailSheet: Grid-View unveraendert; Form-View (email+password) via SheetFieldStack;
  Zurück/Abbrechen-Header-Buttons entfernt (Schliessen = Swipe/Backdrop)
- EditMailAccountSheet: single-password-field via SheetFieldStack; Cancel-Header-Button weg
- AddDomainSheet: domain-field via SheetFieldStack; Favicon-Preview+Warning+Checkbox+Button
  als children; Cancel-Header-Button weg
- CreateRoomSheet: name+description via SheetFieldStack; Public-Toggle+JoinMode+Buttons
  als children; Abbrechen-Button bleibt (kein Header-Button, design-OK)

useSheetKeyboardLift: geloescht (keine Aufrufer mehr nach Migration)
KeyboardAwareSheet bleibt (AddMacSheet + AddWindowsSheet nutzen es noch)

tsc --noEmit: gruen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:37:46 +02:00

154 lines
4.6 KiB
TypeScript

import { useState } from 'react';
import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useTranslation } from 'react-i18next';
import { useMailConnect } from '../../hooks/useMailConnect';
import { useColors } from '../../lib/theme';
import { humanizeMailError } from '../../lib/mailErrors';
import { FormSheet } from '../FormSheet';
import { SheetFieldStack } from '../SheetFieldStack';
type Props = {
visible: boolean;
email: string;
onClose: () => void;
onSuccess: () => void;
};
/**
* Sheet zum Aktualisieren des App-Passworts eines bereits verbundenen Postfachs.
* Nutzt POST /api/mail/connect (upsert) — Backend ersetzt verschlüsseltes Passwort.
*/
export function EditMailAccountSheet({ visible, email, onClose, onSuccess }: Props) {
const { t } = useTranslation();
const { connect, connecting, error: connectError } = useMailConnect();
const [password, setPassword] = useState('');
const [passwordVisible, setPasswordVisible] = useState(false);
const [formError, setFormError] = useState<string | null>(null);
const [fieldsComplete, setFieldsComplete] = useState(false);
function handleClose() {
setPassword('');
setPasswordVisible(false);
setFormError(null);
setFieldsComplete(false);
onClose();
}
async function handleSave() {
setFormError(null);
const result = await connect({ email, password });
if (result.ok) {
handleClose();
onSuccess();
} else {
setFormError(result.error ?? t('mail.connect_failed'));
}
}
return (
<FormSheet
visible={visible}
onClose={handleClose}
title={t('mail.edit_account_title')}
initialHeightPct={0.5}
growWithKeyboard
>
<SheetFieldStack
fields={[
{
key: 'password',
label: t('mail.form_password_label'),
placeholder: t('mail.app_password_placeholder'),
value: password,
onChangeText: (v) => { setPassword(v); setFormError(null); },
secureTextEntry: !passwordVisible,
autoCapitalize: 'none',
autoCorrect: false,
validate: (v) =>
v.trim().length === 0 ? t('mail.form_fields_required') : undefined,
suffix: (
<TouchableOpacity
activeOpacity={0.7}
onPress={() => setPasswordVisible((p) => !p)}
hitSlop={8}
>
<Ionicons
name={passwordVisible ? 'eye-off-outline' : 'eye-outline'}
size={18}
color="#737373"
/>
</TouchableOpacity>
),
},
]}
onComplete={() => setFieldsComplete(true)}
>
{/* Hint + Error + Save-Button */}
<Text
style={{
fontSize: 13,
fontFamily: 'Nunito_400Regular',
color: '#6b7280',
lineHeight: 18,
marginBottom: 10,
}}
>
{t('mail.edit_account_subtitle', { email })}
</Text>
{(formError ?? connectError) && (
<View
style={{
backgroundColor: '#fef2f2',
borderRadius: 10,
padding: 12,
flexDirection: 'row',
gap: 8,
alignItems: 'flex-start',
marginBottom: 10,
}}
>
<Ionicons name="alert-circle" size={16} color="#dc2626" style={{ marginTop: 1 }} />
<Text
style={{
flex: 1,
fontSize: 12,
fontFamily: 'Nunito_400Regular',
color: '#dc2626',
}}
>
{formError ?? t(humanizeMailError(connectError))}
</Text>
</View>
)}
<TouchableOpacity
activeOpacity={0.85}
onPress={handleSave}
disabled={connecting}
style={{ marginTop: 4, marginBottom: 12 }}
>
<View
style={{
paddingVertical: 14,
borderRadius: 12,
backgroundColor: connecting ? '#bfdbfe' : '#007AFF',
alignItems: 'center',
}}
>
{connecting ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={{ fontSize: 15, fontFamily: 'Nunito_700Bold', color: '#fff' }}>
{t('mail.edit_account_save')}
</Text>
)}
</View>
</TouchableOpacity>
</SheetFieldStack>
</FormSheet>
);
}