├── .eslintrc.json ├── .vscode └── settings.json ├── app ├── favicon.ico ├── page.tsx ├── sign-in │ └── [[...sign-in]] │ │ └── page.tsx ├── sign-up │ └── [[...sign-up]] │ │ └── page.tsx ├── api │ ├── me │ │ └── route.ts │ └── webhooks │ │ └── clerk │ │ └── route.ts ├── protected │ ├── server │ │ └── page.tsx │ └── client │ │ └── page.tsx ├── layout.tsx └── globals.css ├── next.config.mjs ├── postcss.config.js ├── .env.example ├── lib ├── utils.ts ├── prisma.ts └── users.ts ├── .prettierrc ├── hooks └── use-mounted.tsx ├── components ├── footer.tsx ├── theme-provider.tsx ├── header.tsx ├── theme-toggle.tsx └── ui │ ├── button.tsx │ └── dropdown-menu.tsx ├── middleware.ts ├── components.json ├── prisma └── schema.prisma ├── .gitignore ├── public ├── vercel.svg └── next.svg ├── tsconfig.json ├── package.json ├── README.md ├── tailwind.config.ts └── pnpm-lock.yaml /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HamedBahram/clerk-webhooks/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Clerk 2 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="" 3 | CLERK_SECRET_KEY="" 4 | CLERK_WEBHOOK_SECRET="" 5 | 6 | # Database 7 | DATABASE_URL="" 8 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Home() { 2 | return ( 3 |
4 |
5 |

Clerk starter

