diff --git a/README.md b/README.md index 65d5913..6070e46 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## To do -- [ ] Add user creation in database +- [ ] Get premium Vercel account for database - [ ] Add middleware for authentication - [ ] Add user profile and settings (i.e. language) - [ ] Add user roles diff --git a/app/api/protected/module/[id]/route.ts b/app/api/protected/customer-form/[id]/route.ts similarity index 100% rename from app/api/protected/module/[id]/route.ts rename to app/api/protected/customer-form/[id]/route.ts diff --git a/app/api/protected/customer-form/route.ts b/app/api/protected/customer-form/route.ts new file mode 100644 index 0000000..4ac5a85 --- /dev/null +++ b/app/api/protected/customer-form/route.ts @@ -0,0 +1,53 @@ +import { getSession, withApiAuthRequired } from '@auth0/nextjs-auth0'; +import prisma from '@prisma/prisma'; +import { randomUUID } from 'crypto'; +import { NextResponse } from 'next/server'; + +export const GET = withApiAuthRequired(async () => { + const session = await getSession(); + + console.log('GET', session?.user); + + // const userModules = await prisma.user.findUniqueOrThrow({ + // where: { + // email: session?.user.email + // }, + // include: { + // CustomerForm: true + // } + // }); + + // const customerForms: CustomerForm[] = userModules.CustomerForm; + + return NextResponse.json([]); +}); + +export const POST = withApiAuthRequired(async request => { + try { + const session = await getSession(); + + const body = await request.json(); + + console.log('POST', session?.user); + + const newCustomerForm = await prisma.customerForm.create({ + data: { + type: body.type, + text: body.text, + createdBy: { + connect: { + id: randomUUID(), + email: session?.user.email + } + } + } + }); + + return NextResponse.json({ success: true, data: newCustomerForm }); + } catch (error) { + return NextResponse.json( + { success: false, message: 'Something went wrong.' }, + { status: 500 } + ); + } +}); diff --git a/app/api/protected/module/route.ts b/app/api/protected/module/route.ts deleted file mode 100644 index 107f3c2..0000000 --- a/app/api/protected/module/route.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; - -export async function GET(request: NextRequest, response: NextResponse) { - return NextResponse.json('GET request'); -} - -export async function POST(request: NextRequest, response: NextResponse) { - return NextResponse.json('POST request'); -} diff --git a/app/costumer-form/[id]/page.tsx b/app/costumer-form/[id]/page.tsx new file mode 100644 index 0000000..cf74286 --- /dev/null +++ b/app/costumer-form/[id]/page.tsx @@ -0,0 +1,9 @@ +'use client'; + +export default function SingleCustomerForm({ + params +}: { + params: { id: string }; +}) { + return
Module {params.id}
; +} diff --git a/app/costumer-form/page.tsx b/app/costumer-form/page.tsx new file mode 100644 index 0000000..5679702 --- /dev/null +++ b/app/costumer-form/page.tsx @@ -0,0 +1,106 @@ +'use client'; + +import { Button } from '@components/Button'; +import { FormControl } from '@components/FormControl'; +import { FormMessage } from '@components/FormMessage'; +import { Input } from '@components/Input'; +import { FormField } from '@contexts/FormField/FormFieldProvider'; +import { FormItem } from '@contexts/FormItem/FormItemProvider'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { CustomerFormType } from '@prisma/client'; +import { + CustomerForm, + CustomerFormListSchema, + CustomerFormSchema +} from '@utils/types'; +import axios from 'axios'; +import { useEffect, useState } from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; + +export default function Modules() { + const [modules, setModules] = useState([]); + + const form = useForm({ + resolver: zodResolver(CustomerFormSchema), + defaultValues: { + type: CustomerFormType.TYPE1, + text: '' + } + }); + + useEffect(() => { + (async () => { + const response = await axios.get('/api/protected/customer-form'); + + const validatedResponse = CustomerFormListSchema.safeParse(response.data); + + if (!validatedResponse.success) { + console.error(validatedResponse.error); + return; + } + + setModules(validatedResponse.data); + })(); + }, []); + + async function handleSubmit(values: CustomerForm) { + console.log('values', values); + + try { + const response = await axios.post( + '/api/protected/customer-form', + { + type: CustomerFormType.TYPE1, + text: values.text + }, + { + headers: { + 'Content-Type': 'application/json' + } + } + ); + + const validatedResponse = CustomerFormSchema.safeParse(response.data); + + if (!validatedResponse.success) { + console.error(validatedResponse.error); + return; + } + + setModules([...modules, validatedResponse.data]); + } catch (error) { + console.error(error); + } + } + + return ( + <> +

Modules

+ {modules && + modules.map(module => ( +
+

{module.type}

+

{module.text}

+
+ ))} +

Create Module

+ +
+ ( + + + + + + + )} + /> + + +
+ + ); +} diff --git a/app/module/[id]/page.tsx b/app/module/[id]/page.tsx deleted file mode 100644 index ef34d7c..0000000 --- a/app/module/[id]/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function SpecificModule({ params }: { params: { id: string } }) { - return
Module {params.id}
; -} diff --git a/app/module/page.tsx b/app/module/page.tsx deleted file mode 100644 index 2f59111..0000000 --- a/app/module/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Module() { - return
Modules home
; -} diff --git a/app/profile/page.tsx b/app/profile/page.tsx index 61d2ce0..6941aad 100644 --- a/app/profile/page.tsx +++ b/app/profile/page.tsx @@ -1,6 +1,7 @@ 'use client'; import { withPageAuthRequired } from '@auth0/nextjs-auth0/client'; +import axios from 'axios'; import { useEffect, useState } from 'react'; export default withPageAuthRequired(function Profile() { @@ -8,18 +9,16 @@ export default withPageAuthRequired(function Profile() { useEffect(() => { (async () => { - const res = await fetch( - `${window.location.origin}/api/protected/profile` - ); - setUser(await res.json()); + const response = await axios.get('/api/protected/profile'); + + setUser(response.data); })(); }, []); return ( -
+ <>

Profile (fetched from API)

-

User

-
{JSON.stringify(user, null, 2)}
-
+ {JSON.stringify(user, null, 2)} + ); }); diff --git a/components/Button.tsx b/components/Button.tsx new file mode 100644 index 0000000..0e94c3b --- /dev/null +++ b/components/Button.tsx @@ -0,0 +1,19 @@ +import { Slot } from '@radix-ui/react-slot'; +import { cn } from '@utils/cn'; +import * as React from 'react'; + +export type ButtonProps = { + asChild?: boolean; +} & React.ButtonHTMLAttributes; + +const Button = React.forwardRef( + ({ asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button'; + return ( + + ); + } +); +Button.displayName = 'Button'; + +export { Button }; diff --git a/components/FormControl.tsx b/components/FormControl.tsx new file mode 100644 index 0000000..91a5485 --- /dev/null +++ b/components/FormControl.tsx @@ -0,0 +1,26 @@ +import { useFormField } from '@hooks/useFormField'; +import { Slot } from '@radix-ui/react-slot'; +import * as React from 'react'; + +export const FormControl = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ ...props }, ref) => { + const { error, formItemId, formDescriptionId, formMessageId } = + useFormField(); + + return ( + + ); +}); +FormControl.displayName = 'FormControl'; diff --git a/components/FormMessage.tsx b/components/FormMessage.tsx new file mode 100644 index 0000000..635da3d --- /dev/null +++ b/components/FormMessage.tsx @@ -0,0 +1,28 @@ +import { useFormField } from '@hooks/useFormField'; +import { cn } from '@utils/cn'; +import * as React from 'react'; + +export const FormMessage = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, children, ...props }, ref) => { + const { error, formMessageId } = useFormField(); + const body = error ? String(error?.message) : children; + + return ( + <> + {!body ? null : ( +

+ {body} +

+ )} + + ); +}); + +FormMessage.displayName = 'FormMessage'; diff --git a/components/Input.tsx b/components/Input.tsx new file mode 100644 index 0000000..ac87667 --- /dev/null +++ b/components/Input.tsx @@ -0,0 +1,22 @@ +import { cn } from '@utils/cn'; +import * as React from 'react'; + +const Input = React.forwardRef< + HTMLInputElement, + React.InputHTMLAttributes +>(({ className, type, ...props }, ref) => { + return ( + + ); +}); +Input.displayName = 'Input'; + +export { Input }; diff --git a/contexts/FormField/FormFieldContext.ts b/contexts/FormField/FormFieldContext.ts new file mode 100644 index 0000000..35793ad --- /dev/null +++ b/contexts/FormField/FormFieldContext.ts @@ -0,0 +1,13 @@ +import React from 'react'; +import { FieldPath, FieldValues } from 'react-hook-form'; + +interface FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +> { + name: TName; +} + +export const FormFieldContext = React.createContext( + {} as FormFieldContextValue +); diff --git a/contexts/FormField/FormFieldProvider.tsx b/contexts/FormField/FormFieldProvider.tsx new file mode 100644 index 0000000..53c3544 --- /dev/null +++ b/contexts/FormField/FormFieldProvider.tsx @@ -0,0 +1,20 @@ +import { + Controller, + ControllerProps, + FieldPath, + FieldValues +} from 'react-hook-form'; +import { FormFieldContext } from './FormFieldContext'; + +export const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +>({ + ...props +}: ControllerProps) => { + return ( + + + + ); +}; diff --git a/contexts/FormItem/FormItemContext.ts b/contexts/FormItem/FormItemContext.ts new file mode 100644 index 0000000..2fa8cdf --- /dev/null +++ b/contexts/FormItem/FormItemContext.ts @@ -0,0 +1,9 @@ +import React from 'react'; + +interface FormItemContextValue { + id: string; +} + +export const FormItemContext = React.createContext( + {} as FormItemContextValue +); diff --git a/contexts/FormItem/FormItemProvider.tsx b/contexts/FormItem/FormItemProvider.tsx new file mode 100644 index 0000000..324f877 --- /dev/null +++ b/contexts/FormItem/FormItemProvider.tsx @@ -0,0 +1,18 @@ +import { cn } from '@utils/cn'; +import * as React from 'react'; +import { FormItemContext } from './FormItemContext'; + +export const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId(); + + return ( + +
+ + ); +}); + +FormItem.displayName = 'FormItem'; diff --git a/hooks/useFormField.tsx b/hooks/useFormField.tsx new file mode 100644 index 0000000..90ee767 --- /dev/null +++ b/hooks/useFormField.tsx @@ -0,0 +1,29 @@ +import { FormFieldContext } from '@contexts/FormField/FormFieldContext'; +import { FormItemContext } from '@contexts/FormItem/FormItemContext'; +import * as React from 'react'; +import { useFormContext } from 'react-hook-form'; + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext); + const itemContext = React.useContext(FormItemContext); + const { getFieldState, formState } = useFormContext(); + + const fieldState = getFieldState(fieldContext.name, formState); + + if (!fieldContext) { + throw new Error('useFormField should be used within '); + } + + const { id } = itemContext; + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState + }; +}; + +export { useFormField }; diff --git a/middleware.ts b/middleware.ts deleted file mode 100644 index c574192..0000000 --- a/middleware.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { withMiddlewareAuthRequired } from '@auth0/nextjs-auth0/edge'; - -export default withMiddlewareAuthRequired(); - -export const config = { - matcher: ['/api/protected/:path*', '/module/:path*', '/user'] -}; diff --git a/package.json b/package.json index 0608e4f..6e96566 100644 --- a/package.json +++ b/package.json @@ -19,15 +19,20 @@ }, "dependencies": { "@auth0/nextjs-auth0": "^3.5.0", + "@hookform/resolvers": "^3.6.0", "@prisma/client": "^5.6.0", + "@radix-ui/react-slot": "^1.1.0", "@vercel/analytics": "^1.1.1", + "axios": "^1.7.2", + "clsx": "^2.1.1", "next": "^14.1.0", "postcss-nesting": "^12.0.2", "react": "^18", "react-dom": "^18", - "react-hook-form": "^7.48.2", + "react-hook-form": "^7.52.0", "resend": "^3.1.0", - "zod": "^3.22.4" + "tailwind-merge": "^2.3.0", + "zod": "^3.23.8" }, "devDependencies": { "@commitlint/cli": "^18.4.3", diff --git a/prisma/migrations/20240626211956_change_table_name/migration.sql b/prisma/migrations/20240626211956_change_table_name/migration.sql new file mode 100644 index 0000000..e6fdb2f --- /dev/null +++ b/prisma/migrations/20240626211956_change_table_name/migration.sql @@ -0,0 +1,36 @@ +/* + Warnings: + + - You are about to drop the `Module` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- CreateEnum +CREATE TYPE "CustomerFormType" AS ENUM ('TYPE1', 'TYPE2', 'TYPE3'); + +-- DropForeignKey +ALTER TABLE "Module" DROP CONSTRAINT "Module_createdById_fkey"; + +-- DropTable +DROP TABLE "Module"; + +-- DropEnum +DROP TYPE "ModuleType"; + +-- CreateTable +CREATE TABLE "CustomerForm" ( + "id" DOUBLE PRECISION NOT NULL, + "type" "CustomerFormType" NOT NULL, + "text" TEXT NOT NULL, + "deleted" BOOLEAN NOT NULL DEFAULT false, + "createdById" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "CustomerForm_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "CustomerForm_id_key" ON "CustomerForm"("id"); + +-- AddForeignKey +ALTER TABLE "CustomerForm" ADD CONSTRAINT "CustomerForm_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20240707152629_string_id_for_user/migration.sql b/prisma/migrations/20240707152629_string_id_for_user/migration.sql new file mode 100644 index 0000000..7003068 --- /dev/null +++ b/prisma/migrations/20240707152629_string_id_for_user/migration.sql @@ -0,0 +1,13 @@ +/* + Warnings: + + - The primary key for the `CustomerForm` table will be changed. If it partially fails, the table could be left without primary key constraint. + +*/ +-- DropIndex +DROP INDEX "CustomerForm_id_key"; + +-- AlterTable +ALTER TABLE "CustomerForm" DROP CONSTRAINT "CustomerForm_pkey", +ALTER COLUMN "id" SET DATA TYPE TEXT, +ADD CONSTRAINT "CustomerForm_pkey" PRIMARY KEY ("id"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 3b695cb..b9f27ad 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -8,29 +8,29 @@ datasource db { directUrl = env("DATABASE_URL_NON_POOLING") // uses a direct connection } -enum ModuleType { +enum CustomerFormType { TYPE1 TYPE2 TYPE3 } model User { - id String @id @default(cuid()) - name String? - email String @unique - deleted Boolean @default(false) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - Module Module[] + id String @id @default(cuid()) + name String? + email String @unique + deleted Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + CustomerForm CustomerForm[] } -model Module { - id Float @id @unique - type ModuleType +model CustomerForm { + id String @id @default(cuid()) + type CustomerFormType text String - deleted Boolean @default(false) + deleted Boolean @default(false) createdById String - createdBy User @relation(fields: [createdById], references: [id]) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdBy User @relation(fields: [createdById], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } diff --git a/tsconfig.json b/tsconfig.json index bd52bbb..8919204 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,11 @@ ], "paths": { "@app/*": ["./app/*"], - "@prisma/*": ["./prisma/*"] + "@prisma/*": ["./prisma/*"], + "@utils/*": ["./utils/*"], + "@contexts/*": ["./contexts/*"], + "@components/*": ["./components/*"], + "@hooks/*": ["./hooks/*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], diff --git a/utils/cn.ts b/utils/cn.ts new file mode 100644 index 0000000..9ad0df4 --- /dev/null +++ b/utils/cn.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/utils/types.ts b/utils/types.ts new file mode 100644 index 0000000..7d65365 --- /dev/null +++ b/utils/types.ts @@ -0,0 +1,24 @@ +import { z } from 'zod'; + +export const UserSchema = z.object({ + id: z.string(), + name: z.string(), + email: z.string().email(), + deleted: z.boolean(), + createdAt: z.date().transform(date => date.toISOString()), + updatedAt: z.date().transform(date => date.toISOString()) +}); + +export type User = z.infer; + +export const CustomerFormSchema = z.object({ + id: z.string(), + type: z.string(), + text: z.string(), + createdAt: z.date().transform(date => date.toISOString()), + updatedAt: z.date().transform(date => date.toISOString()) +}); + +export const CustomerFormListSchema = z.array(CustomerFormSchema); + +export type CustomerForm = z.infer; diff --git a/yarn.lock b/yarn.lock index ce88bda..9022eda 100644 --- a/yarn.lock +++ b/yarn.lock @@ -60,6 +60,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.24.1": + version: 7.24.7 + resolution: "@babel/runtime@npm:7.24.7" + dependencies: + regenerator-runtime: "npm:^0.14.0" + checksum: 7b77f566165dee62db3db0296e71d08cafda3f34e1b0dcefcd68427272e17c1704f4e4369bff76651b07b6e49d3ea5a0ce344818af9116e9292e4381e0918c76 + languageName: node + linkType: hard + "@commitlint/cli@npm:^18.4.3": version: 18.6.1 resolution: "@commitlint/cli@npm:18.6.1" @@ -330,6 +339,15 @@ __metadata: languageName: node linkType: hard +"@hookform/resolvers@npm:^3.6.0": + version: 3.6.0 + resolution: "@hookform/resolvers@npm:3.6.0" + peerDependencies: + react-hook-form: ^7.0.0 + checksum: 6dd1b7ad21ed2b171470740884e0b83982c79a0d4ceddabe60b616e53eeed2b5569cdba5e91ad844e379aeda5ffa835b0c2d2525d702fcd8b263c2194895f9b7 + languageName: node + linkType: hard + "@humanwhocodes/config-array@npm:^0.11.14": version: 0.11.14 resolution: "@humanwhocodes/config-array@npm:0.11.14" @@ -618,6 +636,34 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-compose-refs@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-compose-refs@npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 047a4ed5f87cb848be475507cd62836cf5af5761484681f521ea543ea7c9d59d61d42806d6208863d5e2380bf38cdf4cff73c2bbe5f52dbbe50fb04e1a13ac72 + languageName: node + linkType: hard + +"@radix-ui/react-slot@npm:^1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-slot@npm:1.1.0" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 95e190868418b1c83adf6627256f6b664b0dcbea95d7215de9c64ac2c31102fc09155565d9ca27be6abd20fc63d0b0bacfe1b67d78b2de1d198244c848e1a54e + languageName: node + linkType: hard + "@react-email/render@npm:0.0.15": version: 0.0.15 resolution: "@react-email/render@npm:0.0.15" @@ -1216,6 +1262,13 @@ __metadata: languageName: node linkType: hard +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 3ce727cbc78f69d6a4722517a58ee926c8c21083633b1d3fdf66fd688f6c127a53a592141bd4866f9b63240a86e9d8e974b13919450bd17fa33c2d22c4558ad8 + languageName: node + linkType: hard + "audit-ci@npm:^6.6.1": version: 6.6.1 resolution: "audit-ci@npm:6.6.1" @@ -1268,6 +1321,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.7.2": + version: 1.7.2 + resolution: "axios@npm:1.7.2" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 6ae80dda9736bb4762ce717f1a26ff997d94672d3a5799ad9941c24d4fb019c1dff45be8272f08d1975d7950bac281f3ba24aff5ecd49ef5a04d872ec428782f + languageName: node + linkType: hard + "axobject-query@npm:~3.1.1": version: 3.1.1 resolution: "axobject-query@npm:3.1.1" @@ -1512,6 +1576,13 @@ __metadata: languageName: node linkType: hard +"clsx@npm:^2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: cdfb57fa6c7649bbff98d9028c2f0de2f91c86f551179541cf784b1cfdc1562dcb951955f46d54d930a3879931a980e32a46b598acaea274728dbe068deca919 + languageName: node + linkType: hard + "color-convert@npm:^1.9.0": version: 1.9.3 resolution: "color-convert@npm:1.9.3" @@ -1551,6 +1622,15 @@ __metadata: languageName: node linkType: hard +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 2e969e637d05d09fa50b02d74c83a1186f6914aae89e6653b62595cc75a221464f884f55f231b8f4df7a49537fba60bdc0427acd2bf324c09a1dbb84837e36e4 + languageName: node + linkType: hard + "commander@npm:^10.0.0": version: 10.0.1 resolution: "commander@npm:10.0.1" @@ -1842,6 +1922,13 @@ __metadata: languageName: node linkType: hard +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 46fe6e83e2cb1d85ba50bd52803c68be9bd953282fa7096f51fc29edd5d67ff84ff753c51966061e5ba7cb5e47ef6d36a91924eddb7f3f3483b1c560f77a0020 + languageName: node + linkType: hard + "didyoumean@npm:^1.2.2": version: 1.2.2 resolution: "didyoumean@npm:1.2.2" @@ -2647,6 +2734,16 @@ __metadata: languageName: node linkType: hard +"follow-redirects@npm:^1.15.6": + version: 1.15.6 + resolution: "follow-redirects@npm:1.15.6" + peerDependenciesMeta: + debug: + optional: true + checksum: 70c7612c4cab18e546e36b991bbf8009a1a41cf85354afe04b113d1117569abf760269409cb3eb842d9f7b03d62826687086b081c566ea7b1e6613cf29030bf7 + languageName: node + linkType: hard + "for-each@npm:^0.3.3": version: 0.3.3 resolution: "for-each@npm:0.3.3" @@ -2666,6 +2763,17 @@ __metadata: languageName: node linkType: hard +"form-data@npm:^4.0.0": + version: 4.0.0 + resolution: "form-data@npm:4.0.0" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + mime-types: "npm:^2.1.12" + checksum: 7264aa760a8cf09482816d8300f1b6e2423de1b02bba612a136857413fdc96d7178298ced106817655facc6b89036c6e12ae31c9eb5bdc16aabf502ae8a5d805 + languageName: node + linkType: hard + "fraction.js@npm:^4.3.7": version: 4.3.7 resolution: "fraction.js@npm:4.3.7" @@ -4039,6 +4147,22 @@ __metadata: languageName: node linkType: hard +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 54bb60bf39e6f8689f6622784e668a3d7f8bed6b0d886f5c3c446cb3284be28b30bf707ed05d0fe44a036f8469976b2629bbea182684977b084de9da274694d7 + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 89aa9651b67644035de2784a6e665fc685d79aba61857e02b9c8758da874a754aed4a9aced9265f5ed1171fd934331e5516b84a7f0218031b6fa0270eca1e51a + languageName: node + linkType: hard + "mimic-fn@npm:^2.1.0": version: 2.1.0 resolution: "mimic-fn@npm:2.1.0" @@ -5011,6 +5135,13 @@ __metadata: languageName: node linkType: hard +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: f0bb4a87cfd18f77bc2fba23ae49c3b378fb35143af16cc478171c623eebe181678f09439707ad80081d340d1593cd54a33a0113f3ccb3f4bc9451488780ee23 + languageName: node + linkType: hard + "punycode@npm:^2.1.0": version: 2.3.1 resolution: "punycode@npm:2.3.1" @@ -5044,7 +5175,7 @@ __metadata: languageName: node linkType: hard -"react-hook-form@npm:^7.48.2": +"react-hook-form@npm:^7.52.0": version: 7.52.0 resolution: "react-hook-form@npm:7.52.0" peerDependencies: @@ -5162,6 +5293,13 @@ __metadata: languageName: node linkType: hard +"regenerator-runtime@npm:^0.14.0": + version: 0.14.1 + resolution: "regenerator-runtime@npm:0.14.1" + checksum: 5db3161abb311eef8c45bcf6565f4f378f785900ed3945acf740a9888c792f75b98ecb77f0775f3bf95502ff423529d23e94f41d80c8256e8fa05ed4b07cf471 + languageName: node + linkType: hard + "regexp.prototype.flags@npm:^1.5.1, regexp.prototype.flags@npm:^1.5.2": version: 1.5.2 resolution: "regexp.prototype.flags@npm:1.5.2" @@ -5888,6 +6026,15 @@ __metadata: languageName: node linkType: hard +"tailwind-merge@npm:^2.3.0": + version: 2.3.0 + resolution: "tailwind-merge@npm:2.3.0" + dependencies: + "@babel/runtime": "npm:^7.24.1" + checksum: cf3b126bee82bc9ff3f60f9601b66925418db6906544198b637487b25fba1c9c307734ca37ccb8fa2355f69ad0c5aff34c5dce460809addb459a93205d9a0abe + languageName: node + linkType: hard + "tailwindcss@npm:^3.4.1": version: 3.4.4 resolution: "tailwindcss@npm:3.4.4" @@ -6241,7 +6388,9 @@ __metadata: "@auth0/nextjs-auth0": "npm:^3.5.0" "@commitlint/cli": "npm:^18.4.3" "@commitlint/config-conventional": "npm:^18.4.3" + "@hookform/resolvers": "npm:^3.6.0" "@prisma/client": "npm:^5.6.0" + "@radix-ui/react-slot": "npm:^1.1.0" "@types/node": "npm:^20" "@types/react": "npm:^18" "@types/react-dom": "npm:^18" @@ -6250,6 +6399,8 @@ __metadata: "@vercel/analytics": "npm:^1.1.1" audit-ci: "npm:^6.6.1" autoprefixer: "npm:^10.0.1" + axios: "npm:^1.7.2" + clsx: "npm:^2.1.1" eslint: "npm:^8" eslint-config-next: "npm:14.0.3" eslint-config-prettier: "npm:^9.0.0" @@ -6263,11 +6414,12 @@ __metadata: prisma: "npm:^5.6.0" react: "npm:^18" react-dom: "npm:^18" - react-hook-form: "npm:^7.48.2" + react-hook-form: "npm:^7.52.0" resend: "npm:^3.1.0" + tailwind-merge: "npm:^2.3.0" tailwindcss: "npm:^3.4.1" typescript: "npm:^5" - zod: "npm:^3.22.4" + zod: "npm:^3.23.8" languageName: unknown linkType: soft @@ -6457,7 +6609,7 @@ __metadata: languageName: node linkType: hard -"zod@npm:^3.22.4": +"zod@npm:^3.23.8": version: 3.23.8 resolution: "zod@npm:3.23.8" checksum: 846fd73e1af0def79c19d510ea9e4a795544a67d5b34b7e1c4d0425bf6bfd1c719446d94cdfa1721c1987d891321d61f779e8236fde517dc0e524aa851a6eff1