Implement activate account page
Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<e920b2bbd380e12014a100bfb963c8f7>>
|
||||
* @generated SignedSource<<b79143becc90a4776f321d0e91db49bd>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type ActivateAccountInput = {
|
||||
password: string;
|
||||
password?: string | null | undefined;
|
||||
token: string;
|
||||
};
|
||||
export type ActivateAccountPageMutation$variables = {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { formatError } from "@probo/helpers";
|
||||
import { formatError, type GraphQLError } from "@probo/helpers";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Button, Field, useToast } from "@probo/ui";
|
||||
import { useToast } from "@probo/ui";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { useMutation } from "react-relay";
|
||||
import { Link, useNavigate, useSearchParams } from "react-router";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { z } from "zod";
|
||||
|
||||
import type { ActivateAccountPageMutation } from "#/__generated__/iam/ActivateAccountPageMutation.graphql";
|
||||
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
|
||||
|
||||
const activateAccountMutation = graphql`
|
||||
mutation ActivateAccountPageMutation(
|
||||
@@ -22,64 +21,45 @@ const activateAccountMutation = graphql`
|
||||
}
|
||||
`;
|
||||
|
||||
const schema = z.object({
|
||||
password: z.string().min(8),
|
||||
fullName: z.string().min(2),
|
||||
});
|
||||
|
||||
type FormData = z.infer<typeof schema>;
|
||||
|
||||
export default function ActivateAccountPage() {
|
||||
const { __ } = useTranslate();
|
||||
const { toast } = useToast();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const fullNameFromParams = searchParams.get("fullName") || "";
|
||||
const submittedRef = useRef<boolean>(false);
|
||||
|
||||
usePageTitle(__("Sign up"));
|
||||
|
||||
const { register, handleSubmit, formState } = useFormWithSchema(schema, {
|
||||
defaultValues: {
|
||||
password: "",
|
||||
fullName: fullNameFromParams,
|
||||
},
|
||||
});
|
||||
usePageTitle(__("Activate Account"));
|
||||
|
||||
const [activateAccount] = useMutation<ActivateAccountPageMutation>(activateAccountMutation);
|
||||
|
||||
const onSubmit = (data: FormData) => {
|
||||
const token = searchParams.get("token");
|
||||
if (!token) {
|
||||
toast({
|
||||
title: __("Signup failed"),
|
||||
description: __("Invalid or missing invitation token"),
|
||||
variant: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const handleActivateAccount = useCallback((token: string) => {
|
||||
if (submittedRef.current) return;
|
||||
|
||||
activateAccount({
|
||||
variables: {
|
||||
input: {
|
||||
token,
|
||||
password: data.password,
|
||||
fullName: data.fullName,
|
||||
},
|
||||
input: { token },
|
||||
},
|
||||
onCompleted: (_, e) => {
|
||||
if (e) {
|
||||
onCompleted: (_, errors: GraphQLError[] | null) => {
|
||||
if (errors) {
|
||||
for (const err of errors) {
|
||||
if (err.extensions?.code === "ALREADY_AUTHENTICATED") {
|
||||
window.location.href = "/";
|
||||
return;
|
||||
}
|
||||
}
|
||||
toast({
|
||||
title: __("Signup failed"),
|
||||
description: formatError(__("Signup failed"), e),
|
||||
title: __("Activation failed"),
|
||||
description: formatError(__("Activation failed"), errors),
|
||||
variant: "error",
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: __("Success"),
|
||||
description: __(
|
||||
"Account created successfully. Please accept your invitation to join the organization.",
|
||||
"Account activated successfully.",
|
||||
),
|
||||
variant: "success",
|
||||
});
|
||||
@@ -87,60 +67,37 @@ export default function ActivateAccountPage() {
|
||||
},
|
||||
onError: (e) => {
|
||||
toast({
|
||||
title: __("Signup failed"),
|
||||
title: __("Activation failed"),
|
||||
description: e.message,
|
||||
variant: "error",
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
}, [__, toast, activateAccount, navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
const token = searchParams.get("token");
|
||||
if (!submittedRef.current && token) {
|
||||
void handleActivateAccount(token.trim());
|
||||
submittedRef.current = true;
|
||||
}
|
||||
}, [handleActivateAccount, searchParams]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 w-full max-w-md mx-auto pt-8">
|
||||
<div className="space-y-2 text-center">
|
||||
<h1 className="text-3xl font-bold">{__("Create your account")}</h1>
|
||||
<h1 className="text-3xl font-bold">{__("Account Activation")}</h1>
|
||||
<p className="text-txt-tertiary">
|
||||
{__("Set your password to join the organization")}
|
||||
{__("Activating your account…")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={e => void handleSubmit(onSubmit)(e)} className="space-y-4">
|
||||
<Field
|
||||
label={__("Full Name")}
|
||||
type="text"
|
||||
placeholder={__("John Doe")}
|
||||
{...register("fullName")}
|
||||
required
|
||||
error={formState.errors.fullName?.message}
|
||||
/>
|
||||
|
||||
<Field
|
||||
label={__("Password")}
|
||||
type="password"
|
||||
placeholder="••••••••"
|
||||
{...register("password")}
|
||||
required
|
||||
error={formState.errors.password?.message}
|
||||
/>
|
||||
|
||||
<Button type="submit" className="w-xs h-10 mx-auto mt-6" disabled={formState.isLoading}>
|
||||
{formState.isLoading
|
||||
? __("Creating account...")
|
||||
: __("Create account")}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-txt-tertiary">
|
||||
{__("Already have an account?")}
|
||||
{" "}
|
||||
<Link
|
||||
to="/auth/login"
|
||||
className="underline text-txt-primary hover:text-txt-secondary"
|
||||
>
|
||||
{__("Log in here")}
|
||||
</Link>
|
||||
</p>
|
||||
<div className="text-center mt-6 text-sm text-txt-secondary">
|
||||
<Link
|
||||
to="/auth/login"
|
||||
className="underline hover:text-txt-primary"
|
||||
>
|
||||
{__("Go back")}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user