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", "emailPlaceholderNew": "Geben Sie Ihre neue E-Mail ein",
"password": "Passwort", "password": "Passwort",
"passwordPlaceholder": "Geben Sie Ihr Passwort ein", "passwordPlaceholder": "Geben Sie Ihr Passwort ein",
"passwordPlaceholderNew": "Geben Sie Ihr neues Passwort ein",
"noDataFound": "Keine Einträge gefunden", "noDataFound": "Keine Einträge gefunden",
"calendarMaxFutureBookingDays": "Max. Tage im Voraus", "calendarMaxFutureBookingDays": "Max. Tage im Voraus",
"calendarMinEarliestBookingTime": "Min. früheste Buchungszeit", "calendarMinEarliestBookingTime": "Min. früheste Buchungszeit",
@ -144,6 +145,7 @@
"signUpLink": "Jetzt registrieren", "signUpLink": "Jetzt registrieren",
"login": { "login": {
"button": "Anmelden", "button": "Anmelden",
"forgotPassword": "Passwort vergessen?",
"dontHaveAccount": "Sie haben noch kein Konto?", "dontHaveAccount": "Sie haben noch kein Konto?",
"stateAccountPendingDeletion": { "stateAccountPendingDeletion": {
"title": "Anstehende Kontolöschung", "title": "Anstehende Kontolöschung",
@ -171,7 +173,6 @@
"pendingEmailVerification": { "pendingEmailVerification": {
"title": "E-Mail-Verifizierung ausstehend", "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." "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": { "request": {
"400": { "400": {
@ -180,6 +181,17 @@
} }
} }
}, },
"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"
}
}
},
"storeServices": { "storeServices": {
"pageTitle": "Dienstleistungen", "pageTitle": "Dienstleistungen",
"buttonAddService": "Dienstleistung hinzufügen", "buttonAddService": "Dienstleistung hinzufügen",
@ -395,9 +407,8 @@
"requesting": { "requesting": {
"title": "E-Mail-Verifizierung ausstehend" "title": "E-Mail-Verifizierung ausstehend"
}, },
"Erfolg": { "success": {
"title": "E-Mail-Überprüfung erfolgreich", "title": "E-Mail-Überprüfung erfolgreich"
"button": "Jetzt anmelden"
} }
}, },
{ {
@ -411,6 +422,18 @@
"success": { "success": {
"title": "E-Mail-Änderung erfolgreich" "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", "emailPlaceholderNew": "Enter your new email",
"password": "Password", "password": "Password",
"passwordPlaceholder": "Enter your password", "passwordPlaceholder": "Enter your password",
"passwordPlaceholderNew": "Enter your new password",
"noDataFound": "No data found", "noDataFound": "No data found",
"calendarMaxFutureBookingDays": "Max. future booking days", "calendarMaxFutureBookingDays": "Max. future booking days",
"calendarMinEarliestBookingTime": "Min. earliest booking time", "calendarMinEarliestBookingTime": "Min. earliest booking time",
@ -144,6 +145,7 @@
"signUpLink": "Sign up now", "signUpLink": "Sign up now",
"login": { "login": {
"button": "Login", "button": "Login",
"forgotPassword": "Forgot password?",
"dontHaveAccount": "Don't have an account?", "dontHaveAccount": "Don't have an account?",
"stateAccountPendingDeletion": { "stateAccountPendingDeletion": {
"title": "Pending account deletion", "title": "Pending account deletion",
@ -178,6 +180,16 @@
"description": "Please check your inputs and try again." "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": { "services": {
@ -414,6 +426,18 @@
"success": { "success": {
"title": "Email change successful" "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 <Route
path="*" path="*"
element={<Navigate to={Constants.ROUTE_PATHS.AUTHENTICATION.LOGIN} />} element={<Navigate to={Constants.ROUTE_PATHS.AUTHENTICATION.LOGIN} />}

View File

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

View File

@ -1,6 +1,7 @@
import { import {
Button, Button,
Checkbox, Checkbox,
Divider,
Flex, Flex,
Form, Form,
Modal, Modal,
@ -32,6 +33,7 @@ import ReCAPTCHA from "react-google-recaptcha";
export const AuthenticationMethod = { export const AuthenticationMethod = {
LOGIN: 1, LOGIN: 1,
SIGNUP: 2, SIGNUP: 2,
FORGOT_PASSWORD: 3,
}; };
export default function Authentication({ method }) { export default function Authentication({ method }) {
@ -61,14 +63,38 @@ export default function Authentication({ method }) {
{method === AuthenticationMethod.LOGIN ? ( {method === AuthenticationMethod.LOGIN ? (
<Login notificationApi={notificationApi} /> <Login notificationApi={notificationApi} />
) : ( ) : method === AuthenticationMethod.SIGNUP ? (
<SignUp notificationApi={notificationApi} /> <SignUp notificationApi={notificationApi} />
) : (
<ForgotPassword notificationApi={notificationApi} />
)} )}
</Modal> </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() { function RememberMeCheckbox() {
const { t } = useTranslation(); const { t } = useTranslation();
@ -242,23 +268,22 @@ function Login({ notificationApi }) {
> >
<MyPasswordFormInput /> <MyPasswordFormInput />
<Flex justify="space-between">
<RememberMeCheckbox /> <RememberMeCheckbox />
<Button
<Form.Item type="link"
name="recaptcha" onClick={() =>
rules={[ navigate(Constants.ROUTE_PATHS.AUTHENTICATION.FORGOT_PASSWORD)
{ }
required: true,
message: t("common.inputRules.recaptchaRequired"),
},
]}
> >
<ReCAPTCHA {t("authentication.login.forgotPassword")}
ref={recaptchaRef} </Button>
sitekey={process.env.REACT_APP_RECAPTCHA_SITE_KEY} </Flex>
onChange={(value) => (recaptchaValueRef.current = value)}
<MyRecaptcha
recaptchaRef={recaptchaRef}
recaptchaValueRef={recaptchaValueRef}
/> />
</Form.Item>
<PrivacyPolicyCheckbox /> <PrivacyPolicyCheckbox />
</div> </div>
@ -456,20 +481,7 @@ function SignUp({ notificationApi }) {
<RememberMeCheckbox /> <RememberMeCheckbox />
<Form.Item <MyRecaptcha recaptchaValueRef={recaptchaValueRef} />
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>
<PrivacyPolicyCheckbox /> <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 />, 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); const parsedState = isNaN(state) ? 0 : parseInt(state);

View File

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