Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/blog/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const ContentSecurityPolicy = `
https://vercel.live https://vercel.com data: blob:
https://td.doubleclick.net
https://raw.githubusercontent.com;

connect-src 'self'
https://api.github.com
https://p2zxqf70.api.sanity.io
Expand Down Expand Up @@ -121,7 +122,8 @@ const ContentSecurityPolicy = `
https://unpkg.com
https://proxy.kapa.ai
https://hcaptcha.com
https://*.hcaptcha.com;
https://*.hcaptcha.com
https://ka-p.fontawesome.com;

media-src 'self'
https://*.prisma.io
Expand Down
Binary file not shown.
Binary file added apps/blog/public/fonts/monaspace_neon_var.woff
Binary file not shown.
Binary file added apps/blog/public/fonts/monaspace_neon_var.woff2
Binary file not shown.
37 changes: 21 additions & 16 deletions apps/blog/source.config.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import remarkDirective from 'remark-directive';
import remarkDirective from "remark-directive";
import {
remarkDirectiveAdmonition,
remarkMdxFiles,
} from 'fumadocs-core/mdx-plugins';
import { remarkImage } from 'fumadocs-core/mdx-plugins';
} from "fumadocs-core/mdx-plugins";
import { remarkImage } from "fumadocs-core/mdx-plugins";
import {
defineCollections,
defineConfig,
frontmatterSchema,
} from 'fumadocs-mdx/config';
import lastModified from 'fumadocs-mdx/plugins/last-modified';
import { z } from 'zod';
import convert from 'npm-to-yarn';
} from "fumadocs-mdx/config";
import lastModified from "fumadocs-mdx/plugins/last-modified";
import { z } from "zod";
import convert from "npm-to-yarn";

