reset password

master
alex 2024-02-12 20:35:49 +01:00
parent d6016c3d4d
commit 66b470e46e
7 changed files with 254 additions and 39 deletions

View File

@ -36,6 +36,7 @@
"emailPlaceholderNew": "Geben Sie Ihre neue E-Mail ein",
"password": "Passwort",
"passwordPlaceholder": "Geben Sie Ihr Passwort ein",
"passwordPlaceholderNew": "Geben Sie Ihr neues Passwort ein",
"noDataFound": "Keine Einträge gefunden",
"calendarMaxFutureBookingDays": "Max. Tage im Voraus",
"calendarMinEarliestBookingTime": "Min. früheste Buchungszeit",
@ -144,6 +145,7 @@
"signUpLink": "Jetzt registrieren",
"login": {
"button": "Anmelden",
"forgotPassword": "Passwort vergessen?",
"dontHaveAccount": "Sie haben noch kein Konto?",
"stateAccountPendingDeletion": {
"title": "Anstehende Kontolöschung",
@ -171,12 +173,22 @@
"pendingEmailVerification": {
"title": "E-Mail-Verifizierung ausstehend",
"description": "Bitte überprüfen Sie Ihr E-Mail-Postfach und klicken Sie auf den Link in der E-Mail, um Ihre E-Mail-Adresse zu verifizieren."
},
"request": {
"400": {
"title": "Registrierung fehlgeschlagen",
"description": "Bitte überprüfen Sie Ihre Eingaben und versuchen Sie es erneut."
}
}
},
"request": {
"400": {
"title": "Registrierung fehlgeschlagen",
"description": "Bitte überprüfen Sie Ihre Eingaben und versuchen Sie es erneut."
"forgotPassword": {
"title": "Problem beim Anmelden?",
"info": "Geben Sie Ihre E-Mail-Adresse ein, und wir senden Ihnen einen Link zum Zurücksetzen Ihres Passworts.",
"button": "Passwort zurücksetzen",
"backToLogin": "Zurück zur Anmeldung",
"resetLinkSent": {
"title": "Link zum Zurücksetzen gesendet",
"description": "Wir haben Ihnen eine E-Mail mit einem Link zum Zurücksetzen Ihres Passworts geschickt. Bitte prüfen Sie Ihren E-Mail-Posteingang"
}
}
},
@ -395,9 +407,8 @@
"requesting": {
"title": "E-Mail-Verifizierung ausstehend"
},
"Erfolg": {
"title": "E-Mail-Überprüfung erfolgreich",
"button": "Jetzt anmelden"
"success": {
"title": "E-Mail-Überprüfung erfolgreich"
}
},
{
@ -411,6 +422,18 @@
"success": {
"title": "E-Mail-Änderung erfolgreich"
}
},
{
"init": {
"title": "Passwort zurücksetzen",
"subTitle": "Geben Sie Ihr neues Passwort ein"
},
"requesting": {
"title": "Passwort wird zurückgesetzt"
},
"success": {
"title": "Passwort erfolgreich zurückgesetzt"
}
}
]
},

View File

@ -36,6 +36,7 @@
"emailPlaceholderNew": "Enter your new email",
"password": "Password",
"passwordPlaceholder": "Enter your password",
"passwordPlaceholderNew": "Enter your new password",
"noDataFound": "No data found",
"calendarMaxFutureBookingDays": "Max. future booking days",
"calendarMinEarliestBookingTime": "Min. earliest booking time",
@ -144,6 +145,7 @@
"signUpLink": "Sign up now",
"login": {
"button": "Login",
"forgotPassword": "Forgot password?",
"dontHaveAccount": "Don't have an account?",
"stateAccountPendingDeletion": {
"title": "Pending account deletion",
@ -178,6 +180,16 @@
"description": "Please check your inputs and try again."
}
}
},
"forgotPassword": {
"title": "Problem when logging in?",
"info": "Enter your email address and we will send you a link to reset your password.",
"button": "Send reset link",
"backToLogin": "Back to login",
"resetLinkSent": {
"title": "Reset link sent",
"description": "We have sent you an email with a link to reset your password. Please check your email inbox."
}
}
},
"services": {
@ -414,6 +426,18 @@
"success": {
"title": "Email change successful"
}
},
{
"init": {
"title": "Password reset",
"subTitle": "Enter your new password"
},
"requesting": {
"title": "Resetting password"
},
"success": {
"title": "Password reset successful"
}
}
]
},

