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 3151231032497ed786b88a0a
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/Edit.tsx
// A link's private edit page (/e/<code>#k=<key>): change where it goes, see clicks per day, turn it off, get the QR code.
import { useEffect, useMemo, useState, type FormEvent, type PointerEvent } from "react";
import { set, useQuery } from "@bounded-sh/client";
import { explain } from "./lib/bounded";
import { navigate } from "./lib/router";
import { openLink, saveLink, savedKey, savedLinks, type Link } from "./lib/mine";
import { copy, share, canShare, toast, Icon } from "./ui";
import { QrPanel } from "./Qr";
import { SITE_URL } from "../functions/lib/config";
import { checkUrl, shortUrl, editUrl, dayNumber } from "../functions/lib/links";
const HOST = SITE_URL.replace(/^https?:\/\//, "");
const DAYS = 30;
function go(e: { preventDefault: () => void }, to: string) {
e.preventDefault();
navigate(to);
}
export function EditPage({ code }: { code: string }) {
const [state, setState] = useState<"opening" | "ready" | "nokey" | "badkey" | "error">("opening");
const [key, setKey] = useState("");
const [error, setError] = useState("");
useEffect(() => {
const k = new URLSearchParams(location.hash.slice(1)).get("k") || savedKey(code) || "";
setKey(k);
if (!k) { setState("nokey"); return; }
let live = true;
openLink(code, k).then(
(link) => {
if (!live) return;
if (!link) return setState("badkey");
saveLink(code, k);
setState("ready");
},
(err) => { if (live) { setError(explain(err)); setState("error"); } },
);
return () => { live = false; };
}, [code]);
useEffect(() => {
const before = document.title;
document.title = `${HOST}/${code} | Links`;
return () => { document.title = before; };
}, [code]);
if (state === "opening") return <EditSkeleton code={code} />;
if (state === "ready") return <EditView code={code} ownerKey={key} />;
return (
<div className="wrap page-pad narrow enter">
<div className={state === "error" ? "error-state" : "empty-state"}>
<img className="art" src="/art/card-links.webp" alt="" width="200" height="184" />
<h1>{state === "nokey" ? "This edit link is missing its key." : state === "badkey" ? "This edit link does not work." : "This link could not be opened."}</h1>
<p>
{state === "nokey" ? "Open the full edit link you saved when you made the short link. It ends with #k= and a long code."
: state === "badkey" ? "The link may not exist, or the edit link was cut short. Check that you copied all of it."
: error}
</p>
{state === "error"
? <button className="btn" type="button" onClick={() => location.reload()}>Try again</button>
: <a className="btn" href="/" onClick={(e) => go(e, "/")}>Make a short link</a>}
</div>
</div>
);
}
function Back() {
const mine = savedLinks().length > 0;
return (
<a className="back-link" href={mine ? "/#mine" : "/"} onClick={(e) => go(e, mine ? "/#mine" : "/")}>
<Icon name="back" size={16} />{mine ? "Your links" : "Make a short link"}
</a>
);
}
function EditSkeleton({ code }: { code: string }) {
return (
<div className="edit" aria-busy="true" aria-label="Opening your link">
<header className="edit-head">
<div className="wrap edit-head-inner">
<div className="edit-name">
<Back />
<h1 className="edit-title"><span className="short-host">{HOST}/</span>{code}</h1>
</div>
</div>
</header>
<div className="wrap edit-body">
<div className="edit-main">
<div className="card pad"><span className="skeleton" style={{ height: 112 }} /></div>
<div className="card pad"><span className="skeleton" style={{ height: 196 }} /></div>
</div>
<div className="card pad"><span className="skeleton" style={{ height: 220 }} /></div>
</div>
</div>
);
}
function EditView({ code, ownerKey }: { code: string; ownerKey: string }) {
const { data: link } = useQuery(`links/${code}`) as { data: Link | null | undefined };
const [draft, setDraft] = useState<string | null>(null);
const [saving, setSaving] = useState(false);
const [switching, setSwitching] = useState(false);
const [error, setError] = useState<string | null>(null);
const short = shortUrl(code);
if (link === undefined) return <EditSkeleton code={code} />;
if (!link) return (
<div className="wrap page-pad narrow enter">
<div className="error-state"><h1>This link could not be loaded.</h1><p>Check your connection, then try again.</p>
<button className="btn" type="button" onClick={() => location.reload()}>Try again</button></div>
</div>
);
const value = draft ?? link.url;
const changed = draft !== null && draft.trim() !== link.url;
const save = async (e: FormEvent) => {
e.preventDefault();
if (!changed || saving) return;
const checked = checkUrl(value);
if ("error" in checked) return setError(checked.error);
setSaving(true);
setError(null);
try {
await set(`links/${code}`, { url: checked.url });
setDraft(null);
toast("Saved. The short link and QR code open the new page now.");
} catch (err) {
setError(explain(err));
} finally {
setSaving(false);
}
};
const toggle = async () => {
setSwitching(true);
try {
await set(`links/${code}`, { disabled: !link.disabled });
toast(link.disabled ? "The link is on again." : "The link is off. It opens nothing until you turn it back on.");
} catch (err) {
toast(explain(err));
} finally {
setSwitching(false);
}
};
return (
<div className="edit enter">
<header className="edit-head">
<div className="wrap edit-head-inner">
<div className="edit-name">
<Back />
<h1 className="edit-title"><span className="short-host">{HOST}/</span>{code}</h1>
</div>
<div className="edit-head-actions">
<button className="btn" type="button" onClick={() => copy(short, "Short link copied")}>Copy link</button>
{canShare() && <button className="btn btn-line" type="button" onClick={() => share(short, "Short link copied")}><Icon name="share" />Share</button>}
<a className="btn btn-line" href={short} target="_blank" rel="noopener"><Icon name="open" />Open</a>
</div>
</div>
</header>
<div className="wrap edit-body">
<div className="edit-main">
<form className="card pad stack" onSubmit={save} noValidate>
<div className="field">
<label className="label" htmlFor="dest">Where it goes</label>
<input id="dest" className="input" type="url" inputMode="url" value={value} onChange={(e) => { setDraft(e.target.value); setError(null); }}
autoComplete="off" autoCapitalize="off" spellCheck={false}
aria-invalid={error ? true : undefined} aria-describedby={error ? "dest-error" : "dest-hint"} />
{error ? <p className="field-error" id="dest-error">{error}</p>
: <small id="dest-hint">Change it any time. The short link and its QR code stay the same, even printed.</small>}
</div>
<div className="row">
<button className="btn" type="submit" disabled={!changed} aria-busy={saving || undefined}><span>Save</span></button>
{draft !== null && <button className="btn btn-quiet" type="button" onClick={() => { setDraft(null); setError(null); }}>Undo</button>}
</div>
</form>
<Clicks code={code} link={link} />
</div>
<div className="edit-side">
<aside className="card pad edit-qr">
<h2 className="h3">QR code</h2>
<QrPanel code={code} />
</aside>
<div className="card pad status-card">
<div className="status-text">
<span className={`chip ${link.disabled ? "chip-off" : "chip-free"}`}>{link.disabled ? "Off" : "On"}</span>
<p>{link.disabled ? "People who open it see \"This link was turned off by its owner.\"" : "The link and its QR code open the page you set."}</p>
</div>
<button className="btn btn-sm btn-line" type="button" onClick={toggle} aria-busy={switching || undefined}>
<span>{link.disabled ? "Turn on" : "Turn off"}</span>
</button>
</div>
</div>
<div className="notice edit-key">
<p><strong>This page's address is your private edit link.</strong> Bookmark it or keep it somewhere safe. It works on any device, and anyone with it can change the link.</p>
<button className="btn btn-sm btn-line" type="button" onClick={() => copy(editUrl(code, ownerKey), "Edit link copied")}>Copy edit link</button>
</div>
</div>
</div>
);
}
const fmtDay = (d: Date) => d.toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" });
/** Total clicks, and a bar for each of the last 30 days (UTC). Pointing at a bar reads its day out. */
function Clicks({ code, link }: { code: string; link: Link }) {
const since = useMemo(() => dayNumber(new Date(Date.now() - (DAYS - 1) * 86400000)), []);
const { data: rows } = useQuery(`links/${code}/days`, { filter: { d: { $gte: since } }, limit: DAYS + 5 }) as { data: { d: number; clicks: number }[] | undefined };
const [pick, setPick] = useState<number | null>(null);
const days = useMemo(() => {
const byDay = new Map((rows || []).map((r) => [r.d, r.clicks]));
return Array.from({ length: DAYS }, (_, i) => {
const date = new Date(Date.now() - (DAYS - 1 - i) * 86400000);
return { date, clicks: byDay.get(dayNumber(date)) ?? 0 };
});
}, [rows]);
const recent = days.reduce((a, d) => a + d.clicks, 0);
const max = Math.max(...days.map((d) => d.clicks), 1);
const point = (e: PointerEvent<HTMLDivElement>) => {
const box = e.currentTarget.getBoundingClientRect();
setPick(Math.max(0, Math.min(DAYS - 1, Math.floor(((e.clientX - box.left) / box.width) * DAYS))));
};
const picked = pick === null ? null : days[pick];
const since0 = new Date(link.createdAt * 1000).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
return (
<section className="card pad clicks" aria-labelledby="clicks-title">
<div className="clicks-head">
<div>
<h2 className="label" id="clicks-title">Clicks</h2>
<p className="clicks-total">{link.clicks.toLocaleString("en-US")}</p>
<p className="muted small">since {since0}</p>
</div>
<p className="clicks-readout" aria-live="polite">
{rows === undefined ? <span className="skeleton skeleton-text" style={{ width: 120 }} />
: picked ? <><strong>{picked.clicks.toLocaleString("en-US")}</strong> on {fmtDay(picked.date)}</>
: <><strong>{recent.toLocaleString("en-US")}</strong> in the last 30 days</>}
</p>
</div>
<div className="chart" onPointerMove={point} onPointerDown={point} onPointerLeave={(e) => { if (e.pointerType === "mouse") setPick(null); }}
role="img" aria-label={`Clicks per day for the last 30 days: ${recent} in total.`}>
{recent === 0 && rows !== undefined ? <p className="chart-empty">No clicks in the last 30 days yet. Share the link or print its QR code.</p>
: days.map((d, i) => (
<span key={i} className={`bar${pick === i ? " is-picked" : ""}`}>
{d.clicks > 0 && <i style={{ height: `${Math.max(4, (d.clicks / max) * 100)}%` }} />}
</span>
))}
</div>
<div className="chart-axis muted"><span>{fmtDay(days[0].date)}</span><span>Today</span></div>
<table className="sr-only">
<caption>Clicks per day, last 30 days (UTC)</caption>
<tbody>{days.filter((d) => d.clicks).map((d) => <tr key={d.date.toISOString()}><th scope="row">{fmtDay(d.date)}</th><td>{d.clicks}</td></tr>)}</tbody>
</table>
</section>
);
}
Change history
Quality pass: clicks per day, CSV export, links on other devices
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync