diff --git a/.gitignore b/.gitignore
index fd3dbb5..00bba9b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,6 +27,7 @@ yarn-error.log*
# local env files
.env*.local
+.env
# vercel
.vercel
diff --git a/bun.lockb b/bun.lockb
index c5f367d..8b8efbf 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/components.json b/components.json
new file mode 100644
index 0000000..14331d8
--- /dev/null
+++ b/components.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.ts",
+ "css": "src/app/globals.css",
+ "baseColor": "slate",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils"
+ }
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index bdd0586..0548c7a 100644
--- a/package.json
+++ b/package.json
@@ -9,19 +9,32 @@
"lint": "next lint"
},
"dependencies": {
+ "@clerk/nextjs": "^4.29.11",
+ "@prisma/client": "^5.12.1",
+ "@radix-ui/react-checkbox": "^1.0.4",
+ "@radix-ui/react-dropdown-menu": "^2.0.6",
+ "@radix-ui/react-icons": "^1.3.0",
+ "@radix-ui/react-label": "^2.0.2",
+ "@radix-ui/react-slot": "^1.0.2",
+ "@radix-ui/react-switch": "^1.0.3",
+ "class-variance-authority": "^0.7.0",
+ "clsx": "^2.1.0",
+ "next": "14.1.4",
"react": "^18",
"react-dom": "^18",
- "next": "14.1.4"
+ "tailwind-merge": "^2.2.2",
+ "tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
- "typescript": "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
- "postcss": "^8",
- "tailwindcss": "^3.3.0",
"eslint": "^8",
- "eslint-config-next": "14.1.4"
+ "eslint-config-next": "14.1.4",
+ "postcss": "^8",
+ "prisma": "^5.12.1",
+ "tailwindcss": "^3.3.0",
+ "typescript": "^5"
}
}
diff --git a/prisma/migrations/20240405225206_init/migration.sql b/prisma/migrations/20240405225206_init/migration.sql
new file mode 100644
index 0000000..850309f
--- /dev/null
+++ b/prisma/migrations/20240405225206_init/migration.sql
@@ -0,0 +1,18 @@
+-- CreateTable
+CREATE TABLE "Point" (
+ "id" SERIAL NOT NULL,
+ "userId" TEXT NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "number" INTEGER NOT NULL,
+
+ CONSTRAINT "Point_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "PointCount" (
+ "id" SERIAL NOT NULL,
+ "userId" TEXT NOT NULL,
+ "balance" INTEGER NOT NULL,
+
+ CONSTRAINT "PointCount_pkey" PRIMARY KEY ("id")
+);
diff --git a/prisma/migrations/20240405225349_reason/migration.sql b/prisma/migrations/20240405225349_reason/migration.sql
new file mode 100644
index 0000000..1d59dbc
--- /dev/null
+++ b/prisma/migrations/20240405225349_reason/migration.sql
@@ -0,0 +1,8 @@
+/*
+ Warnings:
+
+ - Added the required column `reason` to the `Point` table without a default value. This is not possible if the table is not empty.
+
+*/
+-- AlterTable
+ALTER TABLE "Point" ADD COLUMN "reason" TEXT NOT NULL;
diff --git a/prisma/migrations/20240405225645_changeid/migration.sql b/prisma/migrations/20240405225645_changeid/migration.sql
new file mode 100644
index 0000000..8f917f6
--- /dev/null
+++ b/prisma/migrations/20240405225645_changeid/migration.sql
@@ -0,0 +1,11 @@
+/*
+ Warnings:
+
+ - The primary key for the `PointCount` table will be changed. If it partially fails, the table could be left without primary key constraint.
+ - You are about to drop the column `id` on the `PointCount` table. All the data in the column will be lost.
+
+*/
+-- AlterTable
+ALTER TABLE "PointCount" DROP CONSTRAINT "PointCount_pkey",
+DROP COLUMN "id",
+ADD CONSTRAINT "PointCount_pkey" PRIMARY KEY ("userId");
diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml
new file mode 100644
index 0000000..fbffa92
--- /dev/null
+++ b/prisma/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (i.e. Git)
+provider = "postgresql"
\ No newline at end of file
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
new file mode 100644
index 0000000..a19de92
--- /dev/null
+++ b/prisma/schema.prisma
@@ -0,0 +1,27 @@
+// This is your Prisma schema file,
+// learn more about it in the docs: https://pris.ly/d/prisma-schema
+
+// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
+// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
+
+generator client {
+ provider = "prisma-client-js"
+}
+
+datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+}
+
+model Point {
+ id Int @id @default(autoincrement())
+ userId String
+ createdAt DateTime @default(now())
+ number Int
+ reason String
+}
+
+model PointCount {
+ userId String @id
+ balance Int
+}
\ No newline at end of file
diff --git a/src/app/fonts/Satoshi-Medium.woff2 b/src/app/fonts/Satoshi-Medium.woff2
new file mode 100644
index 0000000..ffd0ac9
Binary files /dev/null and b/src/app/fonts/Satoshi-Medium.woff2 differ
diff --git a/src/app/globals.css b/src/app/globals.css
index 875c01e..9990664 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -2,32 +2,57 @@
@tailwind components;
@tailwind utilities;
-:root {
- --foreground-rgb: 0, 0, 0;
- --background-start-rgb: 214, 219, 220;
- --background-end-rgb: 255, 255, 255;
-}
-
-@media (prefers-color-scheme: dark) {
+@layer base {
:root {
- --foreground-rgb: 255, 255, 255;
- --background-start-rgb: 0, 0, 0;
- --background-end-rgb: 0, 0, 0;
+ --background: 0 0% 100%;
+ --foreground: 222.2 84% 4.9%;
+ --muted: 210 40% 96.1%;
+ --muted-foreground: 215.4 16.3% 46.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 222.2 84% 4.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 222.2 84% 4.9%;
+ --border: 214.3 31.8% 91.4%;
+ --input: 214.3 31.8% 91.4%;
+ --primary: 220.47 98.26% 36.08%;
+ --primary-foreground: 210 40% 98%;
+ --secondary: 210 40% 96.1%;
+ --secondary-foreground: 222.2 47.4% 11.2%;
+ --accent: 210 40% 96.1%;
+ --accent-foreground: 222.2 47.4% 11.2%;
+ --destructive: 0 92.99% 56.11%;
+ --destructive-foreground: 210 40% 98%;
+ --ring: 220.67 97.83% 36.08%;
+ --radius: 0.5rem;
+ }
+
+ .dark {
+ --background: 240 13.73% 10%;
+ --foreground: 229.76 31.78% 74.71%;
+ --muted: 232.5 15.44% 18.32%;
+ --muted-foreground: 233.79 11.37% 50%;
+ --popover: 234.55 17.46% 12.35%;
+ --popover-foreground: 234 12.4% 52.55%;
+ --card: 234.55 17.46% 12.35%;
+ --card-foreground: 229.76 31.78% 74.71%;
+ --border: 232.5 15.38% 30.59%;
+ --input: 232 20% 14.71%;
+ --primary: 0 0% 82.75%;
+ --primary-foreground: 0 0% 20%;
+ --secondary: 225.45 71.22% 72.75%;
+ --secondary-foreground: 234.55 17.46% 12.35%;
+ --accent: 234.55 17.83% 9.47%;
+ --accent-foreground: 0 0% 82.75%;
+ --destructive: 1.58 47.5% 52.94%;
+ --destructive-foreground: 210 40% 98.04%;
+ --ring: 225.45 71.22% 72.75%;
}
}
-body {
- color: rgb(var(--foreground-rgb));
- background: linear-gradient(
- to bottom,
- transparent,
- rgb(var(--background-end-rgb))
- )
- rgb(var(--background-start-rgb));
-}
-
-@layer utilities {
- .text-balance {
- text-wrap: balance;
- }
-}
+input[type='number']::-webkit-outer-spin-button,
+ input[type='number']::-webkit-inner-spin-button,
+ input[type='number'] {
+ -webkit-appearance: none;
+ margin: 0;
+ -moz-appearance: textfield !important;
+ }
\ No newline at end of file
diff --git a/src/app/history/page.tsx b/src/app/history/page.tsx
new file mode 100644
index 0000000..2ee6523
--- /dev/null
+++ b/src/app/history/page.tsx
@@ -0,0 +1,19 @@
+import History from '@/components/app/History/History'
+import prisma from '@/lib/db'
+import { currentUser } from '@clerk/nextjs'
+
+export default async function Page() {
+ const pointHistory = (await prisma.point.findMany({
+ where: {
+ userId: (await currentUser())!.id
+ }
+ })).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()).map(p => {
+ return {
+ date: p.createdAt,
+ reason: p.reason,
+ points: p.number,
+ id: p.id
+ }
+ })
+ return pointHistory.map(p => )
+}
\ No newline at end of file
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 3314e47..e513cf7 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,12 +1,19 @@
import type { Metadata } from "next";
-import { Inter } from "next/font/google";
+import localFont from 'next/font/local'
import "./globals.css";
+import { ClerkProvider } from "@clerk/nextjs";
+import Navbar from "@/components/app/Navbar/Navbar";
-const inter = Inter({ subsets: ["latin"] });
+const satoshi = localFont({ src: './fonts/Satoshi-Medium.woff2' })
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ title: "Puntos app",
+ description: "haha yes points go brrr",
+ openGraph: {
+ type: 'website',
+ locale: 'es_ES',
+ url: 'https://puntos.srizan.dev',
+ }
};
export default function RootLayout({
@@ -15,8 +22,15 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
-
{children}
-
+
+
+
+
+
+ {children}
+
+
+
+
);
}
diff --git a/src/app/ok/page.tsx b/src/app/ok/page.tsx
new file mode 100644
index 0000000..e114371
--- /dev/null
+++ b/src/app/ok/page.tsx
@@ -0,0 +1,13 @@
+import { Button } from "@/components/ui/button";
+import Link from "next/link";
+
+export default function Page() {
+ return (
+
+
nice
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/page.tsx b/src/app/page.tsx
index b81507d..dfd6231 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,113 +1,19 @@
-import Image from "next/image";
+import DesktopPoints from "@/components/app/Points/Desktop/Desktop";
+import prisma from "@/lib/db";
+import { currentUser } from "@clerk/nextjs";
-export default function Home() {
+export default async function Home() {
+ const pointCount = (await prisma.pointCount.findFirst({
+ where: {
+ userId: (await currentUser())!.id,
+ }
+ }))!.balance
return (
-
-
-
- Get started by editing
- src/app/page.tsx
-
-
+ <>
+
tienes {pointCount} puntos
+
+
-
-
-
-
-
-
-
+ >
);
}
diff --git a/src/app/remove/page.tsx b/src/app/remove/page.tsx
new file mode 100644
index 0000000..3d5de9c
--- /dev/null
+++ b/src/app/remove/page.tsx
@@ -0,0 +1,19 @@
+import RemovePoints from "@/components/app/RemovePoints/RemovePoints"
+import prisma from "@/lib/db"
+import { currentUser } from "@clerk/nextjs"
+
+export default async function Page() {
+ const pointCount = (await prisma.pointCount.findFirst({
+ where: {
+ userId: (await currentUser())!.id,
+ }
+ }))!.balance
+ return (
+ <>
+
tienes {pointCount} puntos
+
+
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/src/components/app/History/History.tsx b/src/components/app/History/History.tsx
new file mode 100644
index 0000000..5df43e7
--- /dev/null
+++ b/src/components/app/History/History.tsx
@@ -0,0 +1,105 @@
+/**
+ * v0 by Vercel.
+ * @see https://v0.dev/t/EhXenFqofUT
+ * Documentation: https://v0.dev/docs#integrating-generated-code-into-your-nextjs-app
+ */
+import { CardContent, Card } from "@/components/ui/card"
+import React, { PropsWithoutRef } from "react"
+
+export default function History(props: Props) {
+ const pointsColor = props.points > 0 ? "text-green-500" : "text-red-500"
+ return (
+
+
+
+
+
+
+ {props.date.toLocaleDateString()}
+
+
+
+ {props.reason}
+
+
+ {props.points} puntos
+
+
+
+
+
+ )
+}
+
+interface Props {
+ date: Date
+ reason: string
+ points: number
+}
+
+function CalendarIcon(props: React.SVGProps
) {
+ return (
+
+ )
+}
+
+
+function CoffeeIcon(props: React.SVGProps) {
+ return (
+
+ )
+}
+
+
+function CreditCardIcon(props: React.SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/components/app/Navbar/Navbar.tsx b/src/components/app/Navbar/Navbar.tsx
new file mode 100644
index 0000000..2443699
--- /dev/null
+++ b/src/components/app/Navbar/Navbar.tsx
@@ -0,0 +1,51 @@
+/**
+ * v0 by Vercel.
+ * @see https://v0.dev/t/igzEEdGqAvH
+ * Documentation: https://v0.dev/docs#integrating-generated-code-into-your-nextjs-app
+ */
+import { Button } from "@/components/ui/button"
+import { DropdownMenuShortcut } from "@/components/ui/dropdown-menu"
+import { UserButton } from "@clerk/nextjs"
+import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuGroup, DropdownMenuItem } from "@/components/ui/dropdown-menu"
+import Link from "next/link"
+
+export default function Navbar() {
+ return (
+
+ )
+}
+
diff --git a/src/components/app/Points/Desktop/Desktop.tsx b/src/components/app/Points/Desktop/Desktop.tsx
new file mode 100644
index 0000000..e48f580
--- /dev/null
+++ b/src/components/app/Points/Desktop/Desktop.tsx
@@ -0,0 +1,69 @@
+import * as React from "react"
+import { Button } from "@/components/ui/button"
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card"
+import { Input } from "@/components/ui/input"
+import prisma from "@/lib/db"
+import { redirect } from "next/navigation"
+import { currentUser } from "@clerk/nextjs"
+
+export default function DesktopPoints() {
+ async function createPoints(formData: FormData) {
+ 'use server'
+
+ const rawFormData = {
+ points: formData.get('points'),
+ reason: formData.get('reason'),
+ }
+
+ await prisma.point.create({
+ data: {
+ userId: (await currentUser())!.id,
+ number: Number(rawFormData.points),
+ reason: rawFormData.reason as string,
+ }
+ })
+ await prisma.pointCount.upsert({
+ where: {
+ userId: (await currentUser())!.id,
+ },
+ update: {
+ balance: {
+ increment: Number(rawFormData.points),
+ }
+ },
+ create: {
+ userId: (await currentUser())!.id,
+ balance: Number(rawFormData.points),
+ }
+ })
+ redirect('/ok')
+ }
+ return (
+
+
+ Añade o elimina puntos
+ wow enhorabuena
+
+
+
+ )
+}
diff --git a/src/components/app/RemovePoints/RemovePoints.tsx b/src/components/app/RemovePoints/RemovePoints.tsx
new file mode 100644
index 0000000..2b89961
--- /dev/null
+++ b/src/components/app/RemovePoints/RemovePoints.tsx
@@ -0,0 +1,69 @@
+import * as React from "react"
+import { Button } from "@/components/ui/button"
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card"
+import { Input } from "@/components/ui/input"
+import prisma from "@/lib/db"
+import { redirect } from "next/navigation"
+import { currentUser } from "@clerk/nextjs"
+
+export default function RemovePoints() {
+ async function createPoints(formData: FormData) {
+ 'use server'
+
+ const rawFormData = {
+ points: `-${formData.get('points')}`,
+ reason: formData.get('reason'),
+ }
+
+ await prisma.point.create({
+ data: {
+ userId: (await currentUser())!.id,
+ number: Number(rawFormData.points),
+ reason: rawFormData.reason as string,
+ }
+ })
+ await prisma.pointCount.upsert({
+ where: {
+ userId: (await currentUser())!.id,
+ },
+ update: {
+ balance: {
+ increment: Number(rawFormData.points),
+ }
+ },
+ create: {
+ userId: (await currentUser())!.id,
+ balance: Number(rawFormData.points),
+ }
+ })
+ redirect('/ok')
+ }
+ return (
+
+
+ Elimina puntos
+ oh no
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
new file mode 100644
index 0000000..0270f64
--- /dev/null
+++ b/src/components/ui/button.tsx
@@ -0,0 +1,57 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "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",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
+ outline:
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
+ secondary:
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2",
+ sm: "h-8 rounded-md px-3 text-xs",
+ lg: "h-10 rounded-md px-8",
+ icon: "h-9 w-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return (
+
+ )
+ }
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
new file mode 100644
index 0000000..77e9fb7
--- /dev/null
+++ b/src/components/ui/card.tsx
@@ -0,0 +1,76 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+Card.displayName = "Card"
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardHeader.displayName = "CardHeader"
+
+const CardTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardTitle.displayName = "CardTitle"
+
+const CardDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardDescription.displayName = "CardDescription"
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardContent.displayName = "CardContent"
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardFooter.displayName = "CardFooter"
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx
new file mode 100644
index 0000000..7d2b3c3
--- /dev/null
+++ b/src/components/ui/checkbox.tsx
@@ -0,0 +1,30 @@
+"use client"
+
+import * as React from "react"
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
+import { CheckIcon } from "@radix-ui/react-icons"
+
+import { cn } from "@/lib/utils"
+
+const Checkbox = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+
+))
+Checkbox.displayName = CheckboxPrimitive.Root.displayName
+
+export { Checkbox }
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 0000000..242b07a
--- /dev/null
+++ b/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,205 @@
+"use client"
+
+import * as React from "react"
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import {
+ CheckIcon,
+ ChevronRightIcon,
+ DotFilledIcon,
+} from "@radix-ui/react-icons"
+
+import { cn } from "@/lib/utils"
+
+const DropdownMenu = DropdownMenuPrimitive.Root
+
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
+
+const DropdownMenuGroup = DropdownMenuPrimitive.Group
+
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal
+
+const DropdownMenuSub = DropdownMenuPrimitive.Sub
+
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
+
+const DropdownMenuSubTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+DropdownMenuSubTrigger.displayName =
+ DropdownMenuPrimitive.SubTrigger.displayName
+
+const DropdownMenuSubContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSubContent.displayName =
+ DropdownMenuPrimitive.SubContent.displayName
+
+const DropdownMenuContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
+
+const DropdownMenuItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
+
+const DropdownMenuCheckboxItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuCheckboxItem.displayName =
+ DropdownMenuPrimitive.CheckboxItem.displayName
+
+const DropdownMenuRadioItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
+
+const DropdownMenuLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
+
+const DropdownMenuSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
+
+const DropdownMenuShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => {
+ return (
+
+ )
+}
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
+
+export {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuGroup,
+ DropdownMenuPortal,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuRadioGroup,
+}
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx
new file mode 100644
index 0000000..a92b8e0
--- /dev/null
+++ b/src/components/ui/input.tsx
@@ -0,0 +1,25 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+export interface InputProps
+ extends React.InputHTMLAttributes {}
+
+const Input = React.forwardRef(
+ ({ className, type, ...props }, ref) => {
+ return (
+
+ )
+ }
+)
+Input.displayName = "Input"
+
+export { Input }
diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx
new file mode 100644
index 0000000..5341821
--- /dev/null
+++ b/src/components/ui/label.tsx
@@ -0,0 +1,26 @@
+"use client"
+
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const labelVariants = cva(
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+)
+
+const Label = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, ...props }, ref) => (
+
+))
+Label.displayName = LabelPrimitive.Root.displayName
+
+export { Label }
diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx
new file mode 100644
index 0000000..5f4117f
--- /dev/null
+++ b/src/components/ui/switch.tsx
@@ -0,0 +1,29 @@
+"use client"
+
+import * as React from "react"
+import * as SwitchPrimitives from "@radix-ui/react-switch"
+
+import { cn } from "@/lib/utils"
+
+const Switch = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+Switch.displayName = SwitchPrimitives.Root.displayName
+
+export { Switch }
diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts
new file mode 100644
index 0000000..f4ba39b
--- /dev/null
+++ b/src/lib/db/index.ts
@@ -0,0 +1,15 @@
+import { PrismaClient } from '@prisma/client'
+
+const prismaClientSingleton = () => {
+ return new PrismaClient()
+}
+
+declare global {
+ var prismaGlobal: undefined | ReturnType
+}
+
+const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()
+
+export default prisma
+
+if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
new file mode 100644
index 0000000..d084cca
--- /dev/null
+++ b/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { type ClassValue, clsx } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/src/middleware.ts b/src/middleware.ts
new file mode 100644
index 0000000..3e006d2
--- /dev/null
+++ b/src/middleware.ts
@@ -0,0 +1,7 @@
+import { authMiddleware } from "@clerk/nextjs";
+
+export default authMiddleware({});
+
+export const config = {
+ matcher: ["/((?!.+.[w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
+};
\ No newline at end of file
diff --git a/tailwind.config.ts b/tailwind.config.ts
index e9a0944..84287e8 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -1,20 +1,80 @@
-import type { Config } from "tailwindcss";
+import type { Config } from "tailwindcss"
-const config: Config = {
+const config = {
+ darkMode: ["class"],
content: [
- "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
- "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
- "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
- ],
+ './pages/**/*.{ts,tsx}',
+ './components/**/*.{ts,tsx}',
+ './app/**/*.{ts,tsx}',
+ './src/**/*.{ts,tsx}',
+ ],
+ prefix: "",
theme: {
+ container: {
+ center: true,
+ padding: "2rem",
+ screens: {
+ "2xl": "1400px",
+ },
+ },
extend: {
- backgroundImage: {
- "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
- "gradient-conic":
- "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
+ colors: {
+ border: "hsl(var(--border))",
+ input: "hsl(var(--input))",
+ ring: "hsl(var(--ring))",
+ background: "hsl(var(--background))",
+ foreground: "hsl(var(--foreground))",
+ primary: {
+ DEFAULT: "hsl(var(--primary))",
+ foreground: "hsl(var(--primary-foreground))",
+ },
+ secondary: {
+ DEFAULT: "hsl(var(--secondary))",
+ foreground: "hsl(var(--secondary-foreground))",
+ },
+ destructive: {
+ DEFAULT: "hsl(var(--destructive))",
+ foreground: "hsl(var(--destructive-foreground))",
+ },
+ muted: {
+ DEFAULT: "hsl(var(--muted))",
+ foreground: "hsl(var(--muted-foreground))",
+ },
+ accent: {
+ DEFAULT: "hsl(var(--accent))",
+ foreground: "hsl(var(--accent-foreground))",
+ },
+ popover: {
+ DEFAULT: "hsl(var(--popover))",
+ foreground: "hsl(var(--popover-foreground))",
+ },
+ card: {
+ DEFAULT: "hsl(var(--card))",
+ foreground: "hsl(var(--card-foreground))",
+ },
+ },
+ borderRadius: {
+ lg: "var(--radius)",
+ md: "calc(var(--radius) - 2px)",
+ sm: "calc(var(--radius) - 4px)",
+ },
+ keyframes: {
+ "accordion-down": {
+ from: { height: "0" },
+ to: { height: "var(--radix-accordion-content-height)" },
+ },
+ "accordion-up": {
+ from: { height: "var(--radix-accordion-content-height)" },
+ to: { height: "0" },
+ },
+ },
+ animation: {
+ "accordion-down": "accordion-down 0.2s ease-out",
+ "accordion-up": "accordion-up 0.2s ease-out",
},
},
},
- plugins: [],
-};
-export default config;
+ plugins: [require("tailwindcss-animate")],
+} satisfies Config
+
+export default config
\ No newline at end of file