From 2456a0ab6deb211a18a4ffbfd7688719152865ef Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 28 Jan 2024 16:17:21 +0100 Subject: [PATCH] authentication --- public/locales/de/translation.json | 46 ++- public/locales/en/translation.json | 50 ++- src/App.js | 4 +- src/Components/SideMenu/index.js | 5 - src/Pages/Authentication/index.js | 482 +++++++++++++++++++++++++++++ src/Pages/Login/index.js | 131 -------- src/Pages/Store/Employees/index.js | 40 ++- src/utils.js | 14 + 8 files changed, 611 insertions(+), 161 deletions(-) create mode 100644 src/Pages/Authentication/index.js delete mode 100644 src/Pages/Login/index.js diff --git a/public/locales/de/translation.json b/public/locales/de/translation.json index 6760a2d..424aba1 100644 --- a/public/locales/de/translation.json +++ b/public/locales/de/translation.json @@ -25,7 +25,6 @@ }, "failed": "Fehlgeschlagen", "action": "Aktion", - "contactAdmin": "Bitte kontaktieren Sie einen Administrator", "username": "Anzeigename", "usernamePlaceholder": "Geben Sie Ihren Anzeigename ein", "accountName": "Benutzername", @@ -71,6 +70,10 @@ "passwordIncorrect": { "title": "Passwort falsch", "description": "Bitte überprüfen Sie Ihr Passwort und versuchen Sie es erneut." + }, + "unknownError": { + "title": "Ein unbekannter Fehler ist aufgetreten", + "description": "Bitte versuchen Sie es erneut." } } }, @@ -110,20 +113,49 @@ "addEmployee": "Mitarbeiter anlegen", "editEmployee": "Mitarbeiter bearbeiten", "modalAddEmployee": { - "checkboxPasswordChange": "Mitarbeiter auffordern, das Passwort zu ändern (empfohlen)" + "checkboxSetPasswordOnLogging": "Mitarbeiter auffordern, beim ersten Anmelden ein eigenes Passwort festzulegen", + "checkboxSetPasswordOnLoggingInfo": "Wenn diese Option aktiviert ist, wird der Mitarbeiter beim ersten Anmelden aufgefordert, ein eigenes Passwort festzulegen. Andernfalls können Sie ein Passwort für den Mitarbeiter festlegen." }, "popConfirmDeleteEmployee": { "title": "Mitarbeiter löschen", "description": "Möchten Sie den Mitarbeiter wirklich löschen?" } }, - "login": { - "login": "Anmelden", - "signUp": "Registrieren", + "authentication": { + "rememberMe": "Angemeldet bleiben", + "loginLink": "Jetzt anmelden", + "signUpLink": "Jetzt registrieren", + "login": { + "button": "Anmelden", + "dontHaveAccount": "Sie haben noch kein Konto?", + "stateAccountPendingDeletion": { + "title": "Anstehende Kontolöschung", + "description": "Wenn Sie sich einloggen, wird die Löschung Ihres Kontos rückgängig gemacht und Ihr Konto wieder aktiviert.", + "button": "Konto wiederherstellen" + }, + "stateAccountBanned": { + "title": "Konto gesperrt", + "infoSupport": "Bitte kontaktieren Sie den Support, um Ihr Konto wiederherzustellen.", + "backButton": "Zurück zur Anmeldung" + }, + "stateInitLogin": { + "info": "Ihr Konto wurde neu erstellt und hat noch kein Passwort. Bitte legen Sie ein Passwort fest, um sich anzumelden." + }, + "request": { + "400": { + "title": "Anmeldung fehlgeschlagen", + "description": "Bitte überprüfen Sie Ihre Eingaben und versuchen Sie es erneut." + } + } + }, + "signUp": { + "button": "Registrieren", + "alreadyHaveAccount": "Sie haben bereits ein Konto?" + }, "request": { "400": { - "title": "Anmeldung fehlgeschlagen", - "description": "Bitte überprüfen Sie Ihre Eingaben." + "title": "Registrierung fehlgeschlagen", + "description": "Bitte überprüfen Sie Ihre Eingaben und versuchen Sie es erneut." } } }, diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 7456518..bb025c6 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -25,7 +25,6 @@ }, "failed": "Failed", "action": "Action", - "contactAdmin": "Please contact an administrator", "username": "Username", "usernamePlaceholder": "Enter your username", "accountName": "Account name", @@ -71,6 +70,10 @@ "passwordIncorrect": { "title": "Password incorrect", "description": "Please check your password and try again." + }, + "unknownError": { + "title": "An unknown error has occurred", + "description": "The request failed. Please try again." } } }, @@ -110,20 +113,49 @@ "addEmployee": "Add employee", "editEmployee": "Edit employee", "modalAddEmployee": { - "checkboxPasswordChange": "Require employee to change password (recommended)" + "checkboxSetPasswordOnLogging": "Ask employees to set their own password when logging in for the first time", + "checkboxSetPasswordOnLoggingInfo": "If this option is activated, the employee is prompted to set their own password when they log in for the first time. Otherwise, you can set a password for the employee." }, "popConfirmDeleteEmployee": { "title": "Delete employee", "description": "Are you sure you want to delete this employee?" } }, - "login": { - "login": "Login", - "signUp": "Sign up", - "request": { - "400": { - "title": "Login failed", - "description": "Please check your inputs and try again." + "authentication": { + "rememberMe": "Remember me", + "loginLink": "Login", + "signUpLink": "Sign up now", + "login": { + "button": "Login", + "dontHaveAccount": "Don't have an account?", + "stateAccountPendingDeletion": { + "title": "Pending account deletion", + "description": "By logging in, the deletion of your account will be cancelled and your account reactivated.", + "button": "Reactivate account" + }, + "stateAccountBanned": { + "title": "Account banned", + "infoSupport": "Please contact support to reactivate your account.", + "backButton": "Back to login" + }, + "stateInitLogin": { + "info": "Your account has been newly created and does not yet have a password. Please set a password to log in." + }, + "request": { + "400": { + "title": "Login failed", + "description": "Please check your inputs and try again." + } + } + }, + "signUp": { + "button": "Sign up", + "alreadyHaveAccount": "Already have an account?", + "request": { + "400": { + "title": "Sign up failed", + "description": "Please check your inputs and try again." + } } } }, diff --git a/src/App.js b/src/App.js index 7eccc7a..8a96ebb 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,5 @@ import "antd/dist/reset.css"; import "./App.css"; -import Login from "./Pages/Login"; import { Layout, Spin, Typography } from "antd"; import { Constants, UseUserSession, myFetch } from "./utils"; import DashboardLayout from "./Components/DashboardLayout"; @@ -14,6 +13,7 @@ import StoresProvider from "./Contexts/StoresContext"; import { clarity } from "react-microsoft-clarity"; import { useTranslation } from "react-i18next"; import MyAppLogo from "./Components/MyAppLogo"; +import Authentication from "./Pages/Authentication"; export function Loading() { const { t } = useTranslation(); @@ -90,7 +90,7 @@ export default function App() { }, []); if (!userSession) { - return ; + return ; } if (appUserData === null) { diff --git a/src/Components/SideMenu/index.js b/src/Components/SideMenu/index.js index 6d3b409..3ec45b0 100644 --- a/src/Components/SideMenu/index.js +++ b/src/Components/SideMenu/index.js @@ -60,11 +60,6 @@ export function SideMenuContent({ key: `${Constants.ROUTE_PATHS.STORE.OVERVIEW}/${store.store_id}`, }; - console.warn( - "here", - `${Constants.ROUTE_PATHS.STORE.OVERVIEW}/${store.store_id}` - ); - if (sideBarContext.permissions.includes("settings")) { groupStore.children.push({ label: t("sideMenu.store.settings"), diff --git a/src/Pages/Authentication/index.js b/src/Pages/Authentication/index.js new file mode 100644 index 0000000..80a16fb --- /dev/null +++ b/src/Pages/Authentication/index.js @@ -0,0 +1,482 @@ +import { LoginOutlined } from "@ant-design/icons"; +import { + Button, + Checkbox, + Flex, + Form, + Modal, + Result, + Space, + Tabs, + Typography, + notification, +} from "antd"; +import { + Constants, + EncodeStringToBase64, + myFetch, + setUserSessionToLocalStorage, + showInputsInvalidNotification, + showUnkownErrorNotification, +} from "../../utils"; +import { useEffect, useState } from "react"; +import { + MyAccountNameFormInput, + MyPasswordFormInput, + MyUsernameFormInput, +} from "../../Components/MyFormInputs"; +import { useTranslation } from "react-i18next"; +import MyAppLogo from "../../Components/MyAppLogo"; + +const AuthenticationMethod = { + LOGIN: 1, + SIGNUP: 2, +}; + +export default function Authentication() { + const { t, i18n } = useTranslation(); + const [form] = Form.useForm(); + + const [notificationApi, notificationContextHolder] = + notification.useNotification(); + + const [selectedMethod, setSelectedMethod] = useState( + AuthenticationMethod.LOGIN + ); + const [isRequesting, setIsRequesting] = useState(false); + + const showErrorNotification = (errStatus) => { + if (errStatus === 400) { + notificationApi["error"]({ + message: t("login.request.400.title"), + description: t("login.request.400.description"), + }); + } + }; + + return ( + <> + {notificationContextHolder} + +
+ +
+ + {selectedMethod === AuthenticationMethod.LOGIN ? ( + setSelectedMethod(AuthenticationMethod.SIGNUP)} + notificationApi={notificationApi} + /> + ) : ( + setSelectedMethod(AuthenticationMethod.LOGIN)} + notificationApi={notificationApi} + /> + )} +
+ + ); +} + +function RememberMeCheckbox() { + const { t } = useTranslation(); + + return ( + + {t("authentication.rememberMe")} + + ); +} + +const LoginStep = { + ACCOUNT_NAME: 1, + PASSWORD: 2, + PENDING_DELETION: 3, + INIT_LOGIN: 4, + BANNED: 5, +}; + +// First step: account name -> get state of account by backend +// Second step: if account is active -> password -> login otherwise show the state of the account (e. g. deleted, banned) +function Login({ switchMethod, notificationApi }) { + const { t } = useTranslation(); + const [isRequesting, setIsRequesting] = useState(false); + const [step, setStep] = useState(LoginStep.ACCOUNT_NAME); + + const [form] = Form.useForm(); + + const accountName = Form.useWatch("accountName", form); + + const showErrorNotification = (errStatus) => { + if (errStatus === 400) { + notificationApi["error"]({ + message: t("authentication.login.request.400.title"), + description: t("authentication.login.request.400.description"), + }); + } + }; + + useEffect(() => { + if (!accountName) return; + + console.log("accountName here", step); + + // reset step if account name changed for handling the state of the account + if (step === LoginStep.PASSWORD || step === LoginStep.INIT_LOGIN) { + console.log("reset step", accountName); + setStep(LoginStep.ACCOUNT_NAME); + } + }, [accountName]); + + if (step === LoginStep.PENDING_DELETION) { + return ( + setStep(LoginStep.ACCOUNT_NAME)}> + {t("common.button.cancel")} + , + , + ]} + /> + ); + } + + if (step === LoginStep.BANNED) { + return ( + + + {t("authentication.login.stateAccountBanned.infoSupport")} + + + + + } + extra={[ + , + ]} + /> + ); + } + + return ( +
+ {step === LoginStep.INIT_LOGIN && ( + + {t("authentication.login.stateInitLogin.info")} + + )} + + + +
+ + + +
+ + + + + + {t("authentication.login.dontHaveAccount")}{" "} + + + + + ); +} + +function SignUp({ switchMethod, notificationApi }) { + const { t, i18n } = useTranslation(); + const [isRequesting, setIsRequesting] = useState(false); + + const [form] = Form.useForm(); + + const showErrorNotification = (errStatus) => { + if (errStatus === 400) { + notificationApi["error"]({ + message: t("authentication.signUp.request.400.title"), + description: t("authentication.signUp.request.400.description"), + }); + } + }; + + const handleSubmit = () => { + form + .validateFields() + .then((values) => { + setIsRequesting(true); + + let body = { + accountName: values.accountName.toLocaleLowerCase(), + password: EncodeStringToBase64(values.password), + username: values.username, + language: i18n.language, + rememberMe: values.rememberMe, + }; + + myFetch({ + url: `/user/auth/signup`, + method: "POST", + body: body, + notificationApi: notificationApi, + t: t, + }) + .then((data) => { + setUserSessionToLocalStorage(data.XAuthorization); + window.location.href = "/"; + }) + .catch((errStatus) => { + showErrorNotification(errStatus); + setIsRequesting(false); + }); + }) + .catch(() => showInputsInvalidNotification(notificationApi, t)); + }; + + return ( +
+ + + + + + + + + + + + + {t("authentication.signUp.alreadyHaveAccount")}{" "} + + + + + ); +} + +/* + +*/ + +/* + setSelectedMethod(activeKey)} + /> + +
+ {selectedMethod === "2" && } + + + + + +*/ diff --git a/src/Pages/Login/index.js b/src/Pages/Login/index.js deleted file mode 100644 index 2971e9b..0000000 --- a/src/Pages/Login/index.js +++ /dev/null @@ -1,131 +0,0 @@ -import { LoginOutlined } from "@ant-design/icons"; -import { Button, Form, Modal, Tabs, notification } from "antd"; -import { - EncodeStringToBase64, - myFetch, - setUserSessionToLocalStorage, - showInputsInvalidNotification, -} from "../../utils"; -import { useState } from "react"; -import { - MyAccountNameFormInput, - MyPasswordFormInput, - MyUsernameFormInput, -} from "../../Components/MyFormInputs"; -import { useTranslation } from "react-i18next"; -import MyAppLogo from "../../Components/MyAppLogo"; - -export default function Login() { - const { t, i18n } = useTranslation(); - const [form] = Form.useForm(); - - const [notificationApi, notificationContextHolder] = - notification.useNotification(); - - const [selectedMethod, setSelectedMethod] = useState("1"); - const [isRequesting, setIsRequesting] = useState(false); - - const showErrorNotification = (errStatus) => { - if (errStatus === 400) { - notificationApi["error"]({ - message: t("login.request.400.title"), - description: t("login.request.400.description"), - }); - } - }; - - return ( - <> - {notificationContextHolder} - } - loading={isRequesting} - onClick={() => { - form - .validateFields() - .then((values) => { - setIsRequesting(true); - - let body = { - accountName: values.accountName.toLocaleLowerCase(), - password: EncodeStringToBase64(values.password), - }; - - if (selectedMethod === "2") { - body.username = values.username; - body.language = i18n.language; - } - - myFetch({ - url: `/user/auth/${ - selectedMethod === "1" ? "login" : "signup" - }`, - method: "POST", - body: body, - notificationApi: notificationApi, - t: t, - }) - .then((data) => { - setUserSessionToLocalStorage(data.XAuthorization); - window.location.href = "/"; - }) - .catch((errStatus) => { - showErrorNotification(errStatus); - setIsRequesting(false); - }); - }) - .catch(() => showInputsInvalidNotification(notificationApi, t)); - }} - > - {selectedMethod === "1" ? t("login.login") : t("login.signUp")} - - } - > -
- -
- - setSelectedMethod(activeKey)} - /> - -
- {selectedMethod === "2" && } - - - - - -
- - ); -} diff --git a/src/Pages/Store/Employees/index.js b/src/Pages/Store/Employees/index.js index f4fa63e..a3d0ae5 100644 --- a/src/Pages/Store/Employees/index.js +++ b/src/Pages/Store/Employees/index.js @@ -202,6 +202,11 @@ function ModalAddEditEmployee({ const screenBreakpoint = useBreakpoint(); const [form] = Form.useForm(); + const checkboxSetPasswordOnLogging = Form.useWatch( + "checkboxSetPasswordOnLogging", + form + ); + const [isRequesting, setIsRequesting] = useState(false); const handleModalClose = () => { @@ -276,10 +281,18 @@ function ModalAddEditEmployee({ storeId: storeId, username: values.username, accountName: values.accountName, - password: EncodeStringToBase64(values.password), language: i18n.language, + passwordSetOnInitLogging: + values.checkboxSetPasswordOnLogging, }; + if ( + !values.checkboxSetPasswordOnLogging && + values.password + ) { + body.password = EncodeStringToBase64(values.password); + } + if ( values.calendarMaxFutureBookingDays !== storeSettings.calendar_max_future_booking_days @@ -415,20 +428,33 @@ function ModalAddEditEmployee({ ) } > -
+ {modalOptions.mode === "add" && ( <> - - - - - {t("employees.modalAddEmployee.checkboxPasswordChange")} + + + {t("employees.modalAddEmployee.checkboxSetPasswordOnLogging")} + + {!checkboxSetPasswordOnLogging && } )} diff --git a/src/utils.js b/src/utils.js index 784cdab..9b93058 100644 --- a/src/utils.js +++ b/src/utils.js @@ -77,6 +77,13 @@ export const Constants = { CLARITY_PROJECT_ID: "kr0pale8uy", KK_JOBS_URL: "https://kk-innovation.eu/jobs/", ACCOUNT_DELETED_AFTER_DAYS: 30, + ACCOUNT_STATE: { + ACTIVE: 0, + PENDING_DELETION: 1, + INIT_LOGGING: 2, + BANNED: 3, + }, + SUPPORT_EMAIL: "support@zeitadler.de", }; export const AppStyle = { @@ -259,6 +266,13 @@ export function showPasswordIncorrectNotification(notificationApi, t) { }); } +export function showUnkownErrorNotification(notificationApi, t) { + notificationApi["error"]({ + message: t("common.request.unknownError.title"), + description: t("common.request.unknownError.description"), + }); +} + export function handleLogout({ setUserSession }) { if (setUserSession !== undefined) setUserSession();