bio-free.openapps.xyz

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.
Owned app · rev 3e98d5e10bac
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.
← All files
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
3e98d5e10b · 5 hours ago
Bounded source sync
c1194348e3 · 1 day ago
Bounded source sync
5b1f3fab35 · 1 day ago
Bounded source sync
21e45889ea · 1 day ago
Bounded source sync
8e21b0e5b7 · 1 day ago
Bounded source sync
0598bcc9b2 · 1 day ago
Bounded source sync
0ea5668865 · 1 day ago