203 lines
10 KiB
TypeScript
203 lines
10 KiB
TypeScript
import React from 'react';
|
|
import { ImageBackground, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
import { useRouter } from 'expo-router';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { useSafeAnalytics } from '../../services/analytics';
|
|
import { ThemeBackdrop } from '../../components/ThemeBackdrop';
|
|
import { useColors } from '../../constants/Colors';
|
|
import { useApp } from '../../context/AppContext';
|
|
|
|
const ONBOARDING_BACKGROUND = {
|
|
light: '#fbfaf3',
|
|
dark: '#0a110b',
|
|
};
|
|
|
|
const getHealthOnboardingCopy = (language: 'de' | 'en' | 'es') => {
|
|
if (language === 'de') {
|
|
return {
|
|
step: 'Schritt 4 von 4',
|
|
title: 'Wo ist der Health-Scan?',
|
|
subtitle: 'Du findest ihn auf jeder gespeicherten Pflanze, direkt unter der Beschreibung.',
|
|
buttonPreview: 'Health-Scan starten',
|
|
cta: 'Weiter',
|
|
skip: 'Spaeter',
|
|
flow: ['Pflanze scannen', 'Speichern', 'Detailseite oeffnen', 'Health-Scan starten'],
|
|
outputTitle: 'Was du danach bekommst',
|
|
outputs: [
|
|
'Gesundheits-Score mit Status: stabil, beobachten oder kritisch.',
|
|
'Ausfuehrliche Analyse mit sichtbaren Hinweisen und Unsicherheit.',
|
|
'Wahrscheinlichste Ursachen mit Confidence-Werten.',
|
|
'Sofortmassnahmen plus konkreter 7-Tage-Pflegeplan.',
|
|
],
|
|
guidanceNote: 'Tipp: Fotografiere die ganze Pflanze, die Blattunterseiten und die Erde. Je klarer das Foto, desto genauer wird der Plan.',
|
|
};
|
|
}
|
|
|
|
if (language === 'es') {
|
|
return {
|
|
step: 'Paso 4 de 4',
|
|
title: 'Donde esta el health-scan?',
|
|
subtitle: 'Lo encuentras en cada planta guardada, justo debajo de la descripcion.',
|
|
buttonPreview: 'Iniciar health-scan',
|
|
cta: 'Continuar',
|
|
skip: 'Mas tarde',
|
|
flow: ['Escanear planta', 'Guardar', 'Abrir detalle', 'Iniciar health-scan'],
|
|
outputTitle: 'Que recibes despues',
|
|
outputs: [
|
|
'Puntaje de salud con estado: estable, observar o critico.',
|
|
'Analisis detallado con senales visibles e incertidumbre.',
|
|
'Causas probables con valores de confianza.',
|
|
'Acciones inmediatas y plan concreto de 7 dias.',
|
|
],
|
|
guidanceNote: 'Consejo: fotografia la planta completa, el reverso de las hojas y el sustrato. Cuanto mas clara sea la foto, mas preciso sera el plan.',
|
|
};
|
|
}
|
|
|
|
return {
|
|
step: 'Step 4 of 4',
|
|
title: 'Where is the health scan?',
|
|
subtitle: 'It lives on every saved plant, directly below the plant description.',
|
|
buttonPreview: 'Start health scan',
|
|
cta: 'Continue',
|
|
skip: 'Later',
|
|
flow: ['Scan plant', 'Save', 'Open detail', 'Start health scan'],
|
|
outputTitle: 'What you get after',
|
|
outputs: [
|
|
'Health score with stable, watch, or critical status.',
|
|
'Detailed analysis with visible signals and uncertainty.',
|
|
'Most likely causes with confidence values.',
|
|
'Immediate actions plus a concrete 7-day care plan.',
|
|
],
|
|
guidanceNote: 'Tip: photograph the full plant, leaf undersides, and the soil. The clearer the photo, the more precise the plan.',
|
|
};
|
|
};
|
|
|
|
export default function HealthCheckOnboardingScreen() {
|
|
const router = useRouter();
|
|
const posthog = useSafeAnalytics();
|
|
const { isDarkMode, colorPalette, language, billingSummary } = useApp();
|
|
const colors = useColors(isDarkMode, colorPalette);
|
|
const screenBackground = isDarkMode ? ONBOARDING_BACKGROUND.dark : ONBOARDING_BACKGROUND.light;
|
|
const copy = getHealthOnboardingCopy(language);
|
|
|
|
const finish = (skipped = false) => {
|
|
posthog.capture('onboarding_health_check_explained', {
|
|
skipped,
|
|
plan: billingSummary?.entitlement?.plan ?? 'free',
|
|
});
|
|
const hasActiveEntitlement = billingSummary?.entitlement?.plan === 'pro'
|
|
&& billingSummary?.entitlement?.status === 'active';
|
|
router.replace(hasActiveEntitlement ? '/(tabs)' : '/profile/billing');
|
|
};
|
|
|
|
return (
|
|
<View style={[styles.container, { backgroundColor: screenBackground }]}>
|
|
{isDarkMode ? <ThemeBackdrop colors={colors} /> : null}
|
|
<SafeAreaView style={styles.safeArea} edges={['top', 'left', 'right', 'bottom']}>
|
|
<View style={styles.header}>
|
|
<View style={[styles.stepPill, { backgroundColor: colors.primarySoft, borderColor: colors.border }]}>
|
|
<Text style={[styles.stepLabel, { color: colors.primaryDark }]}>{copy.step}</Text>
|
|
</View>
|
|
<Text style={[styles.title, { color: colors.text }]}>{copy.title}</Text>
|
|
<Text style={[styles.subtitle, { color: colors.textSecondary }]}>{copy.subtitle}</Text>
|
|
</View>
|
|
|
|
<ScrollView contentContainerStyle={styles.content} showsVerticalScrollIndicator={false}>
|
|
<ImageBackground
|
|
source={require('../../assets/onboarding_health_scan_mockup.png')}
|
|
style={[styles.illustration, { borderColor: colors.border }]}
|
|
imageStyle={styles.illustrationImage}
|
|
resizeMode="cover"
|
|
>
|
|
<View style={[styles.illustrationOverlay, { backgroundColor: isDarkMode ? 'rgba(8, 14, 9, 0.08)' : 'rgba(251, 250, 243, 0.04)' }]} />
|
|
</ImageBackground>
|
|
|
|
<View style={[styles.flowCard, { backgroundColor: colors.surface, borderColor: colors.border }]}>
|
|
{copy.flow.map((item, index) => (
|
|
<View key={item} style={styles.flowRow}>
|
|
<View style={[styles.flowIndex, { backgroundColor: index === 3 ? colors.primary : colors.surfaceMuted }]}>
|
|
<Text style={[styles.flowIndexText, { color: index === 3 ? colors.onPrimary : colors.textMuted }]}>
|
|
{index + 1}
|
|
</Text>
|
|
</View>
|
|
<Text style={[styles.flowText, { color: colors.text }]}>{item}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
|
|
<View style={[styles.outputCard, { backgroundColor: colors.surface, borderColor: colors.border }]}>
|
|
<Text style={[styles.outputTitle, { color: colors.text }]}>{copy.outputTitle}</Text>
|
|
{copy.outputs.map((item) => (
|
|
<View key={item} style={styles.outputRow}>
|
|
<Ionicons name="checkmark-circle" size={16} color={colors.success} />
|
|
<Text style={[styles.outputText, { color: colors.textSecondary }]}>{item}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
|
|
<View style={[styles.guidanceCard, { backgroundColor: colors.primarySoft, borderColor: colors.border }]}>
|
|
<Ionicons name="camera-outline" size={18} color={colors.primaryDark} />
|
|
<Text style={[styles.guidanceText, { color: colors.primaryDark }]}>{copy.guidanceNote}</Text>
|
|
</View>
|
|
</ScrollView>
|
|
|
|
<View style={styles.footer}>
|
|
<TouchableOpacity
|
|
style={[styles.secondaryBtn, { borderColor: colors.borderStrong, backgroundColor: colors.surface }]}
|
|
onPress={() => finish(true)}
|
|
>
|
|
<Text style={[styles.secondaryBtnText, { color: colors.text }]}>{copy.skip}</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity style={[styles.primaryBtn, { backgroundColor: colors.primary }]} onPress={() => finish(false)}>
|
|
<Text style={[styles.primaryBtnText, { color: colors.onPrimary }]}>{copy.cta}</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</SafeAreaView>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: { flex: 1 },
|
|
safeArea: { flex: 1, paddingHorizontal: 20, paddingTop: 24, paddingBottom: 20 },
|
|
header: { gap: 9, marginBottom: 18 },
|
|
stepPill: { alignSelf: 'flex-start', borderWidth: 1, borderRadius: 999, paddingHorizontal: 12, paddingVertical: 7 },
|
|
stepLabel: { fontSize: 12, fontWeight: '800', textTransform: 'uppercase', letterSpacing: 0.4 },
|
|
title: { fontSize: 30, lineHeight: 34, fontWeight: '900' },
|
|
subtitle: { fontSize: 14, lineHeight: 20 },
|
|
content: { gap: 14, paddingBottom: 12 },
|
|
illustration: { height: 230, borderRadius: 28, borderWidth: 1, justifyContent: 'center', overflow: 'hidden' },
|
|
illustrationImage: { borderRadius: 28 },
|
|
illustrationOverlay: { ...StyleSheet.absoluteFillObject },
|
|
phone: { width: 178, minHeight: 156, borderRadius: 26, borderWidth: 1, padding: 12, gap: 10, marginLeft: 16 },
|
|
phoneHeader: { height: 58, borderRadius: 18, justifyContent: 'flex-end', padding: 10 },
|
|
phoneTitle: { fontSize: 13, fontWeight: '800' },
|
|
phoneRows: { gap: 8 },
|
|
phoneRowLong: { height: 8, borderRadius: 999 },
|
|
phoneRowShort: { width: '66%', height: 8, borderRadius: 999 },
|
|
healthButtonPreview: { height: 34, borderRadius: 14, borderWidth: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 5 },
|
|
healthButtonText: { fontSize: 10, fontWeight: '800' },
|
|
scanCard: { position: 'absolute', right: 16, bottom: 20, width: 136, borderRadius: 20, borderWidth: 1, padding: 14, gap: 7 },
|
|
scanScore: { fontSize: 25, lineHeight: 29, fontWeight: '900' },
|
|
scanLabel: { fontSize: 11, fontWeight: '800', textTransform: 'uppercase' },
|
|
scanLine: { height: 8, borderRadius: 999 },
|
|
scanLineShort: { width: '68%', height: 8, borderRadius: 999 },
|
|
flowCard: { borderRadius: 18, borderWidth: 1, padding: 14, gap: 10 },
|
|
flowRow: { flexDirection: 'row', alignItems: 'center', gap: 10 },
|
|
flowIndex: { width: 26, height: 26, borderRadius: 13, alignItems: 'center', justifyContent: 'center' },
|
|
flowIndexText: { fontSize: 12, fontWeight: '900' },
|
|
flowText: { flex: 1, fontSize: 14, fontWeight: '700' },
|
|
outputCard: { borderRadius: 18, borderWidth: 1, padding: 16, gap: 11 },
|
|
outputTitle: { fontSize: 15, fontWeight: '800' },
|
|
outputRow: { flexDirection: 'row', alignItems: 'flex-start', gap: 9 },
|
|
outputText: { flex: 1, fontSize: 13, lineHeight: 18 },
|
|
guidanceCard: { borderRadius: 18, borderWidth: 1, padding: 14, flexDirection: 'row', alignItems: 'flex-start', gap: 10 },
|
|
guidanceText: { flex: 1, fontSize: 12, lineHeight: 18, fontWeight: '600' },
|
|
footer: { flexDirection: 'row', gap: 12, marginTop: 12 },
|
|
secondaryBtn: { flex: 1, height: 52, borderRadius: 16, borderWidth: 1.5, alignItems: 'center', justifyContent: 'center' },
|
|
secondaryBtnText: { fontSize: 15, fontWeight: '600' },
|
|
primaryBtn: { flex: 1.3, height: 52, borderRadius: 16, alignItems: 'center', justifyContent: 'center' },
|
|
primaryBtnText: { fontSize: 15, fontWeight: '700' },
|
|
});
|