authentication

master
alex 2024-01-28 16:17:21 +01:00
parent bfa1a6796c
commit 2456a0ab6d
8 changed files with 611 additions and 161 deletions

View File

@ -25,7 +25,6 @@
}, },
"failed": "Fehlgeschlagen", "failed": "Fehlgeschlagen",
"action": "Aktion", "action": "Aktion",
"contactAdmin": "Bitte kontaktieren Sie einen Administrator",
"username": "Anzeigename", "username": "Anzeigename",
"usernamePlaceholder": "Geben Sie Ihren Anzeigename ein", "usernamePlaceholder": "Geben Sie Ihren Anzeigename ein",
"accountName": "Benutzername", "accountName": "Benutzername",
@ -71,6 +70,10 @@
"passwordIncorrect": { "passwordIncorrect": {
"title": "Passwort falsch", "title": "Passwort falsch",
"description": "Bitte überprüfen Sie Ihr Passwort und versuchen Sie es erneut." "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", "addEmployee": "Mitarbeiter anlegen",
"editEmployee": "Mitarbeiter bearbeiten", "editEmployee": "Mitarbeiter bearbeiten",
"modalAddEmployee": { "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": { "popConfirmDeleteEmployee": {
"title": "Mitarbeiter löschen", "title": "Mitarbeiter löschen",
"description": "Möchten Sie den Mitarbeiter wirklich löschen?" "description": "Möchten Sie den Mitarbeiter wirklich löschen?"
} }
}, },
"authentication": {
"rememberMe": "Angemeldet bleiben",
"loginLink": "Jetzt anmelden",
"signUpLink": "Jetzt registrieren",
"login": { "login": {
"login": "Anmelden", "button": "Anmelden",
"signUp": "Registrieren", "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": { "request": {
"400": { "400": {
"title": "Anmeldung fehlgeschlagen", "title": "Anmeldung fehlgeschlagen",
"description": "Bitte überprüfen Sie Ihre Eingaben." "description": "Bitte überprüfen Sie Ihre Eingaben und versuchen Sie es erneut."
}
}
},
"signUp": {
"button": "Registrieren",
"alreadyHaveAccount": "Sie haben bereits ein Konto?"
},
"request": {
"400": {
"title": "Registrierung fehlgeschlagen",
"description": "Bitte überprüfen Sie Ihre Eingaben und versuchen Sie es erneut."
} }
} }
}, },

View File

@ -25,7 +25,6 @@
}, },
"failed": "Failed", "failed": "Failed",
"action": "Action", "action": "Action",
"contactAdmin": "Please contact an administrator",
"username": "Username", "username": "Username",
"usernamePlaceholder": "Enter your username", "usernamePlaceholder": "Enter your username",
"accountName": "Account name", "accountName": "Account name",
@ -71,6 +70,10 @@
"passwordIncorrect": { "passwordIncorrect": {
"title": "Password incorrect", "title": "Password incorrect",
"description": "Please check your password and try again." "description": "Please check your password and try again."
},
"unknownError": {
"title": "An unknown error has occurred",
"description": "The request failed. Please try again."
} }
} }
}, },
@ -110,16 +113,34 @@
"addEmployee": "Add employee", "addEmployee": "Add employee",
"editEmployee": "Edit employee", "editEmployee": "Edit employee",
"modalAddEmployee": { "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": { "popConfirmDeleteEmployee": {
"title": "Delete employee", "title": "Delete employee",
"description": "Are you sure you want to delete this employee?" "description": "Are you sure you want to delete this employee?"
} }
}, },
"authentication": {
"rememberMe": "Remember me",
"loginLink": "Login",
"signUpLink": "Sign up now",
"login": { "login": {
"login": "Login", "button": "Login",
"signUp": "Sign up", "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": { "request": {
"400": { "400": {
"title": "Login failed", "title": "Login failed",
@ -127,6 +148,17 @@
} }
} }
}, },
"signUp": {
"button": "Sign up",
"alreadyHaveAccount": "Already have an account?",
"request": {
"400": {
"title": "Sign up failed",
"description": "Please check your inputs and try again."
}
}
}
},
"services": { "services": {
"pageTitle": "Services" "pageTitle": "Services"
}, },

