"use client" // "Export my data": one call, and the answer saved as a JSON file in the browser. A section that // could not be exported is named on the page as well as in the file, so a person never takes a // partial export for a whole one. import { useTransport } from "@conusai-kit/ui" import { type ReactElement, useId, useState } from "react" import type { AccountExport } from "../contract/procedures.js" export interface AccountExportLabels { readonly heading: string readonly body: string readonly submit: string readonly busy: string readonly done: string /** With `{sections}` still in it. */ readonly partial: string readonly filename: string /** The app's `app.failed` message, with `{detail}` still in it. */ readonly failed: string /** A readable name per section key; a key without one is shown as it is. */ readonly sectionNames: Readonly> } type State = | { readonly kind: "idle" | "busy" | "done" } | { readonly kind: "partial"; readonly sections: string } | { readonly kind: "failed"; readonly detail: string } function save(exported: AccountExport, filename: string): void { const href = URL.createObjectURL(new Blob([JSON.stringify(exported, null, 2)], { type: "application/json" })) const link = document.createElement("a") link.href = href link.download = `${filename}-${exported.exportedAt.slice(0, 10)}.json` document.body.append(link) link.click() link.remove() setTimeout(() => URL.revokeObjectURL(href), 0) } export function AccountExportPanel({ labels }: { labels: AccountExportLabels }): ReactElement { const { call, describe } = useTransport() const headingId = useId() const [state, setState] = useState({ kind: "idle" }) const run = async (): Promise => { setState({ kind: "busy" }) try { const exported = (await call("account.data.export", {})) as AccountExport save(exported, labels.filename) const missing = Object.entries(exported.sections) .filter(([, section]) => section.status !== "ok") .map(([key]) => labels.sectionNames[key] ?? key) setState(exported.complete ? { kind: "done" } : { kind: "partial", sections: missing.join(", ") }) } catch (error) { setState({ kind: "failed", detail: describe(error) }) } } return (

{labels.heading}

{labels.body}

{state.kind === "done" ?

{labels.done}

: null} {state.kind === "partial" ? (

{labels.partial.replace("{sections}", state.sections)}

) : null} {state.kind === "failed" ? (

{labels.failed.replace("{detail}", state.detail)}

) : null}
) }