├── .eslintrc.json ├── app ├── globals.css ├── favicon.ico ├── analytics.tsx ├── nav.tsx ├── dismiss-button.tsx ├── loading.tsx ├── layout.tsx ├── playground │ ├── chart.tsx │ └── page.tsx ├── page.tsx ├── table.tsx ├── toast.tsx ├── search.tsx └── navbar.tsx ├── postcss.config.js ├── .vscode └── settings.json ├── tailwind.config.js ├── .env.local.example ├── next.config.js ├── pages └── api │ └── auth │ └── [...nextauth].ts ├── lib └── planetscale.ts ├── .gitignore ├── tsconfig.json ├── LICENSE.md ├── package.json ├── README.md └── pnpm-lock.yaml /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sugarforever/shiny-tomato/main/app/favicon.ico -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/.pnpm/typescript@4.9.3/node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true 4 | } -------------------------------------------------------------------------------- /app/analytics.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Analytics } from '@vercel/analytics/react'; 4 | 5 | export default function AnalyticsWrapper() { 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /app/nav.tsx: -------------------------------------------------------------------------------- 1 | import Navbar from './navbar'; 2 | import { getServerSession } from 'next-auth/next'; 3 | 4 | export default async function Nav() { 5 | const session = await getServerSession(); 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './app/**/*.{js,ts,jsx,tsx}', 5 | './node_modules/@tremor/**/*.{js,ts,jsx,tsx}' 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | }; 12 | -------------------------------------------------------------------------------- /.env.local.example: -------------------------------------------------------------------------------- 1 | # https://vercel.com/integrations/planetscale 2 | DATABASE_URL= 3 | 4 | NEXTAUTH_URL=http://localhost:3000 5 | NEXTAUTH_SECRET= # Linux: `openssl rand -hex 32` or go to https://generate-secret.now.sh/32 6 | 7 | # https://next-auth.js.org/providers/github 8 | GITHUB_ID= 9 | GITHUB_SECRET= 10 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | domains: ['avatars.githubusercontent.com', 'avatar.vercel.sh'] 5 | }, 6 | experimental: { 7 | appDir: true, 8 | serverComponentsExternalPackages: ['@tremor/react'] 9 | } 10 | }; 11 | 12 | module.exports = nextConfig; 13 | -------------------------------------------------------------------------------- /pages/api/auth/[...nextauth].ts: -------------------------------------------------------------------------------- 1 | import NextAuth, { NextAuthOptions } from 'next-auth'; 2 | import GithubProvider from 'next-auth/providers/github'; 3 | 4 | export const authOptions: NextAuthOptions = { 5 | providers: [ 6 | GithubProvider({ 7 | clientId: process.env.GITHUB_ID as string, 8 | clientSecret: process.env.GITHUB_SECRET as string, 9 | }), 10 | ], 11 | }; 12 | 13 | export default NextAuth(authOptions); 14 | -------------------------------------------------------------------------------- /app/dismiss-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import Cookies from 'js-cookie'; 4 | import { useRouter } from 'next/navigation'; 5 | 6 | export default function DismissButton() { 7 | const router = useRouter(); 8 | 9 | return ( 10 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /lib/planetscale.ts: -------------------------------------------------------------------------------- 1 | import 'server-only'; 2 | import { Generated, Kysely } from 'kysely'; 3 | import { PlanetScaleDialect } from 'kysely-planetscale'; 4 | 5 | interface User { 6 | id: Generated; 7 | name: string; 8 | username: string; 9 | email: string; 10 | } 11 | 12 | interface Database { 13 | users: User; 14 | // https://github.com/nextauthjs/next-auth/issues/4922 15 | } 16 | 17 | export const queryBuilder = new Kysely({ 18 | dialect: new PlanetScaleDialect({ 19 | url: process.env.DATABASE_URL 20 | }) 21 | }); 22 | -------------------------------------------------------------------------------- /.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 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /app/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Title, Text } from '@tremor/react'; 2 | import Search from './search'; 3 | 4 | export default async function Loading() { 5 | return ( 6 |
7 | Users 8 | 9 | A list of users retrieved from a MySQL database (PlanetScale). 10 | 11 | 12 |
13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ] 22 | }, 23 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 24 | "exclude": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css'; 2 | 3 | import Nav from './nav'; 4 | import AnalyticsWrapper from './analytics'; 5 | import Toast from './toast'; 6 | import { Suspense } from 'react'; 7 | 8 | export const metadata = { 9 | title: 'Next.js 13 + PlanetScale + NextAuth + Tailwind CSS', 10 | description: 11 | 'A user admin dashboard configured with Next.js, PlanetScale, NextAuth, Tailwind CSS, TypeScript, ESLint, and Prettier.' 12 | }; 13 | 14 | export default async function RootLayout({ 15 | children 16 | }: { 17 | children: React.ReactNode; 18 | }) { 19 | return ( 20 | 21 | 22 | 23 | {/* @ts-expect-error Server Component */} 24 |