Introduced a new defaultCryptoId prop for the SellForm component to pre-select a cryptocurrency on mount. Adjusted responsive layout and usability for various components including headers, modals, and navigation links. Ensured better user feedback and message clarity for login and registration processes.
263 lines
7.9 KiB
TypeScript
263 lines
7.9 KiB
TypeScript
"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 { ToastBox, toastType } from "@/components/ui/toast-box";
|
|
import { useToast } from "@/components/ui/use-toast";
|
|
import type {
|
|
IApiLoginReq,
|
|
IApiLoginRes,
|
|
IApiRegisterReq,
|
|
IApiRegisterRes,
|
|
} from "@/interfaces/api.interface";
|
|
import { EReturnState, type IStandardisedReturn } from "@/interfaces/general.interface";
|
|
import type { IUserData } from "@/interfaces/userdata.interface";
|
|
import ApiRequest from "@/services/apiRequest";
|
|
import { useLocalStorage } from "@/services/localStorage";
|
|
import { Bug, RefreshCw } from "lucide-react";
|
|
import Link from "next/link";
|
|
import React, { type Dispatch, type SetStateAction, useContext, useState } from "react";
|
|
import * as z from "zod";
|
|
import Image from "next/image";
|
|
|
|
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: "First name is required.",
|
|
}).describe("First name"),
|
|
lastName: z.string({
|
|
required_error: "Last name is required.",
|
|
}).describe("Last name"),
|
|
pseudo: z.string({
|
|
required_error: "Nickname is required.",
|
|
}).describe("Nickname"),
|
|
email: z
|
|
.string({
|
|
required_error: "Email is required.",
|
|
})
|
|
.email("Email must be valid."),
|
|
password: z
|
|
.string({
|
|
required_error: "Password is required.",
|
|
})
|
|
.min(8, "Password must be at least 8 characters.")
|
|
.max(32, "Password must be less than 32 characters.")
|
|
.regex(/[A-Z]/, "Password must contain at least one uppercase letter.")
|
|
.regex(/[a-z]/, "Password must contain at least one lowercase letter.")
|
|
.regex(/[0-9]/, "Password must contain at least one number.")
|
|
.regex(/[^a-zA-Z0-9]/, "Password must contain at least one special character.")
|
|
.describe("Your account password."),
|
|
|
|
promoCode: z
|
|
.string().max(255, "Your promotional code is too long.").optional(),
|
|
});
|
|
|
|
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/sign-up", registerData);
|
|
console.trace(ReqRes.data);
|
|
if (ReqRes.data.user) {
|
|
userDataSetter({
|
|
...ReqRes.data.user,
|
|
wallet: {
|
|
uat: Date.now(),
|
|
update_interval: 30_000,
|
|
owned_cryptos: [],
|
|
},
|
|
});
|
|
|
|
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/sign-in",
|
|
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 (
|
|
<section className={"flex flex-col gap-6 items-center justify-center"}>
|
|
<Link href={"/"} className={"hidden sm:visible sm:flex flex-row justify-center md:justify-start items-center w-fit gap-2"}>
|
|
<Image src={"neptune.svg"} alt={"Logo of Neptune"} className={"w-24 h-24"} width={128} height={128} />
|
|
<h1 className={"font-bold text-xl lg:text-2xl align-middle text-center text-wrap"}>
|
|
Neptune Crypto
|
|
</h1>
|
|
</Link>
|
|
<Tabs defaultValue="login" className="w-full p-2 gap-4 flex flex-col justify-center sm:p-4 rounded bg-card text-card-foreground 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
|
|
// I pass the Zod 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 to the home page.",
|
|
});
|
|
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 the{" "}
|
|
<Link title={"Go to legal page"} href="/legal" className="text-primary underline">
|
|
GDPR Compliance Policy & Usage Policy
|
|
</Link>
|
|
.
|
|
</p>
|
|
</AutoForm>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</section>
|
|
);
|
|
}
|