feat(components): add authentication forms
This commit introduces authentication forms for both user login and registration. These forms use zod for data validation and context for state management. Login and registration processes have been implemented as asynchronous functions, handling API requests, and error responses.
This commit is contained in:
parent
4c061dc19c
commit
cc286462f0
235
src/components/auth-form.tsx
Normal file
235
src/components/auth-form.tsx
Normal file
@ -0,0 +1,235 @@
|
||||
"use client";
|
||||
|
||||
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
|
||||
|
||||
import AutoForm, {AutoFormSubmit} from "@/components/auto-form";
|
||||
import {UserDataContext} from "@/components/providers/userdata-provider";
|
||||
import type {IApiLoginReq, IApiLoginRes, IApiRegisterReq, IApiRegisterRes} from "@/interfaces/api.interface";
|
||||
import type {IUserData} from "@/interfaces/userdata.interface";
|
||||
import ApiRequest from "@/services/apiRequest";
|
||||
import {EReturnState, type IStandardisedReturn} from "@/services/general.interface";
|
||||
import {useLocalStorage} from "@/services/localStorage";
|
||||
import {Bug, RefreshCw} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import {type Dispatch, type SetStateAction, useContext, useState} from "react";
|
||||
import * as z from "zod";
|
||||
import {ToastBox, toastType} from "@/components/ui/toast-box";
|
||||
import {useToast} from "@/components/ui/use-toast";
|
||||
|
||||
|
||||
const loginSchema = z.object({
|
||||
email: z
|
||||
.string({
|
||||
required_error: "Email is needed.",
|
||||
})
|
||||
.email({
|
||||
message: "Should be a valid email.",
|
||||
})
|
||||
.describe("Your account email."),
|
||||
password: z
|
||||
.string({
|
||||
required_error: "Password is needed.",
|
||||
})
|
||||
.describe("Your account password."),
|
||||
});
|
||||
|
||||
const registerSchema = z.object({
|
||||
firstName: z.string({
|
||||
required_error: "",
|
||||
}),
|
||||
lastName: z.string(),
|
||||
age: z.number().min(18).max(120),
|
||||
pseudo: z.string({
|
||||
required_error: "",
|
||||
}),
|
||||
city: z.string({
|
||||
required_error: "",
|
||||
}),
|
||||
|
||||
email: z
|
||||
.string({
|
||||
required_error: "Email is needed.",
|
||||
})
|
||||
.email("Should be a valid email."),
|
||||
password: z
|
||||
.string({
|
||||
required_error: "Password is needed.",
|
||||
})
|
||||
.describe("Your account password."),
|
||||
});
|
||||
|
||||
export function AuthForms() {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [sub, setSub] = useLocalStorage<string | undefined>("sub", "");
|
||||
const userContext = useContext(UserDataContext);
|
||||
const { toast } = useToast()
|
||||
|
||||
async function doRegister(
|
||||
registerData: IApiRegisterReq,
|
||||
userDataSetter: Dispatch<SetStateAction<IUserData | null>>,
|
||||
): Promise<IStandardisedReturn<IApiRegisterRes>> {
|
||||
console.trace(registerData);
|
||||
try {
|
||||
const ReqRes = await ApiRequest.standard.post.json<
|
||||
IApiRegisterReq,
|
||||
IApiRegisterRes
|
||||
>("auth/signup", registerData);
|
||||
console.trace(ReqRes.data);
|
||||
if (ReqRes.data.user) {
|
||||
userDataSetter(ReqRes.data.user);
|
||||
setSub(ReqRes.data.access_token);
|
||||
}
|
||||
console.debug(ReqRes.data.message || "Not additional message from request");
|
||||
return {
|
||||
state: EReturnState.done,
|
||||
resolved: ReqRes.data,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error during registration:", error);
|
||||
return {
|
||||
state: EReturnState.serverError,
|
||||
message: error as string,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function doLogin(
|
||||
loginData: IApiLoginReq,
|
||||
): Promise<IStandardisedReturn<IApiLoginRes>> {
|
||||
try {
|
||||
const ReqRes = await ApiRequest.standard.post.json<IApiLoginReq, IApiLoginRes>(
|
||||
"auth/signin",
|
||||
loginData,
|
||||
);
|
||||
console.trace(ReqRes.data);
|
||||
if (ReqRes.data.access_token) {
|
||||
setSub(ReqRes.data.access_token);
|
||||
}
|
||||
return {
|
||||
state: EReturnState.done,
|
||||
};
|
||||
} catch (err) {
|
||||
console.error("Error during login:", err);
|
||||
return {
|
||||
state: EReturnState.serverError,
|
||||
message: err as string,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!userContext || !userContext.setUserData) {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"bg-destructive text-destructive-foreground p-3 gap-2 border rounded flex flex-row justify-center items-center"
|
||||
}
|
||||
>
|
||||
<Bug />
|
||||
<p>It seems that the context is missing..</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs defaultValue="login" className="w-full p-2 md:w-[400px] my-4">
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="login">Login</TabsTrigger>
|
||||
<TabsTrigger value="register">Register</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="login">
|
||||
<AutoForm
|
||||
// Pass the schema to the form
|
||||
formSchema={loginSchema}
|
||||
onSubmit={(data: IApiLoginReq) => {
|
||||
setIsLoading(true);
|
||||
doLogin(data).then((res) => {
|
||||
if (res.state !== EReturnState.done) {
|
||||
toast({
|
||||
description: res.message || "An unexpected error occurred..",
|
||||
variant: "destructive",
|
||||
})
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
//toast.custom(<ToastBox message={"Login successful ! \n You will be redirected."} type={toastType.success}/>)
|
||||
toast({
|
||||
description: "Login successful ! \n You will be redirected."
|
||||
})
|
||||
setTimeout(()=>{
|
||||
setIsLoading(false)
|
||||
location.href = "/"
|
||||
console.log('Moving to home.')
|
||||
}, 3_000)
|
||||
});
|
||||
}}
|
||||
fieldConfig={{
|
||||
password: {
|
||||
inputProps: {
|
||||
type: "password",
|
||||
placeholder: "••••••••",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<AutoFormSubmit
|
||||
disabled={!!isLoading}
|
||||
className={"gap-2 disabled:bg-secondary"}
|
||||
>
|
||||
{/* biome-ignore lint/style/useTemplate: <explanation> */}
|
||||
<RefreshCw className={"animate-spin" + isLoading && "hidden"} />
|
||||
<p>Login</p>
|
||||
</AutoFormSubmit>
|
||||
</AutoForm>
|
||||
</TabsContent>
|
||||
<TabsContent value="register">
|
||||
<AutoForm
|
||||
// Pass the schema to the form
|
||||
formSchema={registerSchema}
|
||||
onSubmit={(data: IApiRegisterReq) => {
|
||||
setIsLoading(true);
|
||||
doRegister(
|
||||
data,
|
||||
userContext.setUserData as Dispatch<SetStateAction<IUserData | null>>,
|
||||
).then((res) => {
|
||||
if (res.state !== EReturnState.done) {
|
||||
//toast.custom(<ToastBox message={res.message || "An unexpected error occurred.."} type={toastType.error}/>)
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
//toast.custom(<ToastBox message={"Register successful ! \n You will be redirected."} type={toastType.success}/>)
|
||||
setTimeout(()=>{
|
||||
setIsLoading(false)
|
||||
//location.href = "/"
|
||||
console.log('Moving to home.')
|
||||
}, 5_000)
|
||||
});
|
||||
}}
|
||||
fieldConfig={{
|
||||
password: {
|
||||
inputProps: {
|
||||
type: "password",
|
||||
placeholder: "••••••••",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<AutoFormSubmit
|
||||
disabled={!!isLoading}
|
||||
className={"gap-2 disabled:bg-secondary"}
|
||||
>
|
||||
{/* biome-ignore lint/style/useTemplate: <explanation> */}
|
||||
<RefreshCw className={"animate-spin" + !isLoading && "hidden"} />
|
||||
<p>Register</p>
|
||||
</AutoFormSubmit>
|
||||
<p className="text-gray-500 text-sm">
|
||||
By submitting this form, you agree to our{" "}
|
||||
<Link href="#" className="text-primary underline">
|
||||
terms and conditions
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</AutoForm>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user