View File

@ -41,6 +41,15 @@ export function AuthenticationRoutes() {
}
/>
<Route
path={Constants.ROUTE_PATHS.AUTHENTICATION.FORGOT_PASSWORD}
element={
<MySupsenseFallback>
<Authentication method={AuthenticationMethod.FORGOT_PASSWORD} />
</MySupsenseFallback>
}
/>
<Route
path="*"
element={<Navigate to={Constants.ROUTE_PATHS.AUTHENTICATION.LOGIN} />}

View File

@ -45,6 +45,7 @@ export function MyPasswordFormInput({
label,
inputPlaceholder,
formItemRules,
newPassword,
}) {
const { t } = useTranslation();
@ -63,7 +64,11 @@ export function MyPasswordFormInput({
minLength: Constants.GLOBALS.MIN_PASSWORD_LENGTH,
}
)}
inputPlaceholder={inputPlaceholder || t("common.passwordPlaceholder")}
inputPlaceholder={
inputPlaceholder || newPassword
? t("common.passwordPlaceholderNew")
: t("common.passwordPlaceholder")
}
inputType="password"
formItemRules={formItemRules}
/>

View File

@ -1,6 +1,7 @@
import {
Button,
Checkbox,
Divider,
Flex,
Form,
Modal,
@ -32,6 +33,7 @@ import ReCAPTCHA from "react-google-recaptcha";
export const AuthenticationMethod = {
LOGIN: 1,
SIGNUP: 2,
FORGOT_PASSWORD: 3,
};
export default function Authentication({ method }) {
@ -61,14 +63,38 @@ export default function Authentication({ method }) {
{method === AuthenticationMethod.LOGIN ? (
<Login notificationApi={notificationApi} />
) : (
) : method === AuthenticationMethod.SIGNUP ? (
<SignUp notificationApi={notificationApi} />
) : (
<ForgotPassword notificationApi={notificationApi} />
)}
</Modal>
</>
);
}
export function MyRecaptcha({ recaptchaRef, recaptchaValueRef }) {
const { t } = useTranslation();
return (
<Form.Item
name="recaptcha"
rules={[
{
required: true,
message: t("common.inputRules.recaptchaRequired"),
},
]}
>
<ReCAPTCHA
ref={recaptchaRef}
sitekey={process.env.REACT_APP_RECAPTCHA_SITE_KEY}
onChange={(value) => (recaptchaValueRef.current = value)}
/>
</Form.Item>
);
}
function RememberMeCheckbox() {
const { t } = useTranslation();
@ -242,23 +268,22 @@ function Login({ notificationApi }) {
>
<MyPasswordFormInput />
<RememberMeCheckbox />
<Flex justify="space-between">
<RememberMeCheckbox />
<Button
type="link"
onClick={() =>
navigate(Constants.ROUTE_PATHS.AUTHENTICATION.FORGOT_PASSWORD)
}
>
{t("authentication.login.forgotPassword")}
</Button>
</Flex>
<Form.Item
name="recaptcha"
rules={[
{
required: true,
message: t("common.inputRules.recaptchaRequired"),
},
]}
>
<ReCAPTCHA
ref={recaptchaRef}
sitekey={process.env.REACT_APP_RECAPTCHA_SITE_KEY}
onChange={(value) => (recaptchaValueRef.current = value)}
/>
</Form.Item>
<MyRecaptcha
recaptchaRef={recaptchaRef}
recaptchaValueRef={recaptchaValueRef}
/>
<PrivacyPolicyCheckbox />
</div>
@ -456,20 +481,7 @@ function SignUp({ notificationApi }) {
<RememberMeCheckbox />
<Form.Item
name="recaptcha"
rules={[
{
required: true,
message: t("common.inputRules.recaptchaRequired"),
},
]}
>
<ReCAPTCHA
sitekey={process.env.REACT_APP_RECAPTCHA_SITE_KEY}
onChange={(value) => (recaptchaValueRef.current = value)}
/>
</Form.Item>
<MyRecaptcha recaptchaValueRef={recaptchaValueRef} />
<PrivacyPolicyCheckbox />
@ -510,3 +522,98 @@ function PendingEmailVerification() {
/>
);
}
const FORGOT_PASSWORD_STEP = {
ENTER_EMAIL: 1,
RESET_LINK_SENT: 2,
};
function ForgotPassword({ notificationApi }) {
const { t } = useTranslation();
const [form] = Form.useForm();
const navigate = useNavigate();
const [step, setStep] = useState(SignUpStep.SIGN_UP);
const [isRequesting, setIsRequesting] = useState(false);
const recaptchaValueRef = useRef(null);
if (step === FORGOT_PASSWORD_STEP.RESET_LINK_SENT) {
return (
<Result
status="warning"
title={t("authentication.forgotPassword.resetLinkSent.title")}
subTitle={t("authentication.forgotPassword.resetLinkSent.description")}
/>
);
}
return (
<Form
form={form}
layout="vertical"
requiredMark={false}
initialValues={{
rememberMe: false,
}}
>
<Typography.Title level={3} style={{ textAlign: "center" }}>
{t("authentication.forgotPassword.title")}
</Typography.Title>
<Typography.Paragraph
type="secondary"
style={{
textAlign: "center",
}}
>
{t("authentication.forgotPassword.info")}
</Typography.Paragraph>
<MyEmailFormInput hasFeedback={true} disableEmailCheck />
<MyRecaptcha recaptchaValueRef={recaptchaValueRef} />
<Button
type="primary"
size="large"
block
loading={isRequesting}
onClick={() => {
form
.validateFields()
.then((values) => {
setIsRequesting(true);
myFetch({
url: `/user/auth/forgot-password`,
method: "POST",
body: {
email: values.email.toLocaleLowerCase(),
recaptcha: recaptchaValueRef.current,
},
notificationApi: notificationApi,
t: t,
})
.then(() => setStep(FORGOT_PASSWORD_STEP.RESET_LINK_SENT))
.catch(() => {
setIsRequesting(false);
showUnkownErrorNotification(notificationApi, t);
});
})
.catch(() => showInputsInvalidNotification(notificationApi, t));
}}
>
{t("authentication.forgotPassword.button")}
</Button>
<Flex justify="center">
<Button
type="link"
onClick={() => navigate(Constants.ROUTE_PATHS.AUTHENTICATION.LOGIN)}
>
{t("authentication.forgotPassword.backToLogin")}
</Button>
</Flex>
</Form>
);
}

View File

@ -102,6 +102,52 @@ export default function Verification() {
extra: <ButtonToLogin />,
},
},
{
init: {
title: t("verification.content.2.init.title"),
subTitle: t("verification.content.2.init.subTitle"),
extra: (
<Form form={form} layout="vertical" requiredMark={false}>
<MyPasswordFormInput newPassword />
<Button
type="primary"
htmlType="submit"
block
onClick={() => {
form
.validateFields()
.then((values) => {
setIsRequesting(RequestState.REQUESTING);
sendVerifyRequest({
password: EncodeStringToBase64(values.password),
})
.then(() => setIsRequesting(RequestState.SUCCESS))
.catch(() => {
setIsRequesting(RequestState.INIT);
showPasswordIncorrectNotification(notificationApi, t);
});
})
.catch(() =>
showInputsInvalidNotification(notificationApi, t)
);
}}
>
{t("common.button.confirm")}
</Button>
</Form>
),
},
requesting: {
title: t("verification.content.2.requesting.title"),
extra: <Spin size="large" />,
},
success: {
title: t("verification.content.2.success.title"),
extra: <ButtonToLogin />,
},
},
];
const parsedState = isNaN(state) ? 0 : parseInt(state);

View File

@ -46,6 +46,7 @@ export const Constants = {
AUTHENTICATION: {
LOGIN: "/login",
SIGN_UP: "/signup",
FORGOT_PASSWORD: "/forgot-password",
},
VERIFY: "/verify",
OVERVIEW: "/",