feat(gaze): add Gaze subdomain landing page with direct download

Move the Gaze marketing home from the legacy /marketing/gaze route to
a dedicated gaze subdomain landing page (src/routes/gaze). The new page
adds a richer hero, feature highlights, and a direct .dmg download flow
that resolves a signed S3 URL via the tRPC downloads.getDownloadUrl
endpoint.

Extract the download-resolution logic into a shared, testable
downloadAsset helper (src/lib/download-asset) so current and future
subdomain landing pages (InputHalo, Lineage, …) share one code path.
The helper is pure over an injected DownloadApi + redirect sink, keeping
it unit-testable without importing solid-js / CSRF cookie code.

Replace the old marketing/gaze route with a permanent 308 redirect to
https://gaze.freno.me so existing inbound links keep resolving to the
canonical home.

Add unit tests covering the tRPC call shape, redirect sink, and error
handling.
This commit is contained in:
2026-07-23 12:15:04 -04:00
parent 6fdbbbbe2b
commit f2ff75e6df
4 changed files with 359 additions and 56 deletions

76
src/lib/download-asset.ts Normal file
View File

@@ -0,0 +1,76 @@
/**
* Pure, testable helper for triggering a signed-S3 download via the tRPC
* `downloads.getDownloadUrl` endpoint.
*
* Extracted (task 05) so the Gaze landing page's download button — and any
* other subdomain landing page that needs the same flow (InputHalo, Lineage,
* …) — can share a single code path AND be unit-tested without importing
* `~/lib/api` (which transitively imports solid-js / CSRF cookie access).
*
* The component layer supplies the concrete `api` (dynamic-imported at click
* time) and the `redirect` sink (defaults to `window.location.href = url`).
* Tests inject a fake `api` and a capture sink.
*/
/** Structural shape of the tRPC downloads proxy this helper depends on. */
export interface DownloadApi {
downloads: {
getDownloadUrl: {
query: (input: { asset_name: string }) => Promise<{
downloadURL: string;
}>;
};
};
}
/** A `(url) => void` sink the helper calls with the signed S3 URL. */
export type DownloadRedirect = (url: string) => void;
export interface DownloadAssetOptions {
/** tRPC proxy (or fake) exposing `downloads.getDownloadUrl.query`. */
api: DownloadApi;
/** Asset key known to the downloads router (e.g. `"gaze"`). */
assetName: string;
/** Called with the signed URL. Defaults to `window.location.href = url`. */
redirect?: DownloadRedirect;
/** Invoked on failure; defaults to a no-op (component shows its own UI). */
onError?: (error: unknown) => void;
}
/** Default redirect sink — browser navigation to the signed S3 URL. */
const defaultRedirect: DownloadRedirect = (url) => {
if (typeof window !== "undefined") {
window.location.href = url;
}
};
/**
* Resolve the latest signed download URL for `assetName` and redirect the
* browser to it. Never throws — failures are routed to `onError`.
*
* @example
* ```ts
* import("~/lib/api").then(({ api }) => {
* downloadAsset({ api, assetName: "gaze" });
* });
* ```
*/
export async function downloadAsset(
options: DownloadAssetOptions
): Promise<void> {
const {
api,
assetName,
redirect = defaultRedirect,
onError
} = options;
try {
const data = await api.downloads.getDownloadUrl.query({
asset_name: assetName
});
redirect(data.downloadURL);
} catch (error) {
if (onError) onError(error);
}
}