From a945b1be0043c3cc8b723d1ec7b29383377a2b32 Mon Sep 17 00:00:00 2001 From: Carla Goncalves Date: Tue, 24 Mar 2026 18:43:04 +0000 Subject: [PATCH 1/9] First version --- apps/blog/src/components/PostCard.tsx | 126 +- apps/site/package.json | 11 +- .../site/public/icons/technologies/nextjs.svg | 10 + .../site/public/icons/technologies/prisma.svg | 1 + .../icons/technologies/prisma_light.svg | 1 + apps/site/public/illustrations/hero-grid.svg | 364 ++++++ apps/site/src/app/(index)/page.tsx | 5 +- .../src/app/(prisma-with)/nextjs/page.tsx | 289 +++++ apps/site/src/app/global.css | 37 +- apps/site/src/app/layout.tsx | 50 +- apps/site/src/app/og/image.png/route.tsx | 7 +- apps/site/src/data/prisma-with/nextjs.json | 177 +++ apps/site/src/lib/cn.ts | 1 + apps/site/src/lib/shiki_prisma.ts | 209 +++ .../{blog-metadata.ts => site-metadata.ts} | 0 packages/eclipse/src/components/button.tsx | 79 +- packages/eclipse/src/styles/globals.css | 1125 +++++++++-------- .../ui/src/components/author-avatar-group.tsx | 46 + packages/ui/src/components/post-card.tsx | 129 ++ packages/ui/src/components/quote.tsx | 52 + packages/ui/src/styles/globals.css | 3 +- pnpm-lock.yaml | 94 ++ 22 files changed, 2105 insertions(+), 711 deletions(-) create mode 100644 apps/site/public/icons/technologies/nextjs.svg create mode 100644 apps/site/public/icons/technologies/prisma.svg create mode 100644 apps/site/public/icons/technologies/prisma_light.svg create mode 100644 apps/site/public/illustrations/hero-grid.svg create mode 100644 apps/site/src/app/(prisma-with)/nextjs/page.tsx create mode 100644 apps/site/src/data/prisma-with/nextjs.json create mode 100644 apps/site/src/lib/cn.ts create mode 100644 apps/site/src/lib/shiki_prisma.ts rename apps/site/src/lib/{blog-metadata.ts => site-metadata.ts} (100%) create mode 100644 packages/ui/src/components/author-avatar-group.tsx create mode 100644 packages/ui/src/components/post-card.tsx create mode 100644 packages/ui/src/components/quote.tsx diff --git a/apps/blog/src/components/PostCard.tsx b/apps/blog/src/components/PostCard.tsx index 79a80e9404..085fcaa104 100644 --- a/apps/blog/src/components/PostCard.tsx +++ b/apps/blog/src/components/PostCard.tsx @@ -1,11 +1,11 @@ -'use client'; +"use client"; -import Image from "next/image"; -import Link from "next/link"; -import { Badge, Card } from "@prisma/eclipse"; -import { cn } from "@prisma-docs/ui/lib/cn"; - -import { AuthorAvatarGroup } from "@/components/AuthorAvatarGroup"; +import { + PostCard as SharedPostCard, + type PostCardItem as SharedPostCardItem, +} from "@prisma-docs/ui/components/post-card"; +import { type AuthorProfile } from "@prisma-docs/ui/components/author-avatar-group"; +import { getAuthorProfiles } from "@/lib/authors"; import { formatDate, formatTag } from "@/lib/format"; import { withBlogBasePathForImageSrc } from "@/lib/url"; @@ -30,91 +30,35 @@ export function PostCard({ currentCategory: string; featured?: boolean; }) { - const isFeatured = featured; - const imageSizes = isFeatured ? "(min-width: 640px) 50vw, 100vw" : "384px"; - - const containerClassName = cn( - "group grid overflow-hidden", - isFeatured - ? "grid-cols-1 md:grid-cols-2 gap-4 bg-background-default rounded-square border border-stroke-neutral shadow-box-low" - : "sm:grid-cols-[1fr_384px] border-b pb-4 sm:pb-6 border-stroke-neutral gap-8", - ); - const imageClassName = cn( - "object-cover transition-transform duration-300 group-hover:scale-[1.02]", - !isFeatured && "rounded-square", - ); - const imageWrapperClassName = cn( - "relative aspect-video w-full h-full", - isFeatured ? "order-1" : "order-2 max-w-96 hidden sm:block", - ); - - const titleClassName = cn( - "text-foreground-neutral font-mona-sans mt-4 mb-2", - isFeatured - ? "text-2xl font-bold" - : "text-md md:text-lg font-[650] sm:font-bold", - ); - const excerptClassName = cn( - "text-sm text-foreground-neutral-weak line-clamp-2", - isFeatured && "leading-[20px]!", - ); - const authorClassName = cn( - "items-center gap-2 font-semibold text-sm mt-4 md:mt-0", - isFeatured ? "flex" : "hidden sm:flex", - ); - - const postBody = ( - <> -
-
- {post.tags && post.tags.length > 0 && ( - - )} - {post.date && ( - - {formatDate(new Date(post.date).toISOString())} - - )} -
- {post.title &&

{post.title}

} - {post.excerpt &&

{post.excerpt}

} + // Transform blog-specific post data to shared component format + const authorProfiles = post.author ? getAuthorProfiles([post.author]) : []; + const author: AuthorProfile | null = + authorProfiles.length > 0 + ? { + name: authorProfiles[0].name, + imageSrc: authorProfiles[0].imageSrc + ? withBlogBasePathForImageSrc(authorProfiles[0].imageSrc) + : null, + } + : null; + const badge = + post.tags && post.tags.length > 0 + ? formatTag( + currentCategory !== "show-all" ? currentCategory : post.tags[0], + ) + : null; -
- {post.author && ( - - )} - - ); + const sharedPost: SharedPostCardItem = { + url: post.url, + title: post.title, + date: formatDate(new Date(post.date).toISOString()), + excerpt: post.excerpt, + author, + imageSrc: post.imageSrc ? withBlogBasePathForImageSrc(post.imageSrc) : null, + imageAlt: post.imageAlt, + badge, + }; - return ( - - {post.imageSrc && ( -
- {post.imageAlt -
- )} - {isFeatured ? ( - - {postBody} - - ) : ( -
{postBody}
- )} - - ); + return ; } diff --git a/apps/site/package.json b/apps/site/package.json index f89768ff49..7aa57c86ad 100644 --- a/apps/site/package.json +++ b/apps/site/package.json @@ -13,17 +13,20 @@ }, "dependencies": { "@base-ui/react": "catalog:", - "@prisma/eclipse": "workspace:^", "@prisma-docs/ui": "workspace:*", + "@prisma/eclipse": "workspace:^", "cors": "^2.8.6", + "html-react-parser": "^5.2.17", "lucide-react": "catalog:", "next": "catalog:", "npm-to-yarn": "catalog:", + "posthog-js": "catalog:", "react": "catalog:", "react-dom": "catalog:", "react-tweet": "catalog:", - "posthog-js": "catalog:", "remark-directive": "catalog:", + "shiki": "3.22.0", + "tailwind-merge": "catalog:", "zod": "catalog:" }, "devDependencies": { @@ -32,11 +35,11 @@ "@types/node": "catalog:", "@types/react": "catalog:", "@types/react-dom": "catalog:", + "babel-plugin-react-compiler": "catalog:", "next-validate-link": "catalog:", "postcss": "catalog:", "tailwindcss": "catalog:", "tsx": "catalog:", - "typescript": "catalog:", - "babel-plugin-react-compiler": "catalog:" + "typescript": "catalog:" } } diff --git a/apps/site/public/icons/technologies/nextjs.svg b/apps/site/public/icons/technologies/nextjs.svg new file mode 100644 index 0000000000..65745d8990 --- /dev/null +++ b/apps/site/public/icons/technologies/nextjs.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/site/public/icons/technologies/prisma.svg b/apps/site/public/icons/technologies/prisma.svg new file mode 100644 index 0000000000..e50dbcee81 --- /dev/null +++ b/apps/site/public/icons/technologies/prisma.svg @@ -0,0 +1 @@ + diff --git a/apps/site/public/icons/technologies/prisma_light.svg b/apps/site/public/icons/technologies/prisma_light.svg new file mode 100644 index 0000000000..7a199a7cbf --- /dev/null +++ b/apps/site/public/icons/technologies/prisma_light.svg @@ -0,0 +1 @@ + diff --git a/apps/site/public/illustrations/hero-grid.svg b/apps/site/public/illustrations/hero-grid.svg new file mode 100644 index 0000000000..8f0dd9b55f --- /dev/null +++ b/apps/site/public/illustrations/hero-grid.svg @@ -0,0 +1,364 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/site/src/app/(index)/page.tsx b/apps/site/src/app/(index)/page.tsx index 428125b655..5b8da6ea72 100644 --- a/apps/site/src/app/(index)/page.tsx +++ b/apps/site/src/app/(index)/page.tsx @@ -1,5 +1,8 @@ import type { Metadata } from "next"; -import { SITE_HOME_DESCRIPTION, SITE_HOME_TITLE } from "../../lib/blog-metadata"; +import { + SITE_HOME_DESCRIPTION, + SITE_HOME_TITLE, +} from "../../lib/site-metadata"; export const metadata: Metadata = { title: SITE_HOME_TITLE, diff --git a/apps/site/src/app/(prisma-with)/nextjs/page.tsx b/apps/site/src/app/(prisma-with)/nextjs/page.tsx new file mode 100644 index 0000000000..cd7bd05c81 --- /dev/null +++ b/apps/site/src/app/(prisma-with)/nextjs/page.tsx @@ -0,0 +1,289 @@ +import { PostCard } from "@prisma-docs/ui/components/post-card"; +import { Quote } from "@prisma-docs/ui/components/quote"; + +import type { Metadata } from "next"; +import parse from "html-react-parser"; +import { prisma_highlighter } from "../../../lib/shiki_prisma"; +import * as data from "../../../data/prisma-with/nextjs"; +import { cn } from "../../../lib/cn"; +import { Button, Card, Action } from "@prisma/eclipse"; +import { + CodeBlock, + Tabs, + TabsList, + TabsTrigger, + TabsContent, +} from "@prisma/eclipse"; + +const code_obj = { + "static-data": `// app/blog/[slug]/page.tsx +import { PrismaClient } from '@prisma/client' + +const prisma = new PrismaClient() + +// Return a list of 'params' to populate the [slug] dynamic segment +export async function generateStaticParams() { + const posts = await prisma.post.findMany() + + return posts.map((post) => ({ + slug: post.slug, + })) +} + +// Multiple versions of this page will be statically generated +// using the 'params' returned by 'generateStaticParams' +export default async function Page({ params }: { params: { slug: string } }) { + // Fetch the post based on slug + const post = await prisma.post.findUnique({ + where: { slug: params.slug }, + }) + + // Simple demo rendering + return ( +
+

{post?.title || 'Post not found'}

+

{post?.content || 'No content available'}

+
+ ) +}`, +}; + +export const metadata: Metadata = { + title: "Next.js Database with Prisma | Next-Generation ORM for SQL Databases", + description: + "Prisma is a next-generation ORM for Node.js & TypeScript. It's the easiest way to build Next.js apps with MySQL, PostgreSQL & SQL Server databases.", + canonical: "https://www.prisma.io/nextjs", +}; + +const Hero = () => { + return ( +
+
+
+
+
+ + + {data.hero.eyebrow} + +
+

+ {parse(data.hero.title)} +

+
+

+ {data.hero.description} +

+
+ + +
+
+
+
+ {`Prisma +
+
+ Prisma + Prisma +
+
+
+
+ ); +}; + +export default async function SiteHome() { + return ( +
+ +
+
+
+

+ {data.why.title} +

+
+ {data.why.cards.map((card) => ( + + + + +

+ {parse(card.title)} +

+

+ {parse(card.description)} +

+
+ ))} +
+
+
+
+
+
+

+ {data.how.title} +

+

+ {data.how.description} +

+
+ + + {data.how.tabs.head.map((tab) => ( + + {tab.title} + + ))} + + {await Promise.all( + data.how.tabs.body.map(async (body) => { + return ( + +
+ {parse(body.content)} +
+ {Object.keys(code_obj).includes(body.value) && ( + +
+ + )} +
+
+ + ); + }), + )} + +
+
+
+
+
+

+ {data.why_prisma.title} +

+
+ {data.why_prisma.cards.map((card, idx: number) => ( +
+ +
+ ))} +
+
+
+
+
+ +

{data.quote.text}

+
+
+
+
+
+

+ {data.community.title} +

+
+ {data.community.cards.map((card, idx: number) => ( + = 3 && idx === 3 && "col-start-2", + idx >= 3 && idx === 4 && "col-start-4", + )} + > +
+ + + +

+ {parse(card.title)} +

+
+

+ {parse(card.description)} +

+ + {card.btn.label} + +
+ ))} +
+
+
+
+ ); +} diff --git a/apps/site/src/app/global.css b/apps/site/src/app/global.css index f0100f4a23..b22decad90 100644 --- a/apps/site/src/app/global.css +++ b/apps/site/src/app/global.css @@ -6,6 +6,10 @@ --color-fd-primary: var(--color-stroke-ppg); } +.hero { + @apply pt-30; +} + .bg-blog { background-color: var(--color-background-default); } @@ -39,51 +43,50 @@ .newsletter-bg { background: linear-gradient( - 59deg, - color-mix(in srgb, var(--color-foreground-ppg) 30%, transparent) 6.53%, + 59deg, + color-mix(in srgb, var(--color-foreground-ppg) 30%, transparent) 6.53%, var(--color-background-default) 74.71% - ) + ); } @keyframes glitch-1 { 0% { - clip-path: inset(20% 0 60% 0); + clip-path: inset(20% 0 60% 0); } 20% { - clip-path: inset(10% 0 85% 0); + clip-path: inset(10% 0 85% 0); } 40% { - clip-path: inset(40% 0 40% 0); + clip-path: inset(40% 0 40% 0); } 60% { - clip-path: inset(80% 0 5% 0); + clip-path: inset(80% 0 5% 0); } 80% { - clip-path: inset(50% 0 30% 0); + clip-path: inset(50% 0 30% 0); } 100% { - clip-path: inset(25% 0 55% 0); + clip-path: inset(25% 0 55% 0); } - } +} @keyframes glitch-2 { 0% { - clip-path: inset(80% 0 5% 0); + clip-path: inset(80% 0 5% 0); } 20% { - clip-path: inset(50% 0 30% 0); + clip-path: inset(50% 0 30% 0); } 40% { - clip-path: inset(20% 0 60% 0); + clip-path: inset(20% 0 60% 0); } 60% { - clip-path: inset(10% 0 85% 0); + clip-path: inset(10% 0 85% 0); } 80% { - clip-path: inset(40% 0 40% 0); + clip-path: inset(40% 0 40% 0); } 100% { - clip-path: inset(75% 0 15% 0); + clip-path: inset(75% 0 15% 0); } } - diff --git a/apps/site/src/app/layout.tsx b/apps/site/src/app/layout.tsx index 3fac2aaf3e..eea6746384 100644 --- a/apps/site/src/app/layout.tsx +++ b/apps/site/src/app/layout.tsx @@ -4,10 +4,12 @@ import "./global.css"; import { Inter } from "next/font/google"; import type { Metadata } from "next"; import Script from "next/script"; -import { SITE_HOME_DESCRIPTION, SITE_HOME_TITLE } from "@/lib/blog-metadata"; +import type React from "react"; +import { SITE_HOME_DESCRIPTION, SITE_HOME_TITLE } from "@/lib/site-metadata"; import { WebNavigation } from "@prisma-docs/ui/components/web-navigation"; import { Footer } from "@prisma-docs/ui/components/footer"; import { ThemeProvider } from "@prisma-docs/ui/components/theme-provider"; +import { FontAwesomeScript as WebFA } from "@prisma/eclipse"; const inter = Inter({ subsets: ["latin"], @@ -20,6 +22,28 @@ export const metadata: Metadata = { description: SITE_HOME_DESCRIPTION, }; +const themeInitScript = ` +(() => { + try { + const storageKey = "theme"; + const stored = localStorage.getItem(storageKey); + const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; + const resolved = + stored === "light" || stored === "dark" + ? stored + : prefersDark + ? "dark" + : "light"; + + const root = document.documentElement; + root.setAttribute("data-theme", resolved); + root.classList.toggle("dark", resolved === "dark"); + } catch { + // Ignore storage/media-query failures and use CSS defaults. + } +})(); +`; + function baseOptions() { return { nav: { @@ -69,9 +93,9 @@ function baseOptions() { icon: "fa-regular fa-message-code", }, { - text: "Get started", - url: "https://www.prisma.io/docs", - icon: "fa-regular fa-book-open", + text: "Prisma Partners", + url: "/partners", + icon: "fa-regular fa-lightbulb", }, { text: "Tutorials", @@ -108,8 +132,8 @@ function baseOptions() { ], }, { - url: "/partners", - text: "Partners", + url: "/docs", + text: "Docs", }, { url: "https://www.prisma.io/blog", @@ -121,18 +145,12 @@ function baseOptions() { export default function Layout({ children }: { children: React.ReactNode }) { return ( - + - +