mirror of
https://github.com/ForFarmTeam/ForFarm.git
synced 2025-12-19 14:04:08 +01:00
feat: add login - sign up submit function
This commit is contained in:
parent
125a6a7018
commit
98d4765e0d
9
frontend/app/auth/signin/google-oauth.tsx
Normal file
9
frontend/app/auth/signin/google-oauth.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
export function GoogleSigninButton() {
|
||||||
|
return (
|
||||||
|
<div className="flex w-1/3 justify-center rounded-full border-2 border-border bg-gray-100 hover:bg-gray-300 duration-300 cursor-pointer ">
|
||||||
|
<Image src="/google-logo.png" alt="Google Logo" width={35} height={35} className="object-contain" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,3 +1,10 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
|
import { signInSchema } from "@/schema/authSchema";
|
||||||
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
@ -6,19 +13,67 @@ import ForgotPasswordModal from "./forgot-password-modal";
|
|||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { GoogleSigninButton } from "./google-oauth";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
export default function SigninPage() {
|
export default function SigninPage() {
|
||||||
|
const [serverError, setServerError] = useState(null);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm({
|
||||||
|
resolver: zodResolver(signInSchema),
|
||||||
|
defaultValues: {
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = async (values: z.infer<typeof signInSchema>) => {
|
||||||
|
setServerError(null); // reset previous errors
|
||||||
|
try {
|
||||||
|
const response = await fetch("http://localhost:8000/auth/login", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
Email: values.email,
|
||||||
|
Password: values.password,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(data.message || "Failed to log in.");
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem("token", data.Token);
|
||||||
|
localStorage.setItem("user", values.email);
|
||||||
|
|
||||||
|
router.push("/setup");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error logging in:", error);
|
||||||
|
setServerError(error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="grid grid-cols-[0.7fr_1.2fr] h-screen overflow-hidden">
|
<div className="grid grid-cols-[0.7fr_1.2fr] h-screen overflow-hidden">
|
||||||
<div className="flex bg-[url('/plant-background.jpeg')] bg-cover bg-center"></div>
|
<div className="flex bg-[url('/plant-background.jpeg')] bg-cover bg-center"></div>
|
||||||
|
|
||||||
<div className="flex justify-center items-center">
|
<div className="flex justify-center items-center">
|
||||||
{/* login box */}
|
|
||||||
<div className="container px-[25%]">
|
<div className="container px-[25%]">
|
||||||
<div className="flex flex-col justify-center items-center">
|
<div className="flex flex-col justify-center items-center">
|
||||||
<span>
|
<span>
|
||||||
<Image src={`/forfarm-logo.png`} alt="Forfarm" width={150} height={150}></Image>
|
<Image src="/forfarm-logo.png" alt="Forfarm" width={150} height={150} />
|
||||||
</span>
|
</span>
|
||||||
<h1 className="text-3xl font-semibold">Welcome back.</h1>
|
<h1 className="text-3xl font-semibold">Welcome back.</h1>
|
||||||
<div className="flex whitespace-nowrap gap-x-2 mt-2">
|
<div className="flex whitespace-nowrap gap-x-2 mt-2">
|
||||||
@ -31,27 +86,28 @@ export default function SigninPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col mt-4">
|
{/* Sign in form */}
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col mt-4">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="email">Email</Label>
|
<Label htmlFor="email">Email</Label>
|
||||||
<Input type="email" id="email" placeholder="Email" />
|
<Input type="email" id="email" placeholder="Email" {...register("email")} />
|
||||||
|
{errors.email && <p className="text-red-600 text-sm">{errors.email.message}</p>}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<div>
|
<Label htmlFor="password">Password</Label>
|
||||||
<Label htmlFor="password">Password</Label>
|
<Input type="password" id="password" placeholder="Password" {...register("password")} />
|
||||||
<Input type="empasswordail" id="password" placeholder="Password" />
|
{errors.password && <p className="text-red-600 text-sm">{errors.password.message}</p>}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button className="mt-5 rounded-full">Log in</Button>
|
<Button type="submit" className="mt-5 rounded-full">
|
||||||
</div>
|
Log in
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
<div id="signin-footer" className="flex justify-between mt-5">
|
<div id="signin-footer" className="flex justify-between mt-5">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Checkbox id="terms" />
|
<Checkbox id="terms" />
|
||||||
<label
|
<label htmlFor="terms" className="text-sm font-medium leading-none">
|
||||||
htmlFor="terms"
|
|
||||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
|
||||||
Remember me
|
Remember me
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -60,12 +116,8 @@ export default function SigninPage() {
|
|||||||
|
|
||||||
<div className="my-5">
|
<div className="my-5">
|
||||||
<p className="text-sm">Or log in with</p>
|
<p className="text-sm">Or log in with</p>
|
||||||
{/* OAUTH */}
|
|
||||||
<div className="flex flex-col gap-x-5 mt-3">
|
<div className="flex flex-col gap-x-5 mt-3">
|
||||||
{/* Google */}
|
<GoogleSigninButton />
|
||||||
<div className="flex w-1/3 justify-center rounded-full border-2 border-border bg-gray-100 hover:bg-gray-300 duration-300 cursor-pointer ">
|
|
||||||
<Image src="/google-logo.png" alt="Google Logo" width={35} height={35} className="object-contain" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,62 +1,133 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
import { signUpSchema } from "@/schema/authSchema";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
export default function SignupPage() {
|
export default function SignupPage() {
|
||||||
|
const [serverError, setServerError] = useState(null);
|
||||||
|
const [successMessage, setSuccessMessage] = useState(null);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<z.infer<typeof signUpSchema>>({
|
||||||
|
resolver: zodResolver(signUpSchema),
|
||||||
|
defaultValues: {
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
confirmPassword: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = async (values: z.infer<typeof signUpSchema>) => {
|
||||||
|
setServerError(null);
|
||||||
|
setSuccessMessage(null);
|
||||||
|
try {
|
||||||
|
const response = await fetch("http://localhost:8000/auth/register", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
Email: values.email,
|
||||||
|
Password: values.password,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(data.message || "Failed to register.");
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem("token", data.token);
|
||||||
|
localStorage.setItem("user", values.email);
|
||||||
|
|
||||||
|
// Assume registration returns a token or user data.
|
||||||
|
console.log("Registration successful:", data);
|
||||||
|
setSuccessMessage("Registration successful! You can now sign in.");
|
||||||
|
|
||||||
|
router.push("/setup");
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error during registration:", error);
|
||||||
|
setServerError(error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="grid grid-cols-[0.7fr_1.2fr] h-screen overflow-hidden">
|
<div className="grid grid-cols-[0.7fr_1.2fr] h-screen overflow-hidden">
|
||||||
<div className="flex bg-[url('/plant-background.jpeg')] bg-cover bg-center"></div>
|
<div className="flex bg-[url('/plant-background.jpeg')] bg-cover bg-center"></div>
|
||||||
|
|
||||||
<div className="flex justify-center items-center">
|
<div className="flex justify-center items-center">
|
||||||
{/* login box */}
|
|
||||||
<div className="container px-[25%]">
|
<div className="container px-[25%]">
|
||||||
<div className="flex flex-col justify-center items-center">
|
<div className="flex flex-col justify-center items-center">
|
||||||
<span>
|
<span>
|
||||||
<Image src={`/forfarm-logo.png`} alt="Forfarm" width={150} height={150}></Image>
|
<Image src="/forfarm-logo.png" alt="Forfarm" width={150} height={150} />
|
||||||
</span>
|
</span>
|
||||||
<h1 className="text-3xl font-semibold">Hi! Welcome</h1>
|
<h1 className="text-3xl font-semibold">Hi! Welcome</h1>
|
||||||
<div className="flex whitespace-nowrap gap-x-2 mt-2">
|
<div className="flex whitespace-nowrap gap-x-2 mt-2">
|
||||||
<span className="text-md">Already have accounts?</span>
|
<span className="text-md">Already have an account?</span>
|
||||||
<span className="text-green-600">
|
<span className="text-green-600">
|
||||||
<Link href="signin" className="underline">
|
<Link href="/auth/signin" className="underline">
|
||||||
Sign in
|
Sign in
|
||||||
</Link>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col mt-4">
|
{/* Signup form */}
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col mt-4">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="email">Email</Label>
|
<Label htmlFor="email">Email</Label>
|
||||||
<Input type="email" id="email" placeholder="Email" />
|
<Input type="email" id="email" placeholder="Email" {...register("email")} />
|
||||||
|
{errors.email && <p className="text-red-600 text-sm">{errors.email.message}</p>}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<div>
|
<Label htmlFor="password">Password</Label>
|
||||||
<Label htmlFor="password">Password</Label>
|
<Input type="password" id="password" placeholder="Password" {...register("password")} />
|
||||||
<Input type="empasswordail" id="password" placeholder="Password" />
|
{errors.password && <p className="text-red-600 text-sm">{errors.password.message}</p>}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<div>
|
<Label htmlFor="confirmPassword">Confirm Password</Label>
|
||||||
<Label htmlFor="password">Confirm Password</Label>
|
<Input
|
||||||
<Input type="empasswordail" id="password" placeholder="Password" />
|
type="password"
|
||||||
</div>
|
id="confirmPassword"
|
||||||
|
placeholder="Confirm Password"
|
||||||
|
{...register("confirmPassword")}
|
||||||
|
/>
|
||||||
|
{errors.confirmPassword && <p className="text-red-600 text-sm">{errors.confirmPassword.message}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button className="mt-5 rounded-full">Sign up</Button>
|
{serverError && <p className="text-red-600 mt-2 text-sm">{serverError}</p>}
|
||||||
</div>
|
{successMessage && <p className="text-green-600 mt-2 text-sm">{successMessage}</p>}
|
||||||
|
|
||||||
|
<Button type="submit" className="mt-5 rounded-full">
|
||||||
|
Sign up
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
<div className="my-5">
|
<div className="my-5">
|
||||||
<p className="text-sm">Or log in with</p>
|
<p className="text-sm">Or sign up with</p>
|
||||||
{/* OAUTH */}
|
|
||||||
<div className="flex flex-col gap-x-5 mt-3">
|
<div className="flex flex-col gap-x-5 mt-3">
|
||||||
{/* Google */}
|
{/* Google OAuth button or additional providers */}
|
||||||
<div className="flex w-1/3 justify-center rounded-full border-2 border-border bg-gray-100 hover:bg-gray-300 duration-300 cursor-pointer ">
|
<div className="flex w-1/3 justify-center rounded-full border-2 border-border bg-gray-100 hover:bg-gray-300 duration-300 cursor-pointer">
|
||||||
<Image src="/google-logo.png" alt="Google Logo" width={35} height={35} className="object-contain" />
|
<Image src="/google-logo.png" alt="Google Logo" width={35} height={35} className="object-contain" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import { Open_Sans, Roboto_Mono } from "next/font/google";
|
|||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { ThemeProvider } from "@/components/theme-provider";
|
import { ThemeProvider } from "@/components/theme-provider";
|
||||||
|
|
||||||
|
import { SessionProvider } from "@/context/SessionContext";
|
||||||
|
|
||||||
const openSans = Open_Sans({
|
const openSans = Open_Sans({
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
display: "swap",
|
display: "swap",
|
||||||
@ -33,13 +35,15 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<head />
|
<head />
|
||||||
<body className={`${openSans.variable} ${robotoMono.variable} font-sans antialiased`}>
|
<SessionProvider>
|
||||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
<body className={`${openSans.variable} ${robotoMono.variable} font-sans antialiased`}>
|
||||||
<div className="relative flex min-h-screen flex-col">
|
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||||
<div className="flex-1 bg-background">{children}</div>
|
<div className="relative flex min-h-screen flex-col">
|
||||||
</div>
|
<div className="flex-1 bg-background">{children}</div>
|
||||||
</ThemeProvider>
|
</div>
|
||||||
</body>
|
</ThemeProvider>
|
||||||
|
</body>
|
||||||
|
</SessionProvider>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
12
frontend/components/SessionProviderClient.tsx
Normal file
12
frontend/components/SessionProviderClient.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { ReactNode } from "react";
|
||||||
|
import { SessionProvider } from "next-auth/react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SessesionProviderClient(props: Props) {
|
||||||
|
return <SessionProvider>{props.children}</SessionProvider>;
|
||||||
|
}
|
||||||
178
frontend/components/ui/form.tsx
Normal file
178
frontend/components/ui/form.tsx
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
ControllerProps,
|
||||||
|
FieldPath,
|
||||||
|
FieldValues,
|
||||||
|
FormProvider,
|
||||||
|
useFormContext,
|
||||||
|
} from "react-hook-form"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
|
||||||
|
const Form = FormProvider
|
||||||
|
|
||||||
|
type FormFieldContextValue<
|
||||||
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||||
|
> = {
|
||||||
|
name: TName
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||||
|
{} as FormFieldContextValue
|
||||||
|
)
|
||||||
|
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FormItemContextValue = {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||||
|
{} as FormItemContextValue
|
||||||
|
)
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
const FormLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const { error, formItemId } = useFormField()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn(error && "text-destructive", className)}
|
||||||
|
htmlFor={formItemId}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
FormLabel.displayName = "FormLabel"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
const FormDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const { formDescriptionId } = useFormField()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
ref={ref}
|
||||||
|
id={formDescriptionId}
|
||||||
|
className={cn("text-[0.8rem] text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
FormDescription.displayName = "FormDescription"
|
||||||
|
|
||||||
|
const FormMessage = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, children, ...props }, ref) => {
|
||||||
|
const { error, formMessageId } = useFormField()
|
||||||
|
const body = error ? String(error?.message) : children
|
||||||
|
|
||||||
|
if (!body) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
ref={ref}
|
||||||
|
id={formMessageId}
|
||||||
|
className={cn("text-[0.8rem] font-medium text-destructive", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
FormMessage.displayName = "FormMessage"
|
||||||
|
|
||||||
|
export {
|
||||||
|
useFormField,
|
||||||
|
Form,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormMessage,
|
||||||
|
FormField,
|
||||||
|
}
|
||||||
56
frontend/context/SessionContext.tsx
Normal file
56
frontend/context/SessionContext.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { createContext, useContext, useState, useEffect, ReactNode } from "react";
|
||||||
|
|
||||||
|
interface SessionContextType {
|
||||||
|
token: string | null;
|
||||||
|
user: any | null;
|
||||||
|
setToken: (token: string | null) => void;
|
||||||
|
setUser: (user: any | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SessionContext = createContext<SessionContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
interface SessionProviderProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SessionProvider({ children }: SessionProviderProps) {
|
||||||
|
const [token, setTokenState] = useState<string | null>(null);
|
||||||
|
const [user, setUserState] = useState<any | null>(null);
|
||||||
|
|
||||||
|
const setToken = (newToken: string | null) => {
|
||||||
|
if (newToken) {
|
||||||
|
localStorage.setItem("token", newToken);
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem("token");
|
||||||
|
}
|
||||||
|
setTokenState(newToken);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setUser = (newUser: any | null) => {
|
||||||
|
if (newUser) {
|
||||||
|
localStorage.setItem("user", JSON.stringify(newUser));
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem("user");
|
||||||
|
}
|
||||||
|
setUserState(newUser);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const storedToken = localStorage.getItem("token");
|
||||||
|
const storedUser = localStorage.getItem("user");
|
||||||
|
if (storedToken) {
|
||||||
|
setTokenState(storedToken);
|
||||||
|
}
|
||||||
|
if (storedUser) {
|
||||||
|
try {
|
||||||
|
setUserState(JSON.parse(storedUser));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to parse stored user.", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <SessionContext.Provider value={{ token, user, setToken, setUser }}>{children}</SessionContext.Provider>;
|
||||||
|
}
|
||||||
12
frontend/hooks/useSession.tsx
Normal file
12
frontend/hooks/useSession.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { SessionContext } from "next-auth/react";
|
||||||
|
import { useContext } from "react";
|
||||||
|
|
||||||
|
export function useSession() {
|
||||||
|
const context = useContext(SessionContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useSession must be used within a SessionProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@hookform/resolvers": "^4.0.0",
|
||||||
"@radix-ui/react-avatar": "^1.1.3",
|
"@radix-ui/react-avatar": "^1.1.3",
|
||||||
"@radix-ui/react-checkbox": "^1.1.4",
|
"@radix-ui/react-checkbox": "^1.1.4",
|
||||||
"@radix-ui/react-dialog": "^1.1.6",
|
"@radix-ui/react-dialog": "^1.1.6",
|
||||||
@ -23,6 +24,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.475.0",
|
"lucide-react": "^0.475.0",
|
||||||
"next": "15.1.0",
|
"next": "15.1.0",
|
||||||
|
"next-auth": "^4.24.11",
|
||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
|||||||
@ -8,6 +8,9 @@ importers:
|
|||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@hookform/resolvers':
|
||||||
|
specifier: ^4.0.0
|
||||||
|
version: 4.0.0(react-hook-form@7.54.2(react@19.0.0))
|
||||||
'@radix-ui/react-avatar':
|
'@radix-ui/react-avatar':
|
||||||
specifier: ^1.1.3
|
specifier: ^1.1.3
|
||||||
version: 1.1.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
version: 1.1.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
@ -50,6 +53,9 @@ importers:
|
|||||||
next:
|
next:
|
||||||
specifier: 15.1.0
|
specifier: 15.1.0
|
||||||
version: 15.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
version: 15.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
|
next-auth:
|
||||||
|
specifier: ^4.24.11
|
||||||
|
version: 4.24.11(next@15.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
next-themes:
|
next-themes:
|
||||||
specifier: ^0.4.4
|
specifier: ^0.4.4
|
||||||
version: 0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
version: 0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
@ -106,6 +112,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
'@babel/runtime@7.26.7':
|
||||||
|
resolution: {integrity: sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@emnapi/runtime@1.3.1':
|
'@emnapi/runtime@1.3.1':
|
||||||
resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==}
|
resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==}
|
||||||
|
|
||||||
@ -162,6 +172,11 @@ packages:
|
|||||||
'@floating-ui/utils@0.2.9':
|
'@floating-ui/utils@0.2.9':
|
||||||
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
|
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
|
||||||
|
|
||||||
|
'@hookform/resolvers@4.0.0':
|
||||||
|
resolution: {integrity: sha512-93ZueVlTaeMF0pRbrLbcnzrxeb2mGA/xyO3RgfrsKs5OCtcfjoWcdjBJm+N7096Jfg/JYlGPjuyQCgqVEodSTg==}
|
||||||
|
peerDependencies:
|
||||||
|
react-hook-form: ^7.0.0
|
||||||
|
|
||||||
'@humanfs/core@0.19.1':
|
'@humanfs/core@0.19.1':
|
||||||
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
||||||
engines: {node: '>=18.18.0'}
|
engines: {node: '>=18.18.0'}
|
||||||
@ -379,6 +394,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
|
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
|
||||||
engines: {node: '>=12.4.0'}
|
engines: {node: '>=12.4.0'}
|
||||||
|
|
||||||
|
'@panva/hkdf@1.2.1':
|
||||||
|
resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==}
|
||||||
|
|
||||||
'@pkgjs/parseargs@0.11.0':
|
'@pkgjs/parseargs@0.11.0':
|
||||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@ -1016,6 +1034,10 @@ packages:
|
|||||||
concat-map@0.0.1:
|
concat-map@0.0.1:
|
||||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||||
|
|
||||||
|
cookie@0.7.2:
|
||||||
|
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@ -1538,6 +1560,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
|
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
jose@4.15.9:
|
||||||
|
resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==}
|
||||||
|
|
||||||
js-tokens@4.0.0:
|
js-tokens@4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
@ -1603,6 +1628,10 @@ packages:
|
|||||||
lru-cache@10.4.3:
|
lru-cache@10.4.3:
|
||||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||||
|
|
||||||
|
lru-cache@6.0.0:
|
||||||
|
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
lucide-react@0.475.0:
|
lucide-react@0.475.0:
|
||||||
resolution: {integrity: sha512-NJzvVu1HwFVeZ+Gwq2q00KygM1aBhy/ZrhY9FsAgJtpB+E4R7uxRk9M2iKvHa6/vNxZydIB59htha4c2vvwvVg==}
|
resolution: {integrity: sha512-NJzvVu1HwFVeZ+Gwq2q00KygM1aBhy/ZrhY9FsAgJtpB+E4R7uxRk9M2iKvHa6/vNxZydIB59htha4c2vvwvVg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1648,6 +1677,20 @@ packages:
|
|||||||
natural-compare@1.4.0:
|
natural-compare@1.4.0:
|
||||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||||
|
|
||||||
|
next-auth@4.24.11:
|
||||||
|
resolution: {integrity: sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@auth/core': 0.34.2
|
||||||
|
next: ^12.2.5 || ^13 || ^14 || ^15
|
||||||
|
nodemailer: ^6.6.5
|
||||||
|
react: ^17.0.2 || ^18 || ^19
|
||||||
|
react-dom: ^17.0.2 || ^18 || ^19
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@auth/core':
|
||||||
|
optional: true
|
||||||
|
nodemailer:
|
||||||
|
optional: true
|
||||||
|
|
||||||
next-themes@0.4.4:
|
next-themes@0.4.4:
|
||||||
resolution: {integrity: sha512-LDQ2qIOJF0VnuVrrMSMLrWGjRMkq+0mpgl6e0juCLqdJ+oo8Q84JRWT6Wh11VDQKkMMe+dVzDKLWs5n87T+PkQ==}
|
resolution: {integrity: sha512-LDQ2qIOJF0VnuVrrMSMLrWGjRMkq+0mpgl6e0juCLqdJ+oo8Q84JRWT6Wh11VDQKkMMe+dVzDKLWs5n87T+PkQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1679,10 +1722,17 @@ packages:
|
|||||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
oauth@0.9.15:
|
||||||
|
resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==}
|
||||||
|
|
||||||
object-assign@4.1.1:
|
object-assign@4.1.1:
|
||||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
object-hash@2.2.0:
|
||||||
|
resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
object-hash@3.0.0:
|
object-hash@3.0.0:
|
||||||
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
|
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@ -1715,6 +1765,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
oidc-token-hash@5.0.3:
|
||||||
|
resolution: {integrity: sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==}
|
||||||
|
engines: {node: ^10.13.0 || >=12.0.0}
|
||||||
|
|
||||||
|
openid-client@5.7.1:
|
||||||
|
resolution: {integrity: sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==}
|
||||||
|
|
||||||
optionator@0.9.4:
|
optionator@0.9.4:
|
||||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
@ -1821,10 +1878,21 @@ packages:
|
|||||||
resolution: {integrity: sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==}
|
resolution: {integrity: sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
|
||||||
|
preact-render-to-string@5.2.6:
|
||||||
|
resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==}
|
||||||
|
peerDependencies:
|
||||||
|
preact: '>=10'
|
||||||
|
|
||||||
|
preact@10.25.4:
|
||||||
|
resolution: {integrity: sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==}
|
||||||
|
|
||||||
prelude-ls@1.2.1:
|
prelude-ls@1.2.1:
|
||||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
pretty-format@3.8.0:
|
||||||
|
resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==}
|
||||||
|
|
||||||
prop-types@15.8.1:
|
prop-types@15.8.1:
|
||||||
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
||||||
|
|
||||||
@ -1894,6 +1962,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
|
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
regenerator-runtime@0.14.1:
|
||||||
|
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
||||||
|
|
||||||
regexp.prototype.flags@1.5.4:
|
regexp.prototype.flags@1.5.4:
|
||||||
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -2177,6 +2248,10 @@ packages:
|
|||||||
util-deprecate@1.0.2:
|
util-deprecate@1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
|
||||||
|
uuid@8.3.2:
|
||||||
|
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
which-boxed-primitive@1.1.1:
|
which-boxed-primitive@1.1.1:
|
||||||
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
|
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -2210,6 +2285,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
yallist@4.0.0:
|
||||||
|
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||||
|
|
||||||
yaml@2.7.0:
|
yaml@2.7.0:
|
||||||
resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==}
|
resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==}
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>= 14'}
|
||||||
@ -2226,6 +2304,10 @@ snapshots:
|
|||||||
|
|
||||||
'@alloc/quick-lru@5.2.0': {}
|
'@alloc/quick-lru@5.2.0': {}
|
||||||
|
|
||||||
|
'@babel/runtime@7.26.7':
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime: 0.14.1
|
||||||
|
|
||||||
'@emnapi/runtime@1.3.1':
|
'@emnapi/runtime@1.3.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
@ -2294,6 +2376,10 @@ snapshots:
|
|||||||
|
|
||||||
'@floating-ui/utils@0.2.9': {}
|
'@floating-ui/utils@0.2.9': {}
|
||||||
|
|
||||||
|
'@hookform/resolvers@4.0.0(react-hook-form@7.54.2(react@19.0.0))':
|
||||||
|
dependencies:
|
||||||
|
react-hook-form: 7.54.2(react@19.0.0)
|
||||||
|
|
||||||
'@humanfs/core@0.19.1': {}
|
'@humanfs/core@0.19.1': {}
|
||||||
|
|
||||||
'@humanfs/node@0.16.6':
|
'@humanfs/node@0.16.6':
|
||||||
@ -2452,6 +2538,8 @@ snapshots:
|
|||||||
|
|
||||||
'@nolyfill/is-core-module@1.0.39': {}
|
'@nolyfill/is-core-module@1.0.39': {}
|
||||||
|
|
||||||
|
'@panva/hkdf@1.2.1': {}
|
||||||
|
|
||||||
'@pkgjs/parseargs@0.11.0':
|
'@pkgjs/parseargs@0.11.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -3117,6 +3205,8 @@ snapshots:
|
|||||||
|
|
||||||
concat-map@0.0.1: {}
|
concat-map@0.0.1: {}
|
||||||
|
|
||||||
|
cookie@0.7.2: {}
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
path-key: 3.1.1
|
path-key: 3.1.1
|
||||||
@ -3803,6 +3893,8 @@ snapshots:
|
|||||||
|
|
||||||
jiti@1.21.7: {}
|
jiti@1.21.7: {}
|
||||||
|
|
||||||
|
jose@4.15.9: {}
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
|
|
||||||
js-yaml@4.1.0:
|
js-yaml@4.1.0:
|
||||||
@ -3861,6 +3953,10 @@ snapshots:
|
|||||||
|
|
||||||
lru-cache@10.4.3: {}
|
lru-cache@10.4.3: {}
|
||||||
|
|
||||||
|
lru-cache@6.0.0:
|
||||||
|
dependencies:
|
||||||
|
yallist: 4.0.0
|
||||||
|
|
||||||
lucide-react@0.475.0(react@19.0.0):
|
lucide-react@0.475.0(react@19.0.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.0.0
|
react: 19.0.0
|
||||||
@ -3898,6 +3994,21 @@ snapshots:
|
|||||||
|
|
||||||
natural-compare@1.4.0: {}
|
natural-compare@1.4.0: {}
|
||||||
|
|
||||||
|
next-auth@4.24.11(next@15.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.26.7
|
||||||
|
'@panva/hkdf': 1.2.1
|
||||||
|
cookie: 0.7.2
|
||||||
|
jose: 4.15.9
|
||||||
|
next: 15.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
|
oauth: 0.9.15
|
||||||
|
openid-client: 5.7.1
|
||||||
|
preact: 10.25.4
|
||||||
|
preact-render-to-string: 5.2.6(preact@10.25.4)
|
||||||
|
react: 19.0.0
|
||||||
|
react-dom: 19.0.0(react@19.0.0)
|
||||||
|
uuid: 8.3.2
|
||||||
|
|
||||||
next-themes@0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
next-themes@0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.0.0
|
react: 19.0.0
|
||||||
@ -3930,8 +4041,12 @@ snapshots:
|
|||||||
|
|
||||||
normalize-path@3.0.0: {}
|
normalize-path@3.0.0: {}
|
||||||
|
|
||||||
|
oauth@0.9.15: {}
|
||||||
|
|
||||||
object-assign@4.1.1: {}
|
object-assign@4.1.1: {}
|
||||||
|
|
||||||
|
object-hash@2.2.0: {}
|
||||||
|
|
||||||
object-hash@3.0.0: {}
|
object-hash@3.0.0: {}
|
||||||
|
|
||||||
object-inspect@1.13.4: {}
|
object-inspect@1.13.4: {}
|
||||||
@ -3973,6 +4088,15 @@ snapshots:
|
|||||||
define-properties: 1.2.1
|
define-properties: 1.2.1
|
||||||
es-object-atoms: 1.1.1
|
es-object-atoms: 1.1.1
|
||||||
|
|
||||||
|
oidc-token-hash@5.0.3: {}
|
||||||
|
|
||||||
|
openid-client@5.7.1:
|
||||||
|
dependencies:
|
||||||
|
jose: 4.15.9
|
||||||
|
lru-cache: 6.0.0
|
||||||
|
object-hash: 2.2.0
|
||||||
|
oidc-token-hash: 5.0.3
|
||||||
|
|
||||||
optionator@0.9.4:
|
optionator@0.9.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
deep-is: 0.1.4
|
deep-is: 0.1.4
|
||||||
@ -4071,8 +4195,17 @@ snapshots:
|
|||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
preact-render-to-string@5.2.6(preact@10.25.4):
|
||||||
|
dependencies:
|
||||||
|
preact: 10.25.4
|
||||||
|
pretty-format: 3.8.0
|
||||||
|
|
||||||
|
preact@10.25.4: {}
|
||||||
|
|
||||||
prelude-ls@1.2.1: {}
|
prelude-ls@1.2.1: {}
|
||||||
|
|
||||||
|
pretty-format@3.8.0: {}
|
||||||
|
|
||||||
prop-types@15.8.1:
|
prop-types@15.8.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
@ -4142,6 +4275,8 @@ snapshots:
|
|||||||
get-proto: 1.0.1
|
get-proto: 1.0.1
|
||||||
which-builtin-type: 1.2.1
|
which-builtin-type: 1.2.1
|
||||||
|
|
||||||
|
regenerator-runtime@0.14.1: {}
|
||||||
|
|
||||||
regexp.prototype.flags@1.5.4:
|
regexp.prototype.flags@1.5.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.8
|
call-bind: 1.0.8
|
||||||
@ -4520,6 +4655,8 @@ snapshots:
|
|||||||
|
|
||||||
util-deprecate@1.0.2: {}
|
util-deprecate@1.0.2: {}
|
||||||
|
|
||||||
|
uuid@8.3.2: {}
|
||||||
|
|
||||||
which-boxed-primitive@1.1.1:
|
which-boxed-primitive@1.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-bigint: 1.1.0
|
is-bigint: 1.1.0
|
||||||
@ -4578,6 +4715,8 @@ snapshots:
|
|||||||
string-width: 5.1.2
|
string-width: 5.1.2
|
||||||
strip-ansi: 7.1.0
|
strip-ansi: 7.1.0
|
||||||
|
|
||||||
|
yallist@4.0.0: {}
|
||||||
|
|
||||||
yaml@2.7.0: {}
|
yaml@2.7.0: {}
|
||||||
|
|
||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|||||||
29
frontend/schema/authSchema.ts
Normal file
29
frontend/schema/authSchema.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const signInSchema = z.object({
|
||||||
|
email: z
|
||||||
|
.string({ required_error: "Email is required" })
|
||||||
|
.min(1, { message: "Email is required" })
|
||||||
|
.email({ message: "Invalid email address" }),
|
||||||
|
password: z
|
||||||
|
.string({ required_error: "Password is required" })
|
||||||
|
.min(6, { message: "Password must be at least 6 characters long" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const signUpSchema = z
|
||||||
|
.object({
|
||||||
|
email: z
|
||||||
|
.string({ required_error: "Email is required" })
|
||||||
|
.min(1, { message: "Email is required" })
|
||||||
|
.email({ message: "Invalid email address" }),
|
||||||
|
password: z
|
||||||
|
.string({ required_error: "Password is required" })
|
||||||
|
.min(6, { message: "Password must be at least 6 characters" }),
|
||||||
|
confirmPassword: z
|
||||||
|
.string({ required_error: "Confirm your password" })
|
||||||
|
.min(6, { message: "Confirm Password must be at least 6 characters" }),
|
||||||
|
})
|
||||||
|
.refine((data) => data.password === data.confirmPassword, {
|
||||||
|
message: "Passwords do not match",
|
||||||
|
path: ["confirmPassword"],
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user