export const blogPosts = defineCollections({
type: 'doc',
dir: 'content/blog',
type: "doc",
dir: "content/blog",
schema: frontmatterSchema.extend({
authorSrc: z.string().optional(),
metaDescription: z.string().optional(),
metaTitle: z.string().optional(),
description: z.string().optional(),
authors: z.array(
z.enum([
'Aidan McAlister',
Expand Down Expand Up @@ -93,6 +97,7 @@ export const blogPosts = defineCollections({
},
});


export default defineConfig({
plugins: [lastModified()],
mdxOptions: {
Expand All @@ -104,19 +109,19 @@ export default defineConfig({
],
remarkCodeTabOptions: { parseMdx: true },
remarkNpmOptions: {
persist: { id: 'package-manager' },
persist: { id: "package-manager" },
// Custom package managers to add --bun flag for bunx commands
packageManagers: [
{ command: (cmd: string) => convert(cmd, 'npm'), name: 'npm' },
{ command: (cmd: string) => convert(cmd, 'pnpm'), name: 'pnpm' },
{ command: (cmd: string) => convert(cmd, 'yarn'), name: 'yarn' },
{ command: (cmd: string) => convert(cmd, "npm"), name: "npm" },
{ command: (cmd: string) => convert(cmd, "pnpm"), name: "pnpm" },
{ command: (cmd: string) => convert(cmd, "yarn"), name: "yarn" },
{
command: (cmd: string) => {
const converted = convert(cmd, 'bun');
const converted = convert(cmd, "bun");
if (!converted) return undefined;
return converted.replace(/^bun x /, 'bunx --bun ');
return converted.replace(/^bun x /, "bunx --bun ");
},
name: 'bun',
name: "bun",
},
],
},
Expand Down
113 changes: 68 additions & 45 deletions apps/blog/src/app/(blog)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
import { Suspense } from 'react';
import { blog } from '@/lib/source';
import { BlogGrid } from '@/components/BlogGrid';
import { Suspense } from "react";
import { blog } from "@/lib/source";
import { BlogGrid } from "@/components/BlogGrid";
import { Avatar, Badge, Card } from "@prisma-docs/eclipse";

export default function BlogHome() {
const posts = blog.getPages().sort((a, b) => {
const aTime =
a.data.date instanceof Date
? a.data.date.getTime()
: new Date((a.data.date as unknown as string) ?? '').getTime();
: new Date((a.data.date as unknown as string) ?? "").getTime();
const bTime =
b.data.date instanceof Date
? b.data.date.getTime()
: new Date((b.data.date as unknown as string) ?? '').getTime();
: new Date((b.data.date as unknown as string) ?? "").getTime();
return bTime - aTime;
});

const formatDate = (value: unknown) => {
const date =
value instanceof Date ? value : new Date((value as string) ?? '');
if (Number.isNaN(date.getTime())) return '';
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
value instanceof Date ? value : new Date((value as string) ?? "");
if (Number.isNaN(date.getTime())) return "";
return date.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
});
};
const getPrimaryAuthor = (post: (typeof posts)[number]) => {
const data = post.data as any;
const authors = Array.isArray(data.authors) ? data.authors : [];
const authors = Array.isArray(data?.authors) ? data?.authors : [];
return authors.length > 0 ? authors[0] : null;
};
const getCardImageSrc = (post: (typeof posts)[number]) => {
Expand All @@ -37,12 +38,12 @@ export default function BlogHome() {
(data.metaImagePath as string | undefined);
if (rel) {
// If frontmatter already provides an absolute path, use it directly
if (rel.startsWith('/')) {
if (rel.startsWith("/")) {
return rel;
}
const base = post.url.startsWith('/') ? post.url : `/${post.url}`;
const baseClean = base.endsWith('/') ? base.slice(0, -1) : base;
const relClean = rel.replace(/^\.\//, '').replace(/^\/+/, '');
const base = post.url.startsWith("/") ? post.url : `/${post.url}`;
const baseClean = base.endsWith("/") ? base.slice(0, -1) : base;
const relClean = rel.replace(/^\.\//, "").replace(/^\/+/, "");
return `${baseClean}/${relClean}`;
}
const absolute =
Expand All @@ -52,47 +53,69 @@ export default function BlogHome() {
};
const items = posts.map((post) => {
const data = post.data as any;

// Safely convert date to ISO string with validation
let dateISO = "";
if (data.date) {
try {
const dateObj = new Date(data.date);
if (!isNaN(dateObj.getTime())) {
dateISO = dateObj.toISOString();
}
} catch (error) {
// If date conversion fails, fall back to empty string
dateISO = "";
}
}

return {
url: post.url,
title: data.title as string,
date: data.date ? new Date(data.date).toISOString() : '',
description: (data.description as string) ?? '',
date: dateISO,
description:
(data.description as string) || (data.metaDescription as string) || "",
author: getPrimaryAuthor(post),
authorSrc: null,
imageSrc: getCardImageSrc(post),
imageAlt: (data.heroImageAlt as string) ?? (data.title as string),
seriesTitle: data.series?.title ?? null,
badge: "Release",
};
});
return (
<main className="flex-1 w-full max-w-350 mx-auto px-4 py-8">
<h1 className="text-4xl font-bold mb-2">Prisma Blog</h1>
<p className="text-fd-muted mb-8">
Guides, announcements and articles about Prisma, databases and the data
access layer.
</p>

<main className="flex-1 w-full max-w-249 mx-auto px-4 py-8">
<h1 className="stretch-display text-4xl font-bold mb-2 landing-h1 text-center mt-9 font-display">
Blog
</h1>
{/* Category pills (static "Show all" to match layout) */}
<div className="flex flex-wrap gap-2 mb-8">
<span className="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-fd-primary text-white">
Show all
</span>
</div>
<div className="pt-6 pb-12 mt-10">
<div className="flex flex-wrap gap-2 mb-8">
<Badge color="ppg" label="Show all" />
<Badge color="neutral" label="User Story" />
<Badge color="neutral" label="Release" />
{/*
{categories.map((category: string, idx: number) =>
<Badge color={activeCat === category ? "ppg" : "neutral"} label={category} />
)}
*/}
</div>

{/* Grid with pagination */}
<Suspense
fallback={
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{items.slice(0, 12).map((post) => (
<div
key={post.url}
className="rounded-2xl border border-fd-primary/20 bg-fd-secondary animate-pulse h-64"
/>
))}
</div>
}
>
<BlogGrid items={items} pageSize={12} />
</Suspense>
{/* Grid with pagination */}
<Suspense
fallback={
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{items.slice(0, 12).map((post) => (
<div
key={post.url}
className="rounded-2xl border border-fd-primary/20 bg-fd-secondary animate-pulse h-64"
/>
))}
</div>
}
>
<BlogGrid items={items.slice(1, -1)} pageSize={12} />
</Suspense>
</div>
</main>
);
}
41 changes: 36 additions & 5 deletions apps/blog/src/app/global.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,38 @@
@import "tailwindcss";
@import '@prisma-docs/eclipse/styles/globals.css';
@import 'fumadocs-ui/css/shadcn.css';
@import 'fumadocs-ui/css/preset.css';
@import 'fumadocs-openapi/css/preset.css';
@import '@prisma-docs/ui/styles';
@import "@prisma-docs/eclipse/styles/globals.css";
@import "fumadocs-ui/css/shadcn.css";
@import "fumadocs-ui/css/preset.css";
@import "fumadocs-openapi/css/preset.css";
@import "@prisma-docs/ui/styles";

:root {
--color-fd-primary: var(--color-stroke-ppg);
}

.bg-blog {
background: linear-gradient(
0deg,
var(--color-background-default) 90%,
var(--color-background-ppg) 100%
);
}

.landing-h1 {
color: var(--color-foreground-neutral);
font-variant-numeric: lining-nums tabular-nums slashed-zero;
font-variation-settings: "wdth" 125;
/* title/3xl */
font-family: var(--font-family-display);
font-size: 60px;
font-style: normal;
font-weight: 900;
line-height: 120%; /* 120% */
}

.newsletter-bg {
background: linear-gradient(
59deg,
var(--color-foreground-ppg) 6.53%,
var(--color-background-default) 74.71%
);
}
2 changes: 1 addition & 1 deletion apps/blog/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function Layout({ children }: LayoutProps<"/">) {
crossOrigin="anonymous"
></Script>
</head>
<body className="flex flex-col min-h-screen">
<body className="flex flex-col min-h-screen bg-blog pt-24">
<Provider>{children}</Provider>
</body>
</html>
Expand Down
Loading
Loading