From 588d96b713ac943f36098b6733a219385e64765d Mon Sep 17 00:00:00 2001 From: Sacha Al Himdani Date: Fri, 13 Jun 2025 17:39:59 -0700 Subject: [PATCH] Add confirm email to the new ui Signed-off-by: Sacha Al Himdani --- .../src/pages/auth/ConfirmEmailPage.tsx | 154 ++++++++++++++++++ .../ConfirmEmailPageMutation.graphql.ts | 92 +++++++++++ apps/console/src/routes.tsx | 4 + pkg/usrmgr/usrmgr.go | 2 +- 4 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 apps/console/src/pages/auth/ConfirmEmailPage.tsx create mode 100644 apps/console/src/pages/auth/__generated__/ConfirmEmailPageMutation.graphql.ts diff --git a/apps/console/src/pages/auth/ConfirmEmailPage.tsx b/apps/console/src/pages/auth/ConfirmEmailPage.tsx new file mode 100644 index 000000000..f788e79a5 --- /dev/null +++ b/apps/console/src/pages/auth/ConfirmEmailPage.tsx @@ -0,0 +1,154 @@ +import { useState, useEffect } from "react"; +import { useLocation, useNavigate, Link } from "react-router"; +import { Button, Field, useToast } from "@probo/ui"; +import { useTranslate } from "@probo/i18n"; +import { useMutation } from "react-relay"; +import { graphql } from "relay-runtime"; +import type { PayloadError } from "relay-runtime"; +import { z } from "zod"; +import { useFormWithSchema } from "/hooks/useFormWithSchema"; +import { usePageTitle } from "@probo/hooks"; +import type { ConfirmEmailPageMutation } from "./__generated__/ConfirmEmailPageMutation.graphql"; + +const ConfirmEmailMutation = graphql` + mutation ConfirmEmailPageMutation($input: ConfirmEmailInput!) { + confirmEmail(input: $input) { + success + } + } +`; + +const confirmEmailSchema = z.object({ + token: z.string().min(1, "Please enter a confirmation token"), +}); + +export default function ConfirmEmailPage() { + const [isLoading, setIsLoading] = useState(false); + const [isConfirmed, setIsConfirmed] = useState(false); + const location = useLocation(); + const navigate = useNavigate(); + const { __ } = useTranslate(); + const { toast } = useToast(); + const [commitMutation] = + useMutation(ConfirmEmailMutation); + + const form = useFormWithSchema(confirmEmailSchema, { + defaultValues: { + token: "", + }, + }); + + usePageTitle(__("Confirm Email")); + + useEffect(() => { + const searchParams = new URLSearchParams(location.search); + const urlToken = searchParams.get("token"); + + if (urlToken) { + form.setValue("token", urlToken); + } + }, [location.search, form]); + + const handleSubmit = form.handleSubmit(async (data) => { + setIsLoading(true); + + try { + commitMutation({ + variables: { + input: { + token: data.token.trim(), + }, + }, + onCompleted: (_response, errors: PayloadError[] | null) => { + if (errors) { + toast({ + title: __("Error"), + description: errors[0]?.message || __("Failed to confirm email"), + variant: "error", + }); + setIsLoading(false); + return; + } + + setIsConfirmed(true); + toast({ + title: __("Success"), + description: __("Your email has been confirmed successfully"), + variant: "success", + }); + setIsLoading(false); + }, + onError: (err) => { + toast({ + title: __("Error"), + description: err.message || __("Failed to confirm email. Please try again."), + variant: "error", + }); + setIsLoading(false); + }, + }); + } catch (error) { + toast({ + title: __("Error"), + description: error instanceof Error ? error.message : __("Failed to confirm email. Please try again."), + variant: "error", + }); + setIsLoading(false); + } + }); + + return ( +
+
+

{__("Email Confirmation")}

+

+ {__("Confirm your email address to complete registration")} +

+
+ + {isConfirmed ? ( +
+

+ {__("Your email has been confirmed successfully!")} +

+ +
+ ) : ( +
+ + + + + )} + +
+ {!isConfirmed && ( +

+ + {__("Back to Login")} + +

+ )} +
+
+ ); +} diff --git a/apps/console/src/pages/auth/__generated__/ConfirmEmailPageMutation.graphql.ts b/apps/console/src/pages/auth/__generated__/ConfirmEmailPageMutation.graphql.ts new file mode 100644 index 000000000..95108e081 --- /dev/null +++ b/apps/console/src/pages/auth/__generated__/ConfirmEmailPageMutation.graphql.ts @@ -0,0 +1,92 @@ +/** + * @generated SignedSource<<3de8e69abff0cf5f1000d93dde7a3031>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type ConfirmEmailInput = { + token: string; +}; +export type ConfirmEmailPageMutation$variables = { + input: ConfirmEmailInput; +}; +export type ConfirmEmailPageMutation$data = { + readonly confirmEmail: { + readonly success: boolean; + }; +}; +export type ConfirmEmailPageMutation = { + response: ConfirmEmailPageMutation$data; + variables: ConfirmEmailPageMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" + } +], +v1 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } + ], + "concreteType": "ConfirmEmailPayload", + "kind": "LinkedField", + "name": "confirmEmail", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "success", + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "ConfirmEmailPageMutation", + "selections": (v1/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "ConfirmEmailPageMutation", + "selections": (v1/*: any*/) + }, + "params": { + "cacheID": "b6bde3a559a4ecb70a519ffb7fa83330", + "id": null, + "metadata": {}, + "name": "ConfirmEmailPageMutation", + "operationKind": "mutation", + "text": "mutation ConfirmEmailPageMutation(\n $input: ConfirmEmailInput!\n) {\n confirmEmail(input: $input) {\n success\n }\n}\n" + } +}; +})(); + +(node as any).hash = "e7f442b5acc6d912bf272ca2f5dc1ef1"; + +export default node; diff --git a/apps/console/src/routes.tsx b/apps/console/src/routes.tsx index 16e594fef..ee1e51ce2 100644 --- a/apps/console/src/routes.tsx +++ b/apps/console/src/routes.tsx @@ -62,6 +62,10 @@ const routes = [ path: "register", Component: lazy(() => import("./pages/auth/RegisterPage")), }, + { + path: "confirm-email", + Component: lazy(() => import("./pages/auth/ConfirmEmailPage")), + }, ], }, { diff --git a/pkg/usrmgr/usrmgr.go b/pkg/usrmgr/usrmgr.go index f4eabc271..6add81e4b 100644 --- a/pkg/usrmgr/usrmgr.go +++ b/pkg/usrmgr/usrmgr.go @@ -295,7 +295,7 @@ func (s Service) SignUp( confirmationEmailUrl := url.URL{ Scheme: "https", Host: s.hostname, - Path: "/confirm-email", + Path: "/auth/confirm-email", RawQuery: url.Values{ "token": []string{confirmationToken}, }.Encode(),