when-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 2315d847681e
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 3e1cb86700a5e96238d7a2f1
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/lib/polls.ts
// What this browser remembers: the polls it organizes (so a returning organizer finds them on the
// landing page), which polls its guest already joined, and the name it answers with.
// Admin keys stay on this device; the admin link carries one to another device.
import type { Poll } from "../../functions/lib/when";

export interface MyPoll { id: string; title: string; key?: string; at: number }

const KEY = "when:polls";

function read(key: string): string | null {
  try { return localStorage.getItem(key); } catch { return null; }
}
function write(key: string, value: string | null) {
  try {
    if (value == null) localStorage.removeItem(key);
    else localStorage.setItem(key, value);
  } catch { /* private mode or full storage: the app still works, it just forgets */ }
}

export function myPolls(): MyPoll[] {
  try {
    const list = JSON.parse(read(KEY) || "[]");
    return Array.isArray(list) ? list.filter((p) => p && typeof p.id === "string") : [];
  } catch {
    return [];
  }
}

export function rememberPoll(poll: Partial<MyPoll> & { id: string }) {
  const list = myPolls();
  const prev = list.find((p) => p.id === poll.id);
  const next = { title: "", at: Date.now(), ...prev, ...poll };
  write(KEY, JSON.stringify([next, ...list.filter((p) => p.id !== poll.id)].slice(0, 200)));
}

export function adminKey(id: string): string | undefined {
  return myPolls().find((p) => p.id === id)?.key;
}

/** Joins and owners are never removed, so once this guest is in a poll, a return visit can skip the checks. */
export function membership(uid: string, pollId: string): "in" | "owner" | null {
  const v = read(`when:in:${uid}:${pollId}`);
  return v === "in" || v === "owner" ? v : null;
}
export function rememberMembership(uid: string, pollId: string, role: "in" | "owner") {
  write(`when:in:${uid}:${pollId}`, role);
}

export const savedName = () => read("when:name") ?? "";
export const saveName = (name: string) => write("when:name", name);

export const isNewPoll = (id: string) => read(`when:new:${id}`) === "1";
export const markNewPoll = (id: string, on: boolean) => write(`when:new:${id}`, on ? "1" : null);

/** A poll this tab just made, shown at once while the live copy loads. */
const seeds = new Map<string, Poll>();
export const seedPoll = (id: string, poll: Poll) => seeds.set(id, poll);
export const seededPoll = (id: string) => seeds.get(id);

Change history

Quality pass: comments, CSV export, share the final time, calendar menu, fixes
2315d84768 · 4 hours ago
Bounded source sync
145324066b · 1 day ago
Bounded source sync
ad9b674dac · 1 day ago
Bounded source sync
e5b69608f3 · 1 day ago
Bounded source sync
a1685914b0 · 1 day ago
Bounded source sync
7108f42d4d · 1 day ago
Bounded source sync
ffb51b672e · 1 day ago
Bounded source sync
23b5d0c53b · 1 day ago