6 |
7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "singleQuote": true, 4 | "jsxSingleQuote": true, 5 | "tabWidth": 2, 6 | "trailingComma": "none", 7 | "semi": false, 8 | "proseWrap": "always", 9 | "printWidth": 80, 10 | "plugins": ["prettier-plugin-tailwindcss"] 11 | } 12 | -------------------------------------------------------------------------------- /hooks/use-mounted.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useEffect, useState } from 'react' 4 | 5 | export default function useMounted() { 6 | const [mounted, setMounted] = useState(false) 7 | 8 | useEffect(() => { 9 | setMounted(true) 10 | }, []) 11 | 12 | return mounted 13 | } 14 | -------------------------------------------------------------------------------- /lib/prisma.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client' 2 | 3 | declare global { 4 | var prisma: PrismaClient | undefined 5 | } 6 | 7 | const prisma = global.prisma || new PrismaClient() 8 | 9 | if (process.env.NODE_ENV === 'development') global.prisma = prisma 10 | 11 | export default prisma 12 | -------------------------------------------------------------------------------- /app/sign-in/[[...sign-in]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignIn } from '@clerk/nextjs' 2 | 3 | export default function Page() { 4 | return ( 5 |
6 |
7 | 8 |
9 |
10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /app/sign-up/[[...sign-up]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignUp } from '@clerk/nextjs' 2 | 3 | export default function Page() { 4 | return ( 5 |
6 |
7 | 8 |
9 |
10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /app/api/me/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server' 2 | import { auth } from '@clerk/nextjs/server' 3 | 4 | export async function GET() { 5 | const { userId } = auth() 6 | 7 | if (!userId) { 8 | return new NextResponse('Unauthorized', { status: 401 }) 9 | } 10 | 11 | return NextResponse.json({ userId }, { status: 200 }) 12 | } 13 | -------------------------------------------------------------------------------- /components/footer.tsx: -------------------------------------------------------------------------------- 1 | export default function Footer() { 2 | return ( 3 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server' 2 | 3 | const isProtectedRoute = createRouteMatcher(['/protected(.*)']) 4 | 5 | export default clerkMiddleware((auth, req) => { 6 | if (isProtectedRoute(req)) auth().protect() 7 | }) 8 | 9 | export const config = { 10 | matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'] 11 | } 12 | -------------------------------------------------------------------------------- /components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import { ThemeProvider as NextThemesProvider } from 'next-themes' 5 | import { type ThemeProviderProps } from 'next-themes/dist/types' 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children} 9 | } 10 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "gray", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /app/protected/server/page.tsx: -------------------------------------------------------------------------------- 1 | import { currentUser } from '@clerk/nextjs/server' 2 | 3 | export default async function Page() { 4 | const user = await currentUser() 5 | 6 | return ( 7 |
8 |
9 |

This is a server-side page

10 |

You are logged in as {user?.firstName}

11 |
12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "mongodb" 7 | url = env("DATABASE_URL") 8 | } 9 | 10 | model User { 11 | id String @id @default(auto()) @map("_id") @db.ObjectId 12 | email String @unique 13 | firstName String? 14 | lastName String? 15 | imageUrl String? 16 | clerkUserId String @unique 17 | createdAt DateTime @default(now()) 18 | updatedAt DateTime @updatedAt 19 | } 20 | -------------------------------------------------------------------------------- /app/protected/client/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useUser } from '@clerk/nextjs' 4 | 5 | export default function Page() { 6 | const { isLoaded, isSignedIn, user } = useUser() 7 | 8 | if (!isLoaded || !isSignedIn) { 9 | return null 10 | } 11 | 12 | return ( 13 |
14 |
15 |

This is a client-side page

16 |

You are logged in as {user?.firstName}

17 |
18 |
19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /lib/users.ts: -------------------------------------------------------------------------------- 1 | import prisma from '@/lib/prisma' 2 | import { User } from '@prisma/client' 3 | 4 | export async function createUser(data: User) { 5 | try { 6 | const user = await prisma.user.create({ data }) 7 | return { user } 8 | } catch (error) { 9 | return { error } 10 | } 11 | } 12 | 13 | export async function getUserById({ 14 | id, 15 | clerkUserId 16 | }: { 17 | id?: string 18 | clerkUserId?: string 19 | }) { 20 | try { 21 | if (!id && !clerkUserId) { 22 | throw new Error('id or clerkUserId is required') 23 | } 24 | 25 | const query = id ? { id } : { clerkUserId } 26 | 27 | const user = await prisma.user.findUnique({ where: query }) 28 | return { user } 29 | } catch (error) { 30 | return { error } 31 | } 32 | } 33 | 34 | export async function UpdateUser(id: string, data: Partial) { 35 | try { 36 | const user = await prisma.user.update({ 37 | where: { id }, 38 | data 39 | }) 40 | return { user } 41 | } catch (error) { 42 | return { error } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-shadcn", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@clerk/nextjs": "^5.0.10", 13 | "@prisma/client": "^5.14.0", 14 | "@radix-ui/react-dropdown-menu": "^2.0.6", 15 | "@radix-ui/react-icons": "^1.3.0", 16 | "@radix-ui/react-slot": "^1.0.2", 17 | "class-variance-authority": "^0.7.0", 18 | "clsx": "^2.1.1", 19 | "lucide-react": "^0.378.0", 20 | "next": "14.2.3", 21 | "next-themes": "^0.3.0", 22 | "prisma": "^5.14.0", 23 | "react": "^18.3.1", 24 | "react-dom": "^18.3.1", 25 | "svix": "^1.24.0", 26 | "tailwind-merge": "^2.3.0", 27 | "tailwindcss-animate": "^1.0.7" 28 | }, 29 | "devDependencies": { 30 | "@types/node": "^20.12.12", 31 | "@types/react": "^18.3.2", 32 | "@types/react-dom": "^18.3.0", 33 | "autoprefixer": "^10.4.19", 34 | "eslint": "^9.2.0", 35 | "eslint-config-next": "14.2.3", 36 | "postcss": "^8.4.38", 37 | "prettier": "^3.2.5", 38 | "prettier-plugin-tailwindcss": "^0.5.14", 39 | "tailwindcss": "^3.4.3", 40 | "typescript": "^5.4.5" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import { Inter } from 'next/font/google' 3 | import { ClerkProvider } from '@clerk/nextjs' 4 | 5 | import Header from '@/components/header' 6 | import Footer from '@/components/footer' 7 | import { ThemeProvider } from '@/components/theme-provider' 8 | 9 | import './globals.css' 10 | 11 | const inter = Inter({ subsets: ['latin'] }) 12 | 13 | export const metadata: Metadata = { 14 | title: 'Create Next App', 15 | description: 'Generated by create next app' 16 | } 17 | 18 | export default function RootLayout({ 19 | children 20 | }: Readonly<{ 21 | children: React.ReactNode 22 | }>) { 23 | return ( 24 | 25 | 30 | 31 | 37 |
38 |
{children}
39 |