chahinebrini 312c668ae9 feat(onboarding): back-button between steps + language switcher on welcome
Back-Button:
- OnboardingNavContext liefert der Shell einen optionalen goBack-Handler
  (kein prop-drilling durch 8 Slides).
- OnboardingShell: chevron-back links neben der Progress-Bar wenn goBack
  gesetzt ist.
- Controller: goToLinearPrevious() + BACK_ALLOWED-Liste. Back nur auf
  privacy/nickname/diga_choice/plan/payment — NICHT welcome (erste),
  done (final), diga_code (eigener onBack), protection (Backend-Step +
  Permission-Flow).

Language-Switcher:
- WelcomeSlide: 4 Sprach-Pills (DE/EN/FR/AR) oben rechts. User kommt
  während Onboarding nicht zu Settings — sonst kein Weg die Sprache
  zu wechseln. setLanguage persistiert + flippt RTL für AR.
2026-05-20 04:20:22 +02:00

143 lines
3.9 KiB
TypeScript

import { Text, TouchableOpacity, View } from 'react-native';
import { useTranslation } from 'react-i18next';
import { Ionicons } from '@expo/vector-icons';
import { useColors } from '../../../lib/theme';
import { useLanguageStore, type AppLanguage } from '../../../stores/language';
import { OnboardingShell } from '../OnboardingShell';
import { LyraBubble } from '../LyraBubble';
import { CTABar } from '../CTABar';
const LANGS: { code: AppLanguage; label: string }[] = [
{ code: 'de', label: 'DE' },
{ code: 'en', label: 'EN' },
{ code: 'fr', label: 'FR' },
{ code: 'ar', label: 'AR' },
];
/**
* Kompakter Sprach-Umschalter — nur auf der Welcome-Slide. Während des
* Onboardings kommt der User nicht zu Settings, kann die Sprache also sonst
* nirgends ändern. setLanguage persistiert in AsyncStorage + flippt RTL für AR.
*/
function LanguagePills({ colors }: { colors: import('../../../lib/theme').ColorScheme }) {
const language = useLanguageStore((s) => s.language);
const setLanguage = useLanguageStore((s) => s.setLanguage);
return (
<View style={{ flexDirection: 'row', gap: 8, justifyContent: 'flex-end' }}>
{LANGS.map((l) => {
const active = language === l.code;
return (
<TouchableOpacity
key={l.code}
onPress={() => setLanguage(l.code)}
activeOpacity={0.7}
style={{
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 8,
backgroundColor: active ? colors.brandOrange : colors.surfaceElevated,
}}
>
<Text
style={{
fontFamily: 'Nunito_700Bold',
fontSize: 13,
color: active ? '#ffffff' : colors.textMuted,
}}
>
{l.label}
</Text>
</TouchableOpacity>
);
})}
</View>
);
}
/**
* Slide 1: Lyra stellt sich vor + erklärt die Mission in einem Satz.
* Hat den langen Empathie-Touch, weil's der erste Eindruck nach Signup ist.
*/
export function WelcomeSlide({
onNext,
current,
total,
}: {
onNext: () => void;
current: number;
total: number;
}) {
const { t } = useTranslation();
const colors = useColors();
return (
<OnboardingShell
current={current}
total={total}
cta={<CTABar primaryLabel={t('onboarding.welcome.cta_primary')} onPrimary={onNext} />}
>
<View style={{ marginBottom: 16 }}>
<LanguagePills colors={colors} />
</View>
<LyraBubble text={t('onboarding.lyra.welcome.body')} emotion="happy" />
<View style={{ marginTop: 28, gap: 12 }}>
<BulletRow
icon="eye-off-outline"
text={t('onboarding.welcome.bullet_anon')}
colors={colors}
/>
<BulletRow
icon="shield-checkmark-outline"
text={t('onboarding.welcome.bullet_protect')}
colors={colors}
/>
<BulletRow
icon="people-outline"
text={t('onboarding.welcome.bullet_community')}
colors={colors}
/>
</View>
</OnboardingShell>
);
}
function BulletRow({
icon,
text,
colors,
}: {
icon: keyof typeof Ionicons.glyphMap;
text: string;
colors: import('../../../lib/theme').ColorScheme;
}) {
return (
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
<View
style={{
width: 36,
height: 36,
borderRadius: 12,
backgroundColor: colors.surfaceElevated,
alignItems: 'center',
justifyContent: 'center',
}}
>
<Ionicons name={icon} size={20} color={colors.brandOrange} />
</View>
<Text
style={{
flex: 1,
fontFamily: 'Nunito_600SemiBold',
fontSize: 15,
lineHeight: 21,
color: colors.text,
}}
>
{text}
</Text>
</View>
);
}