mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 20:42:02 +00:00
feat(sign-up): integrate sign-up functionality in the application
This commit is contained in:
+41
-1
@@ -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
@@ -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
@@ -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
|
||||
<Link href="#" size="sm">
|
||||
Terms
|
||||
</Link>
|
||||
and
|
||||
<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
|
||||
<Link href="#" size="sm">
|
||||
Terms
|
||||
</Link>
|
||||
and
|
||||
<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"}
|
||||
|
||||
@@ -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
@@ -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.",
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user