Compare commits

..

6 commits

Author SHA1 Message Date
ea7ef2a779
style: format code
Some checks failed
docker-build / docker (pull_request) Waiting to run
container-scan / Container Scan (pull_request) Has been cancelled
2025-06-16 19:44:09 +02:00
0c529c3cb8 feat: Calendar Layout and Function Update
Some checks failed
container-scan / Container Scan (pull_request) Failing after 4m46s
docker-build / docker (pull_request) Failing after 6m33s
2025-06-12 09:04:10 +02:00
679c90cdbd feat: Custom Calendar Toolbar
Add Custom Calendar Toolbar
2025-06-12 09:04:10 +02:00
93b603317a feat: Calendar Update 1.1 2025-06-12 09:03:45 +02:00
a29580708e feat: Calendar Update 2025-06-12 09:03:19 +02:00
d2a0f6125b feat: Basic Calendar without any functions 2025-06-12 09:02:16 +02:00
12 changed files with 213 additions and 620 deletions

View file

@ -9,7 +9,7 @@ jobs:
name: Container Scan
runs-on: docker
container:
image: ghcr.io/di0ik/forgejo_runner_container:main@sha256:c4667f2702c32b91b4c92db2ff20739edd00409a44a691c0598cf4a09a47743a
image: ghcr.io/di0ik/forgejo_runner_container:main@sha256:c66a37d9af18f8f0f34d16890082bc08d842d52ff2a2bc36d993e3d347b498ac
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

View file

@ -13,7 +13,7 @@ jobs:
docker:
runs-on: docker
container:
image: ghcr.io/di0ik/forgejo_runner_container:main@sha256:c4667f2702c32b91b4c92db2ff20739edd00409a44a691c0598cf4a09a47743a
image: ghcr.io/di0ik/forgejo_runner_container:main@sha256:c66a37d9af18f8f0f34d16890082bc08d842d52ff2a2bc36d993e3d347b498ac
steps:
- name: Login to Docker Hub
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3

View file

