diff --git a/.github/workflows/container-scan.yml b/.github/workflows/container-scan.yml
index 2a266dd..c87c433 100644
--- a/.github/workflows/container-scan.yml
+++ b/.github/workflows/container-scan.yml
@@ -9,7 +9,7 @@ jobs:
name: Container Scan
runs-on: docker
container:
- image: ghcr.io/di0ik/forgejo_runner_container:main@sha256:c66a37d9af18f8f0f34d16890082bc08d842d52ff2a2bc36d993e3d347b498ac
+ image: ghcr.io/di0ik/forgejo_runner_container:main@sha256:c4667f2702c32b91b4c92db2ff20739edd00409a44a691c0598cf4a09a47743a
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml
index 5d8a383..07fe75e 100644
--- a/.github/workflows/docker-build.yml
+++ b/.github/workflows/docker-build.yml
@@ -13,7 +13,7 @@ jobs:
docker:
runs-on: docker
container:
- image: ghcr.io/di0ik/forgejo_runner_container:main@sha256:c66a37d9af18f8f0f34d16890082bc08d842d52ff2a2bc36d993e3d347b498ac
+ image: ghcr.io/di0ik/forgejo_runner_container:main@sha256:c4667f2702c32b91b4c92db2ff20739edd00409a44a691c0598cf4a09a47743a
steps:
- name: Login to Docker Hub
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
diff --git a/package.json b/package.json
index 1dbd9d4..0e7bb30 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"@fortawesome/free-regular-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
+ "@hookform/resolvers": "^5.0.1",
"@prisma/client": "^6.9.0",
"@radix-ui/react-dropdown-menu": "^2.1.14",
"@radix-ui/react-hover-card": "^1.1.13",
@@ -32,6 +33,7 @@
"@radix-ui/react-slot": "^1.2.2",
"@radix-ui/react-switch": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.11",
+ "bcryptjs": "^3.0.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
@@ -43,20 +45,22 @@
"react-big-calendar": "^1.18.0",
"react-datepicker": "^8.4.0",
"react-dom": "^19.0.0",
- "tailwind-merge": "^3.2.0"
+ "react-hook-form": "^7.56.4",
+ "tailwind-merge": "^3.2.0",
+ "zod": "^3.25.60"
},
"devDependencies": {
"@eslint/eslintrc": "3.3.1",
"@tailwindcss/postcss": "4.1.10",
- "@types/node": "22.15.31",
+ "@types/node": "22.15.32",
"@types/react": "19.1.8",
"@types/react-big-calendar": "^1",
"@types/react-dom": "19.1.6",
"dotenv-cli": "8.0.0",
- "eslint": "9.28.0",
+ "eslint": "9.29.0",
"eslint-config-next": "15.3.3",
"eslint-config-prettier": "10.1.5",
- "postcss": "8.5.5",
+ "postcss": "8.5.6",
"prettier": "3.5.3",
"prisma": "6.9.0",
"tailwindcss": "4.1.10",
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx
index adaa1c3..2933872 100644
--- a/src/app/login/page.tsx
+++ b/src/app/login/page.tsx
@@ -6,8 +6,6 @@ import { Button } from '@/components/custom-ui/button';
import Image from 'next/image';
import { Separator } from '@/components/custom-ui/separator';
import Logo from '@/components/logo';
-
-import '@/app/globals.css';
import {
Card,
CardContent,
@@ -28,12 +26,12 @@ export default async function LoginPage() {
}
return (
-
-
-
+
+
+
-
+
@@ -43,8 +41,6 @@ export default async function LoginPage() {
- {providerMap.length > 0}
-
{providerMap.map((provider) => (
) {
return (
@@ -27,7 +30,9 @@ export default function LabeledInput({
id={name}
name={name}
autoComplete={autocomplete}
+ {...rest}
/>
+ {error &&
{error}
}
);
}
diff --git a/src/components/user/login-form.tsx b/src/components/user/login-form.tsx
index 8a00749..a0167e2 100644
--- a/src/components/user/login-form.tsx
+++ b/src/components/user/login-form.tsx
@@ -1,107 +1,214 @@
'use client';
-import { signIn } from '@/auth';
+
+import React, { useState, useRef } from 'react';
+import { useRouter } from 'next/navigation';
+
import LabeledInput from '@/components/labeled-input';
import { Button } from '@/components/custom-ui/button';
-import { AuthError } from 'next-auth';
-import { redirect } from 'next/navigation';
-import { useRef, useState } from 'react';
+import useZodForm from '@/lib/hooks/useZodForm';
+import { loginSchema, registerSchema } from '@/lib/validation/user';
+import { loginAction } from '@/lib/auth/login';
+import { registerAction } from '@/lib/auth/register';
-const SIGNIN_ERROR_URL = '/error';
+function LoginFormElement({
+ setIsSignUp,
+ formRef,
+}: {
+ setIsSignUp: (value: boolean | ((prev: boolean) => boolean)) => void;
+ formRef?: React.RefObject;
+}) {
+ const { handleSubmit, formState, register, setError } =
+ useZodForm(loginSchema);
+ const router = useRouter();
+
+ const onSubmit = handleSubmit(async (data) => {
+ try {
+ const { error } = await loginAction(data);
+
+ if (error) {
+ setError('root', {
+ message: error,
+ });
+ return;
+ } else {
+ router.push('/home');
+ router.refresh();
+ return;
+ }
+ } catch (error: unknown) {
+ if (error instanceof Error)
+ setError('root', {
+ message: error?.message,
+ });
+ else
+ setError('root', {
+ message: 'An unknown error occurred.',
+ });
+ }
+ });
+
+ return (
+
+ );
+}
+
+function RegisterFormElement({
+ setIsSignUp,
+ formRef,
+}: {
+ setIsSignUp: (value: boolean | ((prev: boolean) => boolean)) => void;
+ formRef?: React.RefObject;
+}) {
+ const { handleSubmit, formState, register, setError } =
+ useZodForm(registerSchema);
+
+ const onSubmit = handleSubmit(async (data) => {
+ try {
+ const { error } = await registerAction(data);
+
+ if (error) {
+ setError('root', {
+ message: error,
+ });
+ return;
+ } else {
+ formRef?.current?.reset();
+ setIsSignUp(false);
+ // TODO: Show registration success message (reminder to verify email)
+ return;
+ }
+ } catch (error: unknown) {
+ if (error instanceof Error)
+ setError('root', {
+ message: error?.message,
+ });
+ else
+ setError('root', {
+ message: 'An unknown error occurred.',
+ });
+ }
+ });
+
+ return (
+
+ );
+}
export default function LoginForm() {
const [isSignUp, setIsSignUp] = useState(false);
const formRef = useRef(null);
- return (
-
- );
+ if (isSignUp) {
+ return ;
+ }
+ return ;
}
diff --git a/src/lib/auth/login.ts b/src/lib/auth/login.ts
new file mode 100644
index 0000000..0019ae0
--- /dev/null
+++ b/src/lib/auth/login.ts
@@ -0,0 +1,27 @@
+'use server';
+
+import { z } from 'zod';
+import { loginSchema } from '@/lib/validation/user';
+import { signIn } from '@/auth';
+
+export async function loginAction(data: z.infer) {
+ try {
+ await signIn('credentials', {
+ ...data,
+ redirect: false,
+ });
+
+ return {
+ error: undefined,
+ };
+ } catch (error: unknown) {
+ if (error instanceof Error) {
+ return {
+ error: error.message.toString(),
+ };
+ }
+ return {
+ error: 'An unknown error occurred.',
+ };
+ }
+}
diff --git a/src/lib/auth/register.ts b/src/lib/auth/register.ts
new file mode 100644
index 0000000..9eba8e9
--- /dev/null
+++ b/src/lib/auth/register.ts
@@ -0,0 +1,75 @@
+'use server';
+
+import type { z } from 'zod';
+import bcrypt from 'bcryptjs';
+import { registerSchema } from '@/lib/validation/user';
+import { prisma } from '@/prisma';
+
+export async function registerAction(data: z.infer) {
+ try {
+ const result = await registerSchema.safeParseAsync(data);
+
+ if (!result.success) {
+ return {
+ error: result.error.errors[0].message,
+ };
+ }
+
+ const { email, password, firstName, lastName, username } = result.data;
+
+ const user = await prisma.user.findUnique({
+ where: {
+ email,
+ },
+ });
+
+ if (user) {
+ return {
+ error: 'User already exist with this email',
+ };
+ }
+
+ const existingUsername = await prisma.user.findUnique({
+ where: {
+ name: username,
+ },
+ });
+
+ if (existingUsername) {
+ return {
+ error: 'Username already exists',
+ };
+ }
+
+ const passwordHash = await bcrypt.hash(password, 10);
+
+ await prisma.$transaction(async (tx) => {
+ const { id } = await tx.user.create({
+ data: {
+ email,
+ name: username,
+ password_hash: passwordHash,
+ first_name: firstName,
+ last_name: lastName,
+ emailVerified: new Date(), // TODO: handle email verification
+ },
+ });
+
+ await tx.account.create({
+ data: {
+ userId: id,
+ type: 'credentials',
+ provider: 'credentials',
+ providerAccountId: id,
+ },
+ });
+ });
+
+ return {};
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ } catch (_error) {
+ return {
+ error: 'System error. Please contact support',
+ };
+ }
+}
diff --git a/src/lib/hooks/useZodForm.tsx b/src/lib/hooks/useZodForm.tsx
new file mode 100644
index 0000000..8b8eb62
--- /dev/null
+++ b/src/lib/hooks/useZodForm.tsx
@@ -0,0 +1,14 @@
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
+
+export default function useZodForm<
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ Schema extends z.ZodType,
+ Values extends z.infer,
+>(schema: Schema, defaultValues?: Values) {
+ return useForm({
+ resolver: zodResolver(schema),
+ defaultValues,
+ });
+}
diff --git a/src/lib/validation/user.ts b/src/lib/validation/user.ts
new file mode 100644
index 0000000..a2efa5e
--- /dev/null
+++ b/src/lib/validation/user.ts
@@ -0,0 +1,67 @@
+import zod from 'zod';
+
+export const loginSchema = zod.object({
+ email: zod
+ .string()
+ .email('Invalid email address')
+ .min(3, 'Email is required')
+ .or(
+ zod
+ .string()
+ .min(3, 'Username is required')
+ .max(32, 'Username must be at most 32 characters long')
+ .regex(
+ /^[a-zA-Z0-9_]+$/,
+ 'Username can only contain letters, numbers, and underscores',
+ ),
+ ),
+ password: zod.string().min(1, 'Password is required'),
+});
+
+export const registerSchema = zod
+ .object({
+ firstName: zod
+ .string()
+ .min(1, 'First name is required')
+ .max(32, 'First name must be at most 32 characters long'),
+ lastName: zod
+ .string()
+ .min(1, 'Last name is required')
+ .max(32, 'Last name must be at most 32 characters long'),
+ email: zod
+ .string()
+ .email('Invalid email address')
+ .min(3, 'Email is required'),
+ password: zod
+ .string()
+ .min(8, 'Password must be at least 8 characters long')
+ .max(128, 'Password must be at most 128 characters long'),
+ confirmPassword: zod
+ .string()
+ .min(8, 'Password must be at least 8 characters long')
+ .max(128, 'Password must be at most 128 characters long'),
+ username: zod
+ .string()
+ .min(3, 'Username is required')
+ .max(32, 'Username must be at most 32 characters long')
+ .regex(
+ /^[a-zA-Z0-9_]+$/,
+ 'Username can only contain letters, numbers, and underscores',
+ ),
+ })
+ .refine((data) => data.password === data.confirmPassword, {
+ message: 'Passwords do not match',
+ path: ['confirmPassword'],
+ })
+ .refine(
+ (data) =>
+ !data.password.includes(data.firstName) &&
+ !data.password.includes(data.lastName) &&
+ !data.password.includes(data.email) &&
+ !data.password.includes(data.username),
+ {
+ message:
+ 'Password cannot contain your first name, last name, email, or username',
+ path: ['password'],
+ },
+ );
diff --git a/yarn.lock b/yarn.lock
index 08d5235..03f45f1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -110,7 +110,7 @@ __metadata:
languageName: node
linkType: hard
-"@eslint/config-array@npm:^0.20.0":
+"@eslint/config-array@npm:^0.20.1":
version: 0.20.1
resolution: "@eslint/config-array@npm:0.20.1"
dependencies:
@@ -163,10 +163,10 @@ __metadata:
languageName: node
linkType: hard
-"@eslint/js@npm:9.28.0":
- version: 9.28.0
- resolution: "@eslint/js@npm:9.28.0"
- checksum: 10c0/5a6759542490dd9f778993edfbc8d2f55168fd0f7336ceed20fe3870c65499d72fc0bca8d1ae00ea246b0923ea4cba2e0758a8a5507a3506ddcf41c92282abb8
+"@eslint/js@npm:9.29.0":
+ version: 9.29.0
+ resolution: "@eslint/js@npm:9.29.0"
+ checksum: 10c0/d0ccf37063fa27a3fae9347cb044f84ca10b5a2fa19ffb2b3fedf3b96843ac1ff359ea9f0ab0e80f2f16fda4cb0dc61ea0fed0375090f050fe0a029e7d6de3a3
languageName: node
linkType: hard
@@ -294,6 +294,17 @@ __metadata:
languageName: node
linkType: hard
+"@hookform/resolvers@npm:^5.0.1":
+ version: 5.1.1
+ resolution: "@hookform/resolvers@npm:5.1.1"
+ dependencies:
+ "@standard-schema/utils": "npm:^0.3.0"
+ peerDependencies:
+ react-hook-form: ^7.55.0
+ checksum: 10c0/74601ba4abb3159bbaa25175af9459a2c0337a28d8c0a5be95c7ae7b0a76ddafcf63c03eea8561fd099fe80b226194ad09e3824c53b9beda38393ff9fd264a03
+ languageName: node
+ linkType: hard
+
"@humanfs/core@npm:^0.19.1":
version: 0.19.1
resolution: "@humanfs/core@npm:0.19.1"
@@ -1477,6 +1488,13 @@ __metadata:
languageName: node
linkType: hard
+"@standard-schema/utils@npm:^0.3.0":
+ version: 0.3.0
+ resolution: "@standard-schema/utils@npm:0.3.0"
+ checksum: 10c0/6eb74cd13e52d5fc74054df51e37d947ef53f3ab9e02c085665dcca3c38c60ece8d735cebbdf18fbb13c775fbcb9becb3f53109b0e092a63f0f7389ce0993fd0
+ languageName: node
+ linkType: hard
+
"@swc/counter@npm:0.1.3":
version: 0.1.3
resolution: "@swc/counter@npm:0.1.3"
@@ -1696,12 +1714,12 @@ __metadata:
languageName: node
linkType: hard
-"@types/node@npm:22.15.31":
- version: 22.15.31
- resolution: "@types/node@npm:22.15.31"
+"@types/node@npm:22.15.32":
+ version: 22.15.32
+ resolution: "@types/node@npm:22.15.32"
dependencies:
undici-types: "npm:~6.21.0"
- checksum: 10c0/ef7d5dc890da41cfd554d35ab8998bc18be9e3a0caa642e720599ac4410a94a4879766e52b3c9cafa06c66b7b8aebdc51f322cf67df23a6489927890196a316d
+ checksum: 10c0/63a2fa52adf1134d1b3bee8b1862d4b8e4550fffc190551068d3d41a41d9e5c0c8f1cb81faa18767b260637360f662115c26c5e4e7718868ead40c4a57cbc0e3
languageName: node
linkType: hard
@@ -1749,104 +1767,104 @@ __metadata:
linkType: hard
"@typescript-eslint/eslint-plugin@npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
- version: 8.34.0
- resolution: "@typescript-eslint/eslint-plugin@npm:8.34.0"
+ version: 8.34.1
+ resolution: "@typescript-eslint/eslint-plugin@npm:8.34.1"
dependencies:
"@eslint-community/regexpp": "npm:^4.10.0"
- "@typescript-eslint/scope-manager": "npm:8.34.0"
- "@typescript-eslint/type-utils": "npm:8.34.0"
- "@typescript-eslint/utils": "npm:8.34.0"
- "@typescript-eslint/visitor-keys": "npm:8.34.0"
+ "@typescript-eslint/scope-manager": "npm:8.34.1"
+ "@typescript-eslint/type-utils": "npm:8.34.1"
+ "@typescript-eslint/utils": "npm:8.34.1"
+ "@typescript-eslint/visitor-keys": "npm:8.34.1"
graphemer: "npm:^1.4.0"
ignore: "npm:^7.0.0"
natural-compare: "npm:^1.4.0"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
- "@typescript-eslint/parser": ^8.34.0
+ "@typescript-eslint/parser": ^8.34.1
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.9.0"
- checksum: 10c0/905a05d15f4b0367838ec445f9890321d87470198bf7a589278fc0f38c82cf3ccc1efce4acd3c9c94ee6149d5579ef58606fb7c50f4db50c830de65af8c27c6d
+ checksum: 10c0/f1c9f25e4fe4b59622312dfa0ca1e80fa7945296ba5c04362a5fda084a17e23a6b98dac331f5a13bcb1ba34a2b598a3f5c41aa288f0c51fe60196e912954e56a
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
- version: 8.34.0
- resolution: "@typescript-eslint/parser@npm:8.34.0"
+ version: 8.34.1
+ resolution: "@typescript-eslint/parser@npm:8.34.1"
dependencies:
- "@typescript-eslint/scope-manager": "npm:8.34.0"
- "@typescript-eslint/types": "npm:8.34.0"
- "@typescript-eslint/typescript-estree": "npm:8.34.0"
- "@typescript-eslint/visitor-keys": "npm:8.34.0"
+ "@typescript-eslint/scope-manager": "npm:8.34.1"
+ "@typescript-eslint/types": "npm:8.34.1"
+ "@typescript-eslint/typescript-estree": "npm:8.34.1"
+ "@typescript-eslint/visitor-keys": "npm:8.34.1"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.9.0"
- checksum: 10c0/a829be00ea3455c1e50983c8b44476fbfc9329d019764e407c4d591a95dbd168f83f13e309751242bb4fdc02f89cb51ca5cdc912a12b10f69eebcb1c46dcc39b
+ checksum: 10c0/bf8070245d53ef6926ff6630bb72f245923f545304e2a61508fb944802a83fed8eab961d9010956d07999d51afdfbbec82aea9d6185295551a7c17c00d759183
languageName: node
linkType: hard
-"@typescript-eslint/project-service@npm:8.34.0":
- version: 8.34.0
- resolution: "@typescript-eslint/project-service@npm:8.34.0"
+"@typescript-eslint/project-service@npm:8.34.1":
+ version: 8.34.1
+ resolution: "@typescript-eslint/project-service@npm:8.34.1"
dependencies:
- "@typescript-eslint/tsconfig-utils": "npm:^8.34.0"
- "@typescript-eslint/types": "npm:^8.34.0"
+ "@typescript-eslint/tsconfig-utils": "npm:^8.34.1"
+ "@typescript-eslint/types": "npm:^8.34.1"
debug: "npm:^4.3.4"
peerDependencies:
typescript: ">=4.8.4 <5.9.0"
- checksum: 10c0/88e64b8daf7db9603277fcbeb9e585e70ec6d6e34fa10d4b60f421e48081cc7c1f6acb01e1ee9dd95e10c0601f164c1defbfe6c9d1edc9822089bb72dbb0fc80
+ checksum: 10c0/9333a890625f6777054db17a6b299281ae7502bb7615261d15b885a75b8cf65fc91591389c93b37ecd14b651d8e94851dac8718e5dcc8ed0600533535dae855c
languageName: node
linkType: hard
-"@typescript-eslint/scope-manager@npm:8.34.0":
- version: 8.34.0
- resolution: "@typescript-eslint/scope-manager@npm:8.34.0"
+"@typescript-eslint/scope-manager@npm:8.34.1":
+ version: 8.34.1
+ resolution: "@typescript-eslint/scope-manager@npm:8.34.1"
dependencies:
- "@typescript-eslint/types": "npm:8.34.0"
- "@typescript-eslint/visitor-keys": "npm:8.34.0"
- checksum: 10c0/35af36bddc4c227cb0bac42192c40b38179ced30866b6aac642781e21c3f3b1c72051eb4f685d7c99517c3296dd6ba83dd8360e4072e8dcf604aae266eece1b4
+ "@typescript-eslint/types": "npm:8.34.1"
+ "@typescript-eslint/visitor-keys": "npm:8.34.1"
+ checksum: 10c0/2af608fa3900f4726322e33bf4f3a376fdace3ac0f310cf7d9256bbc2905c3896138176a47dd195d2c2229f27fe43f5deb4bc7729db2eb18389926dedea78077
languageName: node
linkType: hard
-"@typescript-eslint/tsconfig-utils@npm:8.34.0, @typescript-eslint/tsconfig-utils@npm:^8.34.0":
- version: 8.34.0
- resolution: "@typescript-eslint/tsconfig-utils@npm:8.34.0"
+"@typescript-eslint/tsconfig-utils@npm:8.34.1, @typescript-eslint/tsconfig-utils@npm:^8.34.1":
+ version: 8.34.1
+ resolution: "@typescript-eslint/tsconfig-utils@npm:8.34.1"
peerDependencies:
typescript: ">=4.8.4 <5.9.0"
- checksum: 10c0/98246f89d169d3feb453a6a8552c51d10225cb00c4ff1501549b7846e564ad0e218b644cd94ce779dceed07dcb9035c53fd32186b4c0223b7b2a1f7295b120c3
+ checksum: 10c0/8d1ead8b7c279b48e2ed96f083ec119a9aeea1ca9cdd40576ec271b996b9fd8cfa0ddb0aafbb4e14bc27fc62c69c5be66d39b1de68eab9ddd7f1861da267423d
languageName: node
linkType: hard
-"@typescript-eslint/type-utils@npm:8.34.0":
- version: 8.34.0
- resolution: "@typescript-eslint/type-utils@npm:8.34.0"
+"@typescript-eslint/type-utils@npm:8.34.1":
+ version: 8.34.1
+ resolution: "@typescript-eslint/type-utils@npm:8.34.1"
dependencies:
- "@typescript-eslint/typescript-estree": "npm:8.34.0"
- "@typescript-eslint/utils": "npm:8.34.0"
+ "@typescript-eslint/typescript-estree": "npm:8.34.1"
+ "@typescript-eslint/utils": "npm:8.34.1"
debug: "npm:^4.3.4"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.9.0"
- checksum: 10c0/7c25d7f4186411190142390467160e81384d400cfb21183d8a305991c723da0a74e5528cdce30b5f2cb6d9d2f6af7c0981c20c18b45fc084b35632429270ae80
+ checksum: 10c0/502a2cdfe47f1f34206c747b5a70e0242dd99f570511db3dda9c5f999d9abadfbbb1dfa82a1fa437a1689d232715412e61c97d95f19c9314ba5ad23196b4096d
languageName: node
linkType: hard
-"@typescript-eslint/types@npm:8.34.0, @typescript-eslint/types@npm:^8.34.0":
- version: 8.34.0
- resolution: "@typescript-eslint/types@npm:8.34.0"
- checksum: 10c0/5d32b2ac03e4cbc1ac1777a53ee83d6d7887a783363bab4f0a6f7550a9e9df0254971cdf71e13b988e2215f2939e7592404856b8acb086ec63c4479c0225c742
+"@typescript-eslint/types@npm:8.34.1, @typescript-eslint/types@npm:^8.34.1":
+ version: 8.34.1
+ resolution: "@typescript-eslint/types@npm:8.34.1"
+ checksum: 10c0/db1b3dce6a70b28ddb13c76fbb5983240d9395656df5f7cbd99bfd9905e39c0dab2132870f01dbc406b48739c437f7d344a879a824cedaba81b91a53110dc23a
languageName: node
linkType: hard
-"@typescript-eslint/typescript-estree@npm:8.34.0":
- version: 8.34.0
- resolution: "@typescript-eslint/typescript-estree@npm:8.34.0"
+"@typescript-eslint/typescript-estree@npm:8.34.1":
+ version: 8.34.1
+ resolution: "@typescript-eslint/typescript-estree@npm:8.34.1"
dependencies:
- "@typescript-eslint/project-service": "npm:8.34.0"
- "@typescript-eslint/tsconfig-utils": "npm:8.34.0"
- "@typescript-eslint/types": "npm:8.34.0"
- "@typescript-eslint/visitor-keys": "npm:8.34.0"
+ "@typescript-eslint/project-service": "npm:8.34.1"
+ "@typescript-eslint/tsconfig-utils": "npm:8.34.1"
+ "@typescript-eslint/types": "npm:8.34.1"
+ "@typescript-eslint/visitor-keys": "npm:8.34.1"
debug: "npm:^4.3.4"
fast-glob: "npm:^3.3.2"
is-glob: "npm:^4.0.3"
@@ -1855,32 +1873,32 @@ __metadata:
ts-api-utils: "npm:^2.1.0"
peerDependencies:
typescript: ">=4.8.4 <5.9.0"
- checksum: 10c0/e678982b0009e895aee2b4ccc55bb9ea5473a32e846a97c63d0c6a978c72e1a29e506e6a5f9dda45e9b7803e6c3e3abcdf4c316af1c59146abef4e10e0e94129
+ checksum: 10c0/4ee7249db91b9840361f34f80b7b6d646a3af159c7298d79a33d8a11c98792fd3a395343e5e17e0fa29529e8f0113bac8baadcef90d1e140bd736a48f0485042
languageName: node
linkType: hard
-"@typescript-eslint/utils@npm:8.34.0":
- version: 8.34.0
- resolution: "@typescript-eslint/utils@npm:8.34.0"
+"@typescript-eslint/utils@npm:8.34.1":
+ version: 8.34.1
+ resolution: "@typescript-eslint/utils@npm:8.34.1"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.7.0"
- "@typescript-eslint/scope-manager": "npm:8.34.0"
- "@typescript-eslint/types": "npm:8.34.0"
- "@typescript-eslint/typescript-estree": "npm:8.34.0"
+ "@typescript-eslint/scope-manager": "npm:8.34.1"
+ "@typescript-eslint/types": "npm:8.34.1"
+ "@typescript-eslint/typescript-estree": "npm:8.34.1"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.9.0"
- checksum: 10c0/d759cf6f1b1b23d7d8ab922345e7b68b7c829f4bad841164312cfa3a3e8e818b962dd0d96c1aca7fd7c10248d56538d9714df5f3cfec9f159ca0a139feac60b9
+ checksum: 10c0/e3085877f7940c02a37653e6bc52ac6cde115e755b1f788fe4331202f371b3421cc4d0878c7d3eb054e14e9b3a064496a707a73eac471cb2b73593b9e9d4b998
languageName: node
linkType: hard
-"@typescript-eslint/visitor-keys@npm:8.34.0":
- version: 8.34.0
- resolution: "@typescript-eslint/visitor-keys@npm:8.34.0"
+"@typescript-eslint/visitor-keys@npm:8.34.1":
+ version: 8.34.1
+ resolution: "@typescript-eslint/visitor-keys@npm:8.34.1"
dependencies:
- "@typescript-eslint/types": "npm:8.34.0"
- eslint-visitor-keys: "npm:^4.2.0"
- checksum: 10c0/d50997e921a178589913d08ffe14d02eba40666c90bdc0c9751f2b87ce500598f64027e2d866dfc975647b2f8b907158503d0722d6b1976c8f1cf5dd8e1d6d69
+ "@typescript-eslint/types": "npm:8.34.1"
+ eslint-visitor-keys: "npm:^4.2.1"
+ checksum: 10c0/0e5a9b3d93905d16d3cf8cb5fb346dcc6f760482eb7d0ac209aefc09a32f78ef28a687634df6ad08e81fb3e1083e8805f34472de6bbc501c0105ad654d518f40
languageName: node
linkType: hard
@@ -2232,6 +2250,15 @@ __metadata:
languageName: node
linkType: hard
+"bcryptjs@npm:^3.0.2":
+ version: 3.0.2
+ resolution: "bcryptjs@npm:3.0.2"
+ bin:
+ bcrypt: bin/bcrypt
+ checksum: 10c0/a0923cac99f83e913f8f4e4f42df6a27c6593b24d509900331d1280c4050b1544e602a0ac67b43f7bb5c969991c3ed77fd72f19b7dc873be8ee794da3d925c7e
+ languageName: node
+ linkType: hard
+
"brace-expansion@npm:^1.1.7":
version: 1.1.12
resolution: "brace-expansion@npm:1.1.12"
@@ -2309,9 +2336,9 @@ __metadata:
linkType: hard
"caniuse-lite@npm:^1.0.30001579":
- version: 1.0.30001722
- resolution: "caniuse-lite@npm:1.0.30001722"
- checksum: 10c0/a1e344c392e0b138f0b215525108877d725665217a5e8e7504897e30379a5a9b858bc44799ccc0e19f4a64bf1e05c15b4a58eb1c9032293f894aa24e8d9f470f
+ version: 1.0.30001723
+ resolution: "caniuse-lite@npm:1.0.30001723"
+ checksum: 10c0/e019503061759b96017c4d27ddd7ca1b48533eabcd0431b51d2e3156f99f6b031075e46c279c0db63424cdfc874bba992caec2db51b922a0f945e686246886f6
languageName: node
linkType: hard
@@ -2951,7 +2978,7 @@ __metadata:
languageName: node
linkType: hard
-"eslint-scope@npm:^8.3.0":
+"eslint-scope@npm:^8.4.0":
version: 8.4.0
resolution: "eslint-scope@npm:8.4.0"
dependencies:
@@ -2968,24 +2995,24 @@ __metadata:
languageName: node
linkType: hard
-"eslint-visitor-keys@npm:^4.2.0, eslint-visitor-keys@npm:^4.2.1":
+"eslint-visitor-keys@npm:^4.2.1":
version: 4.2.1
resolution: "eslint-visitor-keys@npm:4.2.1"
checksum: 10c0/fcd43999199d6740db26c58dbe0c2594623e31ca307e616ac05153c9272f12f1364f5a0b1917a8e962268fdecc6f3622c1c2908b4fcc2e047a106fe6de69dc43
languageName: node
linkType: hard
-"eslint@npm:9.28.0":
- version: 9.28.0
- resolution: "eslint@npm:9.28.0"
+"eslint@npm:9.29.0":
+ version: 9.29.0
+ resolution: "eslint@npm:9.29.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.2.0"
"@eslint-community/regexpp": "npm:^4.12.1"
- "@eslint/config-array": "npm:^0.20.0"
+ "@eslint/config-array": "npm:^0.20.1"
"@eslint/config-helpers": "npm:^0.2.1"
"@eslint/core": "npm:^0.14.0"
"@eslint/eslintrc": "npm:^3.3.1"
- "@eslint/js": "npm:9.28.0"
+ "@eslint/js": "npm:9.29.0"
"@eslint/plugin-kit": "npm:^0.3.1"
"@humanfs/node": "npm:^0.16.6"
"@humanwhocodes/module-importer": "npm:^1.0.1"
@@ -2997,9 +3024,9 @@ __metadata:
cross-spawn: "npm:^7.0.6"
debug: "npm:^4.3.2"
escape-string-regexp: "npm:^4.0.0"
- eslint-scope: "npm:^8.3.0"
- eslint-visitor-keys: "npm:^4.2.0"
- espree: "npm:^10.3.0"
+ eslint-scope: "npm:^8.4.0"
+ eslint-visitor-keys: "npm:^4.2.1"
+ espree: "npm:^10.4.0"
esquery: "npm:^1.5.0"
esutils: "npm:^2.0.2"
fast-deep-equal: "npm:^3.1.3"
@@ -3021,11 +3048,11 @@ __metadata:
optional: true
bin:
eslint: bin/eslint.js
- checksum: 10c0/513ea7e69d88a0905d4ed35cef3a8f31ebce7ca9f2cdbda3474495c63ad6831d52357aad65094be7a144d6e51850980ced7d25efb807e8ab06a427241f7cd730
+ checksum: 10c0/75e3f841e0f8b0fa93dbb2ba6ae538bd8b611c3654117bc3dadf90bb009923dfd2c15ec2948dc6e6b8b571317cc125c5cceb9255da8cd644ee740020df645dd8
languageName: node
linkType: hard
-"espree@npm:^10.0.1, espree@npm:^10.3.0":
+"espree@npm:^10.0.1, espree@npm:^10.4.0":
version: 10.4.0
resolution: "espree@npm:10.4.0"
dependencies:
@@ -4027,6 +4054,7 @@ __metadata:
"@fortawesome/free-regular-svg-icons": "npm:^6.7.2"
"@fortawesome/free-solid-svg-icons": "npm:^6.7.2"
"@fortawesome/react-fontawesome": "npm:^0.2.2"
+ "@hookform/resolvers": "npm:^5.0.1"
"@prisma/client": "npm:^6.9.0"
"@radix-ui/react-dropdown-menu": "npm:^2.1.14"
"@radix-ui/react-hover-card": "npm:^1.1.13"
@@ -4038,32 +4066,35 @@ __metadata:
"@radix-ui/react-switch": "npm:^1.2.4"
"@radix-ui/react-tabs": "npm:^1.1.11"
"@tailwindcss/postcss": "npm:4.1.10"
- "@types/node": "npm:22.15.31"
+ "@types/node": "npm:22.15.32"
"@types/react": "npm:19.1.8"
"@types/react-big-calendar": "npm:^1"
"@types/react-dom": "npm:19.1.6"
+ bcryptjs: "npm:^3.0.2"
class-variance-authority: "npm:^0.7.1"
clsx: "npm:^2.1.1"
date-fns: "npm:^4.1.0"
dotenv-cli: "npm:8.0.0"
- eslint: "npm:9.28.0"
+ eslint: "npm:9.29.0"
eslint-config-next: "npm:15.3.3"
eslint-config-prettier: "npm:10.1.5"
lucide-react: "npm:^0.511.0"
next: "npm:15.3.3"
next-auth: "npm:^5.0.0-beta.25"
next-themes: "npm:^0.4.6"
- postcss: "npm:8.5.5"
+ postcss: "npm:8.5.6"
prettier: "npm:3.5.3"
prisma: "npm:6.9.0"
react: "npm:^19.1.0"
react-big-calendar: "npm:^1.18.0"
react-datepicker: "npm:^8.4.0"
react-dom: "npm:^19.0.0"
+ react-hook-form: "npm:^7.56.4"
tailwind-merge: "npm:^3.2.0"
tailwindcss: "npm:4.1.10"
tw-animate-css: "npm:1.3.4"
typescript: "npm:5.8.3"
+ zod: "npm:^3.25.60"
languageName: unknown
linkType: soft
@@ -4483,14 +4514,14 @@ __metadata:
languageName: node
linkType: hard
-"postcss@npm:8.5.5, postcss@npm:^8.4.41":
- version: 8.5.5
- resolution: "postcss@npm:8.5.5"
+"postcss@npm:8.5.6, postcss@npm:^8.4.41":
+ version: 8.5.6
+ resolution: "postcss@npm:8.5.6"
dependencies:
nanoid: "npm:^3.3.11"
picocolors: "npm:^1.1.1"
source-map-js: "npm:^1.2.1"
- checksum: 10c0/6415873fab84de05c2d8fd18f72ea6654bca437bb4b9f02ca819c438501e4b3a450023e575e17587c6eaa5bedddaaa4dad3af210f5cf166e30cec09cac58baf8
+ checksum: 10c0/5127cc7c91ed7a133a1b7318012d8bfa112da9ef092dddf369ae699a1f10ebbd89b1b9f25f3228795b84585c72aabd5ced5fc11f2ba467eedf7b081a66fad024
languageName: node
linkType: hard
@@ -4620,6 +4651,15 @@ __metadata:
languageName: node
linkType: hard
+"react-hook-form@npm:^7.56.4":
+ version: 7.58.0
+ resolution: "react-hook-form@npm:7.58.0"
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18 || ^19
+ checksum: 10c0/9e87bf1dfb43157ffec1b112092e8c5b2b9d0056c2a8bdea6c15e08d510b365915101f499f2e7698e16e94541ac82b26ab8e3b05af5b0cccfc82d29cf31c1b0b
+ languageName: node
+ linkType: hard
+
"react-is@npm:^16.13.1":
version: 16.13.1
resolution: "react-is@npm:16.13.1"
@@ -5643,3 +5683,10 @@ __metadata:
checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f
languageName: node
linkType: hard
+
+"zod@npm:^3.25.60":
+ version: 3.25.64
+ resolution: "zod@npm:3.25.64"
+ checksum: 10c0/00d76093a999e377e4ffd037fa7185e861c35917e8c4272f514115c206a0654995168f57fb71708b11e0a9243206d988b7f63b543404e1796402e50d346a6bd7
+ languageName: node
+ linkType: hard