From a3f892ddaceff613e8577be85e2c48e7b5d8eacf Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Tue, 12 May 2026 23:39:22 +0200 Subject: [PATCH] fix(native/mail): duplicate add-button in empty state + intro hints in ConnectMailSheet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - mail.tsx: hide section-header "+" button when accounts.length === 0 — MailEmptyState's CTA is the sole add trigger; also replaces Pressable with TouchableOpacity - MailEmptyState: Pressable → TouchableOpacity (no-Pressable rule) - SheetFieldStack: add optional `intro?: ReactNode` prop rendered in a flexShrink:1 ScrollView above chips/active-input so it compresses gracefully when the keyboard is up - ConnectMailSheet: move app-password guide + green AES block into `intro` prop so they're visible from the start, before the user types anything Co-Authored-By: Claude Sonnet 4.6 --- apps/rebreak-native/app/(app)/mail.tsx | 108 ++++--- .../components/SheetFieldStack.tsx | 270 ++++++++++-------- .../components/mail/ConnectMailSheet.tsx | 151 +++++----- .../components/mail/MailEmptyState.tsx | 12 +- 4 files changed, 280 insertions(+), 261 deletions(-) diff --git a/apps/rebreak-native/app/(app)/mail.tsx b/apps/rebreak-native/app/(app)/mail.tsx index 1c8eab4..9a29a52 100644 --- a/apps/rebreak-native/app/(app)/mail.tsx +++ b/apps/rebreak-native/app/(app)/mail.tsx @@ -5,6 +5,7 @@ import { Pressable, ScrollView, Text, + TouchableOpacity, View, } from 'react-native'; import { useBottomTabBarHeight } from 'react-native-bottom-tabs'; @@ -173,62 +174,59 @@ export default function MailScreen() { )} - {/* Section header with prominent + button */} - - - - {t('mail.section_accounts')} - - - {maxAccounts === Infinity - ? t('mail.section_accounts_count_unlimited', { used: accounts.length }) - : t('mail.section_accounts_count', { - used: accounts.length, - max: maxAccounts, - })} - - - - - + + + )} {/* Account cards or empty */} {accounts.length === 0 ? ( diff --git a/apps/rebreak-native/components/SheetFieldStack.tsx b/apps/rebreak-native/components/SheetFieldStack.tsx index 35febfb..5185efa 100644 --- a/apps/rebreak-native/components/SheetFieldStack.tsx +++ b/apps/rebreak-native/components/SheetFieldStack.tsx @@ -27,6 +27,13 @@ export type SheetField = { type Props = { fields: SheetField[]; + /** + * Immer sichtbarer Bereich über Chips + aktivem Input — für Hinweise, die der User + * sehen soll BEVOR er tippt. Wird in einer eigenen ScrollView mit `flexShrink:1` + * gerendert, sodass er bei kleinem verfügbaren Platz (Tastatur offen) schrumpft, + * der Eingabebereich aber nie weggedrückt wird. + */ + intro?: ReactNode; /** Rendert sich nach dem letzten Feld — sichtbar sobald alle Felder ausgefüllt sind. */ children?: ReactNode; onComplete?: () => void; @@ -41,7 +48,7 @@ type Props = { * * Wird als `children` von `` benutzt. */ -export function SheetFieldStack({ fields, children, onComplete }: Props) { +export function SheetFieldStack({ fields, intro, children, onComplete }: Props) { const colors = useColors(); const [activeIndex, setActiveIndex] = useState(0); const [fieldErrors, setFieldErrors] = useState>({}); @@ -84,142 +91,157 @@ export function SheetFieldStack({ fields, children, onComplete }: Props) { const isLast = activeIndex === fields.length - 1; return ( - - {/* Abgeschlossene Felder als Chips */} - {fields.slice(0, activeIndex).map((field, index) => ( - goToField(index)} - style={{ - flexDirection: 'row', - alignItems: 'center', - backgroundColor: colors.surface, - borderWidth: 1, - borderColor: colors.border, - borderRadius: 12, - paddingHorizontal: 14, - paddingVertical: 10, - gap: 10, - }} + + {/* Intro: immer sichtbar, schrumpft bei wenig Platz (Tastatur offen) */} + {intro != null && ( + - - - {field.label} - - - {field.secureTextEntry ? '••••••••' : field.value} - - - - - ))} + {intro} + + )} - {/* Aktives Feld */} - {!allDone && ( - - + {/* Abgeschlossene Felder als Chips */} + {fields.slice(0, activeIndex).map((field, index) => ( + goToField(index)} style={{ - fontSize: 12, - fontFamily: 'Nunito_600SemiBold', - color: colors.textMuted, - marginBottom: 6, + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.surface, + borderWidth: 1, + borderColor: colors.border, + borderRadius: 12, + paddingHorizontal: 14, + paddingVertical: 10, + gap: 10, }} > - {fields[activeIndex].label} - - - - { - fields[activeIndex].onChangeText(v); - if (fieldErrors[fields[activeIndex].key]) { - setFieldErrors((prev) => { - const next = { ...prev }; - delete next[fields[activeIndex].key]; - return next; - }); - } - }} - placeholder={fields[activeIndex].placeholder} - placeholderTextColor={colors.textMuted} - keyboardType={fields[activeIndex].keyboardType ?? 'default'} - secureTextEntry={fields[activeIndex].secureTextEntry} - autoCapitalize={fields[activeIndex].autoCapitalize ?? 'sentences'} - autoCorrect={fields[activeIndex].autoCorrect ?? true} - returnKeyType={isLast ? 'done' : 'next'} - onSubmitEditing={advanceOrFinish} - blurOnSubmit={false} - style={{ - flex: 1, - paddingVertical: 12, - fontSize: 15, - fontFamily: 'Nunito_400Regular', - color: colors.text, - }} - /> - {fields[activeIndex].suffix} + + + {field.label} + + + {field.secureTextEntry ? '••••••••' : field.value} + + + + ))} - - - - - {fieldErrors[fields[activeIndex].key] && ( + {/* Aktives Feld */} + {!allDone && ( + - {fieldErrors[fields[activeIndex].key]} + {fields[activeIndex].label} - )} - - )} + + + { + fields[activeIndex].onChangeText(v); + if (fieldErrors[fields[activeIndex].key]) { + setFieldErrors((prev) => { + const next = { ...prev }; + delete next[fields[activeIndex].key]; + return next; + }); + } + }} + placeholder={fields[activeIndex].placeholder} + placeholderTextColor={colors.textMuted} + keyboardType={fields[activeIndex].keyboardType ?? 'default'} + secureTextEntry={fields[activeIndex].secureTextEntry} + autoCapitalize={fields[activeIndex].autoCapitalize ?? 'sentences'} + autoCorrect={fields[activeIndex].autoCorrect ?? true} + returnKeyType={isLast ? 'done' : 'next'} + onSubmitEditing={advanceOrFinish} + blurOnSubmit={false} + style={{ + flex: 1, + paddingVertical: 12, + fontSize: 15, + fontFamily: 'Nunito_400Regular', + color: colors.text, + }} + /> + {fields[activeIndex].suffix} + - {/* Rest des Formulars — sichtbar wenn alle Felder durch */} - {allDone && children} - + + + + + {fieldErrors[fields[activeIndex].key] && ( + + {fieldErrors[fields[activeIndex].key]} + + )} + + )} + + {/* Rest des Formulars — sichtbar wenn alle Felder durch */} + {allDone && children} + + ); } diff --git a/apps/rebreak-native/components/mail/ConnectMailSheet.tsx b/apps/rebreak-native/components/mail/ConnectMailSheet.tsx index e746531..40e17b5 100644 --- a/apps/rebreak-native/components/mail/ConnectMailSheet.tsx +++ b/apps/rebreak-native/components/mail/ConnectMailSheet.tsx @@ -189,92 +189,93 @@ export function ConnectMailSheet({ visible, onClose, onSuccess }: Props) { ), }, ]} - onComplete={() => setFieldsComplete(true)} - > - {/* App-Password-Guide — über den Datenschutz-Hinweis */} - {selectedProvider && selectedProvider.id !== 'other' && ( - - - - - {t('mail.app_password_required_title')} - - + {/* App-Password-Guide — provider-spezifisch, nicht für 'other' */} + {selectedProvider && selectedProvider.id !== 'other' && ( + - {t(selectedProvider.guideKey)} - - {selectedProvider.guideUrl.length > 0 && ( - Linking.openURL(selectedProvider.guideUrl)} - > + + + + {t('mail.app_password_required_title')} + - {t('mail.app_password_open_link')} → + {t(selectedProvider.guideKey)} - - )} + {selectedProvider.guideUrl.length > 0 && ( + Linking.openURL(selectedProvider.guideUrl)} + > + + {t('mail.app_password_open_link')} → + + + )} + + + )} + + {/* Datenschutz-Zusicherung — immer sichtbar */} + + + + {t('mail.form_privacy_note')} + - )} - - {/* Datenschutz-Hinweis */} - - - - {t('mail.form_privacy_note')} - - - + } + onComplete={() => setFieldsComplete(true)} + > {/* Fehler */} {(formError ?? (connectError ? t(humanizeMailError(connectError)) : null)) && ( {/* CTA */} - ({ - opacity: pressed ? 0.85 : 1, - alignSelf: 'stretch', - })} + activeOpacity={0.85} + style={{ alignSelf: 'stretch' }} > - + ); }