Skip to content

Commit 142b21d

Browse files
committed
Add blogpost
1 parent 9167ec3 commit 142b21d

3 files changed

Lines changed: 179 additions & 93 deletions

File tree

apps/blog/src/app/(blog)/[slug]/page.tsx

Lines changed: 150 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
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

919
export 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

packages/ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"./lib/*": "./src/lib/*.ts",
99
"./hooks/*": "./src/hooks/*.ts",
1010
"./components/*": "./src/components/*.tsx",
11+
"./data/*": "./src/data/*.ts",
1112
"./styles": "./src/styles/globals.css"
1213
},
1314
"scripts": {

packages/ui/src/data/footer.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const socialIcons = [
2+
{
3+
_type: "iconLink",
4+
title: "Discord",
5+
icon: "discord",
6+
url: "https://pris.ly/discord",
7+
},
8+
{
9+
_type: "iconLink",
10+
title: "Twitter",
11+
icon: "x-twitter",
12+
url: "https://twitter.com/prisma",
13+
},
14+
{
15+
_type: "iconLink",
16+
title: "YouTube",
17+
icon: "youtube",
18+
url: "https://www.youtube.com/c/PrismaData",
19+
},
20+
{
21+
_type: "iconLink",
22+
title: "GitHub",
23+
icon: "github",
24+
url: "https://github.com/prisma",
25+
},
26+
];
27+
28+
export { socialIcons };

0 commit comments

Comments
 (0)