diff --git a/apps/rebreak-native/app/onboarding/index.tsx b/apps/rebreak-native/app/onboarding/index.tsx index 840e6f3..eed7e23 100644 --- a/apps/rebreak-native/app/onboarding/index.tsx +++ b/apps/rebreak-native/app/onboarding/index.tsx @@ -11,6 +11,7 @@ import { PlanSlide } from '../../components/onboarding/slides/PlanSlide'; import { PaymentSlide } from '../../components/onboarding/slides/PaymentSlide'; import { ProtectionSlide } from '../../components/onboarding/slides/ProtectionSlide'; import { DoneSlide } from '../../components/onboarding/slides/DoneSlide'; +import { OnboardingNavProvider } from '../../components/onboarding/OnboardingNavContext'; /** * Duo-Style Onboarding — single route, state-machine intern. @@ -97,6 +98,18 @@ export default function OnboardingScreen() { setSlide(LINEAR_ORDER[idx + 1]); } + function goToLinearPrevious() { + const idx = LINEAR_ORDER.indexOf(slide); + if (idx <= 0) return; + setSlide(LINEAR_ORDER[idx - 1]); + } + + // Back erlaubt nur auf reinen Info-/Auswahl-Slides. NICHT auf: + // welcome (erste), done (final), diga_code (hat eigenen onBack), + // protection (interne Phasen + persistierter Backend-Step + Permission-Flow). + const BACK_ALLOWED: Slide[] = ['privacy', 'nickname', 'diga_choice', 'plan', 'payment']; + const canGoBack = BACK_ALLOWED.includes(slide); + function exitToApp() { router.replace('/(app)'); } @@ -135,42 +148,50 @@ export default function OnboardingScreen() { // Slide-Dispatch ──────────────────────────────────────────────────────────── - switch (slide) { - case 'welcome': - return ; - case 'privacy': - return ; - case 'nickname': - return ; - case 'diga_choice': - return ( - - ); - case 'diga_code': - return ( - setSlide('diga_choice')} - current={current} - total={total} - /> - ); - case 'plan': - return ; - case 'payment': - return ( - - ); - case 'protection': - return ( - - ); - case 'done': - return ; + function renderSlide() { + switch (slide) { + case 'welcome': + return ; + case 'privacy': + return ; + case 'nickname': + return ; + case 'diga_choice': + return ( + + ); + case 'diga_code': + return ( + setSlide('diga_choice')} + current={current} + total={total} + /> + ); + case 'plan': + return ; + case 'payment': + return ( + + ); + case 'protection': + return ( + + ); + case 'done': + return ; + } } + + return ( + + {renderSlide()} + + ); } diff --git a/apps/rebreak-native/components/onboarding/OnboardingNavContext.tsx b/apps/rebreak-native/components/onboarding/OnboardingNavContext.tsx new file mode 100644 index 0000000..d846879 --- /dev/null +++ b/apps/rebreak-native/components/onboarding/OnboardingNavContext.tsx @@ -0,0 +1,30 @@ +import { createContext, useContext, type ReactNode } from 'react'; + +/** + * Stellt der OnboardingShell einen optionalen Back-Handler bereit, ohne + * `onBack` durch alle 8 Slide-Komponenten prop-drillen zu müssen. + * + * Controller (app/onboarding/index.tsx) entscheidet pro Slide ob ein Back + * möglich ist (welcome = erste Slide, done = final → kein Back) und liefert + * entsprechend `goBack` oder `null`. + */ +const OnboardingBackContext = createContext<(() => void) | null>(null); + +export function OnboardingNavProvider({ + goBack, + children, +}: { + goBack: (() => void) | null; + children: ReactNode; +}) { + return ( + + {children} + + ); +} + +/** Gibt den Back-Handler zurück, oder null wenn die aktuelle Slide kein Back erlaubt. */ +export function useOnboardingBack(): (() => void) | null { + return useContext(OnboardingBackContext); +} diff --git a/apps/rebreak-native/components/onboarding/OnboardingShell.tsx b/apps/rebreak-native/components/onboarding/OnboardingShell.tsx index f52fca4..30ecbdf 100644 --- a/apps/rebreak-native/components/onboarding/OnboardingShell.tsx +++ b/apps/rebreak-native/components/onboarding/OnboardingShell.tsx @@ -1,8 +1,10 @@ import { ReactNode } from 'react'; -import { ScrollView, View } from 'react-native'; +import { ScrollView, TouchableOpacity, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { Ionicons } from '@expo/vector-icons'; import { useColors } from '../../lib/theme'; import { SlideProgress } from './SlideProgress'; +import { useOnboardingBack } from './OnboardingNavContext'; /** * Layout-Wrapper für alle Onboarding-Slides. @@ -32,6 +34,7 @@ export function OnboardingShell({ }) { const colors = useColors(); const insets = useSafeAreaInsets(); + const goBack = useOnboardingBack(); return ( @@ -40,9 +43,31 @@ export function OnboardingShell({ paddingTop: insets.top + 12, paddingHorizontal: 20, paddingBottom: 8, + flexDirection: 'row', + alignItems: 'center', + gap: 12, }} > - + {goBack ? ( + + + + ) : null} + + + s.language); + const setLanguage = useLanguageStore((s) => s.setLanguage); + return ( + + {LANGS.map((l) => { + const active = language === l.code; + return ( + setLanguage(l.code)} + activeOpacity={0.7} + style={{ + paddingHorizontal: 12, + paddingVertical: 6, + borderRadius: 8, + backgroundColor: active ? colors.brandOrange : colors.surfaceElevated, + }} + > + + {l.label} + + + ); + })} + + ); +} + /** * 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. @@ -28,6 +76,10 @@ export function WelcomeSlide({ total={total} cta={} > + + + +