diff --git a/apps/console/src/pages/auth/ConfirmInvitationPage.tsx b/apps/console/src/pages/auth/ConfirmInvitationPage.tsx index 741cdf88a..67ff0b9e0 100644 --- a/apps/console/src/pages/auth/ConfirmInvitationPage.tsx +++ b/apps/console/src/pages/auth/ConfirmInvitationPage.tsx @@ -109,7 +109,7 @@ export default function ConfirmInvitationPage() {
{__("Already have an account?")}{" "} {__("Log in here")} diff --git a/apps/console/src/pages/auth/ForgotPasswordPage.tsx b/apps/console/src/pages/auth/ForgotPasswordPage.tsx new file mode 100644 index 000000000..6aeb1ffb5 --- /dev/null +++ b/apps/console/src/pages/auth/ForgotPasswordPage.tsx @@ -0,0 +1,136 @@ +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 { buildEndpoint } from "/providers/RelayProviders"; +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( + buildEndpoint("/api/console/v1/auth/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 ( +
+ {__("We've sent password reset instructions to your email address")} +
++ {__("Didn't receive the email?")}{" "} + +
++ {__("Remember your password?")}{" "} + + {__("Back to login")} + +
++ {__( + "Enter your email address and we'll send you instructions to reset your password" + )} +
++ {__("Remember your password?")}{" "} + + {__("Back to login")} + +
+{__("Already have an account?")}{" "} {__("Log in here")} diff --git a/apps/console/src/pages/auth/ResetPasswordPage.tsx b/apps/console/src/pages/auth/ResetPasswordPage.tsx new file mode 100644 index 000000000..96fe76514 --- /dev/null +++ b/apps/console/src/pages/auth/ResetPasswordPage.tsx @@ -0,0 +1,140 @@ +import { Link, useNavigate } 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({ + token: z.string(), + password: z.string().min(8), + confirmPassword: z.string().min(8), + }) + .refine((data) => data.password === data.confirmPassword, { + message: "Passwords don't match", + path: ["confirmPassword"], + }); + +export default function ResetPasswordPage() { + const { __ } = useTranslate(); + const navigate = useNavigate(); + const { toast } = useToast(); + const { register, handleSubmit, formState, setValue } = useFormWithSchema( + schema, + { + defaultValues: { + token: "", + password: "", + confirmPassword: "", + }, + } + ); + + const onSubmit = handleSubmit(async (data) => { + const response = await fetch( + buildEndpoint("/api/console/v1/auth/reset-password"), + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + credentials: "include", + body: JSON.stringify({ + token: data.token, + password: data.password, + }), + } + ); + + // Reset failed + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + toast({ + title: __("Reset failed"), + description: errorData.message || __("Password reset failed"), + variant: "error", + }); + return; + } + + toast({ + title: __("Success"), + description: __("Password reset successfully"), + variant: "success", + }); + navigate("/auth/login", { replace: true }); + }); + + usePageTitle(__("Reset password")); + + useEffect(() => { + const searchParams = new URLSearchParams(location.search); + const urlToken = searchParams.get("token"); + + if (urlToken) { + setValue("token", urlToken); + } + }, [location.search, setValue]); + + return ( +
+ {__("Enter your new password to reset your account")} +
++ {__("Remember your password?")}{" "} + + {__("Log in here")} + +
+