feat: attempt to fix the crud with auth

This commit is contained in:
Riccardo
2024-07-07 17:47:57 +02:00
parent ea73368cc9
commit 637520dbf2
27 changed files with 622 additions and 53 deletions

View File

@@ -2,7 +2,7 @@
## To do ## To do
- [ ] Add user creation in database - [ ] Get premium Vercel account for database
- [ ] Add middleware for authentication - [ ] Add middleware for authentication
- [ ] Add user profile and settings (i.e. language) - [ ] Add user profile and settings (i.e. language)
- [ ] Add user roles - [ ] Add user roles

View File

@@ -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 }
);
}
});

View File

@@ -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');
}

View File

@@ -0,0 +1,9 @@
'use client';
export default function SingleCustomerForm({
params
}: {
params: { id: string };
}) {
return <div>Module {params.id}</div>;
}

106
app/costumer-form/page.tsx Normal file
View File

@@ -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<CustomerForm[]>([]);
const form = useForm<CustomerForm>({
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 (
<>
<h1>Modules</h1>
{modules &&
modules.map(module => (
<div key={module.id}>
<h2>{module.type}</h2>
<p>{module.text}</p>
</div>
))}
<h1>Create Module</h1>
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)}>
<FormField
control={form.control}
name='text'
render={({ field }) => (
<FormItem>
<FormMessage />
<FormControl>
<Input placeholder='name' {...field} />
</FormControl>
</FormItem>
)}
/>
<Button type='submit'>Submit</Button>
</form>
</FormProvider>
</>
);
}

View File

@@ -1,3 +0,0 @@
export default function SpecificModule({ params }: { params: { id: string } }) {
return <div>Module {params.id}</div>;
}

View File

@@ -1,3 +0,0 @@
export default function Module() {
return <div>Modules home</div>;
}

View File

@@ -1,6 +1,7 @@
'use client'; 'use client';
import { withPageAuthRequired } from '@auth0/nextjs-auth0/client'; import { withPageAuthRequired } from '@auth0/nextjs-auth0/client';
import axios from 'axios';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
export default withPageAuthRequired(function Profile() { export default withPageAuthRequired(function Profile() {
@@ -8,18 +9,16 @@ export default withPageAuthRequired(function Profile() {
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const res = await fetch( const response = await axios.get('/api/protected/profile');
`${window.location.origin}/api/protected/profile`
); setUser(response.data);
setUser(await res.json());
})(); })();
}, []); }, []);
return ( return (
<main> <>
<h1>Profile (fetched from API)</h1> <h1>Profile (fetched from API)</h1>
<h3>User</h3> {JSON.stringify(user, null, 2)}
<pre data-testid='module'>{JSON.stringify(user, null, 2)}</pre> </>
</main>
); );
}); });

19
components/Button.tsx Normal file
View File

@@ -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<HTMLButtonElement>;
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp className={cn('btn-grad', 'btn-grad-hover')} ref={ref} {...props} />
);
}
);
Button.displayName = 'Button';
export { Button };

View File

@@ -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<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } =
useFormField();
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
);
});
FormControl.displayName = 'FormControl';

View File

@@ -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<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children;
return (
<>
{!body ? null : (
<p
ref={ref}
id={formMessageId}
className={cn('text-destructive text-sm font-medium', className)}
{...props}
>
{body}
</p>
)}
</>
);
});
FormMessage.displayName = 'FormMessage';

22
components/Input.tsx Normal file
View File

@@ -0,0 +1,22 @@
import { cn } from '@utils/cn';
import * as React from 'react';
const Input = React.forwardRef<
HTMLInputElement,
React.InputHTMLAttributes<HTMLInputElement>
>(({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className
)}
ref={ref}
{...props}
/>
);
});
Input.displayName = 'Input';
export { Input };

View File

@@ -0,0 +1,13 @@
import React from 'react';
import { FieldPath, FieldValues } from 'react-hook-form';
interface FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> {
name: TName;
}
export const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
);

View File

@@ -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<TFieldValues> = FieldPath<TFieldValues>
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
);
};

View File

@@ -0,0 +1,9 @@
import React from 'react';
interface FormItemContextValue {
id: string;
}
export const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
);

View File

@@ -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<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn('space-y-2', className)} {...props} />
</FormItemContext.Provider>
);
});
FormItem.displayName = 'FormItem';

29
hooks/useFormField.tsx Normal file
View File

@@ -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 <FormField>');
}
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 };

View File

