subs-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 1ae4feb2bebf
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 5af1a249bbbef1db1b4c5ec8
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/Shell.tsx
// The app around every page: routes, the tabs (at the bottom on phones, in the header on wider
// screens) and syncing in the background.
import { useEffect } from "react";
import { createPortal } from "react-dom";
import { startSync, refresh, takeUpgraded } from "./lib/sync";
import { takeLoginError } from "./lib/bounded";
import { A, toast } from "./ui";
import { Home, List, Plus, Gear } from "./icons";
import { OverviewPage } from "./Overview";
import { AllPage } from "./All";
import { AddPage } from "./Add";
import { SubPage } from "./SubPage";
import { SettingsPage } from "./Settings";

export type Route =
  | { name: "home" }
  | { name: "all" }
  | { name: "add"; query: URLSearchParams }
  | { name: "settings" }
  | { name: "sub"; id: string; edit: boolean };

export function routeOf(path: string, query: URLSearchParams): Route | null {
  const p = path.split("/").filter(Boolean);
  if (p[0] === "home" && p.length === 1) return { name: "home" };
  if (p[0] === "all" && p.length === 1) return { name: "all" };
  if (p[0] === "add" && p.length === 1) return { name: "add", query };
  if (p[0] === "settings" && p.length === 1) return { name: "settings" };
  if (p[0] === "s" && p[1] && (p.length === 2 || (p.length === 3 && p[2] === "edit"))) return { name: "sub", id: p[1], edit: p[2] === "edit" };
  return null;
}

const TABS = [
  { href: "/home", label: "Overview", Icon: Home, match: ["home"] },
  { href: "/all", label: "All", Icon: List, match: ["all", "sub"] },
  { href: "/add", label: "Add", Icon: Plus, match: ["add"] },
  { href: "/settings", label: "Settings", Icon: Gear, match: ["settings"] },
];

const TITLES: Record<Route["name"], string> = { home: "Overview", all: "All subscriptions", add: "Add a subscription", settings: "Settings", sub: "Subscription" };

function Tabs({ route, className }: { route: Route; className: string }) {
  return (
    <nav className={className} aria-label="Subs">
      {TABS.map(({ href, label, Icon, match }) => (
        <A key={href} href={href} aria-current={match.includes(route.name) ? "page" : undefined}>
          <Icon /> <span>{label}</span>
        </A>
      ))}
    </nav>
  );
}

export function Shell({ route }: { route: Route }) {
  useEffect(() => {
    startSync()
      .then(() => {
        if (takeUpgraded()) toast("Your subscriptions are saved to your account. Sign in on any device to see them.");
        const err = takeLoginError();
        if (err) toast(err);
      })
      .catch(() => { /* not connected or offline: everything is saved on this device */ });
    const back = () => { if (document.visibilityState === "visible") void refresh().catch(() => null); };
    document.addEventListener("visibilitychange", back);
    window.addEventListener("online", back);
    return () => { document.removeEventListener("visibilitychange", back); window.removeEventListener("online", back); };
  }, []);

  useEffect(() => { if (route.name !== "sub") document.title = `${TITLES[route.name]} | Subs`; }, [route.name]);

  let page: React.ReactNode;
  if (route.name === "home") page = <OverviewPage />;
  else if (route.name === "all") page = <AllPage />;
  else if (route.name === "add") page = <AddPage query={route.query} />;
  else if (route.name === "settings") page = <SettingsPage />;
  else page = <SubPage key={route.id} id={route.id} edit={route.edit} />;

  const slot = document.getElementById("app-nav");
  return (
    <div className="shell">
      {slot && createPortal(<Tabs route={route} className="head-tabs" />, slot)}
      {page}
      <Tabs route={route} className="tabbar" />
    </div>
  );
}

Change history

Point store links to openapps.free
1ae4feb2be · 17 hours ago
Bind agent address in policy
99bbab3f0c · 19 hours ago
Use OpenApps service URLs and add contribution strip
781c3c23e5 · 19 hours ago
Bounded source sync
048e3257a5 · 1 day ago
Bounded source sync
7a358a3be6 · 1 day ago
Bounded source sync
0c833fcb0e · 1 day ago