mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-29 23:04:23 +00:00
298 lines
8.8 KiB
TypeScript
298 lines
8.8 KiB
TypeScript
"use client";
|
|
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
import { useForm, useWatch } from "react-hook-form";
|
|
|
|
import { createNewUser } from "@/actions/auth";
|
|
import {
|
|
getInvitationErrorDisplay,
|
|
INVITATION_ERROR_FLOW,
|
|
isInvitationTokenError,
|
|
} from "@/app/(auth)/invitation/_lib/invitation-errors";
|
|
import { AuthDivider } from "@/components/auth/oss/auth-divider";
|
|
import { AuthFooterLink } from "@/components/auth/oss/auth-footer-link";
|
|
import { AuthLayout } from "@/components/auth/oss/auth-layout";
|
|
import { PasswordRequirementsMessage } from "@/components/auth/oss/password-validator";
|
|
import { SocialButtons } from "@/components/auth/oss/social-buttons";
|
|
import { Button, Checkbox, useToast } from "@/components/shadcn";
|
|
import { CustomInput } from "@/components/shadcn/custom";
|
|
import { CustomLink } from "@/components/shadcn/custom/custom-link";
|
|
import {
|
|
Form,
|
|
FormControl,
|
|
FormField,
|
|
FormMessage,
|
|
} from "@/components/shadcn/form";
|
|
import { appendAttributionToCallbackPath } from "@/lib/auth-callback-url";
|
|
import { stripPasswordManagerHighlight } from "@/lib/password-manager";
|
|
import { extractUtmParams } from "@/lib/utm";
|
|
import { ApiError, SignUpFormData, signUpSchema } from "@/types";
|
|
|
|
const AUTH_ERROR_PATHS = {
|
|
NAME: "/data/attributes/name",
|
|
EMAIL: "/data/attributes/email",
|
|
PASSWORD: "/data/attributes/password",
|
|
COMPANY_NAME: "/data/attributes/company_name",
|
|
} as const;
|
|
|
|
const FORM_ERROR_TYPE = {
|
|
SERVER: "server",
|
|
} as const;
|
|
|
|
export const SignUpForm = ({
|
|
invitationToken,
|
|
isCloudEnv,
|
|
googleAuthUrl,
|
|
githubAuthUrl,
|
|
isGoogleOAuthEnabled,
|
|
isGithubOAuthEnabled,
|
|
}: {
|
|
invitationToken?: string | null;
|
|
isCloudEnv?: boolean;
|
|
googleAuthUrl?: string;
|
|
githubAuthUrl?: string;
|
|
isGoogleOAuthEnabled?: boolean;
|
|
isGithubOAuthEnabled?: boolean;
|
|
}) => {
|
|
const router = useRouter();
|
|
const searchParams = useSearchParams();
|
|
const { toast } = useToast();
|
|
const utmParams = extractUtmParams(searchParams);
|
|
const baseCallbackUrl = invitationToken
|
|
? `/invitation/accept?invitation_token=${encodeURIComponent(invitationToken)}`
|
|
: "/";
|
|
const callbackUrl = appendAttributionToCallbackPath(
|
|
baseCallbackUrl,
|
|
utmParams,
|
|
);
|
|
|
|
const form = useForm<SignUpFormData>({
|
|
resolver: zodResolver(signUpSchema),
|
|
mode: "onSubmit",
|
|
reValidateMode: "onSubmit",
|
|
defaultValues: {
|
|
email: "",
|
|
password: "",
|
|
isSamlMode: false,
|
|
name: "",
|
|
company: "",
|
|
confirmPassword: "",
|
|
...(invitationToken && { invitationToken }),
|
|
},
|
|
});
|
|
|
|
const passwordValue = useWatch({
|
|
control: form.control,
|
|
name: "password",
|
|
defaultValue: "",
|
|
});
|
|
const termsAccepted = useWatch({
|
|
control: form.control,
|
|
name: "termsAndConditions",
|
|
defaultValue: false,
|
|
});
|
|
|
|
const isLoading = form.formState.isSubmitting;
|
|
const isSocialAuthDisabled = Boolean(isCloudEnv && !termsAccepted);
|
|
|
|
const onSubmit = async (data: SignUpFormData) => {
|
|
const newUser = await createNewUser(data, utmParams);
|
|
|
|
if (!newUser.errors) {
|
|
toast({
|
|
title: "Success!",
|
|
description: "The user was registered successfully.",
|
|
});
|
|
form.reset();
|
|
|
|
if (isCloudEnv) {
|
|
router.push("/email-verification");
|
|
} else {
|
|
router.push("/sign-in");
|
|
}
|
|
} else {
|
|
const invitationTokenError = newUser.errors.find((error: ApiError) =>
|
|
isInvitationTokenError(error),
|
|
);
|
|
|
|
if (invitationToken && invitationTokenError) {
|
|
const { message } = getInvitationErrorDisplay(
|
|
{ status: newUser.status, errors: [invitationTokenError] },
|
|
INVITATION_ERROR_FLOW.SIGNUP,
|
|
);
|
|
form.setError("invitationToken", {
|
|
type: FORM_ERROR_TYPE.SERVER,
|
|
message,
|
|
});
|
|
return;
|
|
}
|
|
|
|
newUser.errors.forEach((error: ApiError) => {
|
|
const errorMessage = error.detail;
|
|
const pointer = error.source?.pointer;
|
|
switch (pointer) {
|
|
case AUTH_ERROR_PATHS.NAME:
|
|
form.setError("name", {
|
|
type: FORM_ERROR_TYPE.SERVER,
|
|
message: errorMessage,
|
|
});
|
|
break;
|
|
case AUTH_ERROR_PATHS.EMAIL:
|
|
form.setError("email", {
|
|
type: FORM_ERROR_TYPE.SERVER,
|
|
message: errorMessage,
|
|
});
|
|
break;
|
|
case AUTH_ERROR_PATHS.COMPANY_NAME:
|
|
form.setError("company", {
|
|
type: FORM_ERROR_TYPE.SERVER,
|
|
message: errorMessage,
|
|
});
|
|
break;
|
|
case AUTH_ERROR_PATHS.PASSWORD:
|
|
form.setError("password", {
|
|
type: FORM_ERROR_TYPE.SERVER,
|
|
message: errorMessage,
|
|
});
|
|
break;
|
|
default:
|
|
toast({
|
|
variant: "destructive",
|
|
title: "Oops! Something went wrong",
|
|
description: errorMessage,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AuthLayout
|
|
title="Get started"
|
|
footer={
|
|
<AuthFooterLink
|
|
text="Already have an account?"
|
|
linkText="Log in"
|
|
href="/sign-in"
|
|
/>
|
|
}
|
|
>
|
|
<Form {...form}>
|
|
<form
|
|
ref={stripPasswordManagerHighlight}
|
|
noValidate
|
|
method="post"
|
|
className="flex flex-col gap-4"
|
|
onSubmit={form.handleSubmit(onSubmit)}
|
|
>
|
|
<CustomInput
|
|
control={form.control}
|
|
name="name"
|
|
type="text"
|
|
label="Name"
|
|
placeholder="Enter your name"
|
|
/>
|
|
<CustomInput
|
|
control={form.control}
|
|
name="company"
|
|
type="text"
|
|
label="Company name"
|
|
placeholder="Enter your company name"
|
|
isRequired={false}
|
|
/>
|
|
<CustomInput
|
|
control={form.control}
|
|
name="email"
|
|
type="email"
|
|
label="Email"
|
|
placeholder="Enter your email"
|
|
/>
|
|
<CustomInput control={form.control} name="password" password />
|
|
<PasswordRequirementsMessage password={passwordValue || ""} />
|
|
<CustomInput
|
|
control={form.control}
|
|
name="confirmPassword"
|
|
confirmPassword
|
|
/>
|
|
{invitationToken && (
|
|
<CustomInput
|
|
control={form.control}
|
|
name="invitationToken"
|
|
type="text"
|
|
label="Invitation Token"
|
|
placeholder={invitationToken}
|
|
defaultValue={invitationToken}
|
|
isRequired={false}
|
|
isDisabled={invitationToken !== null && true}
|
|
/>
|
|
)}
|
|
|
|
{isCloudEnv && (
|
|
<FormField
|
|
control={form.control}
|
|
name="termsAndConditions"
|
|
render={({ field }) => (
|
|
<>
|
|
<div className="flex items-center gap-2 py-4">
|
|
<FormControl>
|
|
<Checkbox
|
|
id="termsAndConditions"
|
|
size="sm"
|
|
checked={field.value}
|
|
onCheckedChange={field.onChange}
|
|
/>
|
|
</FormControl>
|
|
<label
|
|
htmlFor="termsAndConditions"
|
|
className="cursor-pointer text-sm"
|
|
>
|
|
I agree with the
|
|
<CustomLink
|
|
href="https://prowler.com/terms-of-service/"
|
|
size="sm"
|
|
>
|
|
Terms of Service
|
|
</CustomLink>
|
|
of Prowler
|
|
<span className="text-text-error-primary">*</span>
|
|
</label>
|
|
</div>
|
|
<FormMessage className="text-text-error" />
|
|
</>
|
|
)}
|
|
/>
|
|
)}
|
|
|
|
<Button
|
|
type="submit"
|
|
aria-label="Sign up"
|
|
aria-disabled={isLoading}
|
|
className="w-full"
|
|
disabled={isLoading}
|
|
>
|
|
{isLoading ? "Loading..." : "Sign up"}
|
|
</Button>
|
|
</form>
|
|
</Form>
|
|
|
|
{(!invitationToken || isCloudEnv) && (
|
|
<>
|
|
<AuthDivider />
|
|
<div className="flex gap-2">
|
|
<SocialButtons
|
|
googleAuthUrl={googleAuthUrl}
|
|
githubAuthUrl={githubAuthUrl}
|
|
callbackUrl={callbackUrl}
|
|
isGoogleOAuthEnabled={isGoogleOAuthEnabled}
|
|
isGithubOAuthEnabled={isGithubOAuthEnabled}
|
|
isDisabled={isSocialAuthDisabled}
|
|
disabledTooltipContent="Accept the Terms of Service to continue."
|
|
/>
|
|
</div>
|
|
</>
|
|
)}
|
|
</AuthLayout>
|
|
);
|
|
};
|