From 62aacced712dc93304177ba569ad0233d12de74a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Tue, 6 Jan 2026 21:58:12 +0100 Subject: [PATCH] Plug verify email MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Émile Ré --- .../iam/VerifyEmailPageMutation.graphql.ts | 92 ++++++++++++ .../src/pages/iam/auth/VerifyEmailPage.tsx | 135 ++++++++++++++++++ .../src/pages/iam/auth/sign-up/SignUpPage.tsx | 8 +- .../_components/NewSAMLConfigurationForm.tsx | 2 +- apps/console/src/routes.tsx | 8 +- pkg/iam/account_service.go | 2 +- pkg/iam/auth_service.go | 2 +- 7 files changed, 239 insertions(+), 10 deletions(-) create mode 100644 apps/console/src/__generated__/iam/VerifyEmailPageMutation.graphql.ts create mode 100644 apps/console/src/pages/iam/auth/VerifyEmailPage.tsx diff --git a/apps/console/src/__generated__/iam/VerifyEmailPageMutation.graphql.ts b/apps/console/src/__generated__/iam/VerifyEmailPageMutation.graphql.ts new file mode 100644 index 000000000..673687d56 --- /dev/null +++ b/apps/console/src/__generated__/iam/VerifyEmailPageMutation.graphql.ts @@ -0,0 +1,92 @@ +/** + * @generated SignedSource<<793a4f7b17b13f925362dab5dfb590e2>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type VerifyEmailInput = { + token: string; +}; +export type VerifyEmailPageMutation$variables = { + input: VerifyEmailInput; +}; +export type VerifyEmailPageMutation$data = { + readonly verifyEmail: { + readonly success: boolean; + } | null | undefined; +}; +export type VerifyEmailPageMutation = { + response: VerifyEmailPageMutation$data; + variables: VerifyEmailPageMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" + } +], +v1 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } + ], + "concreteType": "VerifyEmailPayload", + "kind": "LinkedField", + "name": "verifyEmail", + "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": "VerifyEmailPageMutation", + "selections": (v1/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "VerifyEmailPageMutation", + "selections": (v1/*: any*/) + }, + "params": { + "cacheID": "c1ea88cfa9a90fed624ad996bbbec2ef", + "id": null, + "metadata": {}, + "name": "VerifyEmailPageMutation", + "operationKind": "mutation", + "text": "mutation VerifyEmailPageMutation(\n $input: VerifyEmailInput!\n) {\n verifyEmail(input: $input) {\n success\n }\n}\n" + } +}; +})(); + +(node as any).hash = "3e4812eb6e13aa35849bd929b26268b0"; + +export default node; diff --git a/apps/console/src/pages/iam/auth/VerifyEmailPage.tsx b/apps/console/src/pages/iam/auth/VerifyEmailPage.tsx new file mode 100644 index 000000000..1834c358d --- /dev/null +++ b/apps/console/src/pages/iam/auth/VerifyEmailPage.tsx @@ -0,0 +1,135 @@ +import { usePageTitle } from "@probo/hooks"; +import { useTranslate } from "@probo/i18n"; +import { Button, Field, useToast } from "@probo/ui"; +import { Link, useSearchParams } from "react-router"; +import { useFormWithSchema } from "/hooks/useFormWithSchema"; +import z from "zod"; +import { useState } from "react"; +import { graphql } from "relay-runtime"; +import { useMutation } from "react-relay"; +import { formatError } from "@probo/helpers"; +import type { VerifyEmailPageMutation } from "/__generated__/iam/VerifyEmailPageMutation.graphql"; + +const verifyEmailMutation = graphql` + mutation VerifyEmailPageMutation($input: VerifyEmailInput!) { + verifyEmail(input: $input) { + success + } + } +`; + +const confirmEmailSchema = z.object({ + token: z.string().min(1, "Please enter a confirmation token"), +}); + +export default function VerifyEmailPage() { + const { __ } = useTranslate(); + const { toast } = useToast(); + const [searchParams] = useSearchParams(); + + usePageTitle(__("Confirm Email")); + + const [isConfirmed, setIsConfirmed] = useState(false); + + const form = useFormWithSchema(confirmEmailSchema, { + defaultValues: { + token: searchParams.get("token") ?? "", + }, + }); + + const [verifyEmail] = + useMutation(verifyEmailMutation); + + const handleSubmit = form.handleSubmit(async (data) => { + verifyEmail({ + variables: { + input: { + token: data.token.trim(), + }, + }, + onCompleted: (_, errors) => { + if (errors) { + toast({ + title: __("Error"), + description: formatError(__("Failed to confirm email"), errors), + variant: "error", + }); + return; + } + + setIsConfirmed(true); + toast({ + title: __("Success"), + description: __("Your email has been confirmed successfully"), + variant: "success", + }); + }, + onError: (err) => { + toast({ + title: __("Error"), + description: err.message || __("Failed to confirm email"), + variant: "error", + }); + }, + }); + }); + + 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/iam/auth/sign-up/SignUpPage.tsx b/apps/console/src/pages/iam/auth/sign-up/SignUpPage.tsx index 0651591e3..81373ebed 100644 --- a/apps/console/src/pages/iam/auth/sign-up/SignUpPage.tsx +++ b/apps/console/src/pages/iam/auth/sign-up/SignUpPage.tsx @@ -46,9 +46,11 @@ export default function SignUpPage() { const onSubmit = (data: FormData) => { signUp({ variables: { - email: data.email, - password: data.password, - fullName: data.fullName, + input: { + email: data.email, + password: data.password, + fullName: data.fullName, + }, }, onCompleted: (_, e) => { if (e) { diff --git a/apps/console/src/pages/iam/organizations/settings/_components/NewSAMLConfigurationForm.tsx b/apps/console/src/pages/iam/organizations/settings/_components/NewSAMLConfigurationForm.tsx index 2f1383d82..49def49cc 100644 --- a/apps/console/src/pages/iam/organizations/settings/_components/NewSAMLConfigurationForm.tsx +++ b/apps/console/src/pages/iam/organizations/settings/_components/NewSAMLConfigurationForm.tsx @@ -67,7 +67,7 @@ export function NewSAMLConfigurationForm(props: { onCreate: () => void }) { }, connections: [connectionID], }, - onCompleted: (response, e) => { + onCompleted: (_, e) => { if (e) { toast({ variant: "error", diff --git a/apps/console/src/routes.tsx b/apps/console/src/routes.tsx index 85f327ac2..c14706347 100644 --- a/apps/console/src/routes.tsx +++ b/apps/console/src/routes.tsx @@ -81,10 +81,10 @@ const routes = [ path: "register", Component: lazy(() => import("./pages/iam/auth/sign-up/SignUpPage")), }, - // { - // path: "confirm-email", - // Component: lazy(() => import("./pages/auth/ConfirmEmailPage")), - // }, + { + path: "verify-email", + Component: lazy(() => import("./pages/iam/auth/VerifyEmailPage")), + }, { path: "signup-from-invitation", Component: lazy( diff --git a/pkg/iam/account_service.go b/pkg/iam/account_service.go index 4e4fe528a..1d1b8994b 100644 --- a/pkg/iam/account_service.go +++ b/pkg/iam/account_service.go @@ -92,7 +92,7 @@ func (s AccountService) ChangeEmail(ctx context.Context, identityID gid.GID, req } confirmationUrl := base. - WithPath("/auth/confirm-email"). + WithPath("/auth/verify-email"). WithQuery("token", confirmationToken). MustString() diff --git a/pkg/iam/auth_service.go b/pkg/iam/auth_service.go index 2e26ff9db..7cdbe5d40 100644 --- a/pkg/iam/auth_service.go +++ b/pkg/iam/auth_service.go @@ -349,7 +349,7 @@ func (s AuthService) CreateIdentityWithPassword( } confirmationUrl, err := base. - WithPath("/auth/confirm-email"). + WithPath("/auth/verify-email"). WithQuery("token", confirmationToken). String() if err != nil {