diff --git a/.eslintrc.json b/.eslintrc.json index b62ff2b..0ba73ae 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -15,10 +15,17 @@ }, "settings": { "tailwindcss": { - "callees": ["cn"] + "callees": ["cn"], + "config": "tailwind.config.js" }, "next": { "rootDir": true } - } + }, + "overrides": [ + { + "files": ["*.ts", "*.tsx"], + "parser": "@typescript-eslint/parser" + } + ] } diff --git a/README.md b/README.md index 74c40a4..040a609 100644 --- a/README.md +++ b/README.md @@ -6,23 +6,18 @@ An open source application built using the new router, server components and eve > This app is a work in progress. I'm building this in public. You can follow the progress on Twitter [@shadcn](https://twitter.com/shadcn). > See the roadmap below. -## Demo - -![screenshot-2](https://user-images.githubusercontent.com/124599/198038921-2b16b18b-cb4d-44b1-bd1d-6419d4a8d92c.png) - ## About this project -Right now, I'm using this project as an experiment to see how a modern app (with features like authentication, subscriptions, API routes, static pages for docs ...etc) would work in Next.js 13 and server components. +This project as an experiment to see how a modern app (with features like authentication, subscriptions, API routes, static pages for docs ...etc) would work in Next.js 13 and server components. -I'll be posting updates and issues here. +**This is not a starter template.** A few people have asked me to turn this into a starter. I think we could do that once the new features are out of beta. ## Note on Performance > **Warning** -> This app is using the canary releases for Next.js 13 and React 18. The new router and app dir is still in beta and not production-ready. -> NextAuth.js, which is used for authentication, is also not fully supported in Next.js 13 and RSC. +> This app is using the unstable releases for Next.js 13 and React 18. The new router and app dir is still in beta and not production-ready. > **Expect some performance hits when testing the dashboard**. > If you see something broken, you can ping me [@shadcn](https://twitter.com/shadcn). @@ -32,6 +27,8 @@ A few people have asked me to turn this into a starter. I think we could do that - Routing, Layouts, Nested Layouts and Layout Groups - Data Fetching, Caching and Mutation - Loading UI +- Route handlers +- Metadata files - Server and Client Components - API Routes and Middlewares - Authentication using **NextAuth.js** @@ -51,8 +48,7 @@ A few people have asked me to turn this into a starter. I think we could do that - [x] ~Subscriptions using Stripe~ - [x] ~Responsive styles~ - [x] ~Add OG image for blog using @vercel/og~ -- [ ] Add tests -- [ ] Dark mode +- [x] Dark mode ## Known Issues @@ -61,6 +57,7 @@ A list of things not working right now: 1. ~GitHub authentication (use email)~ 2. ~[Prisma: Error: ENOENT: no such file or directory, open '/var/task/.next/server/chunks/schema.prisma'](https://github.com/prisma/prisma/issues/16117)~ 3. ~[Next.js 13: Client side navigation does not update head](https://github.com/vercel/next.js/issues/42414)~ +4. [Cannot use opengraph-image.tsx inside catch-all routes](https://github.com/vercel/next.js/issues/48162) ## Why not tRPC, Turborepo or X? diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index 036cafb..501384c 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -2,8 +2,8 @@ import { Metadata } from "next" import Link from "next/link" import { cn } from "@/lib/utils" -import { Icons } from "@/components/icons" import { buttonVariants } from "@/components/ui/button" +import { Icons } from "@/components/icons" import { UserAuthForm } from "@/components/user-auth-form" export const metadata: Metadata = { @@ -18,7 +18,7 @@ export default function LoginPage() { href="/" className={cn( buttonVariants({ variant: "ghost" }), - "absolute top-4 left-4 md:top-8 md:left-8" + "absolute left-4 top-4 md:left-8 md:top-8" )} > <> @@ -32,12 +32,12 @@ export default function LoginPage() {

Welcome back

-

+

Enter your email to sign in to your account

-

+

Login -

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

Create an account

-

+

Enter your email below to create your account

-

+

By clicking continue, you agree to our{" "}

- - +
) diff --git a/app/(dashboard)/dashboard/billing/page.tsx b/app/(dashboard)/dashboard/billing/page.tsx index 8630a15..0a601e5 100644 --- a/app/(dashboard)/dashboard/billing/page.tsx +++ b/app/(dashboard)/dashboard/billing/page.tsx @@ -4,10 +4,18 @@ 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" -import { Card } from "@/components/ui/card" export const metadata = { title: "Billing", @@ -38,38 +46,30 @@ export default async function BillingPage() { heading="Billing" text="Manage billing and your subscription plan." /> -
+
+ + + 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 + + . + + - - - Note - - -

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

