diff --git a/apps/site/src/app/layout.tsx b/apps/site/src/app/layout.tsx index 6461277dacd..837e9b40e60 100644 --- a/apps/site/src/app/layout.tsx +++ b/apps/site/src/app/layout.tsx @@ -9,7 +9,6 @@ import Script from "next/script"; import type React from "react"; import { SITE_HOME_DESCRIPTION, SITE_HOME_TITLE } from "@/lib/site-metadata"; import { NavigationWrapper, FooterWrapper } from "@/components/navigation-wrapper"; -import { Footer } from "@prisma-docs/ui/components/footer"; import { JsonLd } from "@prisma-docs/ui/components/json-ld"; import { GoogleTagManager } from "@prisma-docs/ui/components/google-tag-manager"; import { ThemeProvider } from "@prisma-docs/ui/components/theme-provider"; @@ -145,6 +144,11 @@ function baseOptions() { icon: "fa-regular fa-grid-2", external: true, }, + { + text: "Templates", + url: "/templates", + icon: "fa-regular fa-file-code", + }, { text: "Stack", url: "/stack", diff --git a/apps/site/src/app/templates/page.tsx b/apps/site/src/app/templates/page.tsx new file mode 100644 index 00000000000..6d33865d69d --- /dev/null +++ b/apps/site/src/app/templates/page.tsx @@ -0,0 +1,132 @@ +import { createPageMetadata } from "@/lib/page-metadata"; +import { Button, Card } from "@prisma/eclipse"; +import { z } from "zod"; + +const TEMPLATE_MANIFEST_URL = + "https://raw.githubusercontent.com/prisma/prisma-examples/latest/compute/templates.json"; + +const templateManifestSchema = z.object({ + version: z.literal(1), + templates: z.array( + z.object({ + id: z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/), + name: z.string().trim().min(1), + description: z.string().trim().min(1), + path: z.string().regex(/^compute\/[a-z0-9]+(?:[/-][a-z0-9]+)*$/), + }), + ), +}); + +export const metadata = createPageMetadata({ + title: "Prisma Compute templates", + description: + "Browse open-source TypeScript starters and deploy one with Prisma Postgres and Prisma Compute.", + path: "/templates", +}); + +async function getTemplates() { + try { + const response = await fetch(TEMPLATE_MANIFEST_URL, { + next: { revalidate: 300 }, + signal: AbortSignal.timeout(10_000), + }); + if (!response.ok) return []; + + const result = templateManifestSchema.safeParse(await response.json()); + return result.success ? result.data.templates : []; + } catch { + return []; + } +} + +export default async function TemplatesPage() { + const templates = await getTemplates(); + + return ( +
+
+ +
+
+

+ Start from a template. +

+

+ Review the code, then deploy it with Prisma Postgres and Prisma Compute. +

+
+
+ +
+
+ {templates.length > 0 ? ( +
+ {templates.map((template) => { + const deployUrl = new URL(`/templates/${template.id}`, "https://console.prisma.io"); + deployUrl.searchParams.set("utm_source", "website"); + deployUrl.searchParams.set("utm_medium", "templates"); + + return ( + +
+

+ {template.name} +

+

+ {template.description} +

+
+ + +
+ ); + })} +
+ ) : ( + + + + +

Templates are unavailable

+

+ We could not load the template directory. Please try again in a few minutes. +

+
+ )} +
+
+
+ ); +}