Refactor invitation system

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-10-10 01:11:09 +02:00
parent 0f96b8518f
commit 165eb267f2
39 changed files with 2730 additions and 1194 deletions

View File

@@ -1,35 +1,49 @@
import { Link, useNavigate } from "react-router";
import { Link, useNavigate, useSearchParams } from "react-router";
import { Button, Field, useToast } from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { z } from "zod";
import { useFormWithSchema } from "/hooks/useFormWithSchema";
import { usePageTitle } from "@probo/hooks";
import { buildEndpoint } from "/providers/RelayProviders";
import { useEffect } from "react";
const schema = z.object({
fullName: z.string().min(2),
password: z.string().min(8),
});
export default function ConfirmInvitationPage() {
export default function SignupFromInvitationPage() {
const { __ } = useTranslate();
const navigate = useNavigate();
const { toast } = useToast();
const { register, handleSubmit, formState } = useFormWithSchema(
const [searchParams] = useSearchParams();
const { register, handleSubmit, formState, reset } = useFormWithSchema(
schema,
{
defaultValues: {
fullName: "",
password: "",
},
}
);
useEffect(() => {
const fullNameFromParams = searchParams.get("fullName") || "";
if (fullNameFromParams) {
reset({
fullName: fullNameFromParams,
password: "",
});
}
}, [searchParams, reset]);
const onSubmit = handleSubmit(async (data) => {
const searchParams = new URLSearchParams(location.search);
const token = searchParams.get("token");
if (!token) {
toast({
title: __("Confirmation failed"),
title: __("Signup failed"),
description: __("Invalid or missing invitation token"),
variant: "error",
});
@@ -37,7 +51,7 @@ export default function ConfirmInvitationPage() {
}
const response = await fetch(
buildEndpoint("/api/console/v1/auth/invitation"),
buildEndpoint("/api/console/v1/auth/signup-from-invitation"),
{
method: "POST",
headers: {
@@ -47,16 +61,16 @@ export default function ConfirmInvitationPage() {
body: JSON.stringify({
token: token,
password: data.password,
fullName: data.fullName,
}),
}
);
// Registration failed
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
toast({
title: __("Confirmation failed"),
description: errorData.message || __("Confirmation failed"),
title: __("Signup failed"),
description: errorData.message || __("Signup failed"),
variant: "error",
});
return;
@@ -64,24 +78,33 @@ export default function ConfirmInvitationPage() {
toast({
title: __("Success"),
description: __("Invitation confirmed successfully"),
description: __("Account created successfully. Please accept your invitation to join the organization."),
variant: "success",
});
navigate("/", { replace: true });
});
usePageTitle(__("Confirm invitation"));
usePageTitle(__("Create your account"));
return (
<div className="space-y-6 w-full max-w-md mx-auto">
<div className="space-y-2 text-center">
<h1 className="text-3xl font-bold">{__("Confirm invitation")}</h1>
<h1 className="text-3xl font-bold">{__("Create your account")}</h1>
<p className="text-txt-tertiary">
{__("Enter your information to confirm your invitation")}
{__("Set your password to join the organization")}
</p>
</div>
<form onSubmit={onSubmit} 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"
@@ -93,8 +116,8 @@ export default function ConfirmInvitationPage() {
<Button type="submit" className="w-full" disabled={formState.isLoading}>
{formState.isLoading
? __("Confirming invitation...")
: __("Confirm invitation")}
? __("Creating account...")
: __("Create account")}
</Button>
</form>