lists-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 ebccaf0b13fe
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 ee59501e91bae6d2c23a8370
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/Start.tsx
// The start card: in the landing hero and on "Your lists". Making a list loads the app's data code
// only when the button is pressed, so the landing stays light.
import { useState, type FormEvent } from "react";
import { useStored } from "./lib/local";
import { navigate } from "./lib/router";
import type { Mine } from "./lib/data";
import type { Kind } from "../functions/lib/lists";

const TEMPLATES: { name: string; kind: Kind }[] = [
  { name: "Groceries", kind: "grocery" },
  { name: "Costco", kind: "grocery" },
  { name: "To do", kind: "todo" },
  { name: "Packing", kind: "todo" },
];

export function NewList({ compact = false }: { compact?: boolean }) {
  const [name, setName] = useState(compact ? "" : "Groceries");
  const [kind, setKind] = useState<Kind>("grocery");
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const submit = async (e: FormEvent) => {
    e.preventDefault();
    const n = name.trim();
    if (!n) return setError("Give the list a name.");
    setBusy(true);
    setError(null);
    try {
      const { createList } = await import("./lib/data");
      const id = await createList(n.slice(0, 60), kind);
      navigate(`/l/${id}`);
    } catch (err) {
      setError(err instanceof Error ? err.message : "Could not make the list. Try again.");
      setBusy(false);
    }
  };

  return (
    <form className="new-list" onSubmit={submit} noValidate>
      <label className="field">
        <span>List name</span>
        <input className="input input-lg" value={name} onChange={(e) => setName(e.target.value)} maxLength={60} placeholder="Groceries, Costco, To do" autoComplete="off" />
      </label>
      <div className="picks" role="group" aria-label="Quick names">
        {TEMPLATES.map((t) => (
          <button key={t.name} type="button" className="pick" aria-pressed={name === t.name} onClick={() => { setName(t.name); setKind(t.kind); }}>{t.name}</button>
        ))}
      </div>
      <label className="check">
        <input type="checkbox" checked={kind === "grocery"} onChange={(e) => setKind(e.target.checked ? "grocery" : "todo")} />
        Sort items by store aisle
      </label>
      {error && <p className="notice notice-error" role="alert">{error}</p>}
      <button className="btn btn-lg btn-block" type="submit" disabled={busy}>{busy ? "Making the list..." : "Start the list"}</button>
    </form>
  );
}

export function Start() {
  const mine = Object.values(useStored<Record<string, Mine>>("lists:mine", {})).sort((a, b) => (b.opened || 0) - (a.opened || 0));
  return (
    <div className="start card">
      <p className="eyebrow">Free. No sign-up. Nothing to install.</p>
      <NewList />
      {mine.length > 0 && (
        <div className="start-mine">
          <p className="label">Your lists on this device</p>
          <ul>
            {mine.slice(0, 4).map((l) => (
              <li key={l.id}><a href={`/l/${l.id}`} onClick={(e) => { e.preventDefault(); navigate(`/l/${l.id}`); }}><span>{l.name}</span><small>{l.toGet ? `${l.toGet} ${l.kind === "todo" ? "left" : "to get"}` : "Open"}</small></a></li>
            ))}
          </ul>
          {mine.length > 4 && <a href="/lists" onClick={(e) => { e.preventDefault(); navigate("/lists"); }}>All {mine.length} lists</a>}
        </div>
      )}
    </div>
  );
}

Change history

Point OpenApps links to openapps.free
ebccaf0b13 · 17 hours ago
Bind agent address in policy constants
519855687c · 19 hours ago
Use OpenApps service URLs and add contribution strip
2c353edfdc · 19 hours ago
Bounded source sync
58ac7e08ec · 1 day ago
Bounded source sync
150962f3d9 · 1 day ago
Bounded source sync
e52e4676b9 · 1 day ago