diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx deleted file mode 100644 index 2172f70..0000000 --- a/app/(auth)/layout.tsx +++ /dev/null @@ -1,7 +0,0 @@ -interface AuthLayoutProps { - children: React.ReactNode -} - -export default function AuthLayout({ children }: AuthLayoutProps) { - return
{children}
-} diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx deleted file mode 100644 index 501384c..0000000 --- a/app/(auth)/login/page.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Metadata } from "next" -import Link from "next/link" - -import { cn } from "@/lib/utils" -import { buttonVariants } from "@/components/ui/button" -import { Icons } from "@/components/icons" -import { UserAuthForm } from "@/components/user-auth-form" - -export const metadata: Metadata = { - title: "Login", - description: "Login to your account", -} - -export default function LoginPage() { - return ( -
- - <> - - Back - - -
-
- -

- 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 deleted file mode 100644 index 1b61ff0..0000000 --- a/app/(auth)/register/page.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import Link from "next/link" - -import { cn } from "@/lib/utils" -import { buttonVariants } from "@/components/ui/button" -import { Icons } from "@/components/icons" -import { UserAuthForm } from "@/components/user-auth-form" - -export const metadata = { - title: "Create an account", - description: "Create an account to get started.", -} - -export default function RegisterPage() { - return ( -
- - Login - -
-
-
-
- -

- Create an account -

-

- Enter your email below to create your account -

-
- -

- By clicking continue, you agree to our{" "} - - Terms of Service - {" "} - and{" "} - - Privacy Policy - - . -

-
-
-
- ) -} diff --git a/app/(dashboard)/dashboard/billing/loading.tsx b/app/(dashboard)/dashboard/billing/loading.tsx deleted file mode 100644 index 5593522..0000000 --- a/app/(dashboard)/dashboard/billing/loading.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { CardSkeleton } from "@/components/card-skeleton" -import { DashboardHeader } from "@/components/header" -import { DashboardShell } from "@/components/shell" - -export default function DashboardBillingLoading() { - return ( - - -
- -
-
- ) -} diff --git a/app/(dashboard)/dashboard/billing/page.tsx b/app/(dashboard)/dashboard/billing/page.tsx deleted file mode 100644 index 0a601e5..0000000 --- a/app/(dashboard)/dashboard/billing/page.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { redirect } from "next/navigation" - -import { authOptions } from "@/lib/auth" -import { getCurrentUser } from "@/lib/session" -import { stripe } from "@/lib/stripe" -import { getUserSubscriptionPlan } from "@/lib/subscription" -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card" -import { BillingForm } from "@/components/billing-form" -import { DashboardHeader } from "@/components/header" -import { Icons } from "@/components/icons" -import { DashboardShell } from "@/components/shell" - -export const metadata = { - title: "Billing", - description: "Manage billing and your subscription plan.", -} - -export default async function BillingPage() { - const user = await getCurrentUser() - - if (!user) { - redirect(authOptions?.pages?.signIn || "/login") - } - - const subscriptionPlan = await getUserSubscriptionPlan(user.id) - - // If user has a pro plan, check cancel status on Stripe. - let isCanceled = false - if (subscriptionPlan.isPro && subscriptionPlan.stripeSubscriptionId) { - const stripePlan = await stripe.subscriptions.retrieve( - subscriptionPlan.stripeSubscriptionId - ) - isCanceled = stripePlan.cancel_at_period_end - } - - return ( - - -
- - - This is a demo app. - - Taxonomy app is a demo app using a Stripe test environment. You can - find a list of test card numbers on the{" "} - - Stripe docs - - . - - - -
-
- ) -} diff --git a/app/(dashboard)/dashboard/layout.tsx b/app/(dashboard)/dashboard/layout.tsx deleted file mode 100644 index 71a90d6..0000000 --- a/app/(dashboard)/dashboard/layout.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { notFound } from "next/navigation" - -import { dashboardConfig } from "@/config/dashboard" -import { getCurrentUser } from "@/lib/session" -import { MainNav } from "@/components/main-nav" -import { DashboardNav } from "@/components/nav" -import { SiteFooter } from "@/components/site-footer" -import { UserAccountNav } from "@/components/user-account-nav" - -interface DashboardLayoutProps { - children?: React.ReactNode -} - -export default async function DashboardLayout({ - children, -}: DashboardLayoutProps) { - const user = await getCurrentUser() - - if (!user) { - return notFound() - } - - return ( -
-
-
- - -
-
-
- -
- {children} -
-
- -
- ) -} diff --git a/app/(dashboard)/dashboard/loading.tsx b/app/(dashboard)/dashboard/loading.tsx deleted file mode 100644 index 72bd402..0000000 --- a/app/(dashboard)/dashboard/loading.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { DashboardHeader } from "@/components/header" -import { PostCreateButton } from "@/components/post-create-button" -import { PostItem } from "@/components/post-item" -import { DashboardShell } from "@/components/shell" - -export default function DashboardLoading() { - return ( - - - - -
- - - - - -
-
- ) -} diff --git a/app/(dashboard)/dashboard/page.tsx b/app/(dashboard)/dashboard/page.tsx deleted file mode 100644 index 5f4fc6e..0000000 --- a/app/(dashboard)/dashboard/page.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { redirect } from "next/navigation" - -import { authOptions } from "@/lib/auth" -import { db } from "@/lib/db" -import { getCurrentUser } from "@/lib/session" -import { EmptyPlaceholder } from "@/components/empty-placeholder" -import { DashboardHeader } from "@/components/header" -import { PostCreateButton } from "@/components/post-create-button" -import { PostItem } from "@/components/post-item" -import { DashboardShell } from "@/components/shell" - -export const metadata = { - title: "Dashboard", -} - -export default async function DashboardPage() { - const user = await getCurrentUser() - - if (!user) { - redirect(authOptions?.pages?.signIn || "/login") - } - - const posts = await db.post.findMany({ - where: { - authorId: user.id, - }, - select: { - id: true, - title: true, - published: true, - createdAt: true, - }, - orderBy: { - updatedAt: "desc", - }, - }) - - return ( - - - - -
- {posts?.length ? ( -
- {posts.map((post) => ( - - ))} -
- ) : ( - - - No posts created - - You don't have any posts yet. Start creating content. - - - - )} -
-
- ) -} diff --git a/app/(dashboard)/dashboard/settings/loading.tsx b/app/(dashboard)/dashboard/settings/loading.tsx deleted file mode 100644 index 8f9eacb..0000000 --- a/app/(dashboard)/dashboard/settings/loading.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Card } from "@/components/ui/card" -import { CardSkeleton } from "@/components/card-skeleton" -import { DashboardHeader } from "@/components/header" -import { DashboardShell } from "@/components/shell" - -export default function DashboardSettingsLoading() { - return ( - - -
- -
-
- ) -} diff --git a/app/(dashboard)/dashboard/settings/page.tsx b/app/(dashboard)/dashboard/settings/page.tsx deleted file mode 100644 index 6e8545d..0000000 --- a/app/(dashboard)/dashboard/settings/page.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { redirect } from "next/navigation" - -import { authOptions } from "@/lib/auth" -import { getCurrentUser } from "@/lib/session" -import { DashboardHeader } from "@/components/header" -import { DashboardShell } from "@/components/shell" -import { UserNameForm } from "@/components/user-name-form" - -export const metadata = { - title: "Settings", - description: "Manage account and website settings.", -} - -export default async function SettingsPage() { - const user = await getCurrentUser() - - if (!user) { - redirect(authOptions?.pages?.signIn || "/login") - } - - return ( - - -
- {user?.name ? ( - - ) : null} -
-
- ) -} diff --git a/app/(docs)/docs/[[...slug]]/page.tsx b/app/(docs)/docs/[[...slug]]/page.tsx deleted file mode 100644 index 051d113..0000000 --- a/app/(docs)/docs/[[...slug]]/page.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { notFound } from "next/navigation" -import { allDocs } from "contentlayer/generated" - -import { getTableOfContents } from "@/lib/toc" -import { Mdx } from "@/components/mdx-components" -import { DocsPageHeader } from "@/components/page-header" -import { DocsPager } from "@/components/pager" -import { DashboardTableOfContents } from "@/components/toc" - -import "@/styles/mdx.css" -import { Metadata } from "next" - -import { absoluteUrl } from "@/lib/utils" - -interface DocPageProps { - params: { - slug: string[] - } -} - -async function getDocFromParams(params) { - const slug = params.slug?.join("/") || "" - const doc = allDocs.find((doc) => doc.slugAsParams === slug) - - if (!doc) { - null - } - - return doc -} - -export async function generateMetadata({ - params, -}: DocPageProps): Promise { - const doc = await getDocFromParams(params) - - if (!doc) { - return {} - } - - const url = process.env.NEXT_PUBLIC_APP_URL - - const ogUrl = new URL(`${url}/api/og`) - ogUrl.searchParams.set("heading", doc.description ?? doc.title) - ogUrl.searchParams.set("type", "Documentation") - ogUrl.searchParams.set("mode", "dark") - - return { - title: doc.title, - description: doc.description, - openGraph: { - title: doc.title, - description: doc.description, - type: "article", - url: absoluteUrl(doc.slug), - images: [ - { - url: ogUrl.toString(), - width: 1200, - height: 630, - alt: doc.title, - }, - ], - }, - twitter: { - card: "summary_large_image", - title: doc.title, - description: doc.description, - images: [ogUrl.toString()], - }, - } -} - -export async function generateStaticParams(): Promise< - DocPageProps["params"][] -> { - return allDocs.map((doc) => ({ - slug: doc.slugAsParams.split("/"), - })) -} - -export default async function DocPage({ params }: DocPageProps) { - const doc = await getDocFromParams(params) - - 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 deleted file mode 100644 index 38d92a5..0000000 --- a/app/(docs)/docs/layout.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { docsConfig } from "@/config/docs" -import { DocsSidebarNav } from "@/components/sidebar-nav" - -interface DocsLayoutProps { - children: React.ReactNode -} - -export default function DocsLayout({ children }: DocsLayoutProps) { - return ( -
- - {children} -
- ) -} diff --git a/app/(docs)/guides/[...slug]/page.tsx b/app/(docs)/guides/[...slug]/page.tsx deleted file mode 100644 index 11c49e1..0000000 --- a/app/(docs)/guides/[...slug]/page.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import Link from "next/link" -import { notFound } from "next/navigation" -import { allGuides } from "contentlayer/generated" - -import { getTableOfContents } from "@/lib/toc" -import { Icons } from "@/components/icons" -import { Mdx } from "@/components/mdx-components" -import { DocsPageHeader } from "@/components/page-header" -import { DashboardTableOfContents } from "@/components/toc" - -import "@/styles/mdx.css" -import { Metadata } from "next" - -import { absoluteUrl, cn } from "@/lib/utils" -import { buttonVariants } from "@/components/ui/button" - -interface GuidePageProps { - params: { - slug: string[] - } -} - -async function getGuideFromParams(params) { - const slug = params?.slug?.join("/") - const guide = allGuides.find((guide) => guide.slugAsParams === slug) - - if (!guide) { - null - } - - return guide -} - -export async function generateMetadata({ - params, -}: GuidePageProps): Promise { - const guide = await getGuideFromParams(params) - - if (!guide) { - return {} - } - - const url = process.env.NEXT_PUBLIC_APP_URL - - const ogUrl = new URL(`${url}/api/og`) - ogUrl.searchParams.set("heading", guide.title) - ogUrl.searchParams.set("type", "Guide") - ogUrl.searchParams.set("mode", "dark") - - return { - title: guide.title, - description: guide.description, - openGraph: { - title: guide.title, - description: guide.description, - type: "article", - url: absoluteUrl(guide.slug), - images: [ - { - url: ogUrl.toString(), - width: 1200, - height: 630, - alt: guide.title, - }, - ], - }, - twitter: { - card: "summary_large_image", - title: guide.title, - description: guide.description, - images: [ogUrl.toString()], - }, - } -} - -export async function generateStaticParams(): Promise< - GuidePageProps["params"][] -> { - return allGuides.map((guide) => ({ - slug: guide.slugAsParams.split("/"), - })) -} - -export default async function GuidePage({ params }: GuidePageProps) { - const guide = await getGuideFromParams(params) - - 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 deleted file mode 100644 index d708c74..0000000 --- a/app/(docs)/guides/layout.tsx +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 9d0ebd0..0000000 --- a/app/(docs)/guides/page.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import Link from "next/link" -import { allGuides } from "contentlayer/generated" -import { compareDesc } from "date-fns" - -import { formatDate } from "@/lib/utils" -import { DocsPageHeader } from "@/components/page-header" - -export const metadata = { - title: "Guides", - description: - "This section includes end-to-end guides for developing Next.js 13 apps.", -} - -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 deleted file mode 100644 index 8c76140..0000000 --- a/app/(docs)/layout.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import Link from "next/link" - -import { docsConfig } from "@/config/docs" -import { siteConfig } from "@/config/site" -import { Icons } from "@/components/icons" -import { MainNav } from "@/components/main-nav" -import { DocsSearch } from "@/components/search" -import { DocsSidebarNav } from "@/components/sidebar-nav" -import { SiteFooter } from "@/components/site-footer" - -interface DocsLayoutProps { - children: React.ReactNode -} - -export default function DocsLayout({ children }: DocsLayoutProps) { - return ( -
-
-
- - - -
-
- -
- -
-
-
-
{children}
- -
- ) -} diff --git a/app/(editor)/editor/[postId]/loading.tsx b/app/(editor)/editor/[postId]/loading.tsx deleted file mode 100644 index e4f6e95..0000000 --- a/app/(editor)/editor/[postId]/loading.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Skeleton } from "@/components/ui/skeleton" - -export default function Loading() { - return ( -
-
- - -
-
- - - - -
-
- ) -} diff --git a/app/(editor)/editor/[postId]/not-found.tsx b/app/(editor)/editor/[postId]/not-found.tsx deleted file mode 100644 index 37d479e..0000000 --- a/app/(editor)/editor/[postId]/not-found.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import Link from "next/link" - -import { buttonVariants } from "@/components/ui/button" -import { EmptyPlaceholder } from "@/components/empty-placeholder" - -export default function NotFound() { - return ( - - - Uh oh! Not Found - - This post cound not be found. Please try again. - - - Go to Dashboard - - - ) -} diff --git a/app/(editor)/editor/[postId]/page.tsx b/app/(editor)/editor/[postId]/page.tsx deleted file mode 100644 index 784adda..0000000 --- a/app/(editor)/editor/[postId]/page.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { notFound, redirect } from "next/navigation" -import { Post, User } from "@prisma/client" - -import { authOptions } from "@/lib/auth" -import { db } from "@/lib/db" -import { getCurrentUser } from "@/lib/session" -import { Editor } from "@/components/editor" - -async function getPostForUser(postId: Post["id"], userId: User["id"]) { - return await db.post.findFirst({ - where: { - id: postId, - authorId: userId, - }, - }) -} - -interface EditorPageProps { - params: { postId: string } -} - -export default async function EditorPage({ params }: EditorPageProps) { - const user = await getCurrentUser() - - if (!user) { - redirect(authOptions?.pages?.signIn || "/login") - } - - const post = await getPostForUser(params.postId, user.id) - - if (!post) { - notFound() - } - - return ( - - ) -} diff --git a/app/(editor)/editor/layout.tsx b/app/(editor)/editor/layout.tsx deleted file mode 100644 index a9a1ce9..0000000 --- a/app/(editor)/editor/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -interface EditorProps { - children?: React.ReactNode -} - -export default function EditorLayout({ children }: EditorProps) { - return ( -
- {children} -
- ) -} diff --git a/app/(marketing)/[...slug]/page.tsx b/app/(marketing)/[...slug]/page.tsx deleted file mode 100644 index 6219cc1..0000000 --- a/app/(marketing)/[...slug]/page.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { notFound } from "next/navigation" -import { allPages } from "contentlayer/generated" - -import { Mdx } from "@/components/mdx-components" - -import "@/styles/mdx.css" -import { Metadata } from "next" - -import { siteConfig } from "@/config/site" -import { absoluteUrl } from "@/lib/utils" - -interface PageProps { - params: { - slug: string[] - } -} - -async function getPageFromParams(params) { - const slug = params?.slug?.join("/") - const page = allPages.find((page) => page.slugAsParams === slug) - - if (!page) { - null - } - - return page -} - -export async function generateMetadata({ - params, -}: PageProps): Promise { - const page = await getPageFromParams(params) - - if (!page) { - return {} - } - - const url = process.env.NEXT_PUBLIC_APP_URL - - const ogUrl = new URL(`${url}/api/og`) - ogUrl.searchParams.set("heading", page.title) - ogUrl.searchParams.set("type", siteConfig.name) - ogUrl.searchParams.set("mode", "light") - - return { - title: page.title, - description: page.description, - openGraph: { - title: page.title, - description: page.description, - type: "article", - url: absoluteUrl(page.slug), - images: [ - { - url: ogUrl.toString(), - width: 1200, - height: 630, - alt: page.title, - }, - ], - }, - twitter: { - card: "summary_large_image", - title: page.title, - description: page.description, - images: [ogUrl.toString()], - }, - } -} - -export async function generateStaticParams(): Promise { - return allPages.map((page) => ({ - slug: page.slugAsParams.split("/"), - })) -} - -export default async function PagePage({ params }: PageProps) { - const page = await getPageFromParams(params) - - if (!page) { - notFound() - } - - return ( -
-
-

- {page.title} -

- {page.description && ( -

{page.description}

- )} -
-
- -
- ) -} diff --git a/app/(marketing)/blog/[...slug]/page.tsx b/app/(marketing)/blog/[...slug]/page.tsx deleted file mode 100644 index 03fa2ad..0000000 --- a/app/(marketing)/blog/[...slug]/page.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { notFound } from "next/navigation" -import { allAuthors, allPosts } from "contentlayer/generated" - -import { Mdx } from "@/components/mdx-components" - -import "@/styles/mdx.css" -import { Metadata } from "next" -import Image from "next/image" -import Link from "next/link" - -import { absoluteUrl, cn, formatDate } from "@/lib/utils" -import { buttonVariants } from "@/components/ui/button" -import { Icons } from "@/components/icons" - -interface PostPageProps { - params: { - slug: string[] - } -} - -async function getPostFromParams(params) { - const slug = params?.slug?.join("/") - const post = allPosts.find((post) => post.slugAsParams === slug) - - if (!post) { - null - } - - return post -} - -export async function generateMetadata({ - params, -}: PostPageProps): Promise { - const post = await getPostFromParams(params) - - if (!post) { - return {} - } - - const url = process.env.NEXT_PUBLIC_APP_URL - - const ogUrl = new URL(`${url}/api/og`) - ogUrl.searchParams.set("heading", post.title) - ogUrl.searchParams.set("type", "Blog Post") - ogUrl.searchParams.set("mode", "dark") - - return { - title: post.title, - description: post.description, - authors: post.authors.map((author) => ({ - name: author, - })), - openGraph: { - title: post.title, - description: post.description, - type: "article", - url: absoluteUrl(post.slug), - images: [ - { - url: ogUrl.toString(), - width: 1200, - height: 630, - alt: post.title, - }, - ], - }, - twitter: { - card: "summary_large_image", - title: post.title, - description: post.description, - images: [ogUrl.toString()], - }, - } -} - -export async function generateStaticParams(): Promise< - PostPageProps["params"][] -> { - return allPosts.map((post) => ({ - slug: post.slugAsParams.split("/"), - })) -} - -export default async function PostPage({ params }: PostPageProps) { - const post = await getPostFromParams(params) - - if (!post) { - notFound() - } - - const authors = post.authors.map((author) => - allAuthors.find(({ slug }) => slug === `/authors/${author}`) - ) - - return ( -
- - - See all posts - -
- {post.date && ( - - )} -

- {post.title} -

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

{author.title}

-

- @{author.twitter} -

-
- - ) : null - )} -
- ) : null} -
- {post.image && ( - {post.title} - )} - -
-
- - - See all posts - -
-
- ) -} diff --git a/app/(marketing)/blog/page.tsx b/app/(marketing)/blog/page.tsx deleted file mode 100644 index 8dc2df1..0000000 --- a/app/(marketing)/blog/page.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import Image from "next/image" -import Link from "next/link" -import { allPosts } from "contentlayer/generated" -import { compareDesc } from "date-fns" - -import { formatDate } from "@/lib/utils" - -export const metadata = { - title: "Blog", -} - -export default async function BlogPage() { - 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 Contentlayer. Posts are written in MDX. -

-
-
-
- {posts?.length ? ( -
- {posts.map((post, index) => ( -
- {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 deleted file mode 100644 index 902099c..0000000 --- a/app/(marketing)/layout.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import Link from "next/link" - -import { marketingConfig } from "@/config/marketing" -import { cn } from "@/lib/utils" -import { buttonVariants } from "@/components/ui/button" -import { MainNav } from "@/components/main-nav" -import { SiteFooter } from "@/components/site-footer" - -interface MarketingLayoutProps { - children: React.ReactNode -} - -export default async function MarketingLayout({ - children, -}: MarketingLayoutProps) { - return ( -
-
-
- - -
-
-
{children}
- -
- ) -} diff --git a/app/(marketing)/page.tsx b/app/(marketing)/page.tsx deleted file mode 100644 index 3467b03..0000000 --- a/app/(marketing)/page.tsx +++ /dev/null @@ -1,225 +0,0 @@ -import Link from "next/link" - -import { siteConfig } from "@/config/site" -import { cn } from "@/lib/utils" -import { buttonVariants } from "@/components/ui/button" - -async function getGitHubStars(): Promise { - 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) { - return null - } - - const json = await response.json() - - return parseInt(json["stargazers_count"]).toLocaleString() - } catch (error) { - return null - } -} - -export default async function IndexPage() { - const stars = await getGitHubStars() - - return ( - <> -
-
- - Follow along on Twitter - -

- An example app built using Next.js 13 server components. -

-

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

-
- - Get Started - - - GitHub - -
-
-
-
-
-

- Features -

-

- 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

-

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

-
-
-
-
-
- - - -
-

React 18

-

- Server and Client Components. Use hook. -

-
-
-
-
-
- - - -
-

Database

-

- ORM using Prisma and deployed on PlanetScale. -

-
-
-
-
-
- - - -
-

Components

-

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

-
-
-
-
-
- - - -
-

Authentication

-

- Authentication using NextAuth.js and middlewares. -

-
-
-
-
-
- - - -
-

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 - - .{" "} -

- {stars && ( - -
- - - -
-
-
-
- {stars} stars on GitHub -
-
- - )} -
-
- - ) -} diff --git a/app/(marketing)/pricing/page.tsx b/app/(marketing)/pricing/page.tsx deleted file mode 100644 index 505f14c..0000000 --- a/app/(marketing)/pricing/page.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import Link from "next/link" - -import { cn } from "@/lib/utils" -import { buttonVariants } from "@/components/ui/button" -import { Icons } from "@/components/icons" - -export const metadata = { - title: "Pricing", -} - -export default function PricingPage() { - return ( -
-
-

- Simple, transparent pricing -

-

- Unlock all features including unlimited posts for your blog. -

-
-
-
-

- What's included in the PRO plan -

-
    -
  • - Unlimited Posts -
  • -
  • - Unlimited Users -
  • - -
  • - Custom domain -
  • -
  • - Dashboard Analytics -
  • -
  • - Access to Discord -
  • -
  • - Premium Support -
  • -
-
-
-
-

$19

-

- Billed Monthly -

-
- - Get Started - -
-
-
-

- Taxonomy is a demo app.{" "} - You can test the upgrade and won't be charged. -

-
-
- ) -} diff --git a/app/api/og/route.tsx b/app/api/og/route.tsx deleted file mode 100644 index d674f3c..0000000 --- a/app/api/og/route.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { ImageResponse } from "@vercel/og" - -import { ogImageSchema } from "@/lib/validations/og" - -export const runtime = "edge" - -const interRegular = fetch( - new URL("../../../assets/fonts/Inter-Regular.ttf", import.meta.url) -).then((res) => res.arrayBuffer()) - -const interBold = fetch( - new URL("../../../assets/fonts/CalSans-SemiBold.ttf", import.meta.url) -).then((res) => res.arrayBuffer()) - -export async function GET(req: Request) { - try { - const fontRegular = await interRegular - const fontBold = await interBold - - const url = new URL(req.url) - const values = ogImageSchema.parse(Object.fromEntries(url.searchParams)) - const heading = - values.heading.length > 140 - ? `${values.heading.substring(0, 140)}...` - : values.heading - - const { mode } = values - const paint = mode === "dark" ? "#fff" : "#000" - - const fontSize = heading.length > 100 ? "70px" : "100px" - - return new ImageResponse( - ( -
- - - - - - - - - - - - - - -
-
- {values.type} -
-
- {heading} -
-
-
-
- tx.shadcn.com -
-
- - - - -
github.com/shadcn/taxonomy
-
-
-
- ), - { - width: 1200, - height: 630, - fonts: [ - { - name: "Inter", - data: fontRegular, - weight: 400, - style: "normal", - }, - { - name: "Cal Sans", - data: fontBold, - weight: 700, - style: "normal", - }, - ], - } - ) - } catch (error) { - return new Response(`Failed to generate image`, { - status: 500, - }) - } -} diff --git a/app/api/posts/[postId]/route.ts b/app/api/posts/[postId]/route.ts deleted file mode 100644 index 0614640..0000000 --- a/app/api/posts/[postId]/route.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { getServerSession } from "next-auth" -import * as z from "zod" - -import { authOptions } from "@/lib/auth" -import { db } from "@/lib/db" -import { postPatchSchema } from "@/lib/validations/post" - -const routeContextSchema = z.object({ - params: z.object({ - postId: z.string(), - }), -}) - -export async function DELETE( - req: Request, - context: z.infer -) { - try { - // Validate the route params. - const { params } = routeContextSchema.parse(context) - - // Check if the user has access to this post. - if (!(await verifyCurrentUserHasAccessToPost(params.postId))) { - return new Response(null, { status: 403 }) - } - - // Delete the post. - await db.post.delete({ - where: { - id: params.postId as string, - }, - }) - - return new Response(null, { status: 204 }) - } catch (error) { - if (error instanceof z.ZodError) { - return new Response(JSON.stringify(error.issues), { status: 422 }) - } - - return new Response(null, { status: 500 }) - } -} - -export async function PATCH( - req: Request, - context: z.infer -) { - try { - // Validate route params. - const { params } = routeContextSchema.parse(context) - - // Check if the user has access to this post. - if (!(await verifyCurrentUserHasAccessToPost(params.postId))) { - return new Response(null, { status: 403 }) - } - - // Get the request body and validate it. - const json = await req.json() - const body = postPatchSchema.parse(json) - - // Update the post. - // TODO: Implement sanitization for content. - await db.post.update({ - where: { - id: params.postId, - }, - data: { - title: body.title, - content: body.content, - }, - }) - - return new Response(null, { status: 200 }) - } catch (error) { - if (error instanceof z.ZodError) { - return new Response(JSON.stringify(error.issues), { status: 422 }) - } - - return new Response(null, { status: 500 }) - } -} - -async function verifyCurrentUserHasAccessToPost(postId: string) { - const session = await getServerSession(authOptions) - const count = await db.post.count({ - where: { - id: postId, - authorId: session?.user.id, - }, - }) - - return count > 0 -} diff --git a/app/api/posts/route.ts b/app/api/posts/route.ts deleted file mode 100644 index 7c4c8bb..0000000 --- a/app/api/posts/route.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { getServerSession } from "next-auth/next" -import * as z from "zod" - -import { authOptions } from "@/lib/auth" -import { db } from "@/lib/db" -import { RequiresProPlanError } from "@/lib/exceptions" -import { getUserSubscriptionPlan } from "@/lib/subscription" - -const postCreateSchema = z.object({ - title: z.string(), - content: z.string().optional(), -}) - -export async function GET() { - try { - const session = await getServerSession(authOptions) - - if (!session) { - return new Response("Unauthorized", { status: 403 }) - } - - const { user } = session - const posts = await db.post.findMany({ - select: { - id: true, - title: true, - published: true, - createdAt: true, - }, - where: { - authorId: user.id, - }, - }) - - return new Response(JSON.stringify(posts)) - } catch (error) { - return new Response(null, { status: 500 }) - } -} - -export async function POST(req: Request) { - try { - const session = await getServerSession(authOptions) - - if (!session) { - return new Response("Unauthorized", { status: 403 }) - } - - const { user } = session - const subscriptionPlan = await getUserSubscriptionPlan(user.id) - - // If user is on a free plan. - // Check if user has reached limit of 3 posts. - if (!subscriptionPlan?.isPro) { - const count = await db.post.count({ - where: { - authorId: user.id, - }, - }) - - if (count >= 3) { - throw new RequiresProPlanError() - } - } - - const json = await req.json() - const body = postCreateSchema.parse(json) - - const post = await db.post.create({ - data: { - title: body.title, - content: body.content, - authorId: session.user.id, - }, - select: { - id: true, - }, - }) - - return new Response(JSON.stringify(post)) - } catch (error) { - if (error instanceof z.ZodError) { - return new Response(JSON.stringify(error.issues), { status: 422 }) - } - - if (error instanceof RequiresProPlanError) { - return new Response("Requires Pro Plan", { status: 402 }) - } - - return new Response(null, { status: 500 }) - } -} diff --git a/app/api/users/[userId]/route.ts b/app/api/users/[userId]/route.ts deleted file mode 100644 index 6b68bad..0000000 --- a/app/api/users/[userId]/route.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { getServerSession } from "next-auth/next" -import { z } from "zod" - -import { authOptions } from "@/lib/auth" -import { db } from "@/lib/db" -import { userNameSchema } from "@/lib/validations/user" - -const routeContextSchema = z.object({ - params: z.object({ - userId: z.string(), - }), -}) - -export async function PATCH( - req: Request, - context: z.infer -) { - try { - // Validate the route context. - const { params } = routeContextSchema.parse(context) - - // Ensure user is authentication and has access to this user. - const session = await getServerSession(authOptions) - if (!session?.user || params.userId !== session?.user.id) { - return new Response(null, { status: 403 }) - } - - // Get the request body and validate it. - const body = await req.json() - const payload = userNameSchema.parse(body) - - // Update the user. - await db.user.update({ - where: { - id: session.user.id, - }, - data: { - name: payload.name, - }, - }) - - return new Response(null, { status: 200 }) - } catch (error) { - if (error instanceof z.ZodError) { - return new Response(JSON.stringify(error.issues), { status: 422 }) - } - - return new Response(null, { status: 500 }) - } -} diff --git a/app/api/users/stripe/route.ts b/app/api/users/stripe/route.ts deleted file mode 100644 index b6f0938..0000000 --- a/app/api/users/stripe/route.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { getServerSession } from "next-auth/next" -import { z } from "zod" - -import { proPlan } from "@/config/subscriptions" -import { authOptions } from "@/lib/auth" -import { stripe } from "@/lib/stripe" -import { getUserSubscriptionPlan } from "@/lib/subscription" -import { absoluteUrl } from "@/lib/utils" - -const billingUrl = absoluteUrl("/dashboard/billing") - -export async function GET(req: Request) { - try { - const session = await getServerSession(authOptions) - - if (!session?.user || !session?.user.email) { - return new Response(null, { status: 403 }) - } - - const subscriptionPlan = await getUserSubscriptionPlan(session.user.id) - - // The user is on the pro plan. - // Create a portal session to manage subscription. - if (subscriptionPlan.isPro && subscriptionPlan.stripeCustomerId) { - const stripeSession = await stripe.billingPortal.sessions.create({ - customer: subscriptionPlan.stripeCustomerId, - return_url: billingUrl, - }) - - return new Response(JSON.stringify({ url: stripeSession.url })) - } - - // The user is on the free plan. - // Create a checkout session to upgrade. - const stripeSession = await stripe.checkout.sessions.create({ - success_url: billingUrl, - cancel_url: billingUrl, - payment_method_types: ["card"], - mode: "subscription", - billing_address_collection: "auto", - customer_email: session.user.email, - line_items: [ - { - price: proPlan.stripePriceId, - quantity: 1, - }, - ], - metadata: { - userId: session.user.id, - }, - }) - - return new Response(JSON.stringify({ url: stripeSession.url })) - } catch (error) { - if (error instanceof z.ZodError) { - return new Response(JSON.stringify(error.issues), { status: 422 }) - } - - return new Response(null, { status: 500 }) - } -} diff --git a/app/api/webhooks/stripe/route.ts b/app/api/webhooks/stripe/route.ts deleted file mode 100644 index 18c00fb..0000000 --- a/app/api/webhooks/stripe/route.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { headers } from "next/headers" -import Stripe from "stripe" - -import { db } from "@/lib/db" -import { stripe } from "@/lib/stripe" - -export async function POST(req: Request) { - const body = await req.text() - const signature = headers().get("Stripe-Signature") as string - - let event: Stripe.Event - - try { - event = stripe.webhooks.constructEvent( - body, - signature, - process.env.STRIPE_WEBHOOK_SECRET || "" - ) - } catch (error) { - return new Response(`Webhook Error: ${error.message}`, { status: 400 }) - } - - const session = event.data.object as Stripe.Checkout.Session - - if (event.type === "checkout.session.completed") { - // Retrieve the subscription details from Stripe. - const subscription = await stripe.subscriptions.retrieve( - session.subscription as string - ) - - // Update the user stripe into in our database. - // Since this is the initial subscription, we need to update - // the subscription id and customer id. - await db.user.update({ - where: { - id: session?.metadata?.userId, - }, - data: { - stripeSubscriptionId: subscription.id, - stripeCustomerId: subscription.customer as string, - stripePriceId: subscription.items.data[0].price.id, - stripeCurrentPeriodEnd: new Date( - subscription.current_period_end * 1000 - ), - }, - }) - } - - if (event.type === "invoice.payment_succeeded") { - // Retrieve the subscription details from Stripe. - const subscription = await stripe.subscriptions.retrieve( - session.subscription as string - ) - - // Update the price id and set the new period end. - await db.user.update({ - where: { - stripeSubscriptionId: subscription.id, - }, - data: { - stripePriceId: subscription.items.data[0].price.id, - stripeCurrentPeriodEnd: new Date( - subscription.current_period_end * 1000 - ), - }, - }) - } - - return new Response(null, { status: 200 }) -} diff --git a/app/layout.tsx b/app/layout.tsx deleted file mode 100644 index 9ba740f..0000000 --- a/app/layout.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { Inter as FontSans } from "next/font/google" -import localFont from "next/font/local" - -import "@/styles/globals.css" -import { siteConfig } from "@/config/site" -import { absoluteUrl, cn } from "@/lib/utils" -import { Toaster } from "@/components/ui/toaster" -import { Analytics } from "@/components/analytics" -import { TailwindIndicator } from "@/components/tailwind-indicator" -import { ThemeProvider } from "@/components/theme-provider" - -const fontSans = FontSans({ - subsets: ["latin"], - variable: "--font-sans", -}) - -// Font files can be colocated inside of `pages` -const fontHeading = localFont({ - src: "../assets/fonts/CalSans-SemiBold.woff2", - variable: "--font-heading", -}) - -interface RootLayoutProps { - children: React.ReactNode -} - -export const metadata = { - title: { - default: siteConfig.name, - template: `%s | ${siteConfig.name}`, - }, - description: siteConfig.description, - keywords: [ - "Next.js", - "React", - "Tailwind CSS", - "Server Components", - "Radix UI", - ], - authors: [ - { - name: "shadcn", - url: "https://shadcn.com", - }, - ], - creator: "shadcn", - themeColor: [ - { media: "(prefers-color-scheme: light)", color: "white" }, - { media: "(prefers-color-scheme: dark)", color: "black" }, - ], - openGraph: { - type: "website", - locale: "en_US", - url: siteConfig.url, - title: siteConfig.name, - description: siteConfig.description, - siteName: siteConfig.name, - }, - twitter: { - card: "summary_large_image", - title: siteConfig.name, - description: siteConfig.description, - images: [`${siteConfig.url}/og.jpg`], - creator: "@shadcn", - }, - icons: { - icon: "/favicon.ico", - shortcut: "/favicon-16x16.png", - apple: "/apple-touch-icon.png", - }, - manifest: `${siteConfig.url}/site.webmanifest`, -} - -export default function RootLayout({ children }: RootLayoutProps) { - return ( - - - - - {children} - - - - - - - ) -} diff --git a/app/opengraph-image.jpg b/app/opengraph-image.jpg deleted file mode 100644 index 75473bf..0000000 Binary files a/app/opengraph-image.jpg and /dev/null differ diff --git a/app/robots.ts b/app/robots.ts deleted file mode 100644 index f86f167..0000000 --- a/app/robots.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { MetadataRoute } from "next" - -export default function robots(): MetadataRoute.Robots { - return { - rules: { - userAgent: "*", - allow: "/", - }, - } -}