From 1c418b1ae5d2034f9656002aed70f42aea2f79c3 Mon Sep 17 00:00:00 2001 From: shadcn Date: Thu, 17 Nov 2022 19:14:41 +0400 Subject: [PATCH] feat: implement blog and doc sites --- .gitignore | 1 + app/(auth)/layout.tsx | 4 +- app/(auth)/login/page.tsx | 8 +- app/(auth)/register/page.tsx | 8 +- app/(dashboard)/dashboard/layout.tsx | 4 +- app/(dashboard)/dashboard/loading.tsx | 8 +- app/(dashboard)/dashboard/page.tsx | 10 +- .../dashboard/settings/loading.tsx | 4 +- app/(dashboard)/dashboard/settings/page.tsx | 6 +- app/(docs)/docs/[[...slug]]/head.tsx | 5 + app/(docs)/docs/[[...slug]]/page.tsx | 50 + app/(docs)/docs/layout.tsx | 17 + app/(docs)/guides/[...slug]/head.tsx | 5 + app/(docs)/guides/[...slug]/page.tsx | 60 + app/(docs)/guides/layout.tsx | 7 + app/(docs)/guides/page.tsx | 59 + app/(docs)/layout.tsx | 46 + app/(editor)/editor/[postId]/not-found.tsx | 2 +- app/(editor)/editor/[postId]/page.tsx | 2 +- app/(editor)/editor/layout.tsx | 4 - app/(marketing)/[...slug]/head.tsx | 19 +- app/(marketing)/[...slug]/page.tsx | 38 +- app/(marketing)/blog/[...slug]/head.tsx | 19 +- app/(marketing)/blog/[...slug]/page.tsx | 97 +- app/(marketing)/blog/page.tsx | 99 +- app/(marketing)/layout.tsx | 41 +- app/(marketing)/page.tsx | 231 ++- app/layout.tsx | 20 +- components/{ => dashboard}/editor.tsx | 4 +- .../{ => dashboard}/empty-placeholder.tsx | 0 .../header.tsx} | 0 .../{dashboard-nav.tsx => dashboard/nav.tsx} | 16 +- .../{ => dashboard}/post-create-button.tsx | 0 components/{ => dashboard}/post-item.tsx | 2 +- .../{ => dashboard}/post-operations.tsx | 0 .../shell.tsx} | 1 + .../{ => dashboard}/user-account-nav.tsx | 5 +- components/{ => dashboard}/user-auth-form.tsx | 2 +- components/{ => dashboard}/user-avatar.tsx | 0 components/{ => dashboard}/user-name-form.tsx | 0 components/docs/callout.tsx | 28 + components/docs/card.tsx | 38 + components/docs/mdx-head.tsx | 33 + components/docs/mdx.tsx | 179 ++ components/docs/page-header.tsx | 25 + components/docs/pager.tsx | 62 + components/docs/search.tsx | 37 + components/docs/sidebar-nav.tsx | 60 + components/docs/toc.tsx | 108 ++ components/help.tsx | 9 +- components/icons.tsx | 6 + components/main-nav.tsx | 55 + components/mdx-content.tsx | 7 - components/mobile-nav.tsx | 47 + components/site-footer.tsx | 56 + components/tailwind-indicator.tsx | 16 + config/docs.ts | 136 ++ config/marketing.ts | 29 + config/site.ts | 9 + content/authors/shadcn.mdx | 5 + content/blog/deploying-next-apps.mdx | 214 +++ .../dynamic-routing-static-regeneration.mdx | 214 +++ content/blog/next-auth-postmark.mdx | 64 - content/blog/next-single-page-pagination.mdx | 83 - content/blog/preview-mode-headless-cms.mdx | 214 +++ content/blog/prisma-jest-reset-database.mdx | 40 - content/blog/server-client-components.mdx | 214 +++ content/blog/validation-middleware.mdx | 93 - content/docs/documentation/code-blocks.mdx | 73 + content/docs/documentation/components.mdx | 157 ++ content/docs/documentation/index.mdx | 60 + content/docs/documentation/style-guide.mdx | 211 +++ content/docs/in-progress.mdx | 10 + content/docs/index.mdx | 54 + .../build-blog-using-contentlayer-mdx.mdx | 217 +++ content/guides/using-next-auth-next-13.mdx | 217 +++ content/pages/privacy.mdx | 2 +- content/pages/terms.mdx | 2 +- contentlayer.config.js | 180 ++ hooks/use-lock-body.ts | 12 + hooks/use-mounted.ts | 11 + lib/auth.ts | 5 +- lib/mdx/index.ts | 138 -- lib/mdx/sources.ts | 23 - lib/toc.ts | 76 + next.config.js => next.config.mjs | 4 +- package.json | 24 +- public/images/avatars/shadcn.png | Bin 0 -> 24922 bytes public/images/blog/blog-post-1.jpg | Bin 0 -> 69040 bytes public/images/blog/blog-post-2.jpg | Bin 0 -> 69414 bytes public/images/blog/blog-post-3.jpg | Bin 0 -> 70573 bytes public/images/blog/blog-post-4.jpg | Bin 0 -> 89779 bytes public/images/hero.png | Bin 0 -> 61152 bytes public/images/nextjs-icon-dark-background.png | Bin 36630 -> 0 bytes styles/mdx.css | 48 + tailwind.config.js | 8 + tsconfig.json | 14 +- types/config.d.ts | 18 + types/index.d.ts | 25 + ui/dropdown.tsx | 2 +- yarn.lock | 1629 +++++++++++++++-- 101 files changed, 5268 insertions(+), 937 deletions(-) create mode 100644 app/(docs)/docs/[[...slug]]/head.tsx create mode 100644 app/(docs)/docs/[[...slug]]/page.tsx create mode 100644 app/(docs)/docs/layout.tsx create mode 100644 app/(docs)/guides/[...slug]/head.tsx create mode 100644 app/(docs)/guides/[...slug]/page.tsx create mode 100644 app/(docs)/guides/layout.tsx create mode 100644 app/(docs)/guides/page.tsx create mode 100644 app/(docs)/layout.tsx rename components/{ => dashboard}/editor.tsx (97%) rename components/{ => dashboard}/empty-placeholder.tsx (100%) rename components/{dashboard-header.tsx => dashboard/header.tsx} (100%) rename components/{dashboard-nav.tsx => dashboard/nav.tsx} (82%) rename components/{ => dashboard}/post-create-button.tsx (100%) rename components/{ => dashboard}/post-item.tsx (93%) rename components/{ => dashboard}/post-operations.tsx (100%) rename components/{dashboard-shell.tsx => dashboard/shell.tsx} (99%) rename components/{ => dashboard}/user-account-nav.tsx (93%) rename components/{ => dashboard}/user-auth-form.tsx (98%) rename components/{ => dashboard}/user-avatar.tsx (100%) rename components/{ => dashboard}/user-name-form.tsx (100%) create mode 100644 components/docs/callout.tsx create mode 100644 components/docs/card.tsx create mode 100644 components/docs/mdx-head.tsx create mode 100644 components/docs/mdx.tsx create mode 100644 components/docs/page-header.tsx create mode 100644 components/docs/pager.tsx create mode 100644 components/docs/search.tsx create mode 100644 components/docs/sidebar-nav.tsx create mode 100644 components/docs/toc.tsx create mode 100644 components/main-nav.tsx delete mode 100644 components/mdx-content.tsx create mode 100644 components/mobile-nav.tsx create mode 100644 components/site-footer.tsx create mode 100644 components/tailwind-indicator.tsx create mode 100644 config/docs.ts create mode 100644 config/marketing.ts create mode 100644 config/site.ts create mode 100644 content/authors/shadcn.mdx create mode 100644 content/blog/deploying-next-apps.mdx create mode 100644 content/blog/dynamic-routing-static-regeneration.mdx delete mode 100644 content/blog/next-auth-postmark.mdx delete mode 100644 content/blog/next-single-page-pagination.mdx create mode 100644 content/blog/preview-mode-headless-cms.mdx delete mode 100644 content/blog/prisma-jest-reset-database.mdx create mode 100644 content/blog/server-client-components.mdx delete mode 100644 content/blog/validation-middleware.mdx create mode 100644 content/docs/documentation/code-blocks.mdx create mode 100644 content/docs/documentation/components.mdx create mode 100644 content/docs/documentation/index.mdx create mode 100644 content/docs/documentation/style-guide.mdx create mode 100644 content/docs/in-progress.mdx create mode 100644 content/docs/index.mdx create mode 100644 content/guides/build-blog-using-contentlayer-mdx.mdx create mode 100644 content/guides/using-next-auth-next-13.mdx create mode 100644 contentlayer.config.js create mode 100644 hooks/use-lock-body.ts create mode 100644 hooks/use-mounted.ts delete mode 100644 lib/mdx/index.ts delete mode 100644 lib/mdx/sources.ts create mode 100644 lib/toc.ts rename next.config.js => next.config.mjs (72%) create mode 100644 public/images/avatars/shadcn.png create mode 100644 public/images/blog/blog-post-1.jpg create mode 100644 public/images/blog/blog-post-2.jpg create mode 100644 public/images/blog/blog-post-3.jpg create mode 100644 public/images/blog/blog-post-4.jpg create mode 100644 public/images/hero.png delete mode 100644 public/images/nextjs-icon-dark-background.png create mode 100644 styles/mdx.css create mode 100644 types/config.d.ts create mode 100644 types/index.d.ts diff --git a/.gitignore b/.gitignore index 356c143..11a314a 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ yarn-error.log* next-env.d.ts .vscode +.contentlayer \ No newline at end of file diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx index ca7d2eb..2172f70 100644 --- a/app/(auth)/layout.tsx +++ b/app/(auth)/layout.tsx @@ -1,9 +1,7 @@ -import "styles/globals.css" - interface AuthLayoutProps { children: React.ReactNode } -export default function RootLayout({ children }: AuthLayoutProps) { +export default function AuthLayout({ children }: AuthLayoutProps) { return
{children}
} diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index 042421c..2045868 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -1,14 +1,14 @@ import Link from "next/link" import { Icons } from "@/components/icons" -import { UserAuthForm } from "@/components/user-auth-form" +import { UserAuthForm } from "@/components/dashboard/user-auth-form" export default function LoginPage() { return (
<> @@ -19,12 +19,12 @@ export default function LoginPage() {

Welcome back

-

+

Enter your email to sign in to your account

-

+

Don't have an account? Sign Up diff --git a/app/(auth)/register/page.tsx b/app/(auth)/register/page.tsx index 8489bf0..ef26445 100644 --- a/app/(auth)/register/page.tsx +++ b/app/(auth)/register/page.tsx @@ -1,14 +1,14 @@ import Link from "next/link" import { Icons } from "@/components/icons" -import { UserAuthForm } from "@/components/user-auth-form" +import { UserAuthForm } from "@/components/dashboard/user-auth-form" export default function RegisterPage() { return (

Login @@ -18,12 +18,12 @@ export default function RegisterPage() {

Create an account

-

+

Enter your email below to create your account

-

+

By clicking continue, you agree to our{" "} Terms of Service diff --git a/app/(dashboard)/dashboard/layout.tsx b/app/(dashboard)/dashboard/layout.tsx index 95bf670..dd48f0b 100644 --- a/app/(dashboard)/dashboard/layout.tsx +++ b/app/(dashboard)/dashboard/layout.tsx @@ -2,8 +2,8 @@ import { notFound } from "next/navigation" import Link from "next/link" import { getCurrentUser } from "@/lib/session" -import { DashboardNav } from "@/components/dashboard-nav" -import { UserAccountNav } from "@/components/user-account-nav" +import { DashboardNav } from "@/components/dashboard/nav" +import { UserAccountNav } from "@/components/dashboard/user-account-nav" import { Icons } from "@/components/icons" interface DashboardLayoutProps { diff --git a/app/(dashboard)/dashboard/loading.tsx b/app/(dashboard)/dashboard/loading.tsx index 50a8aa0..02bc9f4 100644 --- a/app/(dashboard)/dashboard/loading.tsx +++ b/app/(dashboard)/dashboard/loading.tsx @@ -1,7 +1,7 @@ -import { DashboardHeader } from "@/components/dashboard-header" -import { DashboardShell } from "@/components/dashboard-shell" -import { PostCreateButton } from "@/components/post-create-button" -import { PostItem } from "@/components/post-item" +import { DashboardHeader } from "@/components/dashboard/header" +import { DashboardShell } from "@/components/dashboard/shell" +import { PostCreateButton } from "@/components/dashboard/post-create-button" +import { PostItem } from "@/components/dashboard/post-item" export default function DashboardLoading() { return ( diff --git a/app/(dashboard)/dashboard/page.tsx b/app/(dashboard)/dashboard/page.tsx index 7499b12..70e10ed 100644 --- a/app/(dashboard)/dashboard/page.tsx +++ b/app/(dashboard)/dashboard/page.tsx @@ -4,11 +4,11 @@ import { db } from "@/lib/db" import { getCurrentUser } from "@/lib/session" import { User } from "@prisma/client" import { authOptions } from "@/lib/auth" -import { DashboardHeader } from "@/components/dashboard-header" -import { PostCreateButton } from "@/components/post-create-button" -import { DashboardShell } from "@/components/dashboard-shell" -import { PostItem } from "@/components/post-item" -import { EmptyPlaceholder } from "@/components/empty-placeholder" +import { DashboardHeader } from "@/components/dashboard/header" +import { PostCreateButton } from "@/components/dashboard/post-create-button" +import { DashboardShell } from "@/components/dashboard/shell" +import { PostItem } from "@/components/dashboard/post-item" +import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder" async function getPostsForUser(userId: User["id"]) { return await db.post.findMany({ diff --git a/app/(dashboard)/dashboard/settings/loading.tsx b/app/(dashboard)/dashboard/settings/loading.tsx index 361d5fc..e213ea8 100644 --- a/app/(dashboard)/dashboard/settings/loading.tsx +++ b/app/(dashboard)/dashboard/settings/loading.tsx @@ -1,5 +1,5 @@ -import { DashboardHeader } from "@/components/dashboard-header" -import { DashboardShell } from "@/components/dashboard-shell" +import { DashboardHeader } from "@/components/dashboard/header" +import { DashboardShell } from "@/components/dashboard/shell" import { Card } from "@/ui/card" export default function DashboardSettingsLoading() { diff --git a/app/(dashboard)/dashboard/settings/page.tsx b/app/(dashboard)/dashboard/settings/page.tsx index 7270556..d997dff 100644 --- a/app/(dashboard)/dashboard/settings/page.tsx +++ b/app/(dashboard)/dashboard/settings/page.tsx @@ -2,9 +2,9 @@ import { redirect } from "next/navigation" import { getCurrentUser } from "@/lib/session" import { authOptions } from "@/lib/auth" -import { DashboardHeader } from "@/components/dashboard-header" -import { DashboardShell } from "@/components/dashboard-shell" -import { UserNameForm } from "@/components/user-name-form" +import { DashboardHeader } from "@/components/dashboard/header" +import { DashboardShell } from "@/components/dashboard/shell" +import { UserNameForm } from "@/components/dashboard/user-name-form" export default async function SettingsPage() { const user = await getCurrentUser() diff --git a/app/(docs)/docs/[[...slug]]/head.tsx b/app/(docs)/docs/[[...slug]]/head.tsx new file mode 100644 index 0000000..1d5974c --- /dev/null +++ b/app/(docs)/docs/[[...slug]]/head.tsx @@ -0,0 +1,5 @@ +import MdxHead from "@/components/docs/mdx-head" + +export default function Head({ params }) { + return +} diff --git a/app/(docs)/docs/[[...slug]]/page.tsx b/app/(docs)/docs/[[...slug]]/page.tsx new file mode 100644 index 0000000..9517e35 --- /dev/null +++ b/app/(docs)/docs/[[...slug]]/page.tsx @@ -0,0 +1,50 @@ +import { notFound } from "next/navigation" +import { allDocs } from "contentlayer/generated" + +import { getTableOfContents } from "@/lib/toc" +import { Mdx } from "@/components/docs/mdx" +import { DashboardTableOfContents } from "@/components/docs/toc" +import { DocsPageHeader } from "@/components/docs/page-header" +import { DocsPager } from "@/components/docs/pager" +import "@/styles/mdx.css" + +interface DocPageProps { + params: { + slug: string[] + } +} + +export async function generateStaticParams(): Promise< + DocPageProps["params"][] +> { + return allDocs.map((doc) => ({ + slug: doc.slugAsParams.split("/"), + })) +} + +export default async function DocPage({ params }: DocPageProps) { + const slug = params?.slug?.join("/") || "" + const doc = allDocs.find((doc) => doc.slugAsParams === slug) + + if (!doc) { + notFound() + } + + const toc = await getTableOfContents(doc.body.raw) + + return ( +

+
+ + +
+ +
+
+
+ +
+
+
+ ) +} diff --git a/app/(docs)/docs/layout.tsx b/app/(docs)/docs/layout.tsx new file mode 100644 index 0000000..41fc042 --- /dev/null +++ b/app/(docs)/docs/layout.tsx @@ -0,0 +1,17 @@ +import { docsConfig } from "@/config/docs" +import { DocsSidebarNav } from "@/components/docs/sidebar-nav" + +interface DocsLayoutProps { + children: React.ReactNode +} + +export default function DocsLayout({ children }: DocsLayoutProps) { + return ( +
+ + {children} +
+ ) +} diff --git a/app/(docs)/guides/[...slug]/head.tsx b/app/(docs)/guides/[...slug]/head.tsx new file mode 100644 index 0000000..1d5974c --- /dev/null +++ b/app/(docs)/guides/[...slug]/head.tsx @@ -0,0 +1,5 @@ +import MdxHead from "@/components/docs/mdx-head" + +export default function Head({ params }) { + return +} diff --git a/app/(docs)/guides/[...slug]/page.tsx b/app/(docs)/guides/[...slug]/page.tsx new file mode 100644 index 0000000..56f3692 --- /dev/null +++ b/app/(docs)/guides/[...slug]/page.tsx @@ -0,0 +1,60 @@ +import Link from "next/link" +import { notFound } from "next/navigation" + +import { allGuides } from "contentlayer/generated" + +import { getTableOfContents } from "@/lib/toc" +import { Mdx } from "@/components/docs/mdx" +import { DashboardTableOfContents } from "@/components/docs/toc" +import { DocsPageHeader } from "@/components/docs/page-header" +import { Icons } from "@/components/icons" +import "@/styles/mdx.css" + +interface GuidePageProps { + params: { + slug: string[] + } +} + +export async function generateStaticParams(): Promise< + GuidePageProps["params"][] +> { + return allGuides.map((guide) => ({ + slug: guide.slugAsParams.split("/"), + })) +} + +export default async function GuidePage({ params }: GuidePageProps) { + const slug = params?.slug?.join("/") + const guide = allGuides.find((guide) => guide.slugAsParams === slug) + + if (!guide) { + notFound() + } + + const toc = await getTableOfContents(guide.body.raw) + + return ( +
+
+ + +
+
+ + + See all guides + +
+
+
+
+ +
+
+
+ ) +} diff --git a/app/(docs)/guides/layout.tsx b/app/(docs)/guides/layout.tsx new file mode 100644 index 0000000..d708c74 --- /dev/null +++ b/app/(docs)/guides/layout.tsx @@ -0,0 +1,7 @@ +interface GuidesLayoutProps { + children: React.ReactNode +} + +export default function GuidesLayout({ children }: GuidesLayoutProps) { + return
{children}
+} diff --git a/app/(docs)/guides/page.tsx b/app/(docs)/guides/page.tsx new file mode 100644 index 0000000..3d7efad --- /dev/null +++ b/app/(docs)/guides/page.tsx @@ -0,0 +1,59 @@ +import Link from "next/link" +import { compareDesc } from "date-fns" +import { allGuides } from "contentlayer/generated" + +import { DocsPageHeader } from "@/components/docs/page-header" +import { formatDate } from "@/lib/utils" + +export default function GuidesPage() { + const guides = allGuides + .filter((guide) => guide.published) + .sort((a, b) => { + return compareDesc(new Date(a.date), new Date(b.date)) + }) + + return ( +
+ + {guides?.length ? ( +
+ {guides.map((guide) => ( +
+ {guide.featured && ( + + Featured + + )} +
+
+

+ {guide.title} +

+ {guide.description && ( +

{guide.description}

+ )} +
+ {guide.date && ( +

+ {formatDate(guide.date)} +

+ )} +
+ + View + +
+ ))} +
+ ) : ( +

No guides published.

+ )} +
+ ) +} diff --git a/app/(docs)/layout.tsx b/app/(docs)/layout.tsx new file mode 100644 index 0000000..f9903ed --- /dev/null +++ b/app/(docs)/layout.tsx @@ -0,0 +1,46 @@ +import Link from "next/link" + +import { siteConfig } from "@/config/site" +import { docsConfig } from "@/config/docs" +import { Icons } from "@/components/icons" +import { MainNav } from "@/components/main-nav" +import { DocsSearch } from "@/components/docs/search" +import { SiteFooter } from "@/components/site-footer" +import { DocsSidebarNav } from "@/components/docs/sidebar-nav" + +interface DocsLayoutProps { + children: React.ReactNode +} + +export default function DocsLayout({ children }: DocsLayoutProps) { + return ( +
+
+
+ + + +
+
+ +
+ +
+
+
+
{children}
+ +
+ ) +} diff --git a/app/(editor)/editor/[postId]/not-found.tsx b/app/(editor)/editor/[postId]/not-found.tsx index 2531a09..325960c 100644 --- a/app/(editor)/editor/[postId]/not-found.tsx +++ b/app/(editor)/editor/[postId]/not-found.tsx @@ -1,6 +1,6 @@ import Link from "next/link" -import { EmptyPlaceholder } from "@/components/empty-placeholder" +import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder" export default function NotFound() { return ( diff --git a/app/(editor)/editor/[postId]/page.tsx b/app/(editor)/editor/[postId]/page.tsx index 5f4438b..08377e8 100644 --- a/app/(editor)/editor/[postId]/page.tsx +++ b/app/(editor)/editor/[postId]/page.tsx @@ -4,7 +4,7 @@ import { Post, User } from "@prisma/client" import { db } from "@/lib/db" import { getCurrentUser } from "@/lib/session" import { authOptions } from "@/lib/auth" -import { Editor } from "@/components/editor" +import { Editor } from "@/components/dashboard/editor" async function getPostForUser(postId: Post["id"], userId: User["id"]) { return await db.post.findFirst({ diff --git a/app/(editor)/editor/layout.tsx b/app/(editor)/editor/layout.tsx index 7a97a27..6b0fc0b 100644 --- a/app/(editor)/editor/layout.tsx +++ b/app/(editor)/editor/layout.tsx @@ -1,7 +1,3 @@ -import Link from "next/link" - -import { Icons } from "@/components/icons" - interface EditorProps { children?: React.ReactNode } diff --git a/app/(marketing)/[...slug]/head.tsx b/app/(marketing)/[...slug]/head.tsx index 297426d..1d5974c 100644 --- a/app/(marketing)/[...slug]/head.tsx +++ b/app/(marketing)/[...slug]/head.tsx @@ -1,18 +1,5 @@ -import { Page } from "@/lib/mdx/sources" +import MdxHead from "@/components/docs/mdx-head" -export default async function Head({ params }) { - const page = await Page.getMdxNode(params?.slug) - - if (!page) { - return null - } - - return ( - <> - {page.frontMatter.title} - {page.frontMatter.excerpt && ( - - )} - - ) +export default function Head({ params }) { + return } diff --git a/app/(marketing)/[...slug]/page.tsx b/app/(marketing)/[...slug]/page.tsx index bdcb338..c885946 100644 --- a/app/(marketing)/[...slug]/page.tsx +++ b/app/(marketing)/[...slug]/page.tsx @@ -1,8 +1,8 @@ import { notFound } from "next/navigation" +import { allPages } from "contentlayer/generated" -import { Page } from "@/lib/mdx/sources" -import { MdxContent } from "@/components/mdx-content" -import { serialize } from "next-mdx-remote/serialize" +import { Mdx } from "@/components/docs/mdx" +import "@/styles/mdx.css" interface PageProps { params: { @@ -11,35 +11,31 @@ interface PageProps { } export async function generateStaticParams(): Promise { - const files = await Page.getMdxFiles() - - return files?.map((file) => ({ - slug: file.slug.split("/"), + return allPages.map((page) => ({ + slug: page.slugAsParams.split("/"), })) } -export default async function BasicPage({ params }: PageProps) { - const page = await Page.getMdxNode(params.slug) +export default async function PagePage({ params }: PageProps) { + const slug = params?.slug?.join("/") + const page = allPages.find((page) => page.slugAsParams === slug) if (!page) { notFound() } - const mdx = await serialize(page.content) - return ( -
-
-

- {page.frontMatter.title} +
+
+

+ {page.title}

+ {page.description && ( +

{page.description}

+ )}
-
- {mdx && ( -
- -
- )} +
+
) } diff --git a/app/(marketing)/blog/[...slug]/head.tsx b/app/(marketing)/blog/[...slug]/head.tsx index 01cf4bd..1d5974c 100644 --- a/app/(marketing)/blog/[...slug]/head.tsx +++ b/app/(marketing)/blog/[...slug]/head.tsx @@ -1,18 +1,5 @@ -import { Blog } from "@/lib/mdx/sources" +import MdxHead from "@/components/docs/mdx-head" -export default async function Head({ params }) { - const post = await Blog.getMdxNode(params?.slug) - - if (!post) { - return null - } - - return ( - <> - {post.frontMatter.title} - {post.frontMatter.excerpt && ( - - )} - - ) +export default function Head({ params }) { + return } diff --git a/app/(marketing)/blog/[...slug]/page.tsx b/app/(marketing)/blog/[...slug]/page.tsx index b5e3164..a609c9b 100644 --- a/app/(marketing)/blog/[...slug]/page.tsx +++ b/app/(marketing)/blog/[...slug]/page.tsx @@ -1,9 +1,12 @@ import { notFound } from "next/navigation" -import { serialize } from "next-mdx-remote/serialize" +import { allAuthors, allPosts } from "contentlayer/generated" -import { Blog } from "@/lib/mdx/sources" -import { MdxContent } from "@/components/mdx-content" +import { Mdx } from "@/components/docs/mdx" +import "@/styles/mdx.css" import { formatDate } from "@/lib/utils" +import Link from "next/link" +import { Icons } from "@/components/icons" +import Image from "next/image" interface PostPageProps { params: { @@ -14,42 +17,86 @@ interface PostPageProps { export async function generateStaticParams(): Promise< PostPageProps["params"][] > { - const files = await Blog.getMdxFiles() - - return files?.map((file) => ({ - slug: file.slug.split("/"), + return allPosts.map((post) => ({ + slug: post.slugAsParams.split("/"), })) } export default async function PostPage({ params }: PostPageProps) { - const post = await Blog.getMdxNode(params?.slug) + const slug = params?.slug?.join("/") + const post = allPosts.find((post) => post.slugAsParams === slug) if (!post) { notFound() } - const mdx = await serialize(post.content) + const authors = post.authors.map((author) => + allAuthors.find(({ slug }) => slug === `/authors/${author}`) + ) return ( -
-
-

- {post.frontMatter.title} -

- {post.frontMatter.date && ( -

{formatDate(post.frontMatter.date)}

+
+ + + See all posts + +
+ {post.date && ( + )} +

+ {post.title} +

+ {authors?.length ? ( +
+ {authors.map((author) => ( + + {author.title} +
+

{author.title}

+

+ @{author.twitter} +

+
+ + ))} +
+ ) : null}
-
-
-
- {mdx && ( -
- -
+ {post.image && ( + {post.title} )} -
-
+ +
+
+ + + See all posts +
) diff --git a/app/(marketing)/blog/page.tsx b/app/(marketing)/blog/page.tsx index c4ba1a5..f73fc8c 100644 --- a/app/(marketing)/blog/page.tsx +++ b/app/(marketing)/blog/page.tsx @@ -1,51 +1,70 @@ import Link from "next/link" +import { compareDesc } from "date-fns" +import { allPosts } from "contentlayer/generated" -import { Blog } from "@/lib/mdx/sources" import { formatDate } from "@/lib/utils" +import Image from "next/image" export default async function BlogPage() { - const posts = await Blog.getAllMdxNodes() + const posts = allPosts + .filter((post) => post.published) + .sort((a, b) => { + return compareDesc(new Date(a.date), new Date(b.date)) + }) return ( -
-
-

- Blog -

-

- A blog built using MDX content. I copied some sample blog posts from - my{" "} - - personal site - - . -

+
+
+
+

+ Blog +

+

+ A blog built using Contentlayer. Posts are written in MDX. +

+
+ + Build your own +
-
-
-
- {posts.map((post) => ( -
-
- -

- {post.frontMatter.title} -

- - {post.frontMatter.date && ( -

- {formatDate(post.frontMatter.date)} -

- )} -
- {post.frontMatter.excerpt && ( -

{post.frontMatter.excerpt}

- )} -
-
-
-
- ))} +
+ {posts?.length ? ( +
+ {posts.map((post) => ( +
+ {post.image && ( + {post.title} + )} +

{post.title}

+ {post.description && ( +

{post.description}

+ )} + {post.date && ( +

+ {formatDate(post.date)} +

+ )} + + View Article + +
+ ))} +
+ ) : ( +

No posts published.

+ )}
) } diff --git a/app/(marketing)/layout.tsx b/app/(marketing)/layout.tsx index ba34552..a1597f8 100644 --- a/app/(marketing)/layout.tsx +++ b/app/(marketing)/layout.tsx @@ -1,6 +1,10 @@ -import { Icons } from "@/components/icons" import Link from "next/link" +import { marketingConfig } from "@/config/marketing" +import { siteConfig } from "@/config/site" +import { MainNav } from "@/components/main-nav" +import { SiteFooter } from "@/components/site-footer" + interface MarketingLayoutProps { children: React.ReactNode } @@ -8,40 +12,21 @@ interface MarketingLayoutProps { export default function MarketingLayout({ children }: MarketingLayoutProps) { return (
-
-
- - - Taxonomy - -
) } diff --git a/app/(marketing)/page.tsx b/app/(marketing)/page.tsx index bdf0706..9a1aec6 100644 --- a/app/(marketing)/page.tsx +++ b/app/(marketing)/page.tsx @@ -1,25 +1,34 @@ import Link from "next/link" +import Image from "next/image" -import { Icons } from "@/components/icons" +import hero from "../../public/images/hero.png" +import { siteConfig } from "@/config/site" async function getGitHubStars(): Promise { - const response = await fetch("https://api.github.com/repos/shadcn/taxonomy", { - headers: { - Accept: "application/vnd.github+json", - Authorization: `Bearer ${process.env.GITHUB_ACCESS_TOKEN}`, - }, - next: { - revalidate: 60, - }, - }) + try { + const response = await fetch( + "https://api.github.com/repos/shadcn/taxonomy", + { + headers: { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${process.env.GITHUB_ACCESS_TOKEN}`, + }, + next: { + revalidate: 60, + }, + } + ) - if (!response?.ok) { + if (!response?.ok) { + return null + } + + const json = await response.json() + + return parseInt(json["stargazers_count"]).toLocaleString() + } catch (error) { return null } - - const json = await response.json() - - return parseInt(json["stargazers_count"]).toLocaleString() } export default async function IndexPage() { @@ -27,35 +36,28 @@ export default async function IndexPage() { return ( <> -
-
- - Follow development on Twitter - - - - -

- Publishing Platform for Everyone +
+ Hero image +
+

+ What's going on here?

-

- An open source application built using the new router, server - components and everything new in Next.js 13. +

+ I'm building a web app with Next.js 13 and open sourcing + everything. Follow along as we figure this out together.

- Get Started - + Start Here
-
-
-
-
-
-

+
+
+
+

Features

- This project is as an experiment to see how a modern app, with - features like authentication, subscriptions, API routes, and static - pages, would work in Next.js 13. + This project is an experiment to see how a modern app, with features + like auth, subscriptions, API routes, and static pages would work in + Next.js 13 app dir.

-
-
-
- +
+
+
+
-

Next.js 13

-

+

Next.js 13

+

App dir, Routing, Layouts, Loading UI and API routes.

-
-
- +
+
+
-

React 18

-

+

React 18

+

Server and Client Components. Use hook.

-
-
- +
+
+
-

Database

-

+

Database

+

ORM using Prisma and deployed on PlanetScale.

-
-
- +
+
+
-

Components

-

+

Components

+

UI components built using Radix UI and styled with Tailwind CSS.

-
-
+
+
-

Authentication

-

+

Authentication

+

Authentication using NextAuth.js and middlewares.

-
-
- +
+
+
-

Subscriptions

-

+

Subscriptions

+

Free and paid subscriptions using Stripe.

+
+

+ Taxonomy also includes a blog and a full-featured documentation site + built using Contentlayer and MDX. +

+
-
-
-
-
-
-

+
+
+
+

Proudly Open Source

Taxonomy is open source and powered by open source software. The code is available on{" "} GitHub - . I copied this footer from{" "} - - dub.sh + . I'm also documenting everything{" "} + + here + .

- -
- - - -
-
-
-
- {stars} stars on GitHub + {stars && ( + +
+ + +
-
- +
+
+
+ {stars} stars on GitHub +
+
+ + )}
-
-
-
) } diff --git a/app/layout.tsx b/app/layout.tsx index b234922..ac86488 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,8 +1,17 @@ -import "styles/globals.css" +import { Inter as FontSans } from "@next/font/google" +import "@/styles/globals.css" + +import { cn } from "@/lib/utils" import { Toaster } from "@/ui/toast" import { Help } from "@/components/help" import { Analytics } from "@/components/analytics" +import { TailwindIndicator } from "@/components/tailwind-indicator" + +const fontSans = FontSans({ + subsets: ["latin"], + variable: "--font-inter", +}) interface RootLayoutProps { children: React.ReactNode @@ -10,13 +19,20 @@ interface RootLayoutProps { export default function RootLayout({ children }: RootLayoutProps) { return ( - + {children} + ) diff --git a/components/editor.tsx b/components/dashboard/editor.tsx similarity index 97% rename from components/editor.tsx rename to components/dashboard/editor.tsx index 59faad7..a985031 100644 --- a/components/editor.tsx +++ b/components/dashboard/editor.tsx @@ -122,14 +122,14 @@ export function Editor({ post }: EditorProps) {
<> Back -

+

{post.published ? "Published" : "Draft"}

diff --git a/components/empty-placeholder.tsx b/components/dashboard/empty-placeholder.tsx similarity index 100% rename from components/empty-placeholder.tsx rename to components/dashboard/empty-placeholder.tsx diff --git a/components/dashboard-header.tsx b/components/dashboard/header.tsx similarity index 100% rename from components/dashboard-header.tsx rename to components/dashboard/header.tsx diff --git a/components/dashboard-nav.tsx b/components/dashboard/nav.tsx similarity index 82% rename from components/dashboard-nav.tsx rename to components/dashboard/nav.tsx index 80d3c6e..3718af5 100644 --- a/components/dashboard-nav.tsx +++ b/components/dashboard/nav.tsx @@ -1,19 +1,13 @@ "use client" import Link from "next/link" -import clsx from "clsx" import { usePathname } from "next/navigation" -import { Icon, Icons } from "@/components/icons" +import { NavItem } from "types" +import { cn } from "@/lib/utils" +import { Icons } from "@/components/icons" -export type NavigationItem = { - title: string - href: string - disabled?: boolean - icon?: Icon -} - -export const navigationItems: NavigationItem[] = [ +export const navigationItems: NavItem[] = [ { title: "Posts", href: "/dashboard", @@ -49,7 +43,7 @@ export function DashboardNav() { href={navigationItem.disabled ? "/" : navigationItem.href} > {} diff --git a/components/user-account-nav.tsx b/components/dashboard/user-account-nav.tsx similarity index 93% rename from components/user-account-nav.tsx rename to components/dashboard/user-account-nav.tsx index 6889f6d..18ada1c 100644 --- a/components/user-account-nav.tsx +++ b/components/dashboard/user-account-nav.tsx @@ -4,8 +4,9 @@ import { User } from "next-auth" import { signOut } from "next-auth/react" import Link from "next/link" +import { siteConfig } from "@/config/site" import { DropdownMenu } from "@/ui/dropdown" -import { UserAvatar } from "@/components/user-avatar" +import { UserAvatar } from "@/components/dashboard/user-avatar" interface UserAccountNavProps extends React.HTMLAttributes { user: Pick @@ -43,7 +44,7 @@ export function UserAccountNav({ user }: UserAccountNavProps) { diff --git a/components/user-auth-form.tsx b/components/dashboard/user-auth-form.tsx similarity index 98% rename from components/user-auth-form.tsx rename to components/dashboard/user-auth-form.tsx index 7ccff79..8913d7d 100644 --- a/components/user-auth-form.tsx +++ b/components/dashboard/user-auth-form.tsx @@ -93,7 +93,7 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {

- Or continue with + Or continue with

+ ) +} diff --git a/components/docs/card.tsx b/components/docs/card.tsx new file mode 100644 index 0000000..2ec7577 --- /dev/null +++ b/components/docs/card.tsx @@ -0,0 +1,38 @@ +import Link from "next/link" + +import { cn } from "@/lib/utils" + +interface CardProps extends React.HTMLAttributes { + href?: string + disabled?: boolean +} + +export function Card({ + href, + className, + children, + disabled, + ...props +}: CardProps) { + return ( +
+
+
+ {children} +
+
+ {href && ( + + View + + )} +
+ ) +} diff --git a/components/docs/mdx-head.tsx b/components/docs/mdx-head.tsx new file mode 100644 index 0000000..49a88e6 --- /dev/null +++ b/components/docs/mdx-head.tsx @@ -0,0 +1,33 @@ +import { allDocuments } from "contentlayer/generated" + +interface MdxHeadProps { + params: { + slug?: string[] + } +} + +export default function MdxHead({ params }: MdxHeadProps) { + const slug = params?.slug?.join("/") || "" + const mdxDoc = allDocuments.find((doc) => doc.slugAsParams === slug) + + if (!mdxDoc) { + return null + } + + const title = `${mdxDoc.title} - Taxonomy` + + return ( + <> + {title} + + + + + + + + + + + ) +} diff --git a/components/docs/mdx.tsx b/components/docs/mdx.tsx new file mode 100644 index 0000000..b5273bc --- /dev/null +++ b/components/docs/mdx.tsx @@ -0,0 +1,179 @@ +import * as React from "react" +import Image from "next/image" +import { useMDXComponent } from "next-contentlayer/hooks" + +import { cn } from "@/lib/utils" +import { Callout } from "@/components/docs/callout" +import { Card } from "@/components/docs/card" + +const components = { + h1: ({ className, ...props }) => ( +

+ ), + h2: ({ className, ...props }) => ( +

+ ), + h3: ({ className, ...props }) => ( +

+ ), + h4: ({ className, ...props }) => ( +

+ ), + h5: ({ className, ...props }) => ( +

+ ), + h6: ({ className, ...props }) => ( +
+ ), + a: ({ className, ...props }) => ( + + ), + p: ({ className, ...props }) => ( +

+ ), + ul: ({ className, ...props }) => ( +