273 lines
11 KiB
TypeScript
273 lines
11 KiB
TypeScript
import React, { useEffect, useRef, useState } from 'react';
|
|
import { Animated, Easing, Image, ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { router } from 'expo-router';
|
|
import Svg, { Circle } from 'react-native-svg';
|
|
import { useApp } from '../../context/AppContext';
|
|
import { useColors } from '../../constants/Colors';
|
|
import { useSafeAnalytics } from '../../services/analytics';
|
|
import { PreAuthOnboardingService, PreAuthAnswers } from '../../services/preAuthOnboardingService';
|
|
import { Language } from '../../types';
|
|
|
|
const getCopy = (language: Language) => {
|
|
if (language === 'de') {
|
|
return {
|
|
status: 'Dein Plan wird zusammengestellt…',
|
|
// Schritte beschreiben den Nutzen fuer den Nutzer, nicht unsere Prozesse.
|
|
steps: [
|
|
'Deine Antworten werden ausgewertet',
|
|
'Mögliche Ursachen für dein Symptom werden gewichtet',
|
|
'Prüfschritte für deine Lichtsituation werden ausgewählt',
|
|
'Dein 7-Tage-Plan steht',
|
|
],
|
|
summaryTitle: 'Dein Plan basiert auf',
|
|
noAnswers: 'Deinen Angaben aus dem Gespräch',
|
|
situations: {
|
|
acute: 'Akutes Problem',
|
|
slow: 'Langsame Veränderung',
|
|
prevention: 'Vorbeugung',
|
|
} as Record<string, string>,
|
|
symptoms: {
|
|
yellow: 'Gelbe Blätter',
|
|
brown_tips: 'Braune Spitzen',
|
|
drooping: 'Hängende Blätter',
|
|
spots: 'Flecken oder Belag',
|
|
pests: 'Schädlinge',
|
|
other: 'Anderes Symptom',
|
|
} as Record<string, string>,
|
|
lights: {
|
|
bright_indirect: 'Helles, indirektes Licht',
|
|
low_light: 'Wenig Licht',
|
|
direct_sunlight: 'Direkte Sonne',
|
|
} as Record<string, string>,
|
|
experiences: {
|
|
beginner: 'Einsteigerin',
|
|
intermediate: 'Etwas Erfahrung',
|
|
advanced: 'Erfahren',
|
|
} as Record<string, string>,
|
|
};
|
|
}
|
|
if (language === 'es') {
|
|
return {
|
|
status: 'Preparando tu plan…',
|
|
steps: [
|
|
'Analizando tus respuestas',
|
|
'Ponderando las causas posibles de tu síntoma',
|
|
'Eligiendo pasos de revisión para tu luz',
|
|
'Tu plan de 7 días está listo',
|
|
],
|
|
summaryTitle: 'Tu plan se basa en',
|
|
noAnswers: 'Lo que nos contaste en el chat',
|
|
situations: {
|
|
acute: 'Problema agudo',
|
|
slow: 'Cambio lento',
|
|
prevention: 'Prevención',
|
|
} as Record<string, string>,
|
|
symptoms: {
|
|
yellow: 'Hojas amarillas',
|
|
brown_tips: 'Puntas marrones',
|
|
drooping: 'Hojas caídas',
|
|
spots: 'Manchas o residuo',
|
|
pests: 'Plagas',
|
|
other: 'Otro síntoma',
|
|
} as Record<string, string>,
|
|
lights: {
|
|
bright_indirect: 'Luz brillante indirecta',
|
|
low_light: 'Poca luz',
|
|
direct_sunlight: 'Sol directo',
|
|
} as Record<string, string>,
|
|
experiences: {
|
|
beginner: 'Principiante',
|
|
intermediate: 'Algo de experiencia',
|
|
advanced: 'Con experiencia',
|
|
} as Record<string, string>,
|
|
};
|
|
}
|
|
return {
|
|
status: 'Putting your plan together…',
|
|
steps: [
|
|
'Reviewing your answers',
|
|
'Weighing possible causes for your symptom',
|
|
'Picking checks for your light situation',
|
|
'Your 7-day plan is ready',
|
|
],
|
|
summaryTitle: 'Your plan is based on',
|
|
noAnswers: 'What you told us in the chat',
|
|
situations: {
|
|
acute: 'Acute problem',
|
|
slow: 'Slow change',
|
|
prevention: 'Prevention',
|
|
} as Record<string, string>,
|
|
symptoms: {
|
|
yellow: 'Yellow leaves',
|
|
brown_tips: 'Brown tips',
|
|
drooping: 'Drooping leaves',
|
|
spots: 'Spots or residue',
|
|
pests: 'Pests',
|
|
other: 'Other symptom',
|
|
} as Record<string, string>,
|
|
lights: {
|
|
bright_indirect: 'Bright, indirect light',
|
|
low_light: 'Low light',
|
|
direct_sunlight: 'Direct sun',
|
|
} as Record<string, string>,
|
|
experiences: {
|
|
beginner: 'Beginner',
|
|
intermediate: 'Some experience',
|
|
advanced: 'Experienced',
|
|
} as Record<string, string>,
|
|
};
|
|
};
|
|
|
|
const STEP_THRESHOLDS = [25, 50, 75, 95];
|
|
const RING_SIZE = 150;
|
|
const RING_STROKE_WIDTH = 7;
|
|
const RING_RADIUS = (RING_SIZE - RING_STROKE_WIDTH) / 2;
|
|
const RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS;
|
|
|
|
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
|
|
|
|
export default function OnboardingPersonalizingScreen() {
|
|
const { language, isDarkMode, colorPalette } = useApp();
|
|
const colors = useColors(isDarkMode, colorPalette);
|
|
const posthog = useSafeAnalytics();
|
|
const copy = getCopy(language);
|
|
const progress = useRef(new Animated.Value(0)).current;
|
|
const [percent, setPercent] = useState(0);
|
|
const [answers, setAnswers] = useState<PreAuthAnswers>({});
|
|
const navigated = useRef(false);
|
|
|
|
// Die eigenen Angaben zurueckspiegeln beweist "es geht um MEINE Pflanze"
|
|
// (Belief 4) durch Demonstration statt durch Behauptung.
|
|
useEffect(() => {
|
|
void PreAuthOnboardingService.getAnswers().then(setAnswers);
|
|
}, []);
|
|
|
|
const summaryChips = [
|
|
answers.situation ? copy.situations[answers.situation] : null,
|
|
answers.symptom ? copy.symptoms[answers.symptom] : null,
|
|
answers.lightLevel ? copy.lights[answers.lightLevel] : null,
|
|
answers.experienceLevel ? copy.experiences[answers.experienceLevel] : null,
|
|
].filter((chip): chip is string => Boolean(chip));
|
|
|
|
const strokeDashoffset = progress.interpolate({
|
|
inputRange: [0, 100],
|
|
outputRange: [RING_CIRCUMFERENCE, 0],
|
|
});
|
|
|
|
useEffect(() => {
|
|
posthog.capture('onboarding_personalizing_viewed');
|
|
const listener = progress.addListener(({ value }) => setPercent(Math.round(value)));
|
|
Animated.timing(progress, {
|
|
toValue: 100,
|
|
duration: 6000,
|
|
easing: Easing.inOut(Easing.cubic),
|
|
useNativeDriver: false,
|
|
}).start(({ finished }) => {
|
|
if (finished && !navigated.current) {
|
|
navigated.current = true;
|
|
setTimeout(() => {
|
|
posthog.capture('paywall_opened', { source: 'onboarding' });
|
|
router.replace('/profile/billing?view=paywall&context=onboarding');
|
|
}, 450);
|
|
}
|
|
});
|
|
return () => progress.removeListener(listener);
|
|
}, [progress, posthog]);
|
|
|
|
return (
|
|
<SafeAreaView style={[styles.safe, { backgroundColor: isDarkMode ? '#0a110b' : '#f8fbef' }]}>
|
|
{/* Scrollbar, damit die Zusammenfassungs-Karte auf kleinen Geraeten und
|
|
bei grosser Systemschrift nicht unten wegfaellt. */}
|
|
<ScrollView
|
|
style={styles.scroll}
|
|
contentContainerStyle={styles.scrollContent}
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
<Text style={[styles.percent, { color: colors.primary }]} maxFontSizeMultiplier={1.2}>{percent}%</Text>
|
|
<View style={styles.ringWrap}>
|
|
<Svg width={RING_SIZE} height={RING_SIZE} style={StyleSheet.absoluteFill}>
|
|
<Circle
|
|
cx={RING_SIZE / 2}
|
|
cy={RING_SIZE / 2}
|
|
r={RING_RADIUS}
|
|
stroke={colors.primarySoft}
|
|
strokeWidth={RING_STROKE_WIDTH}
|
|
fill="none"
|
|
/>
|
|
<AnimatedCircle
|
|
cx={RING_SIZE / 2}
|
|
cy={RING_SIZE / 2}
|
|
r={RING_RADIUS}
|
|
stroke={colors.primary}
|
|
strokeWidth={RING_STROKE_WIDTH}
|
|
fill="none"
|
|
strokeLinecap="round"
|
|
strokeDasharray={`${RING_CIRCUMFERENCE}, ${RING_CIRCUMFERENCE}`}
|
|
strokeDashoffset={strokeDashoffset}
|
|
rotation="-90"
|
|
originX={RING_SIZE / 2}
|
|
originY={RING_SIZE / 2}
|
|
/>
|
|
</Svg>
|
|
<Image source={require('../../assets/paywall_scan_background.png')} style={styles.ringImage} />
|
|
</View>
|
|
<View style={[styles.statusPill, { backgroundColor: colors.surfaceMuted }]}>
|
|
<Ionicons name="sync-outline" size={15} color={colors.textSecondary} />
|
|
<Text style={[styles.statusText, { color: colors.textSecondary }]} maxFontSizeMultiplier={1.3}>{copy.status}</Text>
|
|
</View>
|
|
<View style={styles.checklist}>
|
|
{copy.steps.map((label, index) => {
|
|
const done = percent >= STEP_THRESHOLDS[index];
|
|
return (
|
|
<View key={label} style={styles.checkRow}>
|
|
<Ionicons
|
|
name={done ? 'checkmark-circle' : 'ellipse-outline'}
|
|
size={24}
|
|
color={done ? colors.primary : colors.border}
|
|
/>
|
|
<Text style={[styles.checkLabel, { color: done ? colors.text : colors.textMuted }]} maxFontSizeMultiplier={1.3}>{label}</Text>
|
|
</View>
|
|
);
|
|
})}
|
|
</View>
|
|
<View style={[styles.testimonialCard, { backgroundColor: colors.surface }]}>
|
|
<Text style={[styles.summaryTitle, { color: colors.textMuted }]}>{copy.summaryTitle}</Text>
|
|
<View style={styles.summaryChips}>
|
|
{summaryChips.length > 0 ? (
|
|
summaryChips.map((chip) => (
|
|
<View key={chip} style={[styles.summaryChip, { backgroundColor: colors.primarySoft }]}>
|
|
<Text style={[styles.summaryChipText, { color: colors.primaryDark }]}>{chip}</Text>
|
|
</View>
|
|
))
|
|
) : (
|
|
<Text style={[styles.testimonialText, { color: colors.textSecondary }]}>{copy.noAnswers}</Text>
|
|
)}
|
|
</View>
|
|
</View>
|
|
</ScrollView>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
safe: { flex: 1 },
|
|
scroll: { flex: 1 },
|
|
scrollContent: { flexGrow: 1, alignItems: 'center', paddingHorizontal: 24, paddingTop: 30, paddingBottom: 16 },
|
|
percent: { fontSize: 56, fontWeight: '900', marginBottom: 16 },
|
|
ringWrap: { width: 150, height: 150, alignItems: 'center', justifyContent: 'center', marginBottom: 22 },
|
|
ringImage: { width: 112, height: 112, borderRadius: 56 },
|
|
statusPill: { flexDirection: 'row', alignItems: 'center', gap: 8, borderRadius: 999, paddingHorizontal: 18, paddingVertical: 11, marginBottom: 26 },
|
|
statusText: { fontSize: 14.5, fontWeight: '800' },
|
|
checklist: { alignSelf: 'stretch', gap: 15, marginBottom: 26, paddingHorizontal: 8 },
|
|
checkRow: { flexDirection: 'row', alignItems: 'center', gap: 12 },
|
|
checkLabel: { fontSize: 16.5, fontWeight: '700' },
|
|
testimonialCard: { alignSelf: 'stretch', borderRadius: 18, padding: 16, marginBottom: 14 },
|
|
testimonialText: { fontSize: 14, lineHeight: 20 },
|
|
summaryTitle: { fontSize: 10.5, fontWeight: '900', letterSpacing: 0.9, textTransform: 'uppercase', marginBottom: 10 },
|
|
summaryChips: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 },
|
|
summaryChip: { borderRadius: 999, paddingHorizontal: 12, paddingVertical: 7 },
|
|
summaryChipText: { fontSize: 12.5, fontWeight: '700' },
|
|
});
|