// First-run onboarding: builds a rich user profile that personalizes AI assets // and prefills business cards. Shown once (gated by profileStore.onboarded). import { Redirect, Stack, useRouter } from "expo-router"; import { useState } from "react"; import { Pressable, ScrollView, StyleSheet, Text, TextInput, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Chips, MultiChips, wizardInputStyle } from "../src/components/genwizard/Wizard"; import { LinksEditor } from "../src/components/LinksEditor"; import { CardLink } from "../src/stores/localCardsStore"; import { BRAND_COLOR_SWATCHES, EMPTY_PROFILE, Goal, GOAL_OPTIONS, INDUSTRY_OPTIONS, INSPIRATION_OPTIONS, Profile, VIBE_OPTIONS, } from "../src/stores/profileLogic"; import { useProfileStore } from "../src/stores/profileStore"; const STEPS = 8; export default function Onboarding() { const router = useRouter(); const insets = useSafeAreaInsets(); const onboarded = useProfileStore((s) => s.onboarded); const completeOnboarding = useProfileStore((s) => s.completeOnboarding); const [step, setStep] = useState(0); const [p, setP] = useState(EMPTY_PROFILE); const patch = (u: Partial) => setP((cur) => ({ ...cur, ...u })); const toggle = (key: "vibes" | "inspirations" | "brandColors", v: string, max?: number) => setP((cur) => { const has = cur[key].includes(v); let next = has ? cur[key].filter((x) => x !== v) : [...cur[key], v]; if (!has && max && next.length > max) next = next.slice(next.length - max); return { ...cur, [key]: next }; }); if (onboarded) return ; const canNext = step !== 0 || p.displayName.trim().length > 0; const finish = () => { completeOnboarding(p); router.replace("/(tabs)"); }; return ( {Array.from({ length: STEPS }).map((_, i) => ( ))} {step === 0 && ( Your name patch({ displayName: t })} /> Role / title patch({ title: t })} /> )} {step === 1 && ( patch({ industry: v })} /> )} {step === 2 && ( toggle("vibes", v)} /> )} {step === 3 && ( g.label)} value={GOAL_OPTIONS.find((g) => g.value === p.goal)?.label ?? ""} onSelect={(label) => patch({ goal: (GOAL_OPTIONS.find((g) => g.label === label)?.value ?? "") as Goal }) } /> )} {step === 4 && ( {BRAND_COLOR_SWATCHES.map((c) => ( toggle("brandColors", c, 3)} style={[ styles.swatch, { backgroundColor: c }, p.brandColors.includes(c) && styles.swatchOn, ]} /> ))} )} {step === 5 && ( patch({ socialLinks: l })} /> )} {step === 6 && ( toggle("inspirations", v, 2)} /> )} {step === 7 && ( g.value === p.goal)?.label ?? "—"} /> l.url).length}`} /> )} {step > 0 && ( setStep(step - 1)}> Back )} (step === STEPS - 1 ? finish() : setStep(step + 1))} > {step === STEPS - 1 ? "Get started" : "Next"} ); } function Step({ title, subtitle, children }: { title: string; subtitle: string; children: React.ReactNode }) { return ( {title} {subtitle} {children} ); } function ReviewRow({ label, value }: { label: string; value: string }) { return ( {label} {value} ); } const styles = StyleSheet.create({ root: { flex: 1, backgroundColor: "#0a0a0c" }, dots: { flexDirection: "row", gap: 6, paddingHorizontal: 20, paddingBottom: 8 }, dot: { flex: 1, height: 4, borderRadius: 2, backgroundColor: "#222228" }, dotOn: { backgroundColor: "#ff3b30" }, dotDone: { backgroundColor: "#7a2620" }, content: { padding: 20, paddingBottom: 40 }, h1: { color: "#f5f5f7", fontSize: 28, fontWeight: "800" }, sub: { color: "#9a9aa0", fontSize: 15 }, label: { color: "#9a9aa0", fontSize: 14, marginTop: 8 }, input: { ...wizardInputStyle, minHeight: 0 }, swatches: { flexDirection: "row", flexWrap: "wrap", gap: 14 }, swatch: { width: 48, height: 48, borderRadius: 24, borderWidth: 3, borderColor: "transparent" }, swatchOn: { borderColor: "#ffffff" }, review: { backgroundColor: "#15151a", borderRadius: 16, paddingHorizontal: 16 }, reviewRow: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 12, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: "#22222a", gap: 16, }, reviewLabel: { color: "#9a9aa0", fontSize: 14 }, reviewValue: { color: "#f5f5f7", fontSize: 14, fontWeight: "600", flexShrink: 1 }, footer: { flexDirection: "row", gap: 12, paddingHorizontal: 20, paddingTop: 12 }, back: { backgroundColor: "#222228", borderRadius: 16, paddingVertical: 16, paddingHorizontal: 24, alignItems: "center", }, backText: { color: "#f5f5f7", fontWeight: "600", fontSize: 16 }, next: { flex: 1, backgroundColor: "#ff3b30", borderRadius: 16, paddingVertical: 16, alignItems: "center" }, disabled: { opacity: 0.4 }, nextText: { color: "#fff", fontWeight: "700", fontSize: 17 }, });