1import { For, Show, createSignal } from "solid-js";2import { Capacitor } from "@capacitor/core";34import { PeachWordmark } from "../components/PeachMark";5import { RuneBackground } from "../components/RuneBackground";6import { registerPlugin } from "@capacitor/core";78interface OnboardingCard {9 headline: string;10 body: string[];11 warning: string | null;12 mobileCard?: boolean;13}1415const BASE_CARDS: OnboardingCard[] = [16 {17 headline: "Your passwords\nlive here.\nNowhere else.",18 body: [19 "No account. No server. No one but you can ever see your vault.",20 "Everything is encrypted on your device with a password only you know.",21 ],22 warning: null,23 },24 {25 headline: "One rule.",26 body: [27 "Your master password is yours alone. Peach cannot recover it. There is no reset.",28 "Choose something you will remember. Write it down somewhere safe.",29 ],30 warning: "If you forget it, your vault cannot be opened.",31 },32 {33 headline: "Everything\nyou need.",34 body: [35 "Passwords, passkeys, cards, secure notes, two-factor codes.",36 "Autofills on every site. Generates strong passwords. Detects breaches.",37 "Free forever. Pro adds sync, mobile, and passkeys.",38 ],39 warning: null,40 },41];4243function getAutofillPlugin() {44 try {45 return registerPlugin<{46 openAutofillSettings(): Promise<void>;47 }>("Autofill");48 } catch {49 return null;50 }51}5253const AutofillPlugin = getAutofillPlugin();5455const MOBILE_CARD: OnboardingCard = {56 headline: "Enable autofill.",57 body: [58 "Peach fills passwords in Chrome, Safari, and every app on your phone.",59 ],60 warning: null,61 mobileCard: true,62};6364const CARDS: OnboardingCard[] = Capacitor.isNativePlatform()65 ? [...BASE_CARDS, MOBILE_CARD]66 : BASE_CARDS;6768const CARD_EXIT_MS = 120;69const CARD_ENTER_GAP_MS = 60;7071interface OnboardingProps {72 onComplete: () => void | Promise<void>;73}7475export function Onboarding(props: OnboardingProps) {76 const [card, setCard] = createSignal(0);77 const [exiting, setExiting] = createSignal(false);78 const [completing, setCompleting] = createSignal(false);7980 function advance() {81 if (exiting() || completing()) return;82 83 if (CARDS[card()]?.mobileCard) return;84 if (card() < CARDS.length - 1) {85 setExiting(true);86 window.setTimeout(() => {87 setCard((c) => c + 1);88 setExiting(false);89 }, CARD_EXIT_MS + CARD_ENTER_GAP_MS);90 } else {91 setCompleting(true);92 try {93 chrome.storage.local.set({ peach_onboarded: true });94 } catch {95 96 }97 window.setTimeout(() => {98 void props.onComplete();99 }, 200);100 }101 }102103 function handleEnableAutofill() {104 if (AutofillPlugin) {105 void AutofillPlugin.openAutofillSettings();106 }107 setCompleting(true);108 try {109 chrome.storage.local.set({ peach_onboarded: true });110 } catch {111 112 }113 window.setTimeout(() => {114 void props.onComplete();115 }, 200);116 }117118 function handleSkipAutofill() {119 setCompleting(true);120 try {121 chrome.storage.local.set({ peach_onboarded: true });122 } catch {123 124 }125 window.setTimeout(() => {126 void props.onComplete();127 }, 200);128 }129130 return (131 <section132 class="peach-screen peach-theme peach-onboarding"133 classList={{ "is-completing": completing() }}134 >135 <RuneBackground opacity={0.07} />136137 <header class="peach-screen-header peach-onboarding-header">138 <PeachWordmark class="peach-wordmark-wrap" />139 </header>140141 <div142 class="peach-onboarding-card"143 classList={{ "is-exiting": exiting() }}144 onClick={advance}145 role="button"146 tabIndex={0}147 onKeyDown={(e) => {148 if (e.key === "Enter" || e.key === " ") {149 e.preventDefault();150 advance();151 }152 }}153 >154 <h2 class="peach-onboarding-card-headline">155 {CARDS[card()]!.headline}156 </h2>157 <hr class="peach-onboarding-divider" />158 <For each={CARDS[card()]!.body}>159 {(para) => <p class="peach-onboarding-body">{para}</p>}160 </For>161 <Show when={CARDS[card()]!.warning}>162 <p class="peach-onboarding-warning">163 <span class="peach-onboarding-warning-icon" aria-hidden="true">⚠</span>164 <span>{CARDS[card()]!.warning}</span>165 </p>166 </Show>167168 <Show when={CARDS[card()]?.mobileCard}>169 <div class="peach-onboarding-mobile-card-buttons">170 <button171 class="peach-btn peach-btn--accent peach-btn--md"172 onClick={(e) => { e.stopPropagation(); handleEnableAutofill(); }}173 >174 Enable Peach autofill175 </button>176 <button177 class="peach-btn peach-btn--subtle peach-btn--md"178 onClick={(e) => { e.stopPropagation(); handleSkipAutofill(); }}179 >180 Skip for now181 </button>182 </div>183 </Show>184 </div>185186 <div class="peach-onboarding-nav">187 <div class="peach-onboarding-dots" aria-hidden="true">188 <For each={CARDS}>189 {(_, i) => (190 <div191 class="peach-onboarding-dot"192 classList={{ "is-active": i() === card() }}193 />194 )}195 </For>196 </div>197 <button198 class="peach-onboarding-next"199 onClick={(e) => {200 e.stopPropagation();201 advance();202 }}203 disabled={completing()}204 >205 {card() === CARDS.length - 1 ? "Get started →" : "next →"}206 </button>207 </div>208 </section>209 );210}