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 4ce232ee856d7de1ba5f2b6d
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/Home.tsx
// The landing's working parts: "Claim your page" in the hero, and "Your pages" for returning owners.
import { useEffect, useState, type FormEvent } from "react";
import { get, setMany, serverTimestamp } from "@bounded-sh/client";
import { me, explain } from "./lib/bounded";
import { navigate } from "./lib/router";
import { savePage, savedPages } from "./lib/mine";
import { copy } from "./ui";
import { HOST } from "./page";
import { checkHandle, fromDoc, isAvatar, newPage, pageUrl, randomKey, toDoc, type PageDoc } from "../functions/lib/bio";
async function createPage(handle: string): Promise<string> {
const uid = await me();
const key = randomKey();
await setMany([
{ path: `pages/${handle}`, document: { ...toDoc(newPage(handle)), views: 0, createdBy: uid, createdAt: serverTimestamp() } },
{ path: `pages/${handle}/owners/${uid}`, document: { at: serverTimestamp() } },
{ path: `pagekeys/${handle}`, document: { key } },
]);
savePage(handle, key);
return key;
}
const tidy = (s: string) => s.toLowerCase().replace(/^@/, "").replace(/[^a-z0-9-]/g, "").slice(0, 30);
export function Claim() {
const [handle, setHandle] = useState(() => tidy(new URLSearchParams(location.search).get("claim") || ""));
const [taken, setTaken] = useState<Record<string, boolean>>({});
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const checked = handle ? checkHandle(handle) : null;
const valid = checked && "handle" in checked ? checked.handle : null;
useEffect(() => {
if (!valid || valid in taken) return;
let live = true;
const timer = window.setTimeout(async () => {
try {
await me();
const doc = await get(`pages/${valid}`);
if (live) setTaken((t) => ({ ...t, [valid]: Boolean(doc) }));
} catch { /* the claim itself will say what is wrong */ }
}, 300);
return () => { live = false; window.clearTimeout(timer); };
}, [valid, taken]);
const submit = async (e: FormEvent) => {
e.preventDefault();
if (busy) return;
if (!checked) return setError("Type the name you want after the slash.");
if ("error" in checked) return setError(checked.error);
if (taken[checked.handle]) return setError(`${HOST}/${checked.handle} is taken. Try another.`);
setBusy(true);
setError(null);
try {
const key = await createPage(checked.handle);
navigate(`/e/${checked.handle}#k=${key}`);
} catch (err) {
// The rules refuse a page someone already has.
if (/denied|403|forbidden|not allowed/i.test(String((err as Error)?.message ?? err))) {
setTaken((t) => ({ ...t, [checked.handle]: true }));
setError(`${HOST}/${checked.handle} is taken. Try another.`);
} else setError(explain(err));
} finally {
setBusy(false);
}
};
let status: { tone: string; text: string } | null = null;
if (error) status = { tone: "bad", text: error };
else if (checked && "error" in checked) status = { tone: "", text: handle.length < 3 ? "At least 3 characters." : checked.error };
else if (valid && taken[valid] === true) status = { tone: "bad", text: "That one is taken. Try another." };
else if (valid && taken[valid] === false) status = { tone: "good", text: `${HOST}/${valid} is yours to claim.` };
return (
<form className="claim" onSubmit={submit} noValidate>
<label className="sr-only" htmlFor="handle">Your page address</label>
<div className="claim-row">
<div className="prefix-input claim-input">
<span aria-hidden="true">{HOST}/</span>
<input id="handle" value={handle} onChange={(e) => { setHandle(tidy(e.target.value)); setError(null); }}
onFocus={() => me().catch(() => {})} placeholder="yourname" maxLength={30}
autoComplete="off" autoCapitalize="off" spellCheck={false} aria-describedby="claim-status" />
</div>
<button className="btn btn-lg" type="submit" aria-busy={busy || undefined}>Claim your page</button>
</div>
<p id="claim-status" className={`claim-status ${status?.tone ?? ""}`} role={status?.tone === "bad" ? "alert" : undefined}>
{status?.text ?? "Free. No sign-up. Unlimited links."}
</p>
</form>
);
}
export function YourPages() {
const [saved, setSaved] = useState(savedPages);
useEffect(() => {
const onSaved = () => setSaved(savedPages());
window.addEventListener("bio:saved", onSaved);
return () => window.removeEventListener("bio:saved", onSaved);
}, []);
if (!saved.length) return null;
return (
<div className="section-tight">
<div className="wrap">
<div className="spread">
<h2 className="h2">Your pages</h2>
<span className="muted small">Saved in this browser</span>
</div>
<ul className="page-list">
{saved.map((s) => <PageRow key={s.handle} handle={s.handle} ownerKey={s.key} />)}
</ul>
</div>
</div>
);
}
function PageRow({ handle, ownerKey }: { handle: string; ownerKey: string }) {
const [doc, setDoc] = useState<PageDoc | null | undefined>(undefined);
useEffect(() => {
let live = true;
me().then(() => get(`pages/${handle}`)).then((d) => live && setDoc((d as PageDoc | null) ?? null), () => live && setDoc(null));
return () => { live = false; };
}, [handle]);
const page = doc ? fromDoc(doc) : null;
const edit = `/e/${handle}#k=${ownerKey}`;
const views = doc?.views ?? 0;
return (
<li className="page-row">
<span className="row-avatar" aria-hidden="true">
{page && isAvatar(page.avatar) ? <img src={page.avatar} alt="" /> : (page?.name || handle).charAt(0).toUpperCase()}
</span>
<div className="page-main">
<a className="page-name" href={pageUrl(handle)} target="_blank" rel="noopener">{page?.name || handle}</a>
<span className="page-host">{doc === null ? "Could not open this page." : `${HOST}/${handle}`}</span>
</div>
<span className="page-views">{doc ? <><strong>{views.toLocaleString()}</strong> {views === 1 ? "view" : "views"}</> : null}</span>
<span className="page-actions">
<button className="btn btn-sm btn-quiet" type="button" onClick={() => copy(pageUrl(handle), "Page link copied")}>Copy link</button>
<a className="btn btn-sm btn-line" href={edit} onClick={(e) => { e.preventDefault(); navigate(edit); }}>Edit</a>
</span>
</li>
);
}
Change history
Quality pass: reorder, hide links, headers, QR share, 15 social icons, API docs
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync