router for authentication
parent
a2f0a71bf4
commit
f992526f85
|
@ -23,7 +23,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="description" content="Dashboard for ZeitAdler" />
|
||||
|
||||
<title>Dashboard - ZeitAdler</title>
|
||||
<title>ZeitAdler</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"short_name": "Dashboard - ZeitAdler",
|
||||
"name": "Dashboard - ZeitAdler",
|
||||
"short_name": "ZeitAdler",
|
||||
"name": "ZeitAdler",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
|
|
14
src/App.js
14
src/App.js
|
@ -1,7 +1,7 @@
|
|||
import "antd/dist/reset.css";
|
||||
import "./App.css";
|
||||
import { Layout, Spin, Typography } from "antd";
|
||||
import { Constants, UseUserSession, myFetch } from "./utils";
|
||||
import { Constants, UseUserSession, handleLogout, myFetch } from "./utils";
|
||||
import DashboardLayout from "./Components/DashboardLayout";
|
||||
import SideBarProvider from "./Contexts/SideBarContext";
|
||||
import { AppProvider } from "./Contexts/AppContext";
|
||||
|
@ -14,6 +14,7 @@ import { clarity } from "react-microsoft-clarity";
|
|||
import { useTranslation } from "react-i18next";
|
||||
import MyAppLogo from "./Components/MyAppLogo";
|
||||
import Authentication from "./Pages/Authentication";
|
||||
import { AuthenticationRoutes } from "./Components/AppRoutes";
|
||||
|
||||
export function Loading() {
|
||||
const { t } = useTranslation();
|
||||
|
@ -83,14 +84,15 @@ export default function App() {
|
|||
i18n.changeLanguage(data.user.language);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setUserSession();
|
||||
window.location.href = "/";
|
||||
});
|
||||
.catch(() =>
|
||||
handleLogout({
|
||||
setUserSession: setUserSession,
|
||||
})
|
||||
);
|
||||
}, []);
|
||||
|
||||
if (!userSession) {
|
||||
return <Authentication />;
|
||||
return <AuthenticationRoutes setUserSession={setUserSession} />;
|
||||
}
|
||||
|
||||
if (appUserData === null) {
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { Route, Routes } from "react-router-dom";
|
||||
import { Navigate, Route, Routes } from "react-router-dom";
|
||||
import { Constants, isDevelopmentEnv } from "../../utils";
|
||||
import { lazy } from "react";
|
||||
import { MySupsenseFallback } from "../MySupsenseFallback";
|
||||
import Authentication, {
|
||||
AuthenticationMethod,
|
||||
} from "../../Pages/Authentication";
|
||||
|
||||
// Lazy-loaded components
|
||||
const Dashboard = lazy(() => import("../../Pages/Dashboard"));
|
||||
|
@ -17,7 +20,36 @@ const StoreWebsite = lazy(() => import("../../Pages/Store/Website"));
|
|||
//const Feedback = lazy(() => import("../../Pages/Feedback"));
|
||||
const UserProfile = lazy(() => import("../../Pages/UserProfile"));
|
||||
|
||||
export default function AppRoutes({ setUserSession }) {
|
||||
export function AuthenticationRoutes() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route
|
||||
path={Constants.ROUTE_PATHS.AUTHENTICATION.LOGIN}
|
||||
element={
|
||||
<MySupsenseFallback>
|
||||
<Authentication method={AuthenticationMethod.LOGIN} />
|
||||
</MySupsenseFallback>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={Constants.ROUTE_PATHS.AUTHENTICATION.SIGN_UP}
|
||||
element={
|
||||
<MySupsenseFallback>
|
||||
<Authentication method={AuthenticationMethod.SIGNUP} />
|
||||
</MySupsenseFallback>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="*"
|
||||
element={<Navigate to={Constants.ROUTE_PATHS.AUTHENTICATION.LOGIN} />}
|
||||
/>
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
|
||||
export function AppRoutes({ setUserSession }) {
|
||||
return (
|
||||
<Routes>
|
||||
<Route
|
||||
|
|
|
@ -209,13 +209,8 @@ export function MyAvailableCheckFormInput({
|
|||
method: "POST",
|
||||
body: body,
|
||||
})
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch((errStatus) => {
|
||||
console.log(errStatus);
|
||||
reject(ruleMessageValueNotAvailable);
|
||||
});
|
||||
.then(() => resolve())
|
||||
.catch(() => reject(ruleMessageValueNotAvailable));
|
||||
}, fetchDelay);
|
||||
});
|
||||
},
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Content } from "antd/es/layout/layout";
|
||||
import AppRoutes from "../AppRoutes";
|
||||
import { Layout } from "antd";
|
||||
import HeaderMenu from "../Header";
|
||||
import { BreakpointLgWidth } from "../../utils";
|
||||
import { memo } from "react";
|
||||
import { AppRoutes } from "../AppRoutes";
|
||||
|
||||
const PageContent = memo(
|
||||
({
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
import { createContext, useContext, useEffect, useRef } from "react";
|
||||
import { wsConnectionCustomEventName } from "../utils";
|
||||
|
||||
const WebSocketContext = createContext(null);
|
||||
|
||||
export const useWebSocketContext = () => useContext(WebSocketContext);
|
||||
|
||||
let wsConnectionEvent = null;
|
||||
let firstConnection = true;
|
||||
|
||||
export default function WebSocketProvider({
|
||||
children,
|
||||
userSession,
|
||||
setUserSession,
|
||||
isWebSocketReady,
|
||||
setIsWebSocketReady,
|
||||
notificationApi,
|
||||
}) {
|
||||
const ws = useRef(null);
|
||||
const wsMessageCache = useRef([]);
|
||||
|
||||
if (wsConnectionEvent === null) {
|
||||
wsConnectionEvent = new CustomEvent(wsConnectionCustomEventName, {
|
||||
detail: "wsReconnect",
|
||||
});
|
||||
}
|
||||
|
||||
const connect = () => {
|
||||
setIsWebSocketReady(true);
|
||||
|
||||
/*
|
||||
ws.current = new WebSocket(
|
||||
`${Constants.WS_ADDRESS}?auth=${userSession}&bts=${BrowserTabSession}`
|
||||
);
|
||||
|
||||
ws.current.onopen = () => {
|
||||
sideBarContext.setConnectionBadgeStatus("success");
|
||||
setIsWebSocketReady(true);
|
||||
|
||||
if (firstConnection) {
|
||||
firstConnection = false;
|
||||
} else {
|
||||
document.dispatchEvent(wsConnectionEvent);
|
||||
}
|
||||
|
||||
myFetch("/user/", "GET").then((data) => {
|
||||
appContext.userId.current = data.UserId;
|
||||
appContext.setUserPermissions(
|
||||
data.Permissions === null ? [] : data.Permissions
|
||||
);
|
||||
appContext.setUsers(data.Users);
|
||||
headerContext.setTotalNotifications(data.TotalNotifications);
|
||||
sideBarContext.setUsername(data.Username);
|
||||
sideBarContext.setAvatar(data.Avatar);
|
||||
sideBarContext.setAvailableCategories(
|
||||
data.AvailableCategories === null ? [] : data.AvailableCategories
|
||||
);
|
||||
});
|
||||
|
||||
if (wsMessageCache.current.length > 0) {
|
||||
// send cached messages
|
||||
wsMessageCache.current.forEach((message) => {
|
||||
ws.current.send(JSON.stringify(message));
|
||||
});
|
||||
|
||||
wsMessageCache.current = [];
|
||||
}
|
||||
};
|
||||
|
||||
ws.current.onmessage = (event) => {
|
||||
handleWebSocketMessage(
|
||||
event,
|
||||
navigate,
|
||||
notificationApi,
|
||||
sideBarContext,
|
||||
appContext,
|
||||
headerContext,
|
||||
groupTasksContext,
|
||||
userProfileContext,
|
||||
adminAreaRolesContext,
|
||||
usersContext,
|
||||
consolesContext,
|
||||
scannerContext,
|
||||
crmContext
|
||||
);
|
||||
};
|
||||
|
||||
ws.current.onclose = (event) => {
|
||||
setIsWebSocketReady(false);
|
||||
sideBarContext.setConnectionBadgeStatus("error");
|
||||
console.warn("closed", event);
|
||||
|
||||
// custom code defined by the backend server
|
||||
if (event.code === 4001 || event.code === 4002) {
|
||||
//Unauthorized || SessionClosed
|
||||
|
||||
setUserSession();
|
||||
window.location.href = "/";
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.reason.code === 1005) return;
|
||||
|
||||
console.warn("reconnecting...");
|
||||
|
||||
setTimeout(() => connect(), 1000);
|
||||
}; */
|
||||
};
|
||||
|
||||
const SendSocketMessage = (cmd, body) => {
|
||||
if (
|
||||
isWebSocketReady &&
|
||||
ws.current !== null &&
|
||||
ws.current.readyState === 1
|
||||
) {
|
||||
ws.current.send(JSON.stringify({ Cmd: cmd, Body: body }));
|
||||
} else {
|
||||
wsMessageCache.current.push({ Cmd: cmd, Body: body });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
connect();
|
||||
|
||||
return () => ws.current.close();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<WebSocketContext.Provider value={{ SendSocketMessage: SendSocketMessage }}>
|
||||
{children}
|
||||
</WebSocketContext.Provider>
|
||||
);
|
||||
}
|
|
@ -25,20 +25,17 @@ import {
|
|||
} from "../../Components/MyFormInputs";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import MyAppLogo from "../../Components/MyAppLogo";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const AuthenticationMethod = {
|
||||
export const AuthenticationMethod = {
|
||||
LOGIN: 1,
|
||||
SIGNUP: 2,
|
||||
};
|
||||
|
||||
export default function Authentication() {
|
||||
export default function Authentication({ method }) {
|
||||
const [notificationApi, notificationContextHolder] =
|
||||
notification.useNotification();
|
||||
|
||||
const [selectedMethod, setSelectedMethod] = useState(
|
||||
AuthenticationMethod.LOGIN
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{notificationContextHolder}
|
||||
|
@ -60,16 +57,10 @@ export default function Authentication() {
|
|||
<MyAppLogo height={80} />
|
||||
</div>
|
||||
|
||||
{selectedMethod === AuthenticationMethod.LOGIN ? (
|
||||
<Login
|
||||
switchMethod={() => setSelectedMethod(AuthenticationMethod.SIGNUP)}
|
||||
notificationApi={notificationApi}
|
||||
/>
|
||||
{method === AuthenticationMethod.LOGIN ? (
|
||||
<Login notificationApi={notificationApi} />
|
||||
) : (
|
||||
<SignUp
|
||||
switchMethod={() => setSelectedMethod(AuthenticationMethod.LOGIN)}
|
||||
notificationApi={notificationApi}
|
||||
/>
|
||||
<SignUp notificationApi={notificationApi} />
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
|
@ -97,8 +88,10 @@ const LoginStep = {
|
|||
|
||||
// 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 }) {
|
||||
function Login({ notificationApi }) {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [isRequesting, setIsRequesting] = useState(false);
|
||||
const [step, setStep] = useState(LoginStep.ACCOUNT_NAME);
|
||||
|
||||
|
@ -225,6 +218,7 @@ function Login({ switchMethod, notificationApi }) {
|
|||
size="large"
|
||||
block
|
||||
loading={isRequesting}
|
||||
htmlType="submit"
|
||||
onClick={() => {
|
||||
let validateFields = [];
|
||||
|
||||
|
@ -304,7 +298,13 @@ function Login({ switchMethod, notificationApi }) {
|
|||
<Flex justify="center" style={{ paddingTop: 12 }}>
|
||||
<Typography.Text>
|
||||
{t("authentication.login.dontHaveAccount")}{" "}
|
||||
<Button type="link" style={{ padding: 0 }} onClick={switchMethod}>
|
||||
<Button
|
||||
type="link"
|
||||
style={{ padding: 0 }}
|
||||
onClick={() =>
|
||||
navigate(Constants.ROUTE_PATHS.AUTHENTICATION.SIGN_UP)
|
||||
}
|
||||
>
|
||||
{t("authentication.signUpLink")}
|
||||
</Button>
|
||||
</Typography.Text>
|
||||
|
@ -313,8 +313,10 @@ function Login({ switchMethod, notificationApi }) {
|
|||
);
|
||||
}
|
||||
|
||||
function SignUp({ switchMethod, notificationApi }) {
|
||||
function SignUp({ notificationApi }) {
|
||||
const { t, i18n } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [isRequesting, setIsRequesting] = useState(false);
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
@ -358,11 +360,21 @@ function SignUp({ switchMethod, notificationApi }) {
|
|||
setIsRequesting(false);
|
||||
});
|
||||
})
|
||||
.catch(() => showInputsInvalidNotification(notificationApi, t));
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
showInputsInvalidNotification(notificationApi, t);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form form={form} layout="vertical" requiredMark={false}>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
requiredMark={false}
|
||||
initialValues={{
|
||||
rememberMe: false,
|
||||
}}
|
||||
>
|
||||
<MyUsernameFormInput />
|
||||
|
||||
<MyAccountNameFormInput hasFeedback={true} />
|
||||
|
@ -384,7 +396,11 @@ function SignUp({ switchMethod, notificationApi }) {
|
|||
<Flex justify="center" style={{ paddingTop: 12 }}>
|
||||
<Typography.Text>
|
||||
{t("authentication.signUp.alreadyHaveAccount")}{" "}
|
||||
<Button type="link" style={{ padding: 0 }} onClick={switchMethod}>
|
||||
<Button
|
||||
type="link"
|
||||
style={{ padding: 0 }}
|
||||
onClick={() => navigate(Constants.ROUTE_PATHS.AUTHENTICATION.LOGIN)}
|
||||
>
|
||||
{t("authentication.loginLink")}
|
||||
</Button>
|
||||
</Typography.Text>
|
||||
|
@ -392,81 +408,3 @@ function SignUp({ switchMethod, notificationApi }) {
|
|||
</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>
|
||||
*/
|
||||
|
|
|
@ -35,6 +35,10 @@ export const Constants = {
|
|||
// WS_ADDRESS: wsAddress,
|
||||
EMBED_CALENDAR_ADDRESS: "https://calendar.ex.umbach.dev/embed/?id=",
|
||||
ROUTE_PATHS: {
|
||||
AUTHENTICATION: {
|
||||
LOGIN: "/login",
|
||||
SIGN_UP: "/signup",
|
||||
},
|
||||
OVERVIEW: "/",
|
||||
STORE: {
|
||||
// schema: /store/:storeId/:subPage
|
||||
|
@ -276,7 +280,7 @@ export function showUnkownErrorNotification(notificationApi, t) {
|
|||
export function handleLogout({ setUserSession }) {
|
||||
if (setUserSession !== undefined) setUserSession();
|
||||
|
||||
window.location.href = "/";
|
||||
window.location.href = Constants.ROUTE_PATHS.AUTHENTICATION.LOGIN;
|
||||
}
|
||||
|
||||
export function FormatDatetime(datetime) {
|
||||
|
|
Loading…
Reference in New Issue