// Install prompt modal (iOS) + Notifications screen + Settings screen function InstallPrompt({ onDone, onLater }) { return (
f
ADD TO HOME SCREEN
To get the morning bell, Future Self needs to live on your home screen.
1 Tap the Share button in Safari
2 Scroll to “Add to Home Screen”
3 Tap Add
); } function NotificationsScreen({ onEnable, onLater }) { return (
TURN ON THE
MORNING BELL.
8 AM every day. One notification. Tap to listen.
That’s it. No noise.
); } function SettingsRow({ k, v, onSave, kind = "text", options }) { const [editing, setEditing] = React.useState(false); const [draft, setDraft] = React.useState(v || ""); React.useEffect(() => { setDraft(v || ""); }, [v]); const commit = async () => { if (draft === v) { setEditing(false); return; } try { await onSave(draft); } catch (e) { console.warn("save failed:", e); } setEditing(false); }; if (editing && kind === "text") { return (
{k} setDraft(e.target.value)} onBlur={commit} onKeyDown={e => { if (e.key === "Enter") commit(); if (e.key === "Escape") { setDraft(v || ""); setEditing(false); } }} />
); } if (editing && kind === "select") { return (
{k}
); } const displayV = kind === "select" && options ? (options.find(o => o.value === v)?.label || v || "—") : (v || "—"); return (
setEditing(true)} style={{ cursor: "pointer" }}> {k} {displayV} {kind === "select" ? "▾" : "✎"}
); } function SettingsScreen({ onBack, onRerecord, hasCloned, setHasCloned, notifOn, setNotifOn, user }) { const [voice, setVoice] = React.useState(hasCloned ? "mine" : "charlie"); const [profile, setProfile] = React.useState(user || {}); React.useEffect(() => { setProfile(user || {}); }, [user]); // Credential setter — Kaku's migration path. Only rendered when the // current user doesn't yet have a password (has_credentials: false). const [showCreds, setShowCreds] = React.useState(false); const [credU, setCredU] = React.useState(""); const [credP, setCredP] = React.useState(""); const [credBusy, setCredBusy] = React.useState(false); const [credErr, setCredErr] = React.useState(""); const submitCreds = async () => { if (credBusy) return; if (credU.trim().length < 3 || credP.length < 8) { setCredErr("Username needs 3+ chars, password 8+."); return; } setCredBusy(true); setCredErr(""); try { await api.setCredentials({ username: credU.trim(), password: credP }); location.reload(); } catch (e) { setCredErr(e?.status === 409 ? "Username taken." : "Couldn't save. Try again."); } finally { setCredBusy(false); } }; const logout = async () => { try { await api.logout(); } catch {} location.reload(); }; const save = async (field, value) => { setProfile(p => ({ ...p, [field]: value })); await api.updateProfile({ [field]: value }); }; return (
SETTINGS

Your voice

hasCloned && setVoice("mine")}> Your cloned voice {hasCloned ? "Ready" : "Not set"}
setVoice("charlie")}> Charlie Preset

Your profile

save("name", v)}/> save("situation", v)}/> save("goals", v)}/> save("lang_pref", v)} />

Delivery

Notifications setNotifOn(!notifOn)} />
({ value: String(i + 5), label: `${i + 5}:00 AM` }))} onSave={v => save("delivery_hour", parseInt(v, 10))} /> save("city", v)}/>

Account

{profile.username && (
Username {profile.username}
)} {!profile.has_credentials && ( showCreds ? (
setCredU(e.target.value)} placeholder="username" autoCapitalize="none" autoCorrect="off" /> setCredP(e.target.value)} placeholder="8+ char password" /> {credErr &&
{credErr}
}
) : (
setShowCreds(true)} style={{ cursor: "pointer" }}> Set username & password
) )}
Log out
); } Object.assign(window, { InstallPrompt, NotificationsScreen, SettingsScreen });