diff --git a/frontend/app/auth/signin/google-oauth.tsx b/frontend/app/auth/signin/google-oauth.tsx new file mode 100644 index 0000000..7b1f2cb --- /dev/null +++ b/frontend/app/auth/signin/google-oauth.tsx @@ -0,0 +1,9 @@ +import Image from "next/image"; + +export function GoogleSigninButton() { + return ( +
+ Google Logo +
+ ); +} diff --git a/frontend/app/auth/signin/page.tsx b/frontend/app/auth/signin/page.tsx index 55dc3dd..5e6dd18 100644 --- a/frontend/app/auth/signin/page.tsx +++ b/frontend/app/auth/signin/page.tsx @@ -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 { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; @@ -6,19 +13,67 @@ import ForgotPasswordModal from "./forgot-password-modal"; import Link from "next/link"; 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() { + 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) => { + 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 (
-
+
- {/* login box */}
- Forfarm + Forfarm

Welcome back.

@@ -31,27 +86,28 @@ export default function SigninPage() {
-
+ {/* Sign in form */} +
- + + {errors.email &&

{errors.email.message}

}
-
- - -
+ + + {errors.password &&

{errors.password.message}

}
- -
+ + diff --git a/frontend/app/auth/signup/page.tsx b/frontend/app/auth/signup/page.tsx index 5657830..2c3d0e7 100644 --- a/frontend/app/auth/signup/page.tsx +++ b/frontend/app/auth/signup/page.tsx @@ -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 { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; import { Button } from "@/components/ui/button"; +import { signUpSchema } from "@/schema/authSchema"; + import Link from "next/link"; import Image from "next/image"; +import { useState } from "react"; +import { z } from "zod"; + +import { useRouter } from "next/navigation"; export default function SignupPage() { + const [serverError, setServerError] = useState(null); + const [successMessage, setSuccessMessage] = useState(null); + const router = useRouter(); + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm>({ + resolver: zodResolver(signUpSchema), + defaultValues: { + email: "", + password: "", + confirmPassword: "", + }, + }); + + const onSubmit = async (values: z.infer) => { + 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 (
-
+
- {/* login box */}
- Forfarm + Forfarm

Hi! Welcome

- Already have accounts? + Already have an account? - + Sign in
-
+ {/* Signup form */} +
- + + {errors.email &&

{errors.email.message}

}
-
- - -
+ + + {errors.password &&

{errors.password.message}

}
-
- - -
+ + + {errors.confirmPassword &&

{errors.confirmPassword.message}

}
- -
+ {serverError &&

{serverError}

} + {successMessage &&

{successMessage}

} + + +
-

Or log in with

- {/* OAUTH */} +

Or sign up with

- {/* Google */} -
+ {/* Google OAuth button or additional providers */} +
Google Logo
diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 86c19f8..6be9eda 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -3,6 +3,8 @@ import { Open_Sans, Roboto_Mono } from "next/font/google"; import "./globals.css"; import { ThemeProvider } from "@/components/theme-provider"; +import { SessionProvider } from "@/context/SessionContext"; + const openSans = Open_Sans({ subsets: ["latin"], display: "swap", @@ -33,13 +35,15 @@ export default function RootLayout({ return ( - - -
-
{children}
-
-
- + + + +
+
{children}
+
+
+ +
); } diff --git a/frontend/components/SessionProviderClient.tsx b/frontend/components/SessionProviderClient.tsx new file mode 100644 index 0000000..9825d39 --- /dev/null +++ b/frontend/components/SessionProviderClient.tsx @@ -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 {props.children}; +} diff --git a/frontend/components/ui/form.tsx b/frontend/components/ui/form.tsx new file mode 100644 index 0000000..b6daa65 --- /dev/null +++ b/frontend/components/ui/form.tsx @@ -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 = FieldPath +> = { + name: TName +} + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue +) + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +>({ + ...props +}: ControllerProps) => { + return ( + + + + ) +} + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext) + const itemContext = React.useContext(FormItemContext) + const { getFieldState, formState } = useFormContext() + + const fieldState = getFieldState(fieldContext.name, formState) + + if (!fieldContext) { + throw new Error("useFormField should be used within ") + } + + const { id } = itemContext + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} + +type FormItemContextValue = { + id: string +} + +const FormItemContext = React.createContext( + {} as FormItemContextValue +) + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId() + + return ( + +
+ + ) +}) +FormItem.displayName = "FormItem" + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField() + + return ( +