├── src
├── app
│ ├── favicon.ico
│ ├── fonts
│ │ ├── GeistVF.woff
│ │ └── GeistMonoVF.woff
│ ├── page.tsx
│ ├── layout.tsx
│ └── globals.css
├── pages
│ └── api
│ │ └── auth
│ │ └── [...nextauth].ts
├── lib
│ ├── utils.ts
│ ├── prisma.ts
│ └── auth.ts
├── components
│ ├── AuthProvider.tsx
│ ├── Footer.tsx
│ ├── ui
│ │ ├── label.tsx
│ │ ├── textarea.tsx
│ │ ├── input.tsx
│ │ ├── tooltip.tsx
│ │ ├── avatar.tsx
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── select.tsx
│ │ └── dropdown-menu.tsx
│ ├── SignInButton.tsx
│ └── Navbar.tsx
├── utils
│ └── supabase.ts
└── types
│ └── next-auth.d.ts
├── .env.example
├── postcss.config.mjs
├── next.config.mjs
├── components.json
├── .gitignore
├── .eslintrc.json
├── .editorconfig
├── tsconfig.json
├── README.md
├── package.json
├── prisma
└── schema.prisma
└── tailwind.config.ts
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sugarforever/any-card/main/src/app/favicon.ico
--------------------------------------------------------------------------------
/src/app/fonts/GeistVF.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sugarforever/any-card/main/src/app/fonts/GeistVF.woff
--------------------------------------------------------------------------------
/src/app/fonts/GeistMonoVF.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sugarforever/any-card/main/src/app/fonts/GeistMonoVF.woff
--------------------------------------------------------------------------------
/src/pages/api/auth/[...nextauth].ts:
--------------------------------------------------------------------------------
1 | import NextAuth from "next-auth";
2 | import { authOptions } from "@/lib/auth";
3 |
4 | export default NextAuth(authOptions);
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | GOOGLE_CLIENT_ID=
2 | GOOGLE_CLIENT_SECRET=
3 | NEXTAUTH_URL=http://localhost:3000
4 | NEXTAUTH_SECRET=
5 | DATABASE_URL=postgresql://localhost:5432/example
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | export default function Home() {
4 | return (
5 |
6 |
Hello Next.js!
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/AuthProvider.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { SessionProvider } from "next-auth/react"
4 |
5 | export default function AuthProvider({ children }: { children: React.ReactNode }) {
6 | return {children}
7 | }
--------------------------------------------------------------------------------
/src/utils/supabase.ts:
--------------------------------------------------------------------------------
1 | import { createClient } from '@supabase/supabase-js'
2 |
3 | const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
4 | const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
5 |
6 | export const supabase = createClient(supabaseUrl, supabaseAnonKey)
--------------------------------------------------------------------------------
/src/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | const Footer = () => {
2 | return (
3 |
8 | );
9 | };
10 |
11 | export default Footer;
--------------------------------------------------------------------------------
/src/types/next-auth.d.ts:
--------------------------------------------------------------------------------
1 | import { DefaultSession } from "next-auth";
2 |
3 | declare module "next-auth" {
4 | interface Session {
5 | user?: {
6 | id: string;
7 | name?: string | null;
8 | email?: string | null;
9 | image?: string | null;
10 | balance: number;
11 | // add other properties you need
12 | } & DefaultSession["user"];
13 | }
14 | }
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | env: {
4 | GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
5 | GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
6 | NEXTAUTH_URL: process.env.NEXTAUTH_URL,
7 | NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
8 | DATABASE_URL: process.env.DATABASE_URL,
9 | }
10 | };
11 |
12 | export default nextConfig;
13 |
--------------------------------------------------------------------------------
/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": "src/app/globals.css",
9 | "baseColor": "zinc",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | }
20 | }
--------------------------------------------------------------------------------
/src/lib/prisma.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from '@prisma/client'
2 |
3 | const prismaClientSingleton = () => {
4 | return new PrismaClient()
5 | }
6 |
7 | type PrismaClientSingleton = ReturnType
8 |
9 | const globalForPrisma = globalThis as unknown as {
10 | prisma: PrismaClientSingleton | undefined
11 | }
12 |
13 | const prisma = globalForPrisma.prisma ?? prismaClientSingleton()
14 |
15 | export default prisma
16 |
17 | if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
--------------------------------------------------------------------------------
/.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 |
39 | # env files
40 | .env*.local
41 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "next/core-web-vitals",
4 | "next/typescript",
5 | "plugin:@typescript-eslint/recommended",
6 | "prettier"
7 | ],
8 | "plugins": ["@typescript-eslint"],
9 | "parser": "@typescript-eslint/parser",
10 | "rules": {
11 | "@typescript-eslint/no-empty-object-type": "off",
12 | "react/react-in-jsx-scope": "off",
13 | "react/prop-types": "off",
14 | "@typescript-eslint/explicit-module-boundary-types": "off",
15 | "@typescript-eslint/no-explicit-any": "warn",
16 | "no-unused-vars": "off",
17 | "@typescript-eslint/no-unused-vars": ["error"]
18 | }
19 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 |
13 | # TypeScript and JavaScript files
14 | [*.{ts,tsx,js,jsx,mjs}]
15 | indent_style = space
16 | indent_size = 2
17 |
18 | # JSON files
19 | [*.json]
20 | indent_style = space
21 | indent_size = 2
22 |
23 | # CSS, SCSS files
24 | [*.{css,scss}]
25 | indent_style = space
26 | indent_size = 2
27 |
28 | # Markdown files
29 | [*.md]
30 | trim_trailing_whitespace = false
31 |
32 | # YAML files
33 | [*.{yml,yaml}]
34 | indent_style = space
35 | indent_size = 2
--------------------------------------------------------------------------------
/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 | "@/*": ["./src/*"]
22 | },
23 | "typeRoots": ["./node_modules/@types", "./src/types"]
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/src/lib/auth.ts:
--------------------------------------------------------------------------------
1 | import { NextAuthOptions } from "next-auth";
2 | import GoogleProvider from "next-auth/providers/google";
3 | import { PrismaAdapter } from "@next-auth/prisma-adapter";
4 | import prisma from './prisma';
5 |
6 | export const authOptions: NextAuthOptions = {
7 | adapter: PrismaAdapter(prisma),
8 | providers: [
9 | GoogleProvider({
10 | clientId: process.env.GOOGLE_CLIENT_ID!,
11 | clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
12 | }),
13 | ],
14 | callbacks: {
15 | async session({ session, user }) {
16 | if (session.user) {
17 | session.user.id = user.id;
18 | const dbUser = await prisma.user.findUnique({
19 | where: { id: user.id },
20 | select: { balance: true },
21 | });
22 |
23 | session.user.balance = dbUser?.balance ?? 0;
24 | }
25 | return session;
26 | },
27 | },
28 | secret: process.env.NEXTAUTH_SECRET,
29 | };
--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/src/components/SignInButton.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { signIn, signOut, useSession } from "next-auth/react";
4 | import Image from "next/image";
5 |
6 | export default function SignInButton() {
7 | const { data: session } = useSession();
8 |
9 | if (session && session.user) {
10 | return (
11 |
12 | {session.user.image && (
13 |
20 | )}
21 |
{session.user.name}
22 |
余额: {session.user.balance}
23 |
26 |
27 | );
28 | }
29 | return (
30 |
33 | );
34 | }
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import localFont from "next/font/local";
2 | import "./globals.css";
3 | import AuthProvider from "../components/AuthProvider";
4 | import Navbar from "@/components/Navbar";
5 | import Footer from "@/components/Footer";
6 | import { Analytics } from "@vercel/analytics/react"
7 | import React from 'react';
8 |
9 | const geistSans = localFont({
10 | src: "./fonts/GeistVF.woff",
11 | variable: "--font-geist-sans",
12 | weight: "100 900",
13 | });
14 | const geistMono = localFont({
15 | src: "./fonts/GeistMonoVF.woff",
16 | variable: "--font-geist-mono",
17 | weight: "100 900",
18 | });
19 |
20 | export default function RootLayout({
21 | children,
22 | }: Readonly<{
23 | children: React.ReactNode;
24 | }>) {
25 | return (
26 |
27 |
28 |
31 |
32 |
33 | {children}
34 |
35 |
36 |
37 |
38 |
39 |
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider
9 |
10 | const Tooltip = TooltipPrimitive.Root
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger
13 |
14 | const TooltipContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, sideOffset = 4, ...props }, ref) => (
18 |
27 | ))
28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
29 |
30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org) boilerplate with Google OAuth service provider integrated.
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs-boilerplate",
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 | "@next-auth/prisma-adapter": "^1.0.7",
13 | "@prisma/client": "^5.19.1",
14 | "@radix-ui/react-avatar": "^1.1.0",
15 | "@radix-ui/react-dropdown-menu": "^2.1.1",
16 | "@radix-ui/react-icons": "^1.3.0",
17 | "@radix-ui/react-label": "^2.1.0",
18 | "@radix-ui/react-select": "^2.1.1",
19 | "@radix-ui/react-slot": "^1.1.0",
20 | "@radix-ui/react-tooltip": "^1.1.2",
21 | "@supabase/supabase-js": "^2.45.4",
22 | "@vercel/analytics": "^1.3.1",
23 | "class-variance-authority": "^0.7.0",
24 | "clsx": "^2.1.1",
25 | "framer-motion": "^11.8.0",
26 | "lucide-react": "^0.441.0",
27 | "next": "14.2.12",
28 | "next-auth": "^4.24.7",
29 | "react": "^18",
30 | "react-dom": "^18",
31 | "tailwind-merge": "^2.5.2",
32 | "tailwindcss-animate": "^1.0.7"
33 | },
34 | "devDependencies": {
35 | "@types/node": "^20",
36 | "@types/react": "^18",
37 | "@types/react-dom": "^18",
38 | "@typescript-eslint/eslint-plugin": "^8.8.1",
39 | "@typescript-eslint/parser": "^8.8.1",
40 | "eslint": "^8",
41 | "eslint-config-next": "14.2.12",
42 | "eslint-config-prettier": "^9.1.0",
43 | "postcss": "^8",
44 | "prisma": "^5.19.1",
45 | "tailwindcss": "^3.4.1",
46 | "typescript": "^5"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | generator client {
2 | provider = "prisma-client-js"
3 | }
4 |
5 | datasource db {
6 | provider = "postgresql"
7 | url = env("DATABASE_URL")
8 | }
9 |
10 | model Account {
11 | id String @id @default(cuid())
12 | userId String
13 | type String
14 | provider String
15 | providerAccountId String
16 | refresh_token String? @db.Text
17 | access_token String? @db.Text
18 | expires_at Int?
19 | token_type String?
20 | scope String?
21 | id_token String? @db.Text
22 | session_state String?
23 |
24 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
25 |
26 | @@unique([provider, providerAccountId])
27 | }
28 |
29 | model Session {
30 | id String @id @default(cuid())
31 | sessionToken String @unique
32 | userId String
33 | expires DateTime
34 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
35 | }
36 |
37 | model User {
38 | id String @id @default(cuid())
39 | name String?
40 | email String? @unique
41 | emailVerified DateTime?
42 | image String?
43 | balance Int @default(5)
44 | accounts Account[]
45 | sessions Session[]
46 | }
47 |
48 | model VerificationToken {
49 | identifier String
50 | token String @unique
51 | expires DateTime
52 | @@unique([identifier, token])
53 | }
--------------------------------------------------------------------------------
/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | Avatar.displayName = AvatarPrimitive.Root.displayName
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ))
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ))
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49 |
50 | export { Avatar, AvatarImage, AvatarFallback }
51 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const config: Config = {
4 | darkMode: ["class"],
5 | content: [
6 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
8 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
9 | ],
10 | theme: {
11 | extend: {
12 | colors: {
13 | background: 'hsl(var(--background))',
14 | foreground: 'hsl(var(--foreground))',
15 | card: {
16 | DEFAULT: 'hsl(var(--card))',
17 | foreground: 'hsl(var(--card-foreground))'
18 | },
19 | popover: {
20 | DEFAULT: 'hsl(var(--popover))',
21 | foreground: 'hsl(var(--popover-foreground))'
22 | },
23 | primary: {
24 | DEFAULT: 'hsl(var(--primary))',
25 | foreground: 'hsl(var(--primary-foreground))'
26 | },
27 | secondary: {
28 | DEFAULT: 'hsl(var(--secondary))',
29 | foreground: 'hsl(var(--secondary-foreground))'
30 | },
31 | muted: {
32 | DEFAULT: 'hsl(var(--muted))',
33 | foreground: 'hsl(var(--muted-foreground))'
34 | },
35 | accent: {
36 | DEFAULT: 'hsl(var(--accent))',
37 | foreground: 'hsl(var(--accent-foreground))'
38 | },
39 | destructive: {
40 | DEFAULT: 'hsl(var(--destructive))',
41 | foreground: 'hsl(var(--destructive-foreground))'
42 | },
43 | border: 'hsl(var(--border))',
44 | input: 'hsl(var(--input))',
45 | ring: 'hsl(var(--ring))',
46 | chart: {
47 | '1': 'hsl(var(--chart-1))',
48 | '2': 'hsl(var(--chart-2))',
49 | '3': 'hsl(var(--chart-3))',
50 | '4': 'hsl(var(--chart-4))',
51 | '5': 'hsl(var(--chart-5))'
52 | }
53 | },
54 | borderRadius: {
55 | lg: 'var(--radius)',
56 | md: 'calc(var(--radius) - 2px)',
57 | sm: 'calc(var(--radius) - 4px)'
58 | }
59 | }
60 | },
61 | plugins: [require("tailwindcss-animate")],
62 | };
63 | export default config;
64 |
--------------------------------------------------------------------------------
/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14 | destructive:
15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16 | outline:
17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20 | ghost: "hover:bg-accent hover:text-accent-foreground",
21 | link: "text-primary underline-offset-4 hover:underline",
22 | },
23 | size: {
24 | default: "h-9 px-4 py-2",
25 | sm: "h-8 rounded-md px-3 text-xs",
26 | lg: "h-10 rounded-md px-8",
27 | icon: "h-9 w-9",
28 | },
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default",
33 | },
34 | }
35 | )
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : "button"
46 | return (
47 |
52 | )
53 | }
54 | )
55 | Button.displayName = "Button"
56 |
57 | export { Button, buttonVariants }
58 |
--------------------------------------------------------------------------------
/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
41 | ))
42 | CardTitle.displayName = "CardTitle"
43 |
44 | const CardDescription = React.forwardRef<
45 | HTMLParagraphElement,
46 | React.HTMLAttributes
47 | >(({ className, ...props }, ref) => (
48 |
53 | ))
54 | CardDescription.displayName = "CardDescription"
55 |
56 | const CardContent = React.forwardRef<
57 | HTMLDivElement,
58 | React.HTMLAttributes
59 | >(({ className, ...props }, ref) => (
60 |
61 | ))
62 | CardContent.displayName = "CardContent"
63 |
64 | const CardFooter = React.forwardRef<
65 | HTMLDivElement,
66 | React.HTMLAttributes
67 | >(({ className, ...props }, ref) => (
68 |
73 | ))
74 | CardFooter.displayName = "CardFooter"
75 |
76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
77 |
--------------------------------------------------------------------------------
/src/components/Navbar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { signIn, signOut, useSession } from "next-auth/react";
4 | import Link from "next/link";
5 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
6 | import { Button } from "@/components/ui/button";
7 | import {
8 | DropdownMenu,
9 | DropdownMenuContent,
10 | DropdownMenuItem,
11 | DropdownMenuTrigger
12 | } from "@/components/ui/dropdown-menu";
13 |
14 | export default function Navbar() {
15 | const { data: session } = useSession();
16 |
17 | return (
18 |
58 | );
59 | }
--------------------------------------------------------------------------------
/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --background: #ffffff;
7 | --foreground: #171717;
8 | }
9 |
10 | @media (prefers-color-scheme: dark) {
11 | :root {
12 | --background: #0a0a0a;
13 | --foreground: #ededed;
14 | }
15 | }
16 |
17 | body {
18 | color: var(--foreground);
19 | background: var(--background);
20 | font-family: Arial, Helvetica, sans-serif;
21 | }
22 |
23 | @layer utilities {
24 | .text-balance {
25 | text-wrap: balance;
26 | }
27 | }
28 |
29 | @layer base {
30 | :root {
31 | --background: 0 0% 100%;
32 | --foreground: 240 10% 3.9%;
33 | --card: 0 0% 100%;
34 | --card-foreground: 240 10% 3.9%;
35 | --popover: 0 0% 100%;
36 | --popover-foreground: 240 10% 3.9%;
37 | --primary: 240 5.9% 10%;
38 | --primary-foreground: 0 0% 98%;
39 | --secondary: 240 4.8% 95.9%;
40 | --secondary-foreground: 240 5.9% 10%;
41 | --muted: 240 4.8% 95.9%;
42 | --muted-foreground: 240 3.8% 46.1%;
43 | --accent: 240 4.8% 95.9%;
44 | --accent-foreground: 240 5.9% 10%;
45 | --destructive: 0 84.2% 60.2%;
46 | --destructive-foreground: 0 0% 98%;
47 | --border: 240 5.9% 90%;
48 | --input: 240 5.9% 90%;
49 | --ring: 240 10% 3.9%;
50 | --chart-1: 12 76% 61%;
51 | --chart-2: 173 58% 39%;
52 | --chart-3: 197 37% 24%;
53 | --chart-4: 43 74% 66%;
54 | --chart-5: 27 87% 67%;
55 | --radius: 0.5rem;
56 | }
57 | .dark {
58 | --background: 240 10% 3.9%;
59 | --foreground: 0 0% 98%;
60 | --card: 240 10% 3.9%;
61 | --card-foreground: 0 0% 98%;
62 | --popover: 240 10% 3.9%;
63 | --popover-foreground: 0 0% 98%;
64 | --primary: 0 0% 98%;
65 | --primary-foreground: 240 5.9% 10%;
66 | --secondary: 240 3.7% 15.9%;
67 | --secondary-foreground: 0 0% 98%;
68 | --muted: 240 3.7% 15.9%;
69 | --muted-foreground: 240 5% 64.9%;
70 | --accent: 240 3.7% 15.9%;
71 | --accent-foreground: 0 0% 98%;
72 | --destructive: 0 62.8% 30.6%;
73 | --destructive-foreground: 0 0% 98%;
74 | --border: 240 3.7% 15.9%;
75 | --input: 240 3.7% 15.9%;
76 | --ring: 240 4.9% 83.9%;
77 | --chart-1: 220 70% 50%;
78 | --chart-2: 160 60% 45%;
79 | --chart-3: 30 80% 55%;
80 | --chart-4: 280 65% 60%;
81 | --chart-5: 340 75% 55%;
82 | }
83 | }
84 |
85 | @layer base {
86 | * {
87 | @apply border-border;
88 | }
89 | body {
90 | @apply bg-background text-foreground;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import {
5 | CaretSortIcon,
6 | CheckIcon,
7 | ChevronDownIcon,
8 | ChevronUpIcon,
9 | } from "@radix-ui/react-icons"
10 | import * as SelectPrimitive from "@radix-ui/react-select"
11 |
12 | import { cn } from "@/lib/utils"
13 |
14 | const Select = SelectPrimitive.Root
15 |
16 | const SelectGroup = SelectPrimitive.Group
17 |
18 | const SelectValue = SelectPrimitive.Value
19 |
20 | const SelectTrigger = React.forwardRef<
21 | React.ElementRef,
22 | React.ComponentPropsWithoutRef
23 | >(({ className, children, ...props }, ref) => (
24 | span]:line-clamp-1",
28 | className
29 | )}
30 | {...props}
31 | >
32 | {children}
33 |
34 |
35 |
36 |
37 | ))
38 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
39 |
40 | const SelectScrollUpButton = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 |
53 |
54 | ))
55 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
56 |
57 | const SelectScrollDownButton = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, ...props }, ref) => (
61 |
69 |
70 |
71 | ))
72 | SelectScrollDownButton.displayName =
73 | SelectPrimitive.ScrollDownButton.displayName
74 |
75 | const SelectContent = React.forwardRef<
76 | React.ElementRef,
77 | React.ComponentPropsWithoutRef
78 | >(({ className, children, position = "popper", ...props }, ref) => (
79 |
80 |
91 |
92 |
99 | {children}
100 |
101 |
102 |
103 |
104 | ))
105 | SelectContent.displayName = SelectPrimitive.Content.displayName
106 |
107 | const SelectLabel = React.forwardRef<
108 | React.ElementRef,
109 | React.ComponentPropsWithoutRef
110 | >(({ className, ...props }, ref) => (
111 |
116 | ))
117 | SelectLabel.displayName = SelectPrimitive.Label.displayName
118 |
119 | const SelectItem = React.forwardRef<
120 | React.ElementRef,
121 | React.ComponentPropsWithoutRef
122 | >(({ className, children, ...props }, ref) => (
123 |
131 |
132 |
133 |
134 |
135 |
136 | {children}
137 |
138 | ))
139 | SelectItem.displayName = SelectPrimitive.Item.displayName
140 |
141 | const SelectSeparator = React.forwardRef<
142 | React.ElementRef,
143 | React.ComponentPropsWithoutRef
144 | >(({ className, ...props }, ref) => (
145 |
150 | ))
151 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
152 |
153 | export {
154 | Select,
155 | SelectGroup,
156 | SelectValue,
157 | SelectTrigger,
158 | SelectContent,
159 | SelectLabel,
160 | SelectItem,
161 | SelectSeparator,
162 | SelectScrollUpButton,
163 | SelectScrollDownButton,
164 | }
165 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5 | import {
6 | CheckIcon,
7 | ChevronRightIcon,
8 | DotFilledIcon,
9 | } from "@radix-ui/react-icons"
10 |
11 | import { cn } from "@/lib/utils"
12 |
13 | const DropdownMenu = DropdownMenuPrimitive.Root
14 |
15 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
16 |
17 | const DropdownMenuGroup = DropdownMenuPrimitive.Group
18 |
19 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal
20 |
21 | const DropdownMenuSub = DropdownMenuPrimitive.Sub
22 |
23 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
24 |
25 | const DropdownMenuSubTrigger = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef & {
28 | inset?: boolean
29 | }
30 | >(({ className, inset, children, ...props }, ref) => (
31 |
40 | {children}
41 |
42 |
43 | ))
44 | DropdownMenuSubTrigger.displayName =
45 | DropdownMenuPrimitive.SubTrigger.displayName
46 |
47 | const DropdownMenuSubContent = React.forwardRef<
48 | React.ElementRef,
49 | React.ComponentPropsWithoutRef
50 | >(({ className, ...props }, ref) => (
51 |
59 | ))
60 | DropdownMenuSubContent.displayName =
61 | DropdownMenuPrimitive.SubContent.displayName
62 |
63 | const DropdownMenuContent = React.forwardRef<
64 | React.ElementRef,
65 | React.ComponentPropsWithoutRef
66 | >(({ className, sideOffset = 4, ...props }, ref) => (
67 |
68 |
78 |
79 | ))
80 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
81 |
82 | const DropdownMenuItem = React.forwardRef<
83 | React.ElementRef,
84 | React.ComponentPropsWithoutRef & {
85 | inset?: boolean
86 | }
87 | >(({ className, inset, ...props }, ref) => (
88 |
97 | ))
98 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
99 |
100 | const DropdownMenuCheckboxItem = React.forwardRef<
101 | React.ElementRef,
102 | React.ComponentPropsWithoutRef
103 | >(({ className, children, checked, ...props }, ref) => (
104 |
113 |
114 |
115 |
116 |
117 |
118 | {children}
119 |
120 | ))
121 | DropdownMenuCheckboxItem.displayName =
122 | DropdownMenuPrimitive.CheckboxItem.displayName
123 |
124 | const DropdownMenuRadioItem = React.forwardRef<
125 | React.ElementRef,
126 | React.ComponentPropsWithoutRef
127 | >(({ className, children, ...props }, ref) => (
128 |
136 |
137 |
138 |
139 |
140 |
141 | {children}
142 |
143 | ))
144 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
145 |
146 | const DropdownMenuLabel = React.forwardRef<
147 | React.ElementRef,
148 | React.ComponentPropsWithoutRef & {
149 | inset?: boolean
150 | }
151 | >(({ className, inset, ...props }, ref) => (
152 |
161 | ))
162 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
163 |
164 | const DropdownMenuSeparator = React.forwardRef<
165 | React.ElementRef,
166 | React.ComponentPropsWithoutRef
167 | >(({ className, ...props }, ref) => (
168 |
173 | ))
174 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
175 |
176 | const DropdownMenuShortcut = ({
177 | className,
178 | ...props
179 | }: React.HTMLAttributes) => {
180 | return (
181 |
185 | )
186 | }
187 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
188 |
189 | export {
190 | DropdownMenu,
191 | DropdownMenuTrigger,
192 | DropdownMenuContent,
193 | DropdownMenuItem,
194 | DropdownMenuCheckboxItem,
195 | DropdownMenuRadioItem,
196 | DropdownMenuLabel,
197 | DropdownMenuSeparator,
198 | DropdownMenuShortcut,
199 | DropdownMenuGroup,
200 | DropdownMenuPortal,
201 | DropdownMenuSub,
202 | DropdownMenuSubContent,
203 | DropdownMenuSubTrigger,
204 | DropdownMenuRadioGroup,
205 | }
206 |
--------------------------------------------------------------------------------