View File

@ -1,6 +1,5 @@
import "antd/dist/reset.css"; import "antd/dist/reset.css";
import "./App.css"; import "./App.css";
import Login from "./Pages/Login";
import { Layout, Spin, Typography } from "antd"; import { Layout, Spin, Typography } from "antd";
import { Constants, UseUserSession, myFetch } from "./utils"; import { Constants, UseUserSession, myFetch } from "./utils";
import DashboardLayout from "./Components/DashboardLayout"; import DashboardLayout from "./Components/DashboardLayout";
@ -14,6 +13,7 @@ import StoresProvider from "./Contexts/StoresContext";
import { clarity } from "react-microsoft-clarity"; import { clarity } from "react-microsoft-clarity";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import MyAppLogo from "./Components/MyAppLogo"; import MyAppLogo from "./Components/MyAppLogo";
import Authentication from "./Pages/Authentication";
export function Loading() { export function Loading() {
const { t } = useTranslation(); const { t } = useTranslation();
@ -90,7 +90,7 @@ export default function App() {
}, []); }, []);
if (!userSession) { if (!userSession) {
return <Login />; return <Authentication />;
} }
if (appUserData === null) { if (appUserData === null) {

View File

@ -60,11 +60,6 @@ export function SideMenuContent({
key: `${Constants.ROUTE_PATHS.STORE.OVERVIEW}/${store.store_id}`, 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")) { if (sideBarContext.permissions.includes("settings")) {
groupStore.children.push({ groupStore.children.push({
label: t("sideMenu.store.settings"), label: t("sideMenu.store.settings"),

View File

@ -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}
<Modal
open={true}
mask={false}
closable={false}
centered
keyboard={false}
footer={null}
>
<div
style={{
display: "flex",
justifyContent: "center",
}}
>
<MyAppLogo height={80} />
</div>
{selectedMethod === AuthenticationMethod.LOGIN ? (
<Login
switchMethod={() => setSelectedMethod(AuthenticationMethod.SIGNUP)}
notificationApi={notificationApi}
/>
) : (
<SignUp
switchMethod={() => setSelectedMethod(AuthenticationMethod.LOGIN)}
notificationApi={notificationApi}
/>
)}
</Modal>
</>
);
}
function RememberMeCheckbox() {
const { t } = useTranslation();
return (
<Form.Item name="rememberMe" valuePropName="checked">
<Checkbox>{t("authentication.rememberMe")}</Checkbox>
</Form.Item>
);
}
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 (
<Result
status="warning"
title={t("authentication.login.stateAccountPendingDeletion.title")}
subTitle={t(
"authentication.login.stateAccountPendingDeletion.description"
)}
extra={[
<Button key={0} onClick={() => setStep(LoginStep.ACCOUNT_NAME)}>
{t("common.button.cancel")}
</Button>,
<Button
key={1}
type="primary"
onClick={() => {
console.log("reactivate account");
setStep(LoginStep.PASSWORD);
}}
>
{t("authentication.login.stateAccountPendingDeletion.button")}
</Button>,
]}
/>
);
}
if (step === LoginStep.BANNED) {
return (
<Result
status="error"
title={t("authentication.login.stateAccountBanned.title")}
subTitle={
<Space direction="vertical">
<Typography.Text type="secondary">
{t("authentication.login.stateAccountBanned.infoSupport")}
</Typography.Text>
<Button type="link" href={`mailto:${Constants.SUPPORT_EMAIL}`}>
{Constants.SUPPORT_EMAIL}
</Button>
</Space>
}
extra={[
<Button
key={0}
onClick={() => {
setStep(LoginStep.ACCOUNT_NAME);
}}
>
{t("authentication.login.stateAccountBanned.backButton")}
</Button>,
]}
/>
);
}
return (
<Form
form={form}
layout="vertical"
requiredMark={false}
initialValues={{
rememberMe: false,
}}
>
{step === LoginStep.INIT_LOGIN && (
<Typography.Paragraph type="secondary">
{t("authentication.login.stateInitLogin.info")}
</Typography.Paragraph>
)}
<MyAccountNameFormInput
disableAccountNameCheck={true}
hasFeedback={false}
/>
<div
style={{
display:
step === LoginStep.PASSWORD || step === LoginStep.INIT_LOGIN
? "block"
: "none",
}}
>
<MyPasswordFormInput />
<RememberMeCheckbox />
</div>
<Button
type="primary"
size="large"
block
loading={isRequesting}
onClick={() => {
let validateFields = [];
if (step === LoginStep.ACCOUNT_NAME) {
validateFields = ["accountName"];
} else {
validateFields = ["accountName", "password", "rememberMe"];
}
form
.validateFields(validateFields)
.then((values) => {
setIsRequesting(true);
let body = {
accountName: values.accountName.toLocaleLowerCase(),
};
if (
step === LoginStep.PASSWORD ||
step === LoginStep.INIT_LOGIN
) {
body.password = EncodeStringToBase64(values.password);
body.rememberMe = values.rememberMe;
}
myFetch({
url: `/user/auth/login`,
method: "POST",
body: body,
notificationApi: notificationApi,
t: t,
})
.then((data) => {
setIsRequesting(false);
if (
step === LoginStep.PASSWORD ||
step === LoginStep.INIT_LOGIN
) {
setUserSessionToLocalStorage(data.XAuthorization);
window.location.href = "/";
return;
}
if (data.state === undefined) return;
switch (data.state) {
case Constants.ACCOUNT_STATE.ACTIVE:
setStep(LoginStep.PASSWORD);
break;
case Constants.ACCOUNT_STATE.PENDING_DELETION:
setStep(LoginStep.PENDING_DELETION);
break;
case Constants.ACCOUNT_STATE.BANNED:
setStep(LoginStep.BANNED);
break;
case Constants.ACCOUNT_STATE.INIT_LOGGING:
setStep(LoginStep.INIT_LOGIN);
break;
default:
showUnkownErrorNotification(notificationApi, t);
break;
}
})
.catch((errStatus) => {
showErrorNotification(errStatus);
setIsRequesting(false);
});
})
.catch(() => showInputsInvalidNotification(notificationApi, t));
}}
>
{t("authentication.login.button")}
</Button>
<Flex justify="center" style={{ paddingTop: 12 }}>
<Typography.Text>
{t("authentication.login.dontHaveAccount")}{" "}
<Button type="link" style={{ padding: 0 }} onClick={switchMethod}>
{t("authentication.signUpLink")}
</Button>
</Typography.Text>
</Flex>
</Form>
);
}
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 (
<Form form={form} layout="vertical" requiredMark={false}>
<MyUsernameFormInput />
<MyAccountNameFormInput hasFeedback={true} />
<MyPasswordFormInput />
<RememberMeCheckbox />
<Button
type="primary"
size="large"
block
onClick={handleSubmit}
loading={isRequesting}
>
{t("authentication.signUp.button")}
</Button>
<Flex justify="center" style={{ paddingTop: 12 }}>
<Typography.Text>
{t("authentication.signUp.alreadyHaveAccount")}{" "}
<Button type="link" style={{ padding: 0 }} onClick={switchMethod}>
{t("authentication.loginLink")}
</Button>
</Typography.Text>
</Flex>
</Form>
);
}
/*
<Button
type="primary"
block
htmlType="submit"
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 === AuthenticationMethod.LOGIN
? t("authentication.login.button")
: t("authentication.signUp.button")}
</Button>
*/
/*
<Tabs
defaultActiveKey="1"
items={[
{
key: "1",
label: t("login.login"),
},
{
key: "2",
label: t("login.signUp"),
},
]}
centered
onChange={(activeKey) => setSelectedMethod(activeKey)}
/>
<Form form={form} layout="vertical" requiredMark={false}>
{selectedMethod === "2" && <MyUsernameFormInput />}
<MyAccountNameFormInput
disableAccountNameCheck={selectedMethod === "1"}
hasFeedback={selectedMethod === "2"}
/>
<MyPasswordFormInput />
</Form>
*/

View File

@ -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}
<Modal
open={true}
mask={false}
closable={false}
centered
keyboard={false}
footer={
<Button
type="primary"
htmlType="submit"
icon={<LoginOutlined />}
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")}
</Button>
}
>
<div
style={{
display: "flex",
justifyContent: "center",
}}
>
<MyAppLogo height={80} />
</div>
<Tabs
defaultActiveKey="1"
items={[
{
key: "1",
label: t("login.login"),
},
{
key: "2",
label: t("login.signUp"),
},
]}
centered
onChange={(activeKey) => setSelectedMethod(activeKey)}
/>
<Form form={form} layout="vertical" requiredMark={false}>
{selectedMethod === "2" && <MyUsernameFormInput />}
<MyAccountNameFormInput
disableAccountNameCheck={selectedMethod === "1"}
hasFeedback={selectedMethod === "2"}
/>
<MyPasswordFormInput />
</Form>
</Modal>
</>
);
}

