sign-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 528326727848
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 1da91c033c738ab74538d13d
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/Write.tsx
// Write or paste a document's text. An assistant can prefill it with /new#title=...&text=... (the fragment never reaches a server).
import { useEffect, useRef, useState } from "react";
import { navigate } from "./lib/router";
import { startFromText } from "./lib/actions";
import { MAX_TEXT } from "../functions/lib/doc";

const DRAFT = "sign:write";

function initial(): { title: string; text: string } {
  const hash = new URLSearchParams(location.hash.slice(1));
  if (hash.get("text") || hash.get("title")) {
    history.replaceState(null, "", "/new");
    return { title: (hash.get("title") || "").slice(0, 200), text: hash.get("text") || "" };
  }
  try { return { title: "", text: "", ...JSON.parse(localStorage.getItem(DRAFT) || "{}") }; } catch { return { title: "", text: "" }; }
}

export function WriteDocument() {
  const [start] = useState(initial);
  const [title, setTitle] = useState(start.title);
  const [text, setText] = useState(start.text);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<{ on: "title" | "text" | "form"; text: string } | null>(null);
  const titleRef = useRef<HTMLInputElement>(null);
  const textRef = useRef<HTMLTextAreaElement>(null);

  useEffect(() => { document.title = "Write a document | Sign"; }, []);
  // Keep an unsent draft in this browser, so a reload does not lose it.
  useEffect(() => {
    try { localStorage.setItem(DRAFT, JSON.stringify({ title, text })); } catch { /* storage blocked */ }
  }, [title, text]);

  const create = async () => {
    const t = text.trim();
    if (!title.trim()) { titleRef.current?.focus(); return setError({ on: "title", text: "Give the document a title." }); }
    if (t.length < 20) { textRef.current?.focus(); return setError({ on: "text", text: "Write or paste the full text of the document." }); }
    if (t.length > MAX_TEXT) { textRef.current?.focus(); return setError({ on: "text", text: `That is over ${MAX_TEXT.toLocaleString()} characters. Upload it as a PDF instead.` }); }
    setError(null);
    setBusy(true);
    try {
      const id = await startFromText(title, t);
      try { localStorage.removeItem(DRAFT); } catch { /* storage blocked */ }
      navigate(`/d/${id}`);
    } catch (e) {
      setError({ on: "form", text: e instanceof Error ? e.message : String(e) });
      setBusy(false);
    }
  };

  return (
    <div className="wrap page-pad write enter">
      <a className="back" href="/" onClick={(e) => { e.preventDefault(); navigate("/"); }}>← Back</a>
      <h1 className="h1">Write or paste a document</h1>
      <p className="lede">Sign turns the text into a PDF with a signature block for each signer. Start a line with <code>#</code> for a heading or <code>-</code> for a list item, and leave a blank line between paragraphs.</p>
      <form className="card write-card stack" onSubmit={(e) => { e.preventDefault(); create(); }} noValidate>
        <label className="field"><span>Title</span>
          <input ref={titleRef} className="input" value={title} onChange={(e) => setTitle(e.target.value)} maxLength={200} placeholder="Mutual non-disclosure agreement"
            aria-invalid={error?.on === "title" || undefined} aria-describedby={error?.on === "title" ? "write-error" : undefined} />
          {error?.on === "title" && <span className="field-error" id="write-error">{error.text}</span>}
        </label>
        <label className="field"><span>Text</span>
          <textarea ref={textRef} className="textarea write-text" value={text} onChange={(e) => setText(e.target.value)} placeholder={"This agreement is between...\n\n# 1. Confidential information\n\n..."}
            aria-invalid={error?.on === "text" || undefined} aria-describedby={error?.on === "text" ? "write-error" : "write-count"} />
          {error?.on === "text" ? <span className="field-error" id="write-error">{error.text}</span>
            : <small id="write-count">{text.length.toLocaleString()} of {MAX_TEXT.toLocaleString()} characters</small>}
        </label>
        {error?.on === "form" && <p className="notice notice-error" role="alert">{error.text}</p>}
        <div className="action-bar">
          <button className="btn btn-lg" type="submit" aria-busy={busy || undefined}>Continue: add signers</button>
        </div>
      </form>
    </div>
  );
}

Change history

Quality pass: phone signing, edit signers, copy to send again
5283267278 · 5 hours ago
Bounded source sync
15f0fa2e27 · 1 day ago
Bounded source sync
d3aa1ca65b · 1 day ago
Bounded source sync
8e2ca0b773 · 1 day ago
Bounded source sync
5cfa84715c · 1 day ago
Bounded source sync
c849e7b286 · 1 day ago
Bounded source sync
68c1cabe03 · 1 day ago
Bounded source sync
62ae8944f0 · 1 day ago
Bounded source sync
7ed54e7381 · 1 day ago