storytellurx-v1/app/components/utils/head.tsx
Elias Rhouzlane 7fbecd1fa9 fix: new rsc
2025-07-15 14:18:10 +02:00

134 lines
4.8 KiB
TypeScript

import type { S3Object } from "@/default.sqlite.d.ts";
import type { Context } from "@bureaudouble/routeur";
import convertToPlain from "@bureaudouble/outils/convertToPlain.ts";
import { getHmrScriptPath } from "@bureaudouble/outils/routes/createHmrRouter.ts";
import { context } from "@bureaudouble/rsc/server/context";
import { database, getS3Uri } from "@/app/utils/database.ts";
import tailwind from "@/build/tailwind/snapshot.json" with { type: "json" };
import { sharp } from "@/app/components/utils/image.tsx";
const Favicons = async ({ media }: { media?: S3Object | null }) => {
if (!media) return;
const [icosRes, pngsRes] = await Promise.all(
[
{ resizes: [32], format: ["image/x-icon" as const] },
{ resizes: [180, 192, 512], format: ["image/png" as const] },
].map((options) => sharp.createResponsiveImage([media], options)),
);
const pngs = pngsRes
.flatMap((v) => v.sources)
.flatMap((v) => v?.srcSet ?? []);
const ico = icosRes.flatMap((v) => v.sources.at(-1)?.srcSet ?? []).at(0);
const svg = media["content-type"] === "image/svg+xml"
? getS3Uri(media.key).href
: null;
const manifest = "data:application/json;base64,".concat(
btoa(
JSON.stringify({
icons: [
{ src: pngs.at(1), type: "image/png", sizes: "192x192" },
{ src: pngs.at(2), type: "image/png", sizes: "512x512" },
],
}),
),
);
return (
<>
<link rel="icon" href={ico} sizes="32x32" />
<link rel="apple-touch-icon" href={pngs.at(0)} />
{svg && <link rel="icon" href={svg} type="image/svg+xml" />}
<link rel="manifest" href={manifest} />
</>
);
};
const OgImage = async ({ media }: { media?: S3Object | null }) => {
if (!media?.key) return null;
const options = { resizes: [1200], format: ["image/png" as const] };
const sources = await sharp.createResponsiveImage([media], options);
const content = sources.flatMap((v) => v.sources.at(-1)?.srcSet ?? []).at(0);
return <meta property="og:image" content={content} />;
};
const joinArr = <T,>(arr: T[]): string => arr.filter((v) => v).join(" — ");
export default async ({
children,
title,
description,
className,
htmlClassName,
images,
fontsSources,
disableAnalytics,
}: {
title?: string;
className?: string;
htmlClassName?: string;
description?: string | null;
images?: S3Object[];
children?: React.ReactNode;
fontsSources: { href: string }[];
disableAnalytics?: boolean;
}) => {
const ctx = context<Context>();
const site = await database
.selectFrom("sites")
.selectAll()
.executeTakeFirstOrThrow();
const ogTitle = joinArr([title, site.title, site.subtitle]);
const ogDescription = joinArr([
description,
convertToPlain(site.description_html ?? ""),
]);
const siteUrl = site.url
? site.url.startsWith("http") ? site.url : `https://${site.url}`
: null;
const canonical = siteUrl ? new URL(ctx.url.pathname, siteUrl).href : null;
const locale = "fr_FR";
const hmr = getHmrScriptPath();
return (
<html lang={locale.split("_").at(0)} className={htmlClassName}>
<head>
<title>{ogTitle}</title>
<meta name="description" content={ogDescription} />
{fontsSources
.map((f) => ({ rel: "preload", type: "font/woff2", ...f }))
.map((f) => <link key={f.href} {...f} as="font" crossOrigin="" />)}
<link rel="preconnect" href={getS3Uri("").href} />
<Favicons
media={(site.logo_image_json || site.meta_image_json)?.at(0)}
/>
{canonical && <link rel="canonical" href={canonical} />}
{canonical && <meta property="og:url" content={canonical} />}
<OgImage media={(images || site.meta_image_json)?.at(0)} />
<meta property="og:locale" content={locale} />
<meta property="og:site_name" content={site.title} />
<meta property="og:type" content="website" />
<meta property="og:title" content={ogTitle} />
<meta
name="author"
content="Bureau Double(Juliette Lépineau; Elias Rhouzlane)"
/>
<link rel="stylesheet" href={tailwind.pathname} />
{!disableAnalytics && <script src="/analytics/client.js" defer async />}
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=5, viewport-fit=cover"
/>
<meta charSet="utf-8" />
<meta httpEquiv="content-type" content="text/html; charset=utf-8" />
{false && <script src={hmr} defer async />}
</head>
<body className={className}>
<div
className="fixed top-0 h-1 after:block after:h-full after:bg-primary after:w-[calc(var(--value,_0)_*_1%)] w-full pointer-events-none z-50 after:shadow-sm"
id="rsc-fetch-value"
/>
{children}
</body>
</html>
);
};