@ -22,7 +22,6 @@
"@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",
@ -33,7 +32,6 @@
"@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",
@ -45,22 +43,20 @@
"react-big-calendar": "^1.18.0",
"react-datepicker": "^8.4.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.56.4",
"tailwind-merge": "^3.2.0",
"zod": "^3.25.60"
"tailwind-merge": "^3.2.0"
},
"devDependencies": {
"@eslint/eslintrc": "3.3.1",
"@tailwindcss/postcss": "4.1.10",
"@types/node": "22.15.32",
"@types/node": "22.15.31",
"@types/react": "19.1.8",
"@types/react-big-calendar": "^1",
"@types/react-dom": "19.1.6",
"dotenv-cli": "8.0.0",
"eslint": "9.29.0",
"eslint": "9.28.0",
"eslint-config-next": "15.3.3",
"eslint-config-prettier": "10.1.5",
"postcss": "8.5.6",
"postcss": "8.5.5",
"prettier": "3.5.3",
"prisma": "6.9.0",
"tailwindcss": "4.1.10",

View file

@ -6,6 +6,8 @@ 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,
@ -26,12 +28,12 @@ export default async function LoginPage() {
}
return (
<div className='flex flex-col items-center min-h-screen'>
<div className='flex flex-col items-center min-h-screen'>
<div className='fixed top-4 right-4'>
<div className='flex flex-col items-center justify-center h-screen'>
<div className='flex flex-col items-center justify-center h-screen'>
<div className='absolute top-4 right-4'>
<ThemePicker />
</div>
<div className='mt-auto mb-auto'>
<div>
<Card className='w-[350px] max-w-screen;'>
<CardHeader className='grid place-items-center'>
<Logo colorType='colored' logoType='secondary'></Logo>
@ -41,6 +43,8 @@ export default async function LoginPage() {
<Separator className='h-[1px] rounded-sm w-[60%] bg-border' />
{providerMap.length > 0}
{providerMap.map((provider) => (
<SSOLogin
key={provider.id}

View file

@ -1,88 +1,24 @@
import NextAuth, { CredentialsSignin } from 'next-auth';
import NextAuth from 'next-auth';
import { Prisma } from '@/generated/prisma';
import type { Provider } from 'next-auth/providers';
import Credentials from 'next-auth/providers/credentials';
import Authentik from 'next-auth/providers/authentik';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { prisma } from '@/prisma';
import { loginSchema } from './lib/validation/user';
import { ZodError } from 'zod';
class InvalidLoginError extends CredentialsSignin {
constructor(code: string) {
super();
this.code = code;
this.message = code;
}
}
const providers: Provider[] = [
!process.env.DISABLE_PASSWORD_LOGIN &&
Credentials({
credentials: { password: { label: 'Password', type: 'password' } },
async authorize(c) {
if (process.env.NODE_ENV === 'development' && c.password === 'password')
return {
id: 'test',
name: 'Test User',
email: 'test@example.com',
};
if (process.env.DISABLE_PASSWORD_LOGIN) return null;
try {
const { email, password } = await loginSchema.parseAsync(c);
const user = await prisma.user.findFirst({
where: { email },
include: { accounts: true },
});
if (!user)
throw new InvalidLoginError(
'username/email or password is not correct',
);
if (user.accounts[0].provider !== 'credentials') {
throw new InvalidLoginError(
'username/email or password is not correct',
);
}
const passwordsMatch = await (
await import('bcryptjs')
).compare(password, user.password_hash!);
if (!passwordsMatch) {
throw new InvalidLoginError(
'username/email or password is not correct',
);
}
if (!user.emailVerified) {
throw new InvalidLoginError(
'Email not verified. Please check your inbox.',
);
}
return user;
} catch (error) {
if (
error instanceof Prisma?.PrismaClientInitializationError ||
error instanceof Prisma?.PrismaClientKnownRequestError
) {
throw new InvalidLoginError('System error. Please contact support');
}
if (error instanceof ZodError) {
throw new InvalidLoginError(error.issues[0].message);
}
throw error;
}
authorize(c) {
if (c.password !== 'password') return null;
return {
id: 'test',
name: 'Test User',
email: 'test@example.com',
};
},
}),
process.env.AUTH_AUTHENTIK_ID && Authentik,
@ -114,5 +50,4 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
return !!auth?.user;
},
},
debug: process.env.NODE_ENV === 'development',
});

View file

@ -8,8 +8,6 @@ export default function LabeledInput({
value,
name,
autocomplete,
error,
...rest
}: {
type: 'text' | 'email' | 'password';
label: string;
@ -17,8 +15,7 @@ export default function LabeledInput({
value?: string;
name?: string;
autocomplete?: string;
error?: string;
} & React.InputHTMLAttributes<HTMLInputElement>) {
}) {
return (
<div className='grid grid-cols-1 gap-1'>
<Label htmlFor={name}>{label}</Label>
@ -30,9 +27,7 @@ export default function LabeledInput({
id={name}
name={name}
autoComplete={autocomplete}
{...rest}
/>
{error && <p className='text-red-500 text-sm mt-1'>{error}</p>}
</div>
);
}

View file

@ -1,214 +1,107 @@
'use client';
import React, { useState, useRef } from 'react';
import { useRouter } from 'next/navigation';
import { signIn } from '@/auth';
import LabeledInput from '@/components/labeled-input';
import { Button } from '@/components/custom-ui/button';
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';
import { AuthError } from 'next-auth';
import { redirect } from 'next/navigation';
import { useRef, useState } from 'react';
function LoginFormElement({
setIsSignUp,
formRef,
}: {
setIsSignUp: (value: boolean | ((prev: boolean) => boolean)) => void;
formRef?: React.RefObject<HTMLFormElement | null>;
}) {
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 (
<form className='flex flex-col gap-5 w-full' onSubmit={onSubmit}>
<LabeledInput
type='text'
label='E-Mail or Username'
placeholder='What you are known as'
error={formState.errors.email?.message}
{...register('email')}
/>
<LabeledInput
type='password'
label='Password'
placeholder="Let's hope you remember it"
error={formState.errors.password?.message}
{...register('password')}
/>
<div className='grid grid-rows-2 gap-2'>
<Button type='submit' variant='primary'>
Login
</Button>
<Button
type='button'
variant='outline_primary'
onClick={() => {
formRef?.current?.reset();
setIsSignUp((v) => !v);
}}
>
Sign Up
</Button>
</div>
<div>
{formState.errors.root?.message && (
<p className='text-red-500'>{formState.errors.root?.message}</p>
)}
</div>
</form>
);
}
function RegisterFormElement({
setIsSignUp,
formRef,
}: {
setIsSignUp: (value: boolean | ((prev: boolean) => boolean)) => void;
formRef?: React.RefObject<HTMLFormElement | null>;
}) {
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 (
<form
ref={formRef}
className='flex flex-col gap-5 w-full'
onSubmit={onSubmit}
>
<LabeledInput
type='text'
label='First Name'
placeholder='Your first name'
autocomplete='given-name'
error={formState.errors.firstName?.message}
{...register('firstName')}
/>
<LabeledInput
type='text'
label='Last Name'
placeholder='Your last name'
autocomplete='family-name'
error={formState.errors.lastName?.message}
{...register('lastName')}
/>
<LabeledInput
type='email'
label='E-Mail'
placeholder='Your email address'
autocomplete='email'
error={formState.errors.email?.message}
{...register('email')}
/>
<LabeledInput
type='text'
label='Username'
placeholder='Your username'
autocomplete='username'
error={formState.errors.username?.message}
{...register('username')}
/>
<LabeledInput
type='password'
label='Password'
placeholder='Create a password'
autocomplete='new-password'
error={formState.errors.password?.message}
{...register('password')}
/>
<LabeledInput
type='password'
label='Confirm Password'
placeholder='Repeat your password'
autocomplete='new-password'
error={formState.errors.confirmPassword?.message}
{...register('confirmPassword')}
/>
<div className='grid grid-rows-2 gap-2'>
<Button type='submit' variant='primary'>
Sign Up
</Button>
<Button
type='button'
variant='outline_primary'
onClick={() => {
formRef?.current?.reset();
setIsSignUp((v) => !v);
}}
>
Back to Login
</Button>
</div>
<div>
{formState.errors.root?.message && (
<p className='text-red-500'>{formState.errors.root?.message}</p>
)}
</div>
</form>
);
}
const SIGNIN_ERROR_URL = '/error';
export default function LoginForm() {
const [isSignUp, setIsSignUp] = useState(false);
const formRef = useRef<HTMLFormElement>(null);
if (isSignUp) {
return <RegisterFormElement setIsSignUp={setIsSignUp} formRef={formRef} />;
}
return <LoginFormElement setIsSignUp={setIsSignUp} formRef={formRef} />;
return (
<form
ref={formRef}
className='flex flex-col gap-5 w-full'
action={async (formData) => {
'use client';
try {
if (isSignUp) {
// handle sign up logic here
} else {
await signIn('credentials', formData);
}
} catch (error) {
if (error instanceof AuthError) {
return redirect(`${SIGNIN_ERROR_URL}?error=${error.type}`);
}
throw error;
}
}}
>
{isSignUp ? (
<>
<LabeledInput
type='text'
label='First Name'
placeholder='Your first name'
name='firstName'
autocomplete='given-name'
/>
<LabeledInput
type='text'
label='Last Name'
placeholder='Your last name'
name='lastName'
autocomplete='family-name'
/>
<LabeledInput
type='email'
label='E-Mail'
placeholder='Your email address'
name='email'
autocomplete='email'
/>
<LabeledInput
type='password'
label='Password'
placeholder='Create a password'
name='password'
autocomplete='new-password'
/>
<LabeledInput
type='password'
label='Confirm Password'
placeholder='Repeat your password'
name='confirmPassword'
autocomplete='new-password'
/>
</>
) : (
<>
<LabeledInput
type='email'
label='E-Mail or Username'
placeholder='What you are known as'
name='email'
/>
<LabeledInput
type='password'
label='Password'
placeholder="Let's hope you remember it"
name='password'
/>
</>
)}
<div className='grid grid-rows-2 gap-2'>
<Button type='submit' variant='primary'>
{isSignUp ? 'Sign Up' : 'Login'}
</Button>
<Button
type='button'
variant='outline_primary'
onClick={() => {
formRef.current?.reset();
setIsSignUp((v) => !v);
}}
>
{isSignUp ? 'Back to Login' : 'Sign Up'}
</Button>
</div>
</form>
);
}

View file

@ -1,27 +0,0 @@
'use server';
import { z } from 'zod';
import { loginSchema } from '@/lib/validation/user';
import { signIn } from '@/auth';
export async function loginAction(data: z.infer<typeof loginSchema>) {
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.',
};
}
}

View file

@ -1,75 +0,0 @@
'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<typeof registerSchema>) {
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',
};
}
}

View file

@ -1,14 +0,0 @@
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<any, any, any>,
Values extends z.infer<Schema>,
>(schema: Schema, defaultValues?: Values) {
return useForm<Values>({
resolver: zodResolver(schema),
defaultValues,
});
}

View file

@ -1,67 +0,0 @@
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'],
},
);

