diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0db520d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: CI + +on: + push: + branches: + - main + - dev + pull_request: + branches: + - main + - dev + +jobs: + typecheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci --ignore-scripts + - run: npx prisma generate + env: + DIRECT_URL: "postgresql://placeholder" + - run: npx tsc --noEmit + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci --ignore-scripts + - run: npm run lint + + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci --ignore-scripts + - run: npm run format:check diff --git a/README.md b/README.md index 692609b..ee5c16d 100644 --- a/README.md +++ b/README.md @@ -39,13 +39,13 @@ Internal authentication service cp .env.example .env ``` - | Variable | Purpose | - | --- | --- | - | `DATABASE_URL` | Pooled Postgres connection used by the application (via PgBouncer in prod) | - | `DIRECT_URL` | Direct Postgres connection used by Prisma for migrations | - | `NEXT_PUBLIC_SUPABASE_URL` | Supabase project URL (public) | - | `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Supabase anon key — "Publishable" in CLI output (public) | - | `SUPABASE_SERVICE_ROLE_KEY` | Supabase service role key — "Secret" in CLI output (**never expose to the client**) | + | Variable | Purpose | + | ------------------------------- | ----------------------------------------------------------------------------------- | + | `DATABASE_URL` | Pooled Postgres connection used by the application (via PgBouncer in prod) | + | `DIRECT_URL` | Direct Postgres connection used by Prisma for migrations | + | `NEXT_PUBLIC_SUPABASE_URL` | Supabase project URL (public) | + | `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Supabase anon key — "Publishable" in CLI output (public) | + | `SUPABASE_SERVICE_ROLE_KEY` | Supabase service role key — "Secret" in CLI output (**never expose to the client**) | > Locally there is no connection pooling, so `DATABASE_URL` and `DIRECT_URL` will be the same. @@ -63,11 +63,11 @@ Internal authentication service ## Scripts -| Command | Description | -| --- | --- | -| `npm run dev` | Start Next.js dev server | -| `npm run build` | Production build | -| `npm run start` | Start production server | -| `npm run lint` | Run ESLint | -| `npm run format` | Format code with Prettier | +| Command | Description | +| ---------------------- | -------------------------------- | +| `npm run dev` | Start Next.js dev server | +| `npm run build` | Production build | +| `npm run start` | Start production server | +| `npm run lint` | Run ESLint | +| `npm run format` | Format code with Prettier | | `npm run format:check` | Check formatting without writing | diff --git a/eslint.config.mjs b/eslint.config.mjs index 863deca..8c31ce7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -14,6 +14,8 @@ const eslintConfig = defineConfig([ "out/**", "build/**", "next-env.d.ts", + // Generated files: + "src/generated/**", ]), ]); diff --git a/package-lock.json b/package-lock.json index bc507a2..8e10dd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,7 @@ "devDependencies": { "@tailwindcss/postcss": "^4", "@types/node": "^20", - "@types/pg": "^8.20.0", + "@types/pg": "^8.11.11", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", @@ -2065,35 +2065,6 @@ "postgres-array": "3.0.4" } }, - "node_modules/@prisma/adapter-pg/node_modules/@types/pg": { - "version": "8.11.11", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.11.tgz", - "integrity": "sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^4.0.1" - } - }, - "node_modules/@prisma/adapter-pg/node_modules/pg-types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.1.0.tgz", - "integrity": "sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==", - "license": "MIT", - "dependencies": { - "pg-int8": "1.0.1", - "pg-numeric": "1.0.2", - "postgres-array": "~3.0.1", - "postgres-bytea": "~3.0.0", - "postgres-date": "~2.1.0", - "postgres-interval": "^3.0.0", - "postgres-range": "^1.1.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@prisma/adapter-pg/node_modules/postgres-array": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", @@ -2103,36 +2074,6 @@ "node": ">=12" } }, - "node_modules/@prisma/adapter-pg/node_modules/postgres-bytea": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", - "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", - "license": "MIT", - "dependencies": { - "obuf": "~1.1.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@prisma/adapter-pg/node_modules/postgres-date": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz", - "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/@prisma/adapter-pg/node_modules/postgres-interval": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", - "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/@prisma/client": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.6.0.tgz", @@ -3140,15 +3081,71 @@ } }, "node_modules/@types/pg": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz", - "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", - "dev": true, + "version": "8.11.11", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.11.tgz", + "integrity": "sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==", "license": "MIT", "dependencies": { "@types/node": "*", "pg-protocol": "*", - "pg-types": "^2.2.0" + "pg-types": "^4.0.1" + } + }, + "node_modules/@types/pg/node_modules/pg-types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.1.0.tgz", + "integrity": "sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~3.0.1", + "postgres-bytea": "~3.0.0", + "postgres-date": "~2.1.0", + "postgres-interval": "^3.0.0", + "postgres-range": "^1.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/pg/node_modules/postgres-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/pg/node_modules/postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "license": "MIT", + "dependencies": { + "obuf": "~1.1.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@types/pg/node_modules/postgres-date": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz", + "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/pg/node_modules/postgres-interval": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", + "license": "MIT", + "engines": { + "node": ">=12" } }, "node_modules/@types/react": { diff --git a/package.json b/package.json index 283b06d..0769854 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "devDependencies": { "@tailwindcss/postcss": "^4", "@types/node": "^20", - "@types/pg": "^8.20.0", + "@types/pg": "^8.11.11", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", diff --git a/prisma.config.ts b/prisma.config.ts index 56a9206..b7e2c04 100644 --- a/prisma.config.ts +++ b/prisma.config.ts @@ -7,7 +7,7 @@ export default defineConfig({ schema: "prisma/schema.prisma", migrations: { path: "prisma/migrations", - seed: 'npx ts-node prisma/seed.ts', + seed: "npx ts-node prisma/seed.ts", }, datasource: { url: env("DIRECT_URL"), diff --git a/prisma/seed.ts b/prisma/seed.ts index 39a4fa0..21a6561 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,39 +1,39 @@ -import 'dotenv/config' -import pg from 'pg' -import { PrismaPg } from '@prisma/adapter-pg' -import { PrismaClient } from '../src/generated/prisma/index.js' +import "dotenv/config"; +import pg from "pg"; +import { PrismaPg } from "@prisma/adapter-pg"; +import { PrismaClient } from "../src/generated/prisma/index.js"; // Connect to whatever database is specified by DATABASE_URL -const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }) +const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }); -const adapter = new PrismaPg(pool as any) +const adapter = new PrismaPg(pool); -const prisma = new PrismaClient({ adapter }) +const prisma = new PrismaClient({ adapter }); async function main() { // Fake Supabase user IDs - const adminSupabaseId = '00000000-0000-0000-0000-000000000001' - const userSupabaseId = '00000000-0000-0000-0000-000000000002' + const adminSupabaseId = "00000000-0000-0000-0000-000000000001"; + const userSupabaseId = "00000000-0000-0000-0000-000000000002"; const admin = await prisma.user.upsert({ where: { supabaseUserId: adminSupabaseId }, update: {}, create: { supabaseUserId: adminSupabaseId, isAdmin: true }, - }) + }); const user = await prisma.user.upsert({ where: { supabaseUserId: userSupabaseId }, update: {}, create: { supabaseUserId: userSupabaseId, isAdmin: false }, - }) + }); const project1 = await prisma.project.create({ - data: { name: 'Attendance Manager', description: 'Cool project' }, - }) + data: { name: "Attendance Manager", description: "Cool project" }, + }); const project2 = await prisma.project.create({ - data: { name: 'Website Creation', description: 'Less project' }, - }) + data: { name: "Website Creation", description: "Less project" }, + }); await prisma.userProject.createMany({ data: [ @@ -42,22 +42,22 @@ async function main() { { userId: user.id, projectId: project1.id }, ], skipDuplicates: true, - }) + }); await prisma.session.create({ data: { userId: admin.id, projectId: project1.id, - token: 'admin-session-token', + token: "admin-session-token", expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24), // 24h }, - }) + }); - console.log('data seeded!') + console.log("data seeded!"); } main() .catch(console.error) .finally(async () => { - await prisma.$disconnect() - }) + await prisma.$disconnect(); + }); diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx index b20959d..a8cbbac 100644 --- a/src/components/ui/badge.tsx +++ b/src/components/ui/badge.tsx @@ -1,8 +1,8 @@ -import { mergeProps } from "@base-ui/react/merge-props" -import { useRender } from "@base-ui/react/use-render" -import { cva, type VariantProps } from "class-variance-authority" +import { mergeProps } from "@base-ui/react/merge-props"; +import { useRender } from "@base-ui/react/use-render"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const badgeVariants = cva( "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", @@ -24,8 +24,8 @@ const badgeVariants = cva( defaultVariants: { variant: "default", }, - } -) + }, +); function Badge({ className, @@ -39,14 +39,14 @@ function Badge({ { className: cn(badgeVariants({ variant }), className), }, - props + props, ), render, state: { slot: "badge", variant, }, - }) + }); } -export { Badge, badgeVariants } +export { Badge, badgeVariants }; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 09df753..cc44df6 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -1,7 +1,7 @@ -import { Button as ButtonPrimitive } from "@base-ui/react/button" -import { cva, type VariantProps } from "class-variance-authority" +import { Button as ButtonPrimitive } from "@base-ui/react/button"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const buttonVariants = cva( "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", @@ -37,8 +37,8 @@ const buttonVariants = cva( variant: "default", size: "default", }, - } -) + }, +); function Button({ className, @@ -52,7 +52,7 @@ function Button({ className={cn(buttonVariants({ variant, size, className }))} {...props} /> - ) + ); } -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index 40cac5f..325aa00 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -1,6 +1,6 @@ -import * as React from "react" +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; function Card({ className, @@ -13,11 +13,11 @@ function Card({ data-size={size} className={cn( "group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", - className + className, )} {...props} /> - ) + ); } function CardHeader({ className, ...props }: React.ComponentProps<"div">) { @@ -26,11 +26,11 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) { data-slot="card-header" className={cn( "group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3", - className + className, )} {...props} /> - ) + ); } function CardTitle({ className, ...props }: React.ComponentProps<"div">) { @@ -39,11 +39,11 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) { data-slot="card-title" className={cn( "font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm", - className + className, )} {...props} /> - ) + ); } function CardDescription({ className, ...props }: React.ComponentProps<"div">) { @@ -53,7 +53,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) { className={cn("text-sm text-muted-foreground", className)} {...props} /> - ) + ); } function CardAction({ className, ...props }: React.ComponentProps<"div">) { @@ -62,11 +62,11 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) { data-slot="card-action" className={cn( "col-start-2 row-span-2 row-start-1 self-start justify-self-end", - className + className, )} {...props} /> - ) + ); } function CardContent({ className, ...props }: React.ComponentProps<"div">) { @@ -76,7 +76,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) { className={cn("px-4 group-data-[size=sm]/card:px-3", className)} {...props} /> - ) + ); } function CardFooter({ className, ...props }: React.ComponentProps<"div">) { @@ -85,11 +85,11 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) { data-slot="card-footer" className={cn( "flex items-center rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/card:p-3", - className + className, )} {...props} /> - ) + ); } export { @@ -100,4 +100,4 @@ export { CardAction, CardDescription, CardContent, -} +}; diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx index 014f5aa..308e17f 100644 --- a/src/components/ui/dialog.tsx +++ b/src/components/ui/dialog.tsx @@ -1,26 +1,26 @@ -"use client" +"use client"; -import * as React from "react" -import { Dialog as DialogPrimitive } from "@base-ui/react/dialog" +import * as React from "react"; +import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"; -import { cn } from "@/lib/utils" -import { Button } from "@/components/ui/button" -import { XIcon } from "lucide-react" +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { XIcon } from "lucide-react"; function Dialog({ ...props }: DialogPrimitive.Root.Props) { - return + return ; } function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) { - return + return ; } function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) { - return + return ; } function DialogClose({ ...props }: DialogPrimitive.Close.Props) { - return + return ; } function DialogOverlay({ @@ -32,11 +32,11 @@ function DialogOverlay({ data-slot="dialog-overlay" className={cn( "fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0", - className + className, )} {...props} /> - ) + ); } function DialogContent({ @@ -45,7 +45,7 @@ function DialogContent({ showCloseButton = true, ...props }: DialogPrimitive.Popup.Props & { - showCloseButton?: boolean + showCloseButton?: boolean; }) { return ( @@ -54,7 +54,7 @@ function DialogContent({ data-slot="dialog-content" className={cn( "fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", - className + className, )} {...props} > @@ -70,14 +70,13 @@ function DialogContent({ /> } > - + Close )} - ) + ); } function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { @@ -87,7 +86,7 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { className={cn("flex flex-col gap-2", className)} {...props} /> - ) + ); } function DialogFooter({ @@ -96,14 +95,14 @@ function DialogFooter({ children, ...props }: React.ComponentProps<"div"> & { - showCloseButton?: boolean + showCloseButton?: boolean; }) { return (
@@ -114,7 +113,7 @@ function DialogFooter({ )}
- ) + ); } function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) { @@ -123,11 +122,11 @@ function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) { data-slot="dialog-title" className={cn( "font-heading text-base leading-none font-medium", - className + className, )} {...props} /> - ) + ); } function DialogDescription({ @@ -139,11 +138,11 @@ function DialogDescription({ data-slot="dialog-description" className={cn( "text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground", - className + className, )} {...props} /> - ) + ); } export { @@ -157,4 +156,4 @@ export { DialogPortal, DialogTitle, DialogTrigger, -} +}; diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx index 9d5ebbd..0d67f0d 100644 --- a/src/components/ui/dropdown-menu.tsx +++ b/src/components/ui/dropdown-menu.tsx @@ -1,21 +1,21 @@ -"use client" +"use client"; -import * as React from "react" -import { Menu as MenuPrimitive } from "@base-ui/react/menu" +import * as React from "react"; +import { Menu as MenuPrimitive } from "@base-ui/react/menu"; -import { cn } from "@/lib/utils" -import { ChevronRightIcon, CheckIcon } from "lucide-react" +import { cn } from "@/lib/utils"; +import { ChevronRightIcon, CheckIcon } from "lucide-react"; function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) { - return + return ; } function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) { - return + return ; } function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) { - return + return ; } function DropdownMenuContent({ @@ -41,16 +41,19 @@ function DropdownMenuContent({ > - ) + ); } function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) { - return + return ; } function DropdownMenuLabel({ @@ -58,7 +61,7 @@ function DropdownMenuLabel({ inset, ...props }: MenuPrimitive.GroupLabel.Props & { - inset?: boolean + inset?: boolean; }) { return ( - ) + ); } function DropdownMenuItem({ @@ -79,8 +82,8 @@ function DropdownMenuItem({ variant = "default", ...props }: MenuPrimitive.Item.Props & { - inset?: boolean - variant?: "default" | "destructive" + inset?: boolean; + variant?: "default" | "destructive"; }) { return ( - ) + ); } function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) { - return + return ; } function DropdownMenuSubTrigger({ @@ -106,7 +109,7 @@ function DropdownMenuSubTrigger({ children, ...props }: MenuPrimitive.SubmenuTrigger.Props & { - inset?: boolean + inset?: boolean; }) { return ( {children} - ) + ); } function DropdownMenuSubContent({ @@ -135,14 +138,17 @@ function DropdownMenuSubContent({ return ( - ) + ); } function DropdownMenuCheckboxItem({ @@ -152,7 +158,7 @@ function DropdownMenuCheckboxItem({ inset, ...props }: MenuPrimitive.CheckboxItem.Props & { - inset?: boolean + inset?: boolean; }) { return ( - + {children} - ) + ); } function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) { @@ -185,7 +190,7 @@ function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) { data-slot="dropdown-menu-radio-group" {...props} /> - ) + ); } function DropdownMenuRadioItem({ @@ -194,7 +199,7 @@ function DropdownMenuRadioItem({ inset, ...props }: MenuPrimitive.RadioItem.Props & { - inset?: boolean + inset?: boolean; }) { return ( @@ -211,13 +216,12 @@ function DropdownMenuRadioItem({ data-slot="dropdown-menu-radio-item-indicator" > - + {children} - ) + ); } function DropdownMenuSeparator({ @@ -230,7 +234,7 @@ function DropdownMenuSeparator({ className={cn("-mx-1 my-1 h-px bg-border", className)} {...props} /> - ) + ); } function DropdownMenuShortcut({ @@ -242,11 +246,11 @@ function DropdownMenuShortcut({ data-slot="dropdown-menu-shortcut" className={cn( "ml-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground", - className + className, )} {...props} /> - ) + ); } export { @@ -265,4 +269,4 @@ export { DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent, -} +}; diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx index ea5329a..f3b8ee4 100644 --- a/src/components/ui/form.tsx +++ b/src/components/ui/form.tsx @@ -1,8 +1,8 @@ -"use client" +"use client"; -import * as React from "react" -import * as LabelPrimitive from "@radix-ui/react-label" -import { Slot } from "@radix-ui/react-slot" +import * as React from "react"; +import * as LabelPrimitive from "@radix-ui/react-label"; +import { Slot } from "@radix-ui/react-slot"; import { Controller, FormProvider, @@ -10,23 +10,23 @@ import { type ControllerProps, type FieldPath, type FieldValues, -} from "react-hook-form" +} from "react-hook-form"; -import { cn } from "@/lib/utils" -import { Label } from "@/components/ui/label" +import { cn } from "@/lib/utils"; +import { Label } from "@/components/ui/label"; -const Form = FormProvider +const Form = FormProvider; type FormFieldContextValue< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath, > = { - name: TName -} + name: TName; +}; const FormFieldContext = React.createContext( - {} as FormFieldContextValue -) + {} as FormFieldContextValue, +); const FormField = < TFieldValues extends FieldValues = FieldValues, @@ -38,21 +38,21 @@ const FormField = < - ) -} + ); +}; const useFormField = () => { - const fieldContext = React.useContext(FormFieldContext) - const itemContext = React.useContext(FormItemContext) - const { getFieldState, formState } = useFormContext() + const fieldContext = React.useContext(FormFieldContext); + const itemContext = React.useContext(FormItemContext); + const { getFieldState, formState } = useFormContext(); - const fieldState = getFieldState(fieldContext.name, formState) + const fieldState = getFieldState(fieldContext.name, formState); if (!fieldContext) { - throw new Error("useFormField should be used within ") + throw new Error("useFormField should be used within "); } - const { id } = itemContext + const { id } = itemContext; return { id, @@ -61,32 +61,32 @@ const useFormField = () => { formDescriptionId: `${id}-form-item-description`, formMessageId: `${id}-form-item-message`, ...fieldState, - } -} + }; +}; type FormItemContextValue = { - id: string -} + id: string; +}; const FormItemContext = React.createContext( - {} as FormItemContextValue -) + {} as FormItemContextValue, +); function FormItem({ className, ...props }: React.ComponentProps<"div">) { - const id = React.useId() + const id = React.useId(); return (
- ) + ); } function FormLabel({ className, ...props }: React.ComponentProps) { - const { error, formItemId } = useFormField() + const { error, formItemId } = useFormField(); return (