resume-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 3f16b2375723
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 70bace54357d4b57b674108f
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/ResumePage.tsx
// A shared resume. /r/<id> is the public view: the paper and the PDF.
// /r/<id>/admin#k=<key> is the owner's: edit it and save, copy the link.
import { useEffect, useMemo, useState, type MouseEvent } from "react";
import { useQuery } from "@bounded-sh/client";
import type { Resume } from "../functions/lib/resume";
import { navigate } from "./lib/router";
import { Check, Chevron, Dots, DownloadIcon, Menu, ResumeSkeleton, ask, copy, explain, share, toast, type MenuItem } from "./ui";
import { Editor, PaperView, type EditorError, type Mode } from "./Editor";
import { downloadJson, downloadText } from "./files";
import { claim, join, remember, updateShared, type ResumeDoc } from "./share";
import { keyFor, normalize } from "./store";

function keyFromHash(): string | null {
  const m = location.hash.match(/(?:^#|&)k=([A-Za-z0-9]{20,64})/);
  return m ? m[1] : null;
}

const parse = (doc: ResumeDoc | null | undefined): Resume | null => {
  try { return doc ? normalize(JSON.parse(doc.data)) : null; } catch { return null; }
};

const go = (to: string) => (e: MouseEvent) => { e.preventDefault(); navigate(to); };

export function ResumePage({ id, admin }: { id: string; admin: boolean }) {
  const [state, setState] = useState<"opening" | "ready" | "missing" | "badkey" | "error">("opening");
  const [error, setError] = useState("");
  const [key, setKey] = useState<string | null>(null);
  const [attempt, setAttempt] = useState(0);

  useEffect(() => {
    let cancelled = false;
    const done = (s: typeof state) => { if (!cancelled) setState(s); };
    setState("opening");
    (async () => {
      try {
        if (!(await join(id))) return done("missing");
        if (admin) {
          // A key this device already holds wins: an owner device opening a mistyped admin link would otherwise keep the wrong key.
          const k = keyFor(id) || keyFromHash();
          if (!k || !(await claim(id, k))) return done("badkey");
          if (!cancelled) setKey(k);
        }
        done("ready");
      } catch (err) {
        if (!cancelled) { setError(explain(err)); setState("error"); }
      }
    })();
    return () => { cancelled = true; };
  }, [id, admin, attempt]);

  if (state === "opening") return <ResumeSkeleton />;
  if (state === "missing") return (
    <div className="wrap page-pad narrow">
      <div className="empty-state enter">
        <img className="art" src="/art/card-resume.webp" alt="" width="180" height="149" />
        <h1>This resume does not exist.</h1>
        <p>Check the link you were sent. Resume links are long, and every character counts.</p>
        <a className="btn" href="/" onClick={go("/")}>Make a free resume</a>
      </div>
    </div>
  );
  if (state === "badkey") return (
    <div className="wrap page-pad narrow">
      <div className="error-state enter">
        <h1>This admin link does not match.</h1>
        <p>The key at the end of the link is not this resume's. Copy the whole admin link again, including the part after #k=.</p>
        <a className="btn btn-line" href={`/r/${id}`} onClick={go(`/r/${id}`)}>View the resume</a>
      </div>
    </div>
  );
  if (state === "error") return (
    <div className="wrap page-pad narrow">
      <div className="error-state enter">
        <h1>The resume did not open.</h1>
        <p>{error}</p>
        <button className="btn" type="button" onClick={() => setAttempt((n) => n + 1)}>Try again</button>
      </div>
    </div>
  );
  return key ? <OwnerView id={id} ownerKey={key} /> : <PublicView id={id} />;
}

function useResume(id: string) {
  const { data: doc } = useQuery(`resumes/${id}`) as { data: ResumeDoc | null | undefined };
  const resume = useMemo(() => parse(doc), [doc?.data]);
  useEffect(() => {
    const name = resume?.basics.name;
    if (!name) return;
    const before = document.title;
    document.title = `${name}, resume | Resume`;
    return () => { document.title = before; };
  }, [resume?.basics.name]);
  return { doc, resume };
}

const Unreadable = () => (
  <div className="wrap page-pad narrow">
    <div className="error-state enter">
      <h1>This resume could not be read.</h1>
      <p>Its saved copy is damaged. If it is yours, open it from your admin link and save it again.</p>
      <a className="btn btn-line" href="/" onClick={go("/")}>Make a free resume</a>
    </div>
  </div>
);

/** Starts loading the PDF maker when a hand or pointer heads for a download button. */
const warmPdf = () => { import("./pdf").then((m) => m.warm()).catch(() => {}); };
const warmProps = { onPointerEnter: warmPdf, onTouchStart: warmPdf, onFocus: warmPdf };

async function pdfOf(resume: Resume) {
  const { downloadPdf } = await import("./pdf");
  await downloadPdf(resume);
}

function PublicView({ id }: { id: string }) {
  const { doc, resume } = useResume(id);
  const [busy, setBusy] = useState(false);
  if (doc === undefined) return <ResumeSkeleton />;
  if (!resume) return <Unreadable />;
  const mineKey = keyFor(id);

  const pdf = async () => {
    setBusy(true);
    try {
      await pdfOf(resume);
    } catch (err) {
      toast(`The PDF could not be made: ${explain(err)}`);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="doc enter">
      <header className="doc-head">
        <div className="wrap doc-head-inner">
          <div className="doc-head-text">
            <p className="eyebrow">Resume</p>
            <h1 className="doc-title">{resume.basics.name || "Resume"}</h1>
            {resume.basics.label && <p className="muted doc-sub">{resume.basics.label}</p>}
          </div>
          <div className="row doc-actions">
            <button className="btn" type="button" onClick={pdf} aria-busy={busy} {...warmProps}><DownloadIcon /><span>Download PDF</span></button>
            <button className="btn btn-line" type="button" onClick={() => downloadText(resume)}>Plain text</button>
          </div>
        </div>
      </header>
      <div className="wrap doc-body">
        {mineKey && (
          <p className="notice owner-note">
            You shared this resume. <a href={`/r/${id}/admin`} onClick={go(`/r/${id}/admin`)}>Edit it</a> from your admin page.
          </p>
        )}
        <div className="doc-paper"><PaperView resume={resume} placeholders={false} label={`${resume.basics.name || "Resume"}, resume`} /></div>
        <p className="hint center made-with">Made with <a href="/">Resume</a>, a free resume builder with no paywall at download.</p>
      </div>
    </div>
  );
}

function OwnerView({ id, ownerKey }: { id: string; ownerKey: string }) {
  const { doc, resume } = useResume(id);
  const [editing, setEditing] = useState<Resume | null>(null);
  const [base, setBase] = useState("");
  // When the version being edited was stored, so a save can tell that another device changed the link since.
  const [baseAt, setBaseAt] = useState(0);
  const [error, setError] = useState<EditorError | null>(null);
  const [busy, setBusy] = useState<"save" | "pdf" | null>(null);
  const dirty = Boolean(editing) && JSON.stringify(editing) !== base;

  // Start from the stored resume, and follow changes made elsewhere while there is nothing unsaved here.
  useEffect(() => {
    if (!resume) return;
    const stored = JSON.stringify(resume);
    if (!editing || JSON.stringify(editing) === base) {
      setEditing(resume);
      setBase(stored);
      setBaseAt(doc?.updatedAt ?? 0);
    }
    remember(id, ownerKey, resume);
  }, [resume]);

  // The owner is editing, so the PDF maker loads in the background.
  useEffect(() => {
    const idle = window.requestIdleCallback ?? ((f: () => void) => window.setTimeout(f, 1500));
    idle(warmPdf);
  }, []);

  // Leaving with unsaved changes asks first.
  useEffect(() => {
    if (!dirty) return;
    const warn = (e: BeforeUnloadEvent) => { e.preventDefault(); e.returnValue = ""; };
    window.addEventListener("beforeunload", warn);
    return () => window.removeEventListener("beforeunload", warn);
  }, [dirty]);

  if (doc === undefined || (resume && !editing)) return <ResumeSkeleton />;
  if (!resume || !editing) return <Unreadable />;

  const viewUrl = `${location.origin}/r/${id}`;

  const save = async () => {
    if (!editing.basics.name.trim()) return setError({ text: "Add your name first. It prints at the top.", field: "name" });
    setError(null);
    if (doc && doc.updatedAt !== baseAt && !(await ask({
      title: "Replace the version on the link?",
      body: "This resume was changed on another device since you opened it here. Saving shows this version instead.",
      confirm: "Replace it",
    }))) return;
    setBusy("save");
    try {
      setBaseAt(await updateShared(id, ownerKey, editing));
      setBase(JSON.stringify(editing));
      toast("Saved. The link shows it now.");
    } catch (err) {
      setError({ text: explain(err) });
    } finally {
      setBusy(null);
    }
  };
  const pdf = async () => {
    setError(null);
    setBusy("pdf");
    try {
      await pdfOf(editing);
    } catch (err) {
      setError({ text: `The PDF could not be made: ${explain(err)}` });
    } finally {
      setBusy(null);
    }
  };

  const actions = (_mode: Mode, phone: boolean) => {
    const more: MenuItem[] = [
      ...(phone ? [{ label: "Download PDF", onSelect: pdf }] : []),
      { label: "Download as text", onSelect: () => downloadText(editing) },
      { label: "Download JSON Resume", onSelect: () => downloadJson(editing) },
      "-",
      { label: "View the public page", onSelect: () => navigate(`/r/${id}`) },
    ];
    return (
      <>
        <button className={`btn save-btn${dirty ? "" : " btn-line"}`} type="button" onClick={dirty ? save : undefined} aria-disabled={!dirty}
          disabled={busy !== null && busy !== "save"} aria-busy={busy === "save"}>
          {dirty ? <span>Save changes</span> : <><Check /><span>Saved</span></>}
        </button>
        {!phone && <button className="btn btn-line" type="button" onClick={pdf} disabled={busy !== null && busy !== "pdf"} aria-busy={busy === "pdf"} {...warmProps}><DownloadIcon /><span>PDF</span></button>}
        <Menu label={phone ? <Dots /> : <>More<Chevron /></>} className={phone ? "btn btn-line btn-icon" : "btn btn-line menu-btn"} ariaLabel="More" end up={phone} disabled={busy !== null} items={more} />
      </>
    );
  };

  return (
    <div className="wrap page-pad-sm enter">
      <div className="owner-top">
        <div>
          <p className="eyebrow">Your shared resume</p>
          <h1 className="doc-title">{resume.basics.name || "Resume"}</h1>
        </div>
        <div className="card owner-card">
          <div className="field">
            <span className="label">Link to share</span>
            <div className="link-row">
              <code className="link-code">{viewUrl}</code>
              <button className="btn btn-sm" type="button" onClick={() => share(viewUrl, `${resume.basics.name} resume`)}>Copy</button>
            </div>
          </div>
          <p className="hint">This page is your admin link. Bookmark it or <button className="linklike" type="button" onClick={() => copy(`${location.origin}/r/${id}/admin#k=${ownerKey}`, "Admin link copied. Keep it private.")}>copy it</button> to edit from any device. Keep it to yourself.</p>
        </div>
      </div>
      <Editor resume={editing} onChange={(r) => { setEditing(r); setError(null); }} actions={actions} error={error} />
    </div>
  );
}

Change history

Quality pass: several resumes, cover letters, phone-readable shared pages
3f16b23757 · 6 hours ago
Bounded source sync
b7eb5b26b5 · 1 day ago
Bounded source sync
adae97536e · 1 day ago
Bounded source sync
3b0cf36b39 · 1 day ago