import { useQuery } from '@tanstack/react-query'; import { Download, Loader2, Smartphone } from 'lucide-react'; import { useClient } from 'hooks/useClient'; import type { KioskApp, KioskBuild } from '../db/types'; /* The install list. This is the page that has to work on a phone in mobile Safari — see `layout.ts` for why it is first in a horizontal root. Deliberately plain: rows and a button. Somebody is holding a phone, and the whole content is "which apps exist and which one do I want". */ const prettyVersion = (v: string) => v.length === 10 ? `${v.slice(0, 4)}.${v.slice(4, 6)}.${v.slice(6, 8)}.${v.slice(8)}` : v; const mb = (bytes: number) => `${(bytes / 1024 / 1024).toFixed(1)} MB`; const InstallButton = ({ build }: { build: KioskBuild }) => { const client = useClient(); // The link is fetched at the moment of the tap, never rendered ahead of time: it embeds the caller's key, // and the key is re-read on demand precisely so rotating one costs nothing. const install = async () => { const { url } = await client.get<{ url: string; kind: 'itms' | 'apk' }>( `/kiosk/install/${encodeURIComponent(build.appName)}/${build.platform}/${encodeURIComponent(build.version)}`, ); // `itms-services://` is handed to the OS, which takes over from here. There is no completion callback — // the app reports its own installs afterwards. window.location.href = url; }; return ( ); }; const BuildRow = ({ build }: { build: KioskBuild }) => (