-

- 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 index 4893d10..71a90d6 100644 --- a/app/(dashboard)/dashboard/layout.tsx +++ b/app/(dashboard)/dashboard/layout.tsx @@ -4,6 +4,7 @@ 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 { @@ -20,9 +21,9 @@ export default async function DashboardLayout({ } return ( -
-
-
+
+
+
-
+
@@ -41,6 +42,7 @@ export default async function DashboardLayout({ {children}
+
) } diff --git a/app/(dashboard)/dashboard/loading.tsx b/app/(dashboard)/dashboard/loading.tsx index 57f2969..72bd402 100644 --- a/app/(dashboard)/dashboard/loading.tsx +++ b/app/(dashboard)/dashboard/loading.tsx @@ -9,7 +9,7 @@ export default function DashboardLoading() { -
+
diff --git a/app/(dashboard)/dashboard/page.tsx b/app/(dashboard)/dashboard/page.tsx index ef847e5..5f4fc6e 100644 --- a/app/(dashboard)/dashboard/page.tsx +++ b/app/(dashboard)/dashboard/page.tsx @@ -1,26 +1,28 @@ -import { cache } from "react" import { redirect } from "next/navigation" -import { User } from "@prisma/client" import { authOptions } from "@/lib/auth" import { db } from "@/lib/db" import { getCurrentUser } from "@/lib/session" -import { cn } from "@/lib/utils" 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" -import { buttonVariants } from "@/components/ui/button" export const metadata = { title: "Dashboard", } -const getPostsForUser = cache(async (userId: User["id"]) => { - return await db.post.findMany({ +export default async function DashboardPage() { + const user = await getCurrentUser() + + if (!user) { + redirect(authOptions?.pages?.signIn || "/login") + } + + const posts = await db.post.findMany({ where: { - authorId: userId, + authorId: user.id, }, select: { id: true, @@ -32,16 +34,6 @@ const getPostsForUser = cache(async (userId: User["id"]) => { updatedAt: "desc", }, }) -}) - -export default async function DashboardPage() { - const user = await getCurrentUser() - - if (!user) { - redirect(authOptions?.pages?.signIn || "/login") - } - - const posts = await getPostsForUser(user.id) return ( @@ -50,7 +42,7 @@ export default async function DashboardPage() {
{posts?.length ? ( -
+
{posts.map((post) => ( ))} @@ -62,12 +54,7 @@ export default async function DashboardPage() { 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 index 613a5f5..8f9eacb 100644 --- a/app/(dashboard)/dashboard/settings/loading.tsx +++ b/app/(dashboard)/dashboard/settings/loading.tsx @@ -1,6 +1,7 @@ +import { Card } from "@/components/ui/card" +import { CardSkeleton } from "@/components/card-skeleton" import { DashboardHeader } from "@/components/header" import { DashboardShell } from "@/components/shell" -import { Card } from "@/components/ui/card" export default function DashboardSettingsLoading() { return ( @@ -10,8 +11,7 @@ export default function DashboardSettingsLoading() { text="Manage account and website settings." />
- - +
) diff --git a/app/(docs)/docs/[[...slug]]/page.tsx b/app/(docs)/docs/[[...slug]]/page.tsx index 7e26ef6..051d113 100644 --- a/app/(docs)/docs/[[...slug]]/page.tsx +++ b/app/(docs)/docs/[[...slug]]/page.tsx @@ -2,10 +2,11 @@ import { notFound } from "next/navigation" import { allDocs } from "contentlayer/generated" import { getTableOfContents } from "@/lib/toc" -import { Mdx } from "@/components/mdx" +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" @@ -92,7 +93,7 @@ export default async function DocPage({ params }: DocPageProps) {
-
+
diff --git a/app/(docs)/docs/layout.tsx b/app/(docs)/docs/layout.tsx index 2b08491..38d92a5 100644 --- a/app/(docs)/docs/layout.tsx +++ b/app/(docs)/docs/layout.tsx @@ -8,7 +8,7 @@ interface DocsLayoutProps { export default function DocsLayout({ children }: DocsLayoutProps) { return (
-