View File

@ -202,6 +202,11 @@ function ModalAddEditEmployee({
const screenBreakpoint = useBreakpoint(); const screenBreakpoint = useBreakpoint();
const [form] = Form.useForm(); const [form] = Form.useForm();
const checkboxSetPasswordOnLogging = Form.useWatch(
"checkboxSetPasswordOnLogging",
form
);
const [isRequesting, setIsRequesting] = useState(false); const [isRequesting, setIsRequesting] = useState(false);
const handleModalClose = () => { const handleModalClose = () => {
@ -276,10 +281,18 @@ function ModalAddEditEmployee({
storeId: storeId, storeId: storeId,
username: values.username, username: values.username,
accountName: values.accountName, accountName: values.accountName,
password: EncodeStringToBase64(values.password),
language: i18n.language, language: i18n.language,
passwordSetOnInitLogging:
values.checkboxSetPasswordOnLogging,
}; };
if (
!values.checkboxSetPasswordOnLogging &&
values.password
) {
body.password = EncodeStringToBase64(values.password);
}
if ( if (
values.calendarMaxFutureBookingDays !== values.calendarMaxFutureBookingDays !==
storeSettings.calendar_max_future_booking_days storeSettings.calendar_max_future_booking_days
@ -415,20 +428,33 @@ function ModalAddEditEmployee({
) )
} }
> >
<Form form={form} layout="vertical" requiredMark={false}> <Form
form={form}
layout="vertical"
requiredMark={false}
initialValues={{
checkboxSetPasswordOnLogging: true,
}}
>
<MyUsernameFormInput /> <MyUsernameFormInput />
<MyAccountNameFormInput hasFeedback /> <MyAccountNameFormInput hasFeedback />
{modalOptions.mode === "add" && ( {modalOptions.mode === "add" && (
<> <>
<MyPasswordFormInput /> <Form.Item
name="checkboxSetPasswordOnLogging"
<Form.Item> valuePropName="checked"
<Checkbox defaultChecked disabled> extra={t(
{t("employees.modalAddEmployee.checkboxPasswordChange")} "employees.modalAddEmployee.checkboxSetPasswordOnLoggingInfo"
)}
>
<Checkbox>
{t("employees.modalAddEmployee.checkboxSetPasswordOnLogging")}
</Checkbox> </Checkbox>
</Form.Item> </Form.Item>
{!checkboxSetPasswordOnLogging && <MyPasswordFormInput />}
</> </>
)} )}

View File

@ -77,6 +77,13 @@ export const Constants = {
CLARITY_PROJECT_ID: "kr0pale8uy", CLARITY_PROJECT_ID: "kr0pale8uy",
KK_JOBS_URL: "https://kk-innovation.eu/jobs/", KK_JOBS_URL: "https://kk-innovation.eu/jobs/",
ACCOUNT_DELETED_AFTER_DAYS: 30, 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 = { 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 }) { export function handleLogout({ setUserSession }) {
if (setUserSession !== undefined) setUserSession(); if (setUserSession !== undefined) setUserSession();