From 468a0191ca3867b239ce1ef3c244d3eb3caee35d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Tue, 6 Jan 2026 19:41:24 +0100 Subject: [PATCH] Plug forgot password page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Émile Ré --- .../iam/ForgotPasswordPageMutation.graphql.ts | 92 ++++++++++++ .../src/pages/auth/ForgotPasswordPage.tsx | 132 ---------------- .../src/pages/iam/auth/ForgotPasswordPage.tsx | 142 ++++++++++++++++++ apps/console/src/routes.tsx | 2 +- 4 files changed, 235 insertions(+), 133 deletions(-) create mode 100644 apps/console/src/__generated__/iam/ForgotPasswordPageMutation.graphql.ts delete mode 100644 apps/console/src/pages/auth/ForgotPasswordPage.tsx create mode 100644 apps/console/src/pages/iam/auth/ForgotPasswordPage.tsx diff --git a/apps/console/src/__generated__/iam/ForgotPasswordPageMutation.graphql.ts b/apps/console/src/__generated__/iam/ForgotPasswordPageMutation.graphql.ts new file mode 100644 index 000000000..31bef8998 --- /dev/null +++ b/apps/console/src/__generated__/iam/ForgotPasswordPageMutation.graphql.ts @@ -0,0 +1,92 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type ForgotPasswordInput = { + email: string; +}; +export type ForgotPasswordPageMutation$variables = { + input: ForgotPasswordInput; +}; +export type ForgotPasswordPageMutation$data = { + readonly forgotPassword: { + readonly success: boolean; + } | null | undefined; +}; +export type ForgotPasswordPageMutation = { + response: ForgotPasswordPageMutation$data; + variables: ForgotPasswordPageMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" + } +], +v1 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } + ], + "concreteType": "ForgotPasswordPayload", + "kind": "LinkedField", + "name": "forgotPassword", + "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": "ForgotPasswordPageMutation", + "selections": (v1/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "ForgotPasswordPageMutation", + "selections": (v1/*: any*/) + }, + "params": { + "cacheID": "bb493ef26d51e7d560857008669c2ec5", + "id": null, + "metadata": {}, + "name": "ForgotPasswordPageMutation", + "operationKind": "mutation", + "text": "mutation ForgotPasswordPageMutation(\n $input: ForgotPasswordInput!\n) {\n forgotPassword(input: $input) {\n success\n }\n}\n" + } +}; +})(); + +(node as any).hash = "de03243068e655c7f48b7e96e145a617"; + +export default node; diff --git a/apps/console/src/pages/auth/ForgotPasswordPage.tsx b/apps/console/src/pages/auth/ForgotPasswordPage.tsx deleted file mode 100644 index 5d9e712f5..000000000 --- a/apps/console/src/pages/auth/ForgotPasswordPage.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { Link } 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 { useState } from "react"; - -const schema = z.object({ - email: z.string().email(), -}); - -export default function ForgotPasswordPage() { - const { __ } = useTranslate(); - const { toast } = useToast(); - const [isSubmitted, setIsSubmitted] = useState(false); - const { register, handleSubmit, formState } = useFormWithSchema(schema, { - defaultValues: { - email: "", - }, - }); - - const onSubmit = handleSubmit(async (data) => { - const response = await fetch("/connect/forget-password", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - credentials: "include", - body: JSON.stringify(data), - }); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - toast({ - title: __("Request failed"), - description: - errorData.message || __("Failed to send reset instructions"), - variant: "error", - }); - return; - } - - toast({ - title: __("Success"), - description: __("Password reset instructions sent to your email"), - variant: "success", - }); - setIsSubmitted(true); - }); - - usePageTitle(__("Forgot Password")); - - if (isSubmitted) { - return ( -
-
-

{__("Check your email")}

-

- {__("We've sent password reset instructions to your email address")} -

-
- -
-

- {__("Didn't receive the email?")}{" "} - -

-
- -
-

- {__("Remember your password?")}{" "} - - {__("Back to login")} - -

-
-
- ); - } - - return ( -
-
-

{__("Forgot Password")}

-

- {__( - "Enter your email address and we'll send you instructions to reset your password" - )} -

-
- -
- - - - - -
-

- {__("Remember your password?")}{" "} - - {__("Back to login")} - -

-
-
- ); -} diff --git a/apps/console/src/pages/iam/auth/ForgotPasswordPage.tsx b/apps/console/src/pages/iam/auth/ForgotPasswordPage.tsx new file mode 100644 index 000000000..0b27edddb --- /dev/null +++ b/apps/console/src/pages/iam/auth/ForgotPasswordPage.tsx @@ -0,0 +1,142 @@ +import { usePageTitle } from "@probo/hooks"; +import { useTranslate } from "@probo/i18n"; +import { Button, Field, useToast } from "@probo/ui"; +import { useState } from "react"; +import { Link } from "react-router"; +import { useFormWithSchema } from "/hooks/useFormWithSchema"; +import z from "zod"; +import { graphql } from "relay-runtime"; +import { useMutation } from "react-relay"; +import type { ForgotPasswordPageMutation } from "/__generated__/iam/ForgotPasswordPageMutation.graphql"; + +const sendInstructionsMutation = graphql` + mutation ForgotPasswordPageMutation($input: ForgotPasswordInput!) { + forgotPassword(input: $input) { + success + } + } +`; + +const schema = z.object({ + email: z.string().email(), +}); + +export default function ForgotPasswordPage() { + const { toast } = useToast(); + const { __ } = useTranslate(); + + usePageTitle(__("Forgot Password")); + + const [instructionsSent, setInstructionsSent] = useState(); + const { register, handleSubmit, formState } = useFormWithSchema(schema, { + defaultValues: { + email: "", + }, + }); + + const [sendInstructions] = useMutation( + sendInstructionsMutation, + ); + + const onSubmit = handleSubmit(async ({ email }) => { + sendInstructions({ + variables: { + input: { email }, + }, + onError: (e: Error) => { + toast({ + title: __("Request failed"), + description: e.message || __("Failed to send reset instructions"), + variant: "error", + }); + }, + onCompleted: () => { + toast({ + title: __("Success"), + description: __("Password reset instructions sent to your email"), + variant: "success", + }); + setInstructionsSent(true); + }, + }); + }); + + return instructionsSent ? ( +
+
+

{__("Check your email")}

+

+ {__("We've sent password reset instructions to your email address")} +

+
+ +
+

+ {__("Didn't receive the email?")}{" "} + +

+
+ +
+

+ {__("Remember your password?")}{" "} + + {__("Back to login")} + +

+
+
+ ) : ( +
+
+

{__("Forgot Password")}

+

+ {__( + "Enter your email address and we'll send you instructions to reset your password", + )} +

+
+ +
+ + + + + +
+

+ {__("Remember your password?")}{" "} + + {__("Back to login")} + +

+
+
+ ); +} diff --git a/apps/console/src/routes.tsx b/apps/console/src/routes.tsx index f5aa07888..3ace33bb8 100644 --- a/apps/console/src/routes.tsx +++ b/apps/console/src/routes.tsx @@ -93,7 +93,7 @@ const routes = [ }, { path: "forgot-password", - Component: lazy(() => import("./pages/auth/ForgotPasswordPage")), + Component: lazy(() => import("./pages/iam/auth/ForgotPasswordPage")), }, { path: "reset-password",