router for authentication

master
alex 2024-01-28 19:46:35 +01:00
parent a2f0a71bf4
commit f992526f85
9 changed files with 90 additions and 252 deletions

View File

@ -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>

View File

@ -1,6 +1,6 @@
{
"short_name": "Dashboard - ZeitAdler",
"name": "Dashboard - ZeitAdler",
"short_name": "ZeitAdler",
"name": "ZeitAdler",
"icons": [
{
"src": "/android-chrome-192x192.png",

View File

@ -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) {

View File

@ -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

View File

@ -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);
});
},

View File

@ -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(
({

View File

@ -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>
);
}

View File

@ -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>
*/

View File

@ -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) {