polls-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 c06cea4889e9
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 334801de44cb0fe911d5a88b
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/Event.tsx
// Opening an event: /e/<id> for the audience, /e/<id>/admin and /e/<id>/present for its owners,
// and /<code> for someone who typed the 6-digit code into the address bar.
import { useEffect, useState } from "react";
import { get, set } from "@bounded-sh/client";
import { me, explain } from "./lib/bounded";
import { useEventAccess } from "./lib/event";
import { Audience } from "./Audience";
import { Admin } from "./Admin";
import { Present } from "./Present";

export type View = "audience" | "admin" | "present";

export function Loading({ text }: { text: string }) {
  return <div className="wrap page-pad"><div className="loading"><span className="spinner" /> {text}</div></div>;
}

function Stop({ title, text, action }: { title: string; text: string; action?: [string, string] }) {
  return (
    <div className="wrap page-pad narrow">
      <h1 className="h1">{title}</h1>
      <p className="lede" style={{ marginTop: 14 }}>{text}</p>
      <a className="btn" style={{ marginTop: 24 }} href={action?.[1] ?? "/"} data-nav>{action?.[0] ?? "Go to Polls"}</a>
    </div>
  );
}

export function EventPage({ eventId, view }: { eventId: string; view: View }) {
  const access = useEventAccess(eventId);
  if (access.state === "opening") return <Loading text="Opening the event..." />;
  if (access.state === "missing") return <Stop title="This event does not exist." text="Check the link or the code. Event links are long and every character counts." />;
  if (access.state === "error") return <div className="wrap page-pad narrow"><p className="notice notice-error">{access.error}</p></div>;
  if (access.state === "badkey") return <Stop title="That admin link is not right." text="The key at the end of the link does not match this event. Copy the whole admin link again." action={["Open the event", `/e/${eventId}`]} />;
  if (view !== "audience" && !access.owner) {
    return <Stop title="Only the event's host can open this." text="To run this event on this device, open the admin link you got when the event was made." action={["Join as audience", `/e/${eventId}`]} />;
  }
  if (view === "admin") return <Admin eventId={eventId} />;
  if (view === "present") return <Present eventId={eventId} />;
  return <Audience eventId={eventId} uid={access.uid} owner={access.owner} />;
}

/** Looks up a join code. Each try is recorded first, because tries are what keep codes from being scanned. */
export async function findEvent(code: string): Promise<string> {
  const uid = await me();
  if (!(await get(`codetries/${uid}/t/${code}`))) {
    try {
      await set(`codetries/${uid}/t/${code}`, { n: 1 });
    } catch (err) {
      const e = err as { decline?: { invariant?: string }; message?: string };
      if (e?.decline?.invariant || /exceed|cap|409/i.test(String(e?.message))) throw new Error("Too many codes tried on this device. Wait 10 minutes, or ask for the event link.");
      throw new Error(explain(err));
    }
  }
  const found = (await get(`codes/${code}`)) as { event: string } | null;
  if (!found?.event) throw new Error("No event has that code. Check the numbers and try again.");
  return found.event;
}

export function CodeJoin({ code }: { code: string }) {
  const [error, setError] = useState("");
  useEffect(() => {
    let cancelled = false;
    findEvent(code).then(
      (eventId) => {
        if (cancelled) return;
        // Replace the code in history, so Back does not look the code up again.
        history.replaceState(null, "", `/e/${eventId}`);
        window.dispatchEvent(new PopStateEvent("popstate"));
      },
      (err) => { if (!cancelled) setError(err instanceof Error ? err.message : explain(err)); },
    );
    return () => { cancelled = true; };
  }, [code]);
  if (!error) return <Loading text="Finding the event..." />;
  return <Stop title="We could not find that event." text={error} action={["Try another code", "/#join"]} />;
}

Change history

Point OpenApps links to openapps.free
c06cea4889 · 17 hours ago
Bind agent address in policy constants
a7de116d52 · 19 hours ago
Use OpenApps service URLs and add contribution strip
e8ce0b0bf0 · 19 hours ago
Bounded source sync
dcdc4d56e6 · 1 day ago
Bounded source sync
7acdace1e1 · 1 day ago
Bounded source sync
2140a66ea0 · 1 day ago
Bounded source sync
0db2fe3ca0 · 1 day ago
Bounded source sync
996f633a3c · 1 day ago