Source
This app’s code and revision history are public. Download a copy or explore the files below.
Public
The app’s code and revision history are public.
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 381bd6356d4b794ac2a7c1b8
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
src/Start.tsx
// Starting a family log (the landing hero, and the app when there is no family yet) and joining one
// from a family link. The data layer loads only when someone starts or joins.
import { useEffect, useState, type FormEvent } from "react";
import { navigate, onLink } from "./lib/router";
import { read, useMyName } from "./lib/prefs";
import { explain } from "./lib/explain";
export function StartForm({ compact = false }: { compact?: boolean }) {
const [myName, setMyName] = useMyName();
const [baby, setBaby] = useState("");
const [birth, setBirth] = useState("");
const [name, setName] = useState(myName);
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const today = new Date().toLocaleDateString("en-CA");
const submit = async (e: FormEvent) => {
e.preventDefault();
if (!baby.trim()) return setError("Add your baby's name, or a nickname.");
setBusy(true);
setError(null);
try {
const who = name.trim() || "Parent";
setMyName(who);
const { createFamily } = await import("./lib/family");
await createFamily({ babyName: baby.trim().slice(0, 40), birth: /^\d{4}-\d{2}-\d{2}$/.test(birth) ? birth : "", myName: who.slice(0, 40) });
navigate("/app");
} catch (err) {
setError(explain(err));
setBusy(false);
}
};
return (
<form className={`start-form ${compact ? "" : "card"}`} onSubmit={submit} noValidate>
<label className="field"><span>Baby's name</span>
<input className="input input-lg" value={baby} onChange={(e) => { setBaby(e.target.value); setError(null); }} maxLength={40} placeholder="Ada, or a nickname" autoComplete="off" aria-invalid={error && !baby.trim() ? true : undefined} />
</label>
<div className="two start-two">
<label className="field"><span>Your name</span>
<input className="input input-lg" value={name} onChange={(e) => setName(e.target.value)} maxLength={40} placeholder="Mom, Dad, Sam" autoComplete="given-name" />
</label>
<label className="field"><span>Birth date <em>optional</em></span>
<input className="input input-lg" type="date" value={birth} max={today} onChange={(e) => setBirth(e.target.value)} />
</label>
</div>
{error && <p className="notice notice-error" role="alert">{error}</p>}
<button className="btn btn-lg btn-block" type="submit" aria-busy={busy || undefined}>Start the log</button>
<p className="hint center">Free. No sign-up. Invite your partner with a link after.</p>
</form>
);
}
/** The landing hero's card: a returning family goes straight in; a new one starts here. */
export function StartCard() {
const returning = Boolean(read<{ family?: string }>("baby:active", {}).family);
const [fresh, setFresh] = useState(false);
if (returning && !fresh) {
return (
<div className="start-form card">
<p className="eyebrow">Welcome back</p>
<a className="btn btn-lg btn-block" href="/app" onClick={onLink}>Open your log</a>
<button type="button" className="btn btn-quiet btn-block" onClick={() => setFresh(true)}>Start a log for another baby or family</button>
</div>
);
}
return <StartForm />;
}
export function JoinPage() {
const key = decodeURIComponent(location.hash.replace(/^#/, ""));
const [myName, setMyName] = useMyName();
const [name, setName] = useState(myName);
const [savedName] = useState(myName);
const [state, setState] = useState<"checking" | "ready" | "joining">("checking");
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
(async () => {
try {
const { alreadyIn, joinFamily } = await import("./lib/family");
if (await alreadyIn(key)) {
await joinFamily(key, savedName || "Me");
if (!cancelled) navigate("/app", true);
return;
}
} catch { /* show the form; joining explains any problem */ }
if (!cancelled) setState("ready");
})();
return () => { cancelled = true; };
}, [key]);
const join = async (e: FormEvent) => {
e.preventDefault();
const who = name.trim();
if (!who) return setError("Add your name, so the family sees who logged what.");
setState("joining");
setError(null);
try {
setMyName(who);
const { joinFamily } = await import("./lib/family");
await joinFamily(key, who.slice(0, 40));
navigate("/app", true);
} catch (err) {
setError(explain(err));
setState("ready");
}
};
return (
<div className="page narrow">
<div className="join card">
<img className="art join-art" src="/art/card-baby.webp" alt="" width="200" height="170" />
<h1 className="page-title">Join the family log</h1>
<p className="muted">Someone shared their baby log with you. You will see every feed, sleep and diaper as it is logged, and can log too.</p>
{state === "checking" ? <div className="stack" aria-busy="true" aria-label="Opening the link"><span className="skeleton" style={{ height: 76 }} /><span className="skeleton" style={{ height: 54 }} /></div> : (
<form className="stack" onSubmit={join} noValidate>
<label className="field"><span>Your name</span>
<input className="input input-lg" value={name} onChange={(e) => setName(e.target.value)} maxLength={40} placeholder="Grandma, Dad, Sam" autoFocus autoComplete="given-name" />
</label>
{error && <p className="notice notice-error" role="alert">{error}</p>}
<button className="btn btn-lg btn-block" type="submit" aria-busy={state === "joining" || undefined}>Join the log</button>
<p className="hint">Anyone with this link can see and add to the log, so only open links from people you know.</p>
</form>
)}
</div>
</div>
);
}
Change history
Quality pass: undo on timers, sleep by hour chart, diaper color, fixes
Bounded source sync
Bounded source sync
Bounded source sync