233
yarn.lock
View file

@ -110,7 +110,7 @@ __metadata:
languageName: node
linkType: hard
"@eslint/config-array@npm:^0.20.1":
"@eslint/config-array@npm:^0.20.0":
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.29.0":
version: 9.29.0
resolution: "@eslint/js@npm:9.29.0"
checksum: 10c0/d0ccf37063fa27a3fae9347cb044f84ca10b5a2fa19ffb2b3fedf3b96843ac1ff359ea9f0ab0e80f2f16fda4cb0dc61ea0fed0375090f050fe0a029e7d6de3a3
"@eslint/js@npm:9.28.0":
version: 9.28.0
resolution: "@eslint/js@npm:9.28.0"
checksum: 10c0/5a6759542490dd9f778993edfbc8d2f55168fd0f7336ceed20fe3870c65499d72fc0bca8d1ae00ea246b0923ea4cba2e0758a8a5507a3506ddcf41c92282abb8
languageName: node
linkType: hard
@ -294,17 +294,6 @@ __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"
@ -1488,13 +1477,6 @@ __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"
@ -1714,12 +1696,12 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:22.15.32":
version: 22.15.32
resolution: "@types/node@npm:22.15.32"
"@types/node@npm:22.15.31":
version: 22.15.31
resolution: "@types/node@npm:22.15.31"
dependencies:
undici-types: "npm:~6.21.0"
checksum: 10c0/63a2fa52adf1134d1b3bee8b1862d4b8e4550fffc190551068d3d41a41d9e5c0c8f1cb81faa18767b260637360f662115c26c5e4e7718868ead40c4a57cbc0e3
checksum: 10c0/ef7d5dc890da41cfd554d35ab8998bc18be9e3a0caa642e720599ac4410a94a4879766e52b3c9cafa06c66b7b8aebdc51f322cf67df23a6489927890196a316d
languageName: node
linkType: hard
@ -1767,104 +1749,104 @@ __metadata:
linkType: hard
"@typescript-eslint/eslint-plugin@npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
version: 8.34.1
resolution: "@typescript-eslint/eslint-plugin@npm:8.34.1"
version: 8.34.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.34.0"
dependencies:
"@eslint-community/regexpp": "npm:^4.10.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"
"@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"
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.1
"@typescript-eslint/parser": ^8.34.0
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.9.0"
checksum: 10c0/f1c9f25e4fe4b59622312dfa0ca1e80fa7945296ba5c04362a5fda084a17e23a6b98dac331f5a13bcb1ba34a2b598a3f5c41aa288f0c51fe60196e912954e56a
checksum: 10c0/905a05d15f4b0367838ec445f9890321d87470198bf7a589278fc0f38c82cf3ccc1efce4acd3c9c94ee6149d5579ef58606fb7c50f4db50c830de65af8c27c6d
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
version: 8.34.1
resolution: "@typescript-eslint/parser@npm:8.34.1"
version: 8.34.0
resolution: "@typescript-eslint/parser@npm:8.34.0"
dependencies:
"@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"
"@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"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.9.0"
checksum: 10c0/bf8070245d53ef6926ff6630bb72f245923f545304e2a61508fb944802a83fed8eab961d9010956d07999d51afdfbbec82aea9d6185295551a7c17c00d759183
checksum: 10c0/a829be00ea3455c1e50983c8b44476fbfc9329d019764e407c4d591a95dbd168f83f13e309751242bb4fdc02f89cb51ca5cdc912a12b10f69eebcb1c46dcc39b
languageName: node
linkType: hard
"@typescript-eslint/project-service@npm:8.34.1":
version: 8.34.1
resolution: "@typescript-eslint/project-service@npm:8.34.1"
"@typescript-eslint/project-service@npm:8.34.0":
version: 8.34.0
resolution: "@typescript-eslint/project-service@npm:8.34.0"
dependencies:
"@typescript-eslint/tsconfig-utils": "npm:^8.34.1"
"@typescript-eslint/types": "npm:^8.34.1"
"@typescript-eslint/tsconfig-utils": "npm:^8.34.0"
"@typescript-eslint/types": "npm:^8.34.0"
debug: "npm:^4.3.4"
peerDependencies:
typescript: ">=4.8.4 <5.9.0"
checksum: 10c0/9333a890625f6777054db17a6b299281ae7502bb7615261d15b885a75b8cf65fc91591389c93b37ecd14b651d8e94851dac8718e5dcc8ed0600533535dae855c
checksum: 10c0/88e64b8daf7db9603277fcbeb9e585e70ec6d6e34fa10d4b60f421e48081cc7c1f6acb01e1ee9dd95e10c0601f164c1defbfe6c9d1edc9822089bb72dbb0fc80
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.34.1":
version: 8.34.1
resolution: "@typescript-eslint/scope-manager@npm:8.34.1"
"@typescript-eslint/scope-manager@npm:8.34.0":
version: 8.34.0
resolution: "@typescript-eslint/scope-manager@npm:8.34.0"
dependencies:
"@typescript-eslint/types": "npm:8.34.1"
"@typescript-eslint/visitor-keys": "npm:8.34.1"
checksum: 10c0/2af608fa3900f4726322e33bf4f3a376fdace3ac0f310cf7d9256bbc2905c3896138176a47dd195d2c2229f27fe43f5deb4bc7729db2eb18389926dedea78077
"@typescript-eslint/types": "npm:8.34.0"
"@typescript-eslint/visitor-keys": "npm:8.34.0"
checksum: 10c0/35af36bddc4c227cb0bac42192c40b38179ced30866b6aac642781e21c3f3b1c72051eb4f685d7c99517c3296dd6ba83dd8360e4072e8dcf604aae266eece1b4
languageName: node
linkType: hard
"@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"
"@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"
peerDependencies:
typescript: ">=4.8.4 <5.9.0"
checksum: 10c0/8d1ead8b7c279b48e2ed96f083ec119a9aeea1ca9cdd40576ec271b996b9fd8cfa0ddb0aafbb4e14bc27fc62c69c5be66d39b1de68eab9ddd7f1861da267423d
checksum: 10c0/98246f89d169d3feb453a6a8552c51d10225cb00c4ff1501549b7846e564ad0e218b644cd94ce779dceed07dcb9035c53fd32186b4c0223b7b2a1f7295b120c3
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.34.1":
version: 8.34.1
resolution: "@typescript-eslint/type-utils@npm:8.34.1"
"@typescript-eslint/type-utils@npm:8.34.0":
version: 8.34.0
resolution: "@typescript-eslint/type-utils@npm:8.34.0"
dependencies:
"@typescript-eslint/typescript-estree": "npm:8.34.1"
"@typescript-eslint/utils": "npm:8.34.1"
"@typescript-eslint/typescript-estree": "npm:8.34.0"
"@typescript-eslint/utils": "npm:8.34.0"
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/502a2cdfe47f1f34206c747b5a70e0242dd99f570511db3dda9c5f999d9abadfbbb1dfa82a1fa437a1689d232715412e61c97d95f19c9314ba5ad23196b4096d
checksum: 10c0/7c25d7f4186411190142390467160e81384d400cfb21183d8a305991c723da0a74e5528cdce30b5f2cb6d9d2f6af7c0981c20c18b45fc084b35632429270ae80
languageName: node
linkType: hard
"@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
"@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
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.34.1":
version: 8.34.1
resolution: "@typescript-eslint/typescript-estree@npm:8.34.1"
"@typescript-eslint/typescript-estree@npm:8.34.0":
version: 8.34.0
resolution: "@typescript-eslint/typescript-estree@npm:8.34.0"
dependencies:
"@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"
"@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"
debug: "npm:^4.3.4"
fast-glob: "npm:^3.3.2"
is-glob: "npm:^4.0.3"
@ -1873,32 +1855,32 @@ __metadata:
ts-api-utils: "npm:^2.1.0"
peerDependencies:
typescript: ">=4.8.4 <5.9.0"
checksum: 10c0/4ee7249db91b9840361f34f80b7b6d646a3af159c7298d79a33d8a11c98792fd3a395343e5e17e0fa29529e8f0113bac8baadcef90d1e140bd736a48f0485042
checksum: 10c0/e678982b0009e895aee2b4ccc55bb9ea5473a32e846a97c63d0c6a978c72e1a29e506e6a5f9dda45e9b7803e6c3e3abcdf4c316af1c59146abef4e10e0e94129
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.34.1":
version: 8.34.1
resolution: "@typescript-eslint/utils@npm:8.34.1"
"@typescript-eslint/utils@npm:8.34.0":
version: 8.34.0
resolution: "@typescript-eslint/utils@npm:8.34.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.7.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/scope-manager": "npm:8.34.0"
"@typescript-eslint/types": "npm:8.34.0"
"@typescript-eslint/typescript-estree": "npm:8.34.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.9.0"
checksum: 10c0/e3085877f7940c02a37653e6bc52ac6cde115e755b1f788fe4331202f371b3421cc4d0878c7d3eb054e14e9b3a064496a707a73eac471cb2b73593b9e9d4b998
checksum: 10c0/d759cf6f1b1b23d7d8ab922345e7b68b7c829f4bad841164312cfa3a3e8e818b962dd0d96c1aca7fd7c10248d56538d9714df5f3cfec9f159ca0a139feac60b9
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.34.1":
version: 8.34.1
resolution: "@typescript-eslint/visitor-keys@npm:8.34.1"
"@typescript-eslint/visitor-keys@npm:8.34.0":
version: 8.34.0
resolution: "@typescript-eslint/visitor-keys@npm:8.34.0"
dependencies:
"@typescript-eslint/types": "npm:8.34.1"
eslint-visitor-keys: "npm:^4.2.1"
checksum: 10c0/0e5a9b3d93905d16d3cf8cb5fb346dcc6f760482eb7d0ac209aefc09a32f78ef28a687634df6ad08e81fb3e1083e8805f34472de6bbc501c0105ad654d518f40
"@typescript-eslint/types": "npm:8.34.0"
eslint-visitor-keys: "npm:^4.2.0"
checksum: 10c0/d50997e921a178589913d08ffe14d02eba40666c90bdc0c9751f2b87ce500598f64027e2d866dfc975647b2f8b907158503d0722d6b1976c8f1cf5dd8e1d6d69
languageName: node
linkType: hard
@ -2250,15 +2232,6 @@ __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"
@ -2336,9 +2309,9 @@ __metadata:
linkType: hard
"caniuse-lite@npm:^1.0.30001579":
version: 1.0.30001723
resolution: "caniuse-lite@npm:1.0.30001723"
checksum: 10c0/e019503061759b96017c4d27ddd7ca1b48533eabcd0431b51d2e3156f99f6b031075e46c279c0db63424cdfc874bba992caec2db51b922a0f945e686246886f6
version: 1.0.30001722
resolution: "caniuse-lite@npm:1.0.30001722"
checksum: 10c0/a1e344c392e0b138f0b215525108877d725665217a5e8e7504897e30379a5a9b858bc44799ccc0e19f4a64bf1e05c15b4a58eb1c9032293f894aa24e8d9f470f
languageName: node
linkType: hard
@ -2978,7 +2951,7 @@ __metadata:
languageName: node
linkType: hard
"eslint-scope@npm:^8.4.0":
"eslint-scope@npm:^8.3.0":
version: 8.4.0
resolution: "eslint-scope@npm:8.4.0"
dependencies:
@ -2995,24 +2968,24 @@ __metadata:
languageName: node
linkType: hard
"eslint-visitor-keys@npm:^4.2.1":
"eslint-visitor-keys@npm:^4.2.0, 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.29.0":
version: 9.29.0
resolution: "eslint@npm:9.29.0"
"eslint@npm:9.28.0":
version: 9.28.0
resolution: "eslint@npm:9.28.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.2.0"
"@eslint-community/regexpp": "npm:^4.12.1"
"@eslint/config-array": "npm:^0.20.1"
"@eslint/config-array": "npm:^0.20.0"
"@eslint/config-helpers": "npm:^0.2.1"
"@eslint/core": "npm:^0.14.0"
"@eslint/eslintrc": "npm:^3.3.1"
"@eslint/js": "npm:9.29.0"
"@eslint/js": "npm:9.28.0"
"@eslint/plugin-kit": "npm:^0.3.1"
"@humanfs/node": "npm:^0.16.6"
"@humanwhocodes/module-importer": "npm:^1.0.1"
@ -3024,9 +2997,9 @@ __metadata:
cross-spawn: "npm:^7.0.6"
debug: "npm:^4.3.2"
escape-string-regexp: "npm:^4.0.0"
eslint-scope: "npm:^8.4.0"
eslint-visitor-keys: "npm:^4.2.1"
espree: "npm:^10.4.0"
eslint-scope: "npm:^8.3.0"
eslint-visitor-keys: "npm:^4.2.0"
espree: "npm:^10.3.0"
esquery: "npm:^1.5.0"
esutils: "npm:^2.0.2"
fast-deep-equal: "npm:^3.1.3"
@ -3048,11 +3021,11 @@ __metadata:
optional: true
bin:
eslint: bin/eslint.js
checksum: 10c0/75e3f841e0f8b0fa93dbb2ba6ae538bd8b611c3654117bc3dadf90bb009923dfd2c15ec2948dc6e6b8b571317cc125c5cceb9255da8cd644ee740020df645dd8
checksum: 10c0/513ea7e69d88a0905d4ed35cef3a8f31ebce7ca9f2cdbda3474495c63ad6831d52357aad65094be7a144d6e51850980ced7d25efb807e8ab06a427241f7cd730
languageName: node
linkType: hard
"espree@npm:^10.0.1, espree@npm:^10.4.0":
"espree@npm:^10.0.1, espree@npm:^10.3.0":
version: 10.4.0
resolution: "espree@npm:10.4.0"
dependencies:
@ -4054,7 +4027,6 @@ __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"
@ -4066,35 +4038,32 @@ __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.32"
"@types/node": "npm:22.15.31"
"@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.29.0"
eslint: "npm:9.28.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.6"
postcss: "npm:8.5.5"
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
@ -4514,14 +4483,14 @@ __metadata:
languageName: node
linkType: hard
"postcss@npm:8.5.6, postcss@npm:^8.4.41":
version: 8.5.6
resolution: "postcss@npm:8.5.6"
"postcss@npm:8.5.5, postcss@npm:^8.4.41":
version: 8.5.5
resolution: "postcss@npm:8.5.5"
dependencies:
nanoid: "npm:^3.3.11"
picocolors: "npm:^1.1.1"
source-map-js: "npm:^1.2.1"
checksum: 10c0/5127cc7c91ed7a133a1b7318012d8bfa112da9ef092dddf369ae699a1f10ebbd89b1b9f25f3228795b84585c72aabd5ced5fc11f2ba467eedf7b081a66fad024
checksum: 10c0/6415873fab84de05c2d8fd18f72ea6654bca437bb4b9f02ca819c438501e4b3a450023e575e17587c6eaa5bedddaaa4dad3af210f5cf166e30cec09cac58baf8
languageName: node
linkType: hard
@ -4651,15 +4620,6 @@ __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"
@ -5683,10 +5643,3 @@ __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