@@ -1,7 +0,0 @@
import { withMiddlewareAuthRequired } from '@auth0/nextjs-auth0/edge';
export default withMiddlewareAuthRequired();
export const config = {
matcher: ['/api/protected/:path*', '/module/:path*', '/user']
};

View File

@@ -19,15 +19,20 @@
}, },
"dependencies": { "dependencies": {
"@auth0/nextjs-auth0": "^3.5.0", "@auth0/nextjs-auth0": "^3.5.0",
"@hookform/resolvers": "^3.6.0",
"@prisma/client": "^5.6.0", "@prisma/client": "^5.6.0",
"@radix-ui/react-slot": "^1.1.0",
"@vercel/analytics": "^1.1.1", "@vercel/analytics": "^1.1.1",
"axios": "^1.7.2",
"clsx": "^2.1.1",
"next": "^14.1.0", "next": "^14.1.0",
"postcss-nesting": "^12.0.2", "postcss-nesting": "^12.0.2",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"react-hook-form": "^7.48.2", "react-hook-form": "^7.52.0",
"resend": "^3.1.0", "resend": "^3.1.0",
"zod": "^3.22.4" "tailwind-merge": "^2.3.0",
"zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^18.4.3", "@commitlint/cli": "^18.4.3",

View File

@@ -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;

View File

@@ -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");

View File

@@ -8,7 +8,7 @@ datasource db {
directUrl = env("DATABASE_URL_NON_POOLING") // uses a direct connection directUrl = env("DATABASE_URL_NON_POOLING") // uses a direct connection
} }
enum ModuleType { enum CustomerFormType {
TYPE1 TYPE1
TYPE2 TYPE2
TYPE3 TYPE3
@@ -21,12 +21,12 @@ model User {
deleted Boolean @default(false) deleted Boolean @default(false)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
Module Module[] CustomerForm CustomerForm[]
} }
model Module { model CustomerForm {
id Float @id @unique id String @id @default(cuid())
type ModuleType type CustomerFormType
text String text String
deleted Boolean @default(false) deleted Boolean @default(false)
createdById String createdById String

View File

@@ -20,7 +20,11 @@
], ],
"paths": { "paths": {
"@app/*": ["./app/*"], "@app/*": ["./app/*"],
"@prisma/*": ["./prisma/*"] "@prisma/*": ["./prisma/*"],
"@utils/*": ["./utils/*"],
"@contexts/*": ["./contexts/*"],
"@components/*": ["./components/*"],
"@hooks/*": ["./hooks/*"]
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],

6
utils/cn.ts Normal file
View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

24
utils/types.ts Normal file
View File

@@ -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<typeof UserSchema>;
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<typeof CustomerFormSchema>;

160
yarn.lock
View File

@@ -60,6 +60,15 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "@commitlint/cli@npm:^18.4.3":
version: 18.6.1 version: 18.6.1
resolution: "@commitlint/cli@npm:18.6.1" resolution: "@commitlint/cli@npm:18.6.1"
@@ -330,6 +339,15 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "@humanwhocodes/config-array@npm:^0.11.14":
version: 0.11.14 version: 0.11.14
resolution: "@humanwhocodes/config-array@npm:0.11.14" resolution: "@humanwhocodes/config-array@npm:0.11.14"
@@ -618,6 +636,34 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "@react-email/render@npm:0.0.15":
version: 0.0.15 version: 0.0.15
resolution: "@react-email/render@npm:0.0.15" resolution: "@react-email/render@npm:0.0.15"
@@ -1216,6 +1262,13 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "audit-ci@npm:^6.6.1":
version: 6.6.1 version: 6.6.1
resolution: "audit-ci@npm:6.6.1" resolution: "audit-ci@npm:6.6.1"
@@ -1268,6 +1321,17 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "axobject-query@npm:~3.1.1":
version: 3.1.1 version: 3.1.1
resolution: "axobject-query@npm:3.1.1" resolution: "axobject-query@npm:3.1.1"
@@ -1512,6 +1576,13 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "color-convert@npm:^1.9.0":
version: 1.9.3 version: 1.9.3
resolution: "color-convert@npm:1.9.3" resolution: "color-convert@npm:1.9.3"
@@ -1551,6 +1622,15 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "commander@npm:^10.0.0":
version: 10.0.1 version: 10.0.1
resolution: "commander@npm:10.0.1" resolution: "commander@npm:10.0.1"
@@ -1842,6 +1922,13 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "didyoumean@npm:^1.2.2":
version: 1.2.2 version: 1.2.2
resolution: "didyoumean@npm:1.2.2" resolution: "didyoumean@npm:1.2.2"
@@ -2647,6 +2734,16 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "for-each@npm:^0.3.3":
version: 0.3.3 version: 0.3.3
resolution: "for-each@npm:0.3.3" resolution: "for-each@npm:0.3.3"
@@ -2666,6 +2763,17 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "fraction.js@npm:^4.3.7":
version: 4.3.7 version: 4.3.7
resolution: "fraction.js@npm:4.3.7" resolution: "fraction.js@npm:4.3.7"
@@ -4039,6 +4147,22 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "mimic-fn@npm:^2.1.0":
version: 2.1.0 version: 2.1.0
resolution: "mimic-fn@npm:2.1.0" resolution: "mimic-fn@npm:2.1.0"
@@ -5011,6 +5135,13 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "punycode@npm:^2.1.0":
version: 2.3.1 version: 2.3.1
resolution: "punycode@npm:2.3.1" resolution: "punycode@npm:2.3.1"
@@ -5044,7 +5175,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react-hook-form@npm:^7.48.2": "react-hook-form@npm:^7.52.0":
version: 7.52.0 version: 7.52.0
resolution: "react-hook-form@npm:7.52.0" resolution: "react-hook-form@npm:7.52.0"
peerDependencies: peerDependencies:
@@ -5162,6 +5293,13 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "regexp.prototype.flags@npm:^1.5.1, regexp.prototype.flags@npm:^1.5.2":
version: 1.5.2 version: 1.5.2
resolution: "regexp.prototype.flags@npm:1.5.2" resolution: "regexp.prototype.flags@npm:1.5.2"
@@ -5888,6 +6026,15 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "tailwindcss@npm:^3.4.1":
version: 3.4.4 version: 3.4.4
resolution: "tailwindcss@npm:3.4.4" resolution: "tailwindcss@npm:3.4.4"
@@ -6241,7 +6388,9 @@ __metadata:
"@auth0/nextjs-auth0": "npm:^3.5.0" "@auth0/nextjs-auth0": "npm:^3.5.0"
"@commitlint/cli": "npm:^18.4.3" "@commitlint/cli": "npm:^18.4.3"
"@commitlint/config-conventional": "npm:^18.4.3" "@commitlint/config-conventional": "npm:^18.4.3"
"@hookform/resolvers": "npm:^3.6.0"
"@prisma/client": "npm:^5.6.0" "@prisma/client": "npm:^5.6.0"
"@radix-ui/react-slot": "npm:^1.1.0"
"@types/node": "npm:^20" "@types/node": "npm:^20"
"@types/react": "npm:^18" "@types/react": "npm:^18"
"@types/react-dom": "npm:^18" "@types/react-dom": "npm:^18"
@@ -6250,6 +6399,8 @@ __metadata:
"@vercel/analytics": "npm:^1.1.1" "@vercel/analytics": "npm:^1.1.1"
audit-ci: "npm:^6.6.1" audit-ci: "npm:^6.6.1"
autoprefixer: "npm:^10.0.1" autoprefixer: "npm:^10.0.1"
axios: "npm:^1.7.2"
clsx: "npm:^2.1.1"
eslint: "npm:^8" eslint: "npm:^8"
eslint-config-next: "npm:14.0.3" eslint-config-next: "npm:14.0.3"
eslint-config-prettier: "npm:^9.0.0" eslint-config-prettier: "npm:^9.0.0"
@@ -6263,11 +6414,12 @@ __metadata:
prisma: "npm:^5.6.0" prisma: "npm:^5.6.0"
react: "npm:^18" react: "npm:^18"
react-dom: "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" resend: "npm:^3.1.0"
tailwind-merge: "npm:^2.3.0"
tailwindcss: "npm:^3.4.1" tailwindcss: "npm:^3.4.1"
typescript: "npm:^5" typescript: "npm:^5"
zod: "npm:^3.22.4" zod: "npm:^3.23.8"
languageName: unknown languageName: unknown
linkType: soft linkType: soft
@@ -6457,7 +6609,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"zod@npm:^3.22.4": "zod@npm:^3.23.8":
version: 3.23.8 version: 3.23.8
resolution: "zod@npm:3.23.8" resolution: "zod@npm:3.23.8"
checksum: 846fd73e1af0def79c19d510ea9e4a795544a67d5b34b7e1c4d0425bf6bfd1c719446d94cdfa1721c1987d891321d61f779e8236fde517dc0e524aa851a6eff1 checksum: 846fd73e1af0def79c19d510ea9e4a795544a67d5b34b7e1c4d0425bf6bfd1c719446d94cdfa1721c1987d891321d61f779e8236fde517dc0e524aa851a6eff1