1- import { notFound } from 'next/navigation' ;
2- import Link from 'next/link' ;
3- import { InlineTOC } from 'fumadocs-ui/components/inline-toc' ;
4- import { getMDXComponents } from '@/mdx-components' ;
5- import { createRelativeLink } from 'fumadocs-ui/mdx' ;
6- import { blog } from '@/lib/source' ;
7- import Image from 'next/image' ;
1+ import { notFound } from "next/navigation" ;
2+ import Link from "next/link" ;
3+ import { getMDXComponents } from "@/mdx-components" ;
4+ import { createRelativeLink } from "fumadocs-ui/mdx" ;
5+ import { blog } from "@/lib/source" ;
6+ import {
7+ Action ,
8+ Avatar ,
9+ Badge ,
10+ Button ,
11+ cn ,
12+ InlineTOC ,
13+ Input ,
14+ Label ,
15+ Separator ,
16+ } from "@prisma-docs/eclipse" ;
17+ import { socialIcons } from "@prisma-docs/ui/data/footer" ;
818
919export default async function Page ( props : {
1020 params : Promise < { slug : string } > ;
@@ -16,104 +26,151 @@ export default async function Page(props: {
1626 const MDX = page . data . body ;
1727 const formatDate = ( value : unknown ) => {
1828 const date =
19- value instanceof Date ? value : new Date ( ( value as string ) ?? '' ) ;
20- if ( Number . isNaN ( date . getTime ( ) ) ) return '' ;
21- return date . toLocaleDateString ( ' en-US' , {
22- year : ' numeric' ,
23- month : ' long' ,
24- day : ' numeric' ,
29+ value instanceof Date ? value : new Date ( ( value as string ) ?? "" ) ;
30+ if ( Number . isNaN ( date . getTime ( ) ) ) return "" ;
31+ return date . toLocaleDateString ( " en-US" , {
32+ year : " numeric" ,
33+ month : " long" ,
34+ day : " numeric" ,
2535 } ) ;
2636 } ;
27- const getHeroImageSrc = ( ) => {
28- const data = page . data as any ;
29- const rel =
30- ( data . heroImagePath as string | undefined ) ??
31- ( data . metaImagePath as string | undefined ) ;
32- if ( rel ) {
33- if ( rel . startsWith ( '/' ) ) return rel ;
34- const base = page . url . startsWith ( '/' ) ? page . url : `/${ page . url } ` ;
35- const baseClean = base . endsWith ( '/' ) ? base . slice ( 0 , - 1 ) : base ;
36- const relClean = rel . replace ( / ^ \. \/ / , '' ) . replace ( / ^ \/ + / , '' ) ;
37- return `${ baseClean } /${ relClean } ` ;
38- }
39- const absolute =
40- ( data . heroImageUrl as string | undefined ) ??
41- ( data . metaImageUrl as string | undefined ) ;
42- return absolute ?? null ;
43- } ;
44- const heroSrc = getHeroImageSrc ( ) ;
4537
4638 return (
47- < >
48- { /* Hero image */ }
49- { heroSrc ? (
50- < div className = "w-full" >
51- < div className = "relative w-full aspect-video" >
52- < Image
53- src = { heroSrc }
54- alt = { ( page . data as any ) . heroImageAlt ?? page . data . title }
55- fill
56- priority
57- className = "object-cover"
58- sizes = "100vw"
39+ < div className = "w-full px-4 mx-auto md:grid md:grid-cols-[1fr_180px] mt-4 md:mt-22 gap-6 max-w-257" >
40+ < div className = "post-contents w-full" >
41+ { /* Title + meta */ }
42+ < header className = "w-full relative" >
43+ < Link
44+ href = "/blog"
45+ className = "text-fd-primary hover:underline text-sm absolute -top-8"
46+ >
47+ ← Back to Blog
48+ </ Link >
49+ < h1 className = "mt-3 mb-8 font-bold max-md:text-3xl md:text-5xl stretch-display font-display text-foreground-neutral" >
50+ { page . data . title }
51+ </ h1 >
52+ < div className = "text-sm text-fd-muted-foreground flex gap-2 items-center text-foreground-neutral mb-4" >
53+ { page . data . authors ?. length > 1 ? (
54+ page . data . authors . join ( ", " )
55+ ) : page . data . authors . length === 1 ? (
56+ < span className = "mt-auto flex items-center gap-2 font-semibold text-sm" >
57+ { page . data ?. authorSrc && (
58+ < Avatar
59+ format = "image"
60+ src = "/avatar.jpg"
61+ alt = "Disabled user"
62+ size = "lg"
63+ disabled
64+ />
65+ ) }
66+ < span > { page . data . authors [ 0 ] } </ span >
67+ </ span >
68+ ) : null }
69+ { page . data . date ? (
70+ < >
71+ < Separator orientation = "vertical" className = "h-4" />
72+ < span className = "text-foreground-neutral-weak" >
73+ { formatDate ( page . data . date ) }
74+ </ span >
75+ </ >
76+ ) : null }
77+ </ div >
78+ < div className = "filter-badge flex gap-2" >
79+ < Badge
80+ color = "neutral"
81+ label = "Release"
82+ className = "border border-stroke-neutral-strong bg-transparent text-foreground-neutral-weak"
83+ />
84+ < Badge
85+ color = "neutral"
86+ label = "Postgres"
87+ className = "border border-stroke-neutral-strong bg-transparent text-foreground-neutral-weak"
88+ />
89+ < Badge
90+ color = "neutral"
91+ label = "ORM"
92+ className = "border border-stroke-neutral-strong bg-transparent text-foreground-neutral-weak"
93+ />
94+ < Badge
95+ color = "neutral"
96+ label = "Customer Story"
97+ className = "border border-stroke-neutral-strong bg-transparent text-foreground-neutral-weak"
5998 />
6099 </ div >
61- </ div >
62- ) : null }
100+ </ header >
63101
64- { /* Title + meta */ }
65- < header className = "w-full max-w-350 mx-auto px-4 md:px-8 py-10" >
66- < Link href = "/blog" className = "text-fd-primary hover:underline text-sm" >
67- ← Back to Blog
68- </ Link >
69- < h1 className = "mt-3 mb-3 text-3xl md:text-4xl font-bold" >
70- { page . data . title }
71- </ h1 >
72- { page . data . description ? (
73- < p className = "text-fd-muted-foreground mb-3" > { page . data . description } </ p >
74- ) : null }
75- < p className = "text-sm text-fd-muted-foreground" >
76- { page . data . authors ?. length ? page . data . authors . join ( ', ' ) : null }
77- { page . data . date ? (
78- < >
79- { ' • ' }
80- < span > { formatDate ( page . data . date ) } </ span >
81- </ >
82- ) : null }
83- </ p >
84- </ header >
102+ { /* Body */ }
103+ < article className = "w-full flex flex-col pb-8 mt-12" >
104+ < div className = "prose min-w-0 [&_figure]:w-full! [&_figure]:md:max-w-140! [&_figure]:lg:max-w-200!" >
105+ < MDX
106+ components = { getMDXComponents ( {
107+ a : createRelativeLink ( blog , page ) ,
108+ } ) }
109+ />
110+ </ div >
111+ </ article >
112+ < Separator className = "my-12" />
85113
86- { /* Body */ }
87- < article className = "w-full max-w-350 mx-auto flex flex-col px-4 md:px-8 pb-12" >
88- < div className = "prose min-w-0" >
89- < InlineTOC items = { page . data . toc } />
90- < MDX
91- components = { getMDXComponents ( {
92- a : createRelativeLink ( blog , page )
93- } ) }
94- />
114+ { /* Share Container */ }
115+ < div className = "w-fit mx-auto" >
116+ < h5 className = "text-center mb-4 font-semibold text-background-default-reverse" >
117+ Share this article
118+ </ h5 >
119+ < div className = "flex justify-start gap-2 md:max-w-[190px]" >
120+ { socialIcons . map ( ( socialLink : any , idx : number ) => (
121+ < a
122+ href = { socialLink . url }
123+ target = "_blank"
124+ rel = "noopener"
125+ key = { idx }
126+ aria-label = { socialLink . title }
127+ className = { cn (
128+ "text-[1.375rem] transition-colors hover:[&>div]:bg-background-ppg-strong" ,
129+ ) }
130+ >
131+ < Action color = "neutral" size = "2xl" >
132+ < i
133+ className = { `fa-brands fa-${ socialLink . icon } text-current text-foreground-neutral-weak transition-colors` }
134+ />
135+ </ Action >
136+ </ a >
137+ ) ) }
138+ </ div >
95139 </ div >
96- </ article >
97140
98- { /* Newsletter CTA */ }
99- < div className = "w-full max-w-350 mx-auto px-4 md:px-8 pb-16" >
100- < div className = "rounded-2xl border border-fd-primary/20 bg-fd-secondary p-6 md:p-8" >
101- < h4 className = "text-2xl font-semibold mb-2" >
102- Don’t miss the next post!
103- </ h4 >
104- < p className = "text-fd-muted mb-4" >
105- Sign up for the Prisma Newsletter to stay up to date with the latest
106- releases and posts.
107- </ p >
108- < Link
109- href = "https://www.prisma.io/newsletter"
110- className = "inline-flex items-center px-4 py-2 rounded-md bg-fd-primary text-white hover:opacity-90 transition"
111- >
112- Sign up
113- </ Link >
141+ { /* Newsletter CTA */ }
142+ < div className = "w-full px-8 py-12 shadow-box-low newsletter-bg rounded-square border border-background-neutral flex max-sm:flex-col wrap items-start gap-4 sm:items-center justify-between my-12" >
143+ < h5 className = "font-family-sans font-[650] text-white w-fit" >
144+ Subscribe to our newsletter
145+ </ h5 >
146+ < div className = "input flex items-center gap-2 justify-center" >
147+ < Input
148+ type = "email"
149+ id = "newsletter"
150+ size = "2xl"
151+ placeholder = "email@example.com"
152+ className = "w-62"
153+ />
154+ < Button
155+ size = "xl"
156+ variant = "ppg"
157+ type = "submit"
158+ className = "whitespace-nowrap font-semibold"
159+ >
160+ Subscribe
161+ </ Button >
162+ </ div >
163+ </ div >
164+ </ div >
165+ < div className = "max-md:hidden toc" >
166+ < div className = "sticky top-24 [&_a[data-state=inactive]]:text-foreground-neutral-weak! [&_a[data-state=active]]:text-foreground-neutral!" >
167+ < span className = "text-shadow-foreground-neutral-reverse font-semibold text-md mb-4 mt-0 block" >
168+ On this page
169+ </ span >
170+ < InlineTOC items = { page . data . toc } className = "px-0" />
114171 </ div >
115172 </ div >
116- </ >
173+ </ div >
117174 ) ;
118175}
119176
0 commit comments