-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Web: Better registration form (#775)
- Loading branch information
1 parent
1156677
commit e0a3d53
Showing
14 changed files
with
587 additions
and
307 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from "./useCheckCapabilities"; | ||
export { useNavRedirect } from "./useNavRedirect"; | ||
export { useCheckCapabilities } from "./useCheckCapabilities"; |
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,22 @@ | ||
import { useCallback } from "react"; | ||
import { useNavigate, useLocation } from "react-router-dom"; | ||
|
||
export function useNavRedirect() { | ||
const navigate = useNavigate(); | ||
const location = useLocation(); | ||
|
||
function redirectTo(url) { | ||
navigate(url); | ||
} | ||
|
||
const goBackToPrevLocation = useCallback(() => { | ||
const locationState = location.state || {}; | ||
const prevLocation = locationState.prevLocation || "/"; | ||
navigate(prevLocation); | ||
}, [location]); | ||
|
||
return { | ||
goBackToPrevLocation, | ||
redirectTo, | ||
}; | ||
} |
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,15 @@ | ||
import React from "react"; | ||
import { isNil } from "lodash"; | ||
|
||
export default function FormError(props) { | ||
const { errorField } = props; | ||
if (isNil(errorField)) { | ||
return <></>; | ||
} | ||
|
||
return ( | ||
<div className="invalid-feedback" style={{ display: "block" }}> | ||
{errorField.message} | ||
</div> | ||
); | ||
} |
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,11 @@ | ||
import React from "react"; | ||
|
||
export default function Label(props) { | ||
const { label, required, htmlFor } = props; | ||
|
||
return ( | ||
<label className={required ? "required" : ""} htmlFor={htmlFor}> | ||
{label} | ||
</label> | ||
); | ||
} |
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,21 @@ | ||
import React, { useMemo } from "react"; | ||
|
||
export default function LoadingSpinner(props) { | ||
//types is one of: [primary, secondary, success, danger, warning, info, light, dark] | ||
const { loading, type } = props; | ||
if (!loading) { | ||
return <></>; | ||
} | ||
|
||
const className = useMemo(() => { | ||
return `spinner-border ${ | ||
type ? `text-${type}` : "" | ||
} mr-2 spinner-border-sm`.trim(); | ||
}, [type]); | ||
|
||
return ( | ||
<span className={className} role="status"> | ||
<span className="sr-only">Loading...</span> | ||
</span> | ||
); | ||
} |
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
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,109 +1,167 @@ | ||
import React, { useState, useContext } from "react"; | ||
import React, { useState, useContext, useRef } from "react"; | ||
import ReCAPTCHA from "react-google-recaptcha"; | ||
import { toast } from "react-toastify"; | ||
import { useForm } from "react-hook-form"; | ||
import { yupResolver } from "@hookform/resolvers/yup"; | ||
import * as Yup from "yup"; | ||
|
||
import { api } from "@mwdb-web/commons/api"; | ||
import { ConfigContext } from "@mwdb-web/commons/config"; | ||
import { View, getErrorMessage } from "@mwdb-web/commons/ui"; | ||
import { | ||
View, | ||
getErrorMessage, | ||
Label, | ||
FormError, | ||
LoadingSpinner, | ||
} from "@mwdb-web/commons/ui"; | ||
import { useNavRedirect } from "@mwdb-web/commons/hooks"; | ||
|
||
export default function UserPasswordRecover() { | ||
const initialState = { | ||
login: "", | ||
email: "", | ||
}; | ||
const formFields = { | ||
login: "login", | ||
email: "email", | ||
recaptcha: "recaptcha", | ||
}; | ||
|
||
const config = useContext(ConfigContext); | ||
const [fieldState, setFieldState] = useState(initialState); | ||
const [success, setSuccess] = useState(false); | ||
const [recaptcha, setRecaptcha] = useState(null); | ||
const validationSchema = Yup.object().shape({ | ||
[formFields.login]: Yup.string().required("Login is required"), | ||
[formFields.email]: Yup.string() | ||
.required("Email is required") | ||
.matches(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g, "Value is not email"), | ||
[formFields.recaptcha]: Yup.string() | ||
.nullable() | ||
.required("Recaptcha is required"), | ||
}); | ||
|
||
const handleInputChange = (event) => { | ||
const value = event.target.value; | ||
const name = event.target.name; | ||
const formOptions = { | ||
resolver: yupResolver(validationSchema), | ||
mode: "onSubmit", | ||
reValidateMode: "onSubmit", | ||
shouldFocusError: true, | ||
}; | ||
|
||
export default function UserPasswordRecover() { | ||
const config = useContext(ConfigContext); | ||
const { redirectTo } = useNavRedirect(); | ||
const { | ||
register, | ||
handleSubmit, | ||
formState: { errors }, | ||
reset, | ||
setValue, | ||
} = useForm(formOptions); | ||
|
||
setFieldState((fieldState) => ({ | ||
...fieldState, | ||
[name]: value, | ||
})); | ||
}; | ||
const [loading, setLoading] = useState(false); | ||
const captchaRef = useRef(null); | ||
|
||
async function recoverPassword() { | ||
async function recoverPassword(values) { | ||
try { | ||
setLoading(true); | ||
await api.authRecoverPassword( | ||
fieldState.login, | ||
fieldState.email, | ||
recaptcha | ||
values.login, | ||
values.email, | ||
values.recaptcha | ||
); | ||
setSuccess(true); | ||
|
||
toast("Password reset link has been sent to the e-mail address", { | ||
type: "success", | ||
}); | ||
setLoading(false); | ||
redirectTo("/login"); | ||
} catch (error) { | ||
toast(getErrorMessage(error), { | ||
type: "error", | ||
}); | ||
setFieldState(initialState); | ||
setLoading(false); | ||
reset(); | ||
} finally { | ||
captchaRef.current?.reset(); | ||
setValue(formFields.recaptcha, null); | ||
} | ||
} | ||
|
||
const onCaptchaChange = (value) => { | ||
setRecaptcha(value); | ||
}; | ||
|
||
const handleSubmit = (event) => { | ||
event.preventDefault(); | ||
recoverPassword(); | ||
}; | ||
|
||
return ( | ||
<View> | ||
<h2>Recover password</h2> | ||
<form onSubmit={handleSubmit}> | ||
<div className="form-group"> | ||
<label>Login</label> | ||
<input | ||
type="text" | ||
name="login" | ||
value={fieldState.login} | ||
onChange={handleInputChange} | ||
className="form-control" | ||
disabled={success} | ||
required | ||
/> | ||
</div> | ||
<div className="form-group"> | ||
<label>Email</label> | ||
<input | ||
type="email" | ||
name="email" | ||
value={fieldState.email} | ||
onChange={handleInputChange} | ||
className="form-control" | ||
disabled={success} | ||
required | ||
/> | ||
</div> | ||
<div> | ||
<label> | ||
Please enter the information above to recover your | ||
password. | ||
</label> | ||
</div> | ||
{config.config["recaptcha_site_key"] ? ( | ||
<ReCAPTCHA | ||
sitekey={config.config["recaptcha_site_key"]} | ||
onChange={onCaptchaChange} | ||
/> | ||
) : ( | ||
[] | ||
)} | ||
<input | ||
type="submit" | ||
value="Submit" | ||
className="btn btn-primary" | ||
disabled={success} | ||
/> | ||
</form> | ||
</View> | ||
<div className="user-password-recover"> | ||
<div className="user-password-recover__background" /> | ||
<div className="user-password-recover__container"> | ||
<View> | ||
<h2 className="text-center">Recover password</h2> | ||
<form onSubmit={handleSubmit(recoverPassword)}> | ||
<div className="form-group"> | ||
<Label | ||
label="Login" | ||
required | ||
htmlFor={formFields.login} | ||
/> | ||
<input | ||
{...register(formFields.login)} | ||
id={formFields.login} | ||
className={`form-control ${ | ||
errors.login ? "is-invalid" : "" | ||
}`} | ||
/> | ||
<FormError errorField={errors.login} /> | ||
</div> | ||
<div className="form-group"> | ||
<Label | ||
label="Email" | ||
required | ||
htmlFor={formFields.email} | ||
/> | ||
<input | ||
{...register(formFields.email)} | ||
id={formFields.email} | ||
className={`form-control ${ | ||
errors.email ? "is-invalid" : "" | ||
}`} | ||
/> | ||
<FormError errorField={errors.email} /> | ||
</div> | ||
<div> | ||
<p> | ||
Please enter the information above to recover | ||
your password. | ||
</p> | ||
</div> | ||
{config.config["recaptcha_site_key"] && ( | ||
<> | ||
<ReCAPTCHA | ||
ref={captchaRef} | ||
style={{ | ||
display: "flex", | ||
justifyContent: "center", | ||
marginBottom: 12, | ||
}} | ||
sitekey={ | ||
config.config["recaptcha_site_key"] | ||
} | ||
onChange={(val) => | ||
setValue(formFields.recaptcha, val) | ||
} | ||
/> | ||
<div className="text-center"> | ||
<FormError errorField={errors.recaptcha} /> | ||
</div> | ||
</> | ||
)} | ||
<div className="d-flex justify-content-between"> | ||
<button | ||
className="btn btn-outline-primary btn-lg" | ||
onClick={() => redirectTo("/login")} | ||
> | ||
Back | ||
</button> | ||
<button | ||
type="submit" | ||
className="btn btn-primary btn-lg" | ||
disabled={loading} | ||
> | ||
<LoadingSpinner loading={loading} /> | ||
Submit | ||
</button> | ||
</div> | ||
</form> | ||
</View> | ||
</div> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.