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 }) => (
{build.displayName}
{build.platform} {prettyVersion(build.version)} {build.shortVersion} {mb(build.sizeBytes)} {build.signing ? {build.signing} : null} {/* The date the app stops launching. Absent for an unsigned build, which has no profile. */} {build.profileExpiresAt ? ( expires {new Date(build.profileExpiresAt).toISOString().slice(0, 10)} ) : null}
); export const AppsPanel = () => { const client = useClient(); const { data, isLoading } = useQuery({ queryKey: ['kiosk', 'apps'], queryFn: () => client.get<{ apps: KioskApp[] }>('/kiosk/apps'), }); if (isLoading) { return (
); } const apps = data?.apps ?? []; if (!apps.length) { return (
No builds published yet. Builds are published from the machine that signs them.
); } return (
{apps.map((app) => (
{app.ios ? : null} {app.android ? : null}
))}
); };