feat(sign-up): integrate sign-up functionality in the application

This commit is contained in:
Pablo Lara
2024-10-05 19:08:28 +02:00
parent e2261af59f
commit f0f4e85f06
6 changed files with 163 additions and 74 deletions
+41 -1
View File
@@ -7,7 +7,7 @@ import { signIn, signOut } from "@/auth.config";
import { authFormSchema } from "@/types";
const formSchemaSignIn = authFormSchema("sign-in");
// const formSchemaSignUp = authFormSchema("sign-up");
const formSchemaSignUp = authFormSchema("sign-up");
const defaultValues: z.infer<typeof formSchemaSignIn> = {
email: "",
@@ -50,6 +50,46 @@ export async function authenticate(
}
}
export const createNewUser = async (
formData: z.infer<typeof formSchemaSignUp>,
) => {
const keyServer = process.env.API_BASE_URL;
const url = new URL(`${keyServer}/users`);
const bodyData = {
data: {
type: "User",
attributes: {
name: formData.name,
company_name: formData.company,
email: formData.email,
password: formData.password,
},
},
};
try {
const response = await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/vnd.api+json",
Accept: "application/vnd.api+json",
},
body: JSON.stringify(bodyData),
});
const parsedResponse = await response.json();
if (!response.ok) {
return parsedResponse;
}
return parsedResponse;
} catch (error) {
return { errors: [{ detail: "Network error or server is unreachable" }] };
}
};
export const getToken = async (formData: z.infer<typeof formSchemaSignIn>) => {
const keyServer = process.env.API_BASE_URL;
const url = new URL(`${keyServer}/tokens`);
+1 -1
View File
@@ -74,7 +74,7 @@ export const authConfig = {
const parsedCredentials = z
.object({
email: z.string().email(),
password: z.string().min(6),
password: z.string().min(12),
})
.safeParse(credentials);
+100 -57
View File
@@ -9,18 +9,19 @@ import { useFormState } from "react-dom";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { authenticate } from "@/actions/auth";
import { authenticate, createNewUser } from "@/actions/auth";
import {
Form,
FormControl,
FormField,
FormMessage,
} from "@/components/ui/form";
import { authFormSchema } from "@/types";
import { ApiError, authFormSchema } from "@/types";
import { NotificationIcon, ProwlerExtended } from "../icons";
import { ThemeSwitch } from "../ThemeSwitch";
import { CustomButton, CustomInput } from "../ui/custom";
import { useToast } from "../ui/toast";
export const AuthForm = ({ type }: { type: string }) => {
const formSchema = authFormSchema(type);
@@ -31,17 +32,20 @@ export const AuthForm = ({ type }: { type: string }) => {
email: "",
password: "",
...(type === "sign-up" && {
firstName: "",
companyName: "",
name: "",
company: "",
termsAndConditions: false,
confirmPassword: "",
}),
},
});
const isLoading = form.formState.isSubmitting;
const [state, dispatch] = useFormState(authenticate, undefined);
const { toast } = useToast();
const isLoading = form.formState.isSubmitting;
useEffect(() => {
if (state?.message === "Success") {
router.push("/");
@@ -49,20 +53,53 @@ export const AuthForm = ({ type }: { type: string }) => {
}, [state]);
const onSubmit = async (data: z.infer<typeof formSchema>) => {
try {
// Sign-up logic will be here.
if (type === "sign-in") {
dispatch({
email: data.email.toLowerCase(),
password: data.password,
if (type === "sign-in") {
dispatch({
email: data.email.toLowerCase(),
password: data.password,
});
}
if (type === "sign-up") {
const newUser = await createNewUser(data);
if (newUser?.errors && newUser.errors.length > 0) {
newUser.errors.forEach((error: ApiError) => {
const errorMessage = error.detail;
switch (error.source.pointer) {
case "/data/attributes/name":
form.setError("name", { type: "server", message: errorMessage });
break;
case "/data/attributes/email":
form.setError("email", { type: "server", message: errorMessage });
break;
case "/data/attributes/password":
form.setError("password", {
type: "server",
message: errorMessage,
});
break;
default:
toast({
variant: "destructive",
title: "Oops! Something went wrong",
description: errorMessage,
});
}
});
} else {
toast({
title: "Success!",
description: "The user was registered successfully.",
});
form.reset({
name: "",
company: "",
email: "",
password: "",
termsAndConditions: false,
});
}
if (type === "sign-up") {
// const newUser = await signUp(data);
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
};
@@ -107,18 +144,20 @@ export const AuthForm = ({ type }: { type: string }) => {
<>
<CustomInput
control={form.control}
name="firstName"
name="name"
type="text"
label="Name"
placeholder="Enter your name"
isInvalid={!!form.formState.errors.name}
/>
<CustomInput
control={form.control}
name="companyName"
name="company"
type="text"
label="Company Name"
placeholder="Enter your company name"
isRequired={false}
isInvalid={!!form.formState.errors.company}
/>
</>
)}
@@ -128,9 +167,15 @@ export const AuthForm = ({ type }: { type: string }) => {
type="email"
label="Email"
placeholder="Enter your email"
isInvalid={!!form.formState.errors.email}
/>
<CustomInput control={form.control} name="password" password />
<CustomInput
control={form.control}
name="password"
password
isInvalid={!!form.formState.errors.password}
/>
{type === "sign-in" && (
<div className="flex items-center justify-between px-1 py-2">
@@ -143,40 +188,40 @@ export const AuthForm = ({ type }: { type: string }) => {
</div>
)}
{type === "sign-up" && (
<FormField
control={form.control}
name="termsAndConditions"
render={({ field }) => (
<>
<CustomInput
control={form.control}
name="confirmPassword"
confirmPassword
/>
<FormControl>
<Checkbox
isRequired
className="py-4"
size="sm"
checked={field.value === "true"}
onChange={(e) =>
field.onChange(e.target.checked ? "true" : "false")
}
>
I agree with the&nbsp;
<Link href="#" size="sm">
Terms
</Link>
&nbsp; and&nbsp;
<Link href="#" size="sm">
Privacy Policy
</Link>
</Checkbox>
</FormControl>
<FormMessage className="text-system-error dark:text-system-error" />
</>
)}
/>
<>
<CustomInput
control={form.control}
name="confirmPassword"
confirmPassword
/>
<FormField
control={form.control}
name="termsAndConditions"
render={({ field }) => (
<>
<FormControl>
<Checkbox
isRequired
className="py-4"
size="sm"
checked={field.value}
onChange={(e) => field.onChange(e.target.checked)}
>
I agree with the&nbsp;
<Link href="#" size="sm">
Terms
</Link>
&nbsp; and&nbsp;
<Link href="#" size="sm">
Privacy Policy
</Link>
</Checkbox>
</FormControl>
<FormMessage className="text-system-error dark:text-system-error" />
</>
)}
/>
</>
)}
{state?.message === "Credentials error" && (
@@ -186,8 +231,6 @@ export const AuthForm = ({ type }: { type: string }) => {
</div>
)}
{isLoading && <p>Loading...</p>}
<CustomButton
type="submit"
ariaLabel={type === "sign-in" ? "Log In" : "Sign Up"}
+3 -3
View File
@@ -18,7 +18,7 @@ import {
} from "../../icons/prowler/ProwlerIcons";
import { ThemeSwitch } from "../../ThemeSwitch";
import Sidebar from "./Sidebar";
import { sectionItems, sectionItemsWithTeams } from "./SidebarItems";
import { sectionItemsWithTeams } from "./SidebarItems";
import { UserAvatar } from "./UserAvatar";
export const SidebarWrap = () => {
@@ -74,8 +74,8 @@ export const SidebarWrap = () => {
<Link href={"/profile"}>
<Suspense fallback={<p>Loading...</p>}>
<UserAvatar
userName={session?.user?.name ?? "Guest"}
position={session?.user?.companyName ?? "Company Name"}
userName={session?.user.name ?? "Guest"}
position={session?.user.companyName ?? "Company Name"}
isCompact={isCompact}
/>
</Suspense>
+9 -12
View File
@@ -4,11 +4,9 @@ export const authFormSchema = (type: string) =>
z
.object({
// Sign Up
companyName:
type === "sign-in"
? z.string().optional()
: z.string().min(3).optional(),
firstName:
company:
type === "sign-in" ? z.string().optional() : z.string().optional(),
name:
type === "sign-in"
? z.string().optional()
: z
@@ -20,20 +18,19 @@ export const authFormSchema = (type: string) =>
confirmPassword:
type === "sign-in"
? z.string().optional()
: z.string().min(3, {
: z.string().min(12, {
message: "It must contain at least 12 characters.",
}),
termsAndConditions:
type === "sign-in"
? z.enum(["true"]).optional()
: z.enum(["true"], {
errorMap: () => ({
message: "You must accept the terms and conditions.",
}),
? z.boolean().optional()
: z.boolean().refine((value) => value === true, {
message: "You must accept the terms and conditions.",
}),
// Fields for Sign In and Sign Up
email: z.string().email(),
password: z.string().min(3, {
password: z.string().min(12, {
message: "It must contain at least 12 characters.",
}),
})
+9
View File
@@ -30,6 +30,15 @@ export interface SearchParamsProps {
[key: string]: string | string[] | undefined;
}
export interface ApiError {
detail: string;
status: string;
source: {
pointer: string;
};
code: string;
}
export interface ProviderProps {
id: string;
type: "providers";