-
Notifications
You must be signed in to change notification settings - Fork 315
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: smtp for password reset (#4630)
- Loading branch information
1 parent
cc453e0
commit 34d5763
Showing
29 changed files
with
1,088 additions
and
150 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import React, { useCallback, useState } from "react"; | ||
import { Controller, useForm } from "react-hook-form"; | ||
import { css } from "@emotion/react"; | ||
|
||
import { Alert, Button, Form, TextField, View } from "@arizeai/components"; | ||
|
||
type ForgotPasswordFormParams = { | ||
email: string; | ||
}; | ||
|
||
export function ForgotPasswordForm() { | ||
const [message, setMessage] = useState<string | null>(null); | ||
const [error, setError] = useState<string | null>(null); | ||
const [isLoading, setIsLoading] = useState<boolean>(false); | ||
const onSubmit = useCallback( | ||
async (params: ForgotPasswordFormParams) => { | ||
setMessage(null); | ||
setError(null); | ||
setIsLoading(true); | ||
try { | ||
const response = await fetch("/auth/password-reset-email", { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify(params), | ||
}); | ||
if (!response.ok) { | ||
setError("Failed attempt"); | ||
return; | ||
} | ||
} catch (error) { | ||
setError("Failed attempt"); | ||
return; | ||
} finally { | ||
setIsLoading(() => false); | ||
} | ||
setMessage( | ||
"A link to reset your password has been sent. Check your email for details." | ||
); | ||
}, | ||
[setMessage, setError] | ||
); | ||
const { control, handleSubmit } = useForm<ForgotPasswordFormParams>({ | ||
defaultValues: { email: "" }, | ||
}); | ||
return ( | ||
<> | ||
{message ? ( | ||
<View paddingBottom="size-100"> | ||
<Alert variant="success">{message}</Alert> | ||
</View> | ||
) : null} | ||
{error ? ( | ||
<View paddingBottom="size-100"> | ||
<Alert variant="danger">{error}</Alert> | ||
</View> | ||
) : null} | ||
<Form> | ||
<Controller | ||
name="email" | ||
control={control} | ||
render={({ field: { onChange, value } }) => ( | ||
<TextField | ||
label="Email" | ||
name="email" | ||
isRequired | ||
type="email" | ||
onChange={onChange} | ||
value={value} | ||
placeholder="your email address" | ||
/> | ||
)} | ||
/> | ||
<div | ||
css={css` | ||
margin-top: var(--ac-global-dimension-size-400); | ||
margin-bottom: var(--ac-global-dimension-size-50); | ||
button { | ||
width: 100%; | ||
} | ||
`} | ||
> | ||
<Button | ||
variant="primary" | ||
loading={isLoading} | ||
onClick={handleSubmit(onSubmit)} | ||
> | ||
Submit | ||
</Button> | ||
</div> | ||
</Form> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import React from "react"; | ||
|
||
import { Flex, View } from "@arizeai/components"; | ||
|
||
import { AuthLayout } from "./AuthLayout"; | ||
import { ForgotPasswordForm } from "./ForgotPasswordForm"; | ||
import { PhoenixLogo } from "./PhoenixLogo"; | ||
|
||
export function ForgotPasswordPage() { | ||
return ( | ||
<AuthLayout> | ||
<Flex direction="column" gap="size-200" alignItems="center"> | ||
<View paddingBottom="size-200"> | ||
<PhoenixLogo /> | ||
</View> | ||
</Flex> | ||
<ForgotPasswordForm /> | ||
</AuthLayout> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import React, { useCallback, useState } from "react"; | ||
import { Controller, useForm } from "react-hook-form"; | ||
import { useNavigate } from "react-router"; | ||
|
||
import { | ||
Alert, | ||
Button, | ||
Flex, | ||
Form, | ||
TextField, | ||
View, | ||
} from "@arizeai/components"; | ||
|
||
const MIN_PASSWORD_LENGTH = 4; | ||
|
||
export type ResetPasswordWithTokenFormParams = { | ||
resetToken: string; | ||
newPassword: string; | ||
confirmPassword: string; | ||
}; | ||
|
||
interface ResetPasswordWithTokenFormProps { | ||
resetToken: string; | ||
} | ||
|
||
export function ResetPasswordWithTokenForm({ | ||
resetToken, | ||
}: ResetPasswordWithTokenFormProps) { | ||
const navigate = useNavigate(); | ||
const [message, setMessage] = useState<string | null>(null); | ||
const [error, setError] = useState<string | null>(null); | ||
const [isLoading, setIsLoading] = useState<boolean>(false); | ||
const onSubmit = useCallback( | ||
async ({ resetToken, newPassword }: ResetPasswordWithTokenFormParams) => { | ||
setMessage(null); | ||
setError(null); | ||
setIsLoading(true); | ||
try { | ||
const response = await fetch("/auth/password-reset", { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify({ token: resetToken, password: newPassword }), | ||
}); | ||
if (!response.ok) { | ||
setError("Failed attempt"); | ||
return; | ||
} | ||
} catch (error) { | ||
setError("Failed attempt"); | ||
return; | ||
} finally { | ||
setIsLoading(() => false); | ||
} | ||
setMessage("Success"); | ||
navigate("/login"); | ||
}, | ||
[setMessage, setError, navigate] | ||
); | ||
const { | ||
control, | ||
handleSubmit, | ||
formState: { isDirty }, | ||
} = useForm<ResetPasswordWithTokenFormParams>({ | ||
defaultValues: { | ||
resetToken: resetToken, | ||
newPassword: "", | ||
confirmPassword: "", | ||
}, | ||
}); | ||
return ( | ||
<> | ||
{message ? ( | ||
<View paddingBottom="size-100"> | ||
<Alert variant="success">{message}</Alert> | ||
</View> | ||
) : null} | ||
{error ? ( | ||
<View paddingBottom="size-100"> | ||
<Alert variant="danger">{error}</Alert> | ||
</View> | ||
) : null} | ||
<Form onSubmit={handleSubmit(onSubmit)}> | ||
<Controller | ||
name="newPassword" | ||
control={control} | ||
rules={{ | ||
required: "Password is required", | ||
minLength: { | ||
value: MIN_PASSWORD_LENGTH, | ||
message: `Password must be at least ${MIN_PASSWORD_LENGTH} characters`, | ||
}, | ||
}} | ||
render={({ | ||
field: { name, onChange, onBlur, value }, | ||
fieldState: { invalid, error }, | ||
}) => ( | ||
<TextField | ||
label="New Password" | ||
type="password" | ||
isRequired | ||
description={`Password must be at least ${MIN_PASSWORD_LENGTH} characters`} | ||
name={name} | ||
errorMessage={error?.message} | ||
validationState={invalid ? "invalid" : "valid"} | ||
onChange={onChange} | ||
onBlur={onBlur} | ||
defaultValue={value} | ||
/> | ||
)} | ||
/> | ||
<Controller | ||
name="confirmPassword" | ||
control={control} | ||
rules={{ | ||
required: "Password is required", | ||
minLength: { | ||
value: MIN_PASSWORD_LENGTH, | ||
message: `Password must be at least ${MIN_PASSWORD_LENGTH} characters`, | ||
}, | ||
validate: (value, formValues) => | ||
value === formValues.newPassword || "Passwords do not match", | ||
}} | ||
render={({ | ||
field: { name, onChange, onBlur, value }, | ||
fieldState: { invalid, error }, | ||
}) => ( | ||
<TextField | ||
label="Confirm Password" | ||
isRequired | ||
type="password" | ||
description="Confirm the new password" | ||
name={name} | ||
errorMessage={error?.message} | ||
validationState={invalid ? "invalid" : "valid"} | ||
onChange={onChange} | ||
onBlur={onBlur} | ||
defaultValue={value} | ||
/> | ||
)} | ||
/> | ||
<View paddingTop="size-200"> | ||
<Flex direction="row" gap="size-100" justifyContent="end"> | ||
<Button | ||
variant={isDirty ? "primary" : "default"} | ||
type="submit" | ||
disabled={isLoading} | ||
> | ||
{isLoading ? "Resetting..." : "Reset Password"} | ||
</Button> | ||
</Flex> | ||
</View> | ||
</Form> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import React from "react"; | ||
import { useNavigate } from "react-router"; | ||
import { useSearchParams } from "react-router-dom"; | ||
|
||
import { Flex, View } from "@arizeai/components"; | ||
|
||
import { AuthLayout } from "./AuthLayout"; | ||
import { PhoenixLogo } from "./PhoenixLogo"; | ||
import { ResetPasswordWithTokenForm } from "./ResetPasswordWithTokenForm"; | ||
|
||
export function ResetPasswordWithTokenPage() { | ||
const navigate = useNavigate(); | ||
const [searchParams] = useSearchParams(); | ||
const token = searchParams.get("token"); | ||
if (!token) { | ||
navigate("/login"); | ||
return null; | ||
} | ||
return ( | ||
<AuthLayout> | ||
<Flex direction="column" gap="size-200" alignItems="center"> | ||
<View paddingBottom="size-200"> | ||
<PhoenixLogo /> | ||
</View> | ||
</Flex> | ||
<ResetPasswordWithTokenForm resetToken={token} /> | ||
</AuthLayout> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
export * from "./LoginPage"; | ||
export * from "./ResetPasswordPage"; | ||
export * from "./ResetPasswordWithTokenPage"; | ||
export * from "./resetPasswordLoader"; | ||
export * from "./ForgotPasswordPage"; |
Oops, something went wrong.