// 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 (
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 ? (
) : (
setShowCreds(true)} style={{ cursor: "pointer" }}>
Set username & password
›
)
)}
Log out
›
);
}
Object.assign(window, { InstallPrompt, NotificationsScreen, SettingsScreen });