working header!
This commit is contained in:
41
frontend/app/package-lock.json
generated
41
frontend/app/package-lock.json
generated
@ -13,7 +13,8 @@
|
|||||||
"antd": "^5.15.4",
|
"antd": "^5.15.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-redux": "^9.1.0"
|
"react-redux": "^9.1.0",
|
||||||
|
"react-router-dom": "^6.22.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rsbuild/core": "^0.5.4",
|
"@rsbuild/core": "^0.5.4",
|
||||||
@ -424,6 +425,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@remix-run/router": {
|
||||||
|
"version": "1.15.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz",
|
||||||
|
"integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rsbuild/core": {
|
"node_modules/@rsbuild/core": {
|
||||||
"version": "0.5.4",
|
"version": "0.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-0.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-0.5.4.tgz",
|
||||||
@ -4012,6 +4021,36 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "6.22.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz",
|
||||||
|
"integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@remix-run/router": "1.15.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router-dom": {
|
||||||
|
"version": "6.22.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz",
|
||||||
|
"integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@remix-run/router": "1.15.3",
|
||||||
|
"react-router": "6.22.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8",
|
||||||
|
"react-dom": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/redux": {
|
"node_modules/redux": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||||
|
|||||||
@ -13,7 +13,8 @@
|
|||||||
"antd": "^5.15.4",
|
"antd": "^5.15.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-redux": "^9.1.0"
|
"react-redux": "^9.1.0",
|
||||||
|
"react-router-dom": "^6.22.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rsbuild/core": "^0.5.4",
|
"@rsbuild/core": "^0.5.4",
|
||||||
|
|||||||
@ -11,7 +11,6 @@ body {
|
|||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content h1 {
|
.content h1 {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { theme } from "./config/style";
|
|||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { store } from "./config/store";
|
import { store } from "./config/store";
|
||||||
import { MessageInstance } from "antd/es/message/interface";
|
import { MessageInstance } from "antd/es/message/interface";
|
||||||
|
import AppRoutes from "./pages/AppRoutes";
|
||||||
|
|
||||||
export const MessageContext = createContext({} as MessageInstance);
|
export const MessageContext = createContext({} as MessageInstance);
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ const App = () => {
|
|||||||
<div className="content">
|
<div className="content">
|
||||||
{contextHolder}
|
{contextHolder}
|
||||||
<HeaderComponent />
|
<HeaderComponent />
|
||||||
|
<AppRoutes />
|
||||||
</div>
|
</div>
|
||||||
</MessageContext.Provider>
|
</MessageContext.Provider>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
|
|||||||
@ -17,17 +17,16 @@ const AuthModal = (props: {
|
|||||||
const [loginForm] = Form.useForm();
|
const [loginForm] = Form.useForm();
|
||||||
const [registerForm] = Form.useForm();
|
const [registerForm] = Form.useForm();
|
||||||
|
|
||||||
|
const { data, refetch, isFetching, isError } = useGetUserQuery({});
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isFetching && !isError) {
|
||||||
|
store.dispatch(updateUser(data));
|
||||||
|
}
|
||||||
|
}, [data, isFetching, useGetUserQuery]);
|
||||||
|
|
||||||
const [current, setCurrent] = useState("login");
|
const [current, setCurrent] = useState("login");
|
||||||
const messageApi = useContext(MessageContext);
|
const messageApi = useContext(MessageContext);
|
||||||
|
|
||||||
// const { data, refetch, isFetching, isError } = useGetUserQuery({});
|
|
||||||
// useEffect(() => {
|
|
||||||
// console.log(data);
|
|
||||||
// if (!isFetching && !isError) {
|
|
||||||
// store.dispatch(updateUser(data));
|
|
||||||
// }
|
|
||||||
// }, [data, isFetching, useGetUserQuery]);
|
|
||||||
|
|
||||||
const [loginUser, { isLoading: isLoggingIn }] = useLoginMutation();
|
const [loginUser, { isLoading: isLoggingIn }] = useLoginMutation();
|
||||||
const [registerUser, { isLoading: isRegistering }] = useRegisterMutation();
|
const [registerUser, { isLoading: isRegistering }] = useRegisterMutation();
|
||||||
|
|
||||||
@ -41,6 +40,7 @@ const AuthModal = (props: {
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
store.dispatch(updateToken(data.access_token));
|
store.dispatch(updateToken(data.access_token));
|
||||||
})
|
})
|
||||||
|
.then(() => refetch())
|
||||||
.then(() => props.setOpen(false))
|
.then(() => props.setOpen(false))
|
||||||
.catch(() => messageApi.error("Login failed!"));
|
.catch(() => messageApi.error("Login failed!"));
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { UserOutlined } from "@ant-design/icons";
|
import { LogoutOutlined, UserOutlined } from "@ant-design/icons";
|
||||||
import { Layout, Menu, MenuProps } from "antd";
|
import { Button, Layout, Menu, MenuProps, Tooltip } from "antd";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import AuthModal from "./AuthModal";
|
import AuthModal from "./AuthModal";
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
import { getLocalToken, store, updateUser } from "../config/store";
|
import { StorePrototype, logOut, store } from "../config/store";
|
||||||
import { User, useGetUserQuery } from "../slice/AuthApi";
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
const { Header } = Layout;
|
const { Header } = Layout;
|
||||||
|
|
||||||
@ -12,31 +12,43 @@ type NullableUser = { name: string | null; username: string } | null;
|
|||||||
|
|
||||||
const HeaderComponent = () => {
|
const HeaderComponent = () => {
|
||||||
const [authModalOpen, setAuthModalOpen] = useState(false);
|
const [authModalOpen, setAuthModalOpen] = useState(false);
|
||||||
store.dispatch(getLocalToken());
|
|
||||||
const user: NullableUser = store.getState().auth.user;
|
|
||||||
|
|
||||||
// useEffect(() => {
|
const user: NullableUser = useSelector(
|
||||||
// console.log(data);
|
(state: StorePrototype) => state.auth.user
|
||||||
// if (!isFetching && !isError) {
|
);
|
||||||
// store.dispatch(updateUser(data));
|
|
||||||
// }
|
|
||||||
// }, [data, isFetching, useGetUserQuery]);
|
|
||||||
|
|
||||||
console.log(store.getState());
|
// const userMenuItems: MenuProps["userMenuItems"] = [
|
||||||
|
// {
|
||||||
|
// label: <Button>Log out</Button>,
|
||||||
|
// key: "logout",
|
||||||
|
// icon: <LogoutOutlined />,
|
||||||
|
// onClick: () => store.dispatch(logOut()),
|
||||||
|
// },
|
||||||
|
// ]
|
||||||
|
|
||||||
const items: MenuProps["items"] = [
|
const items: MenuProps["items"] = [
|
||||||
{
|
{
|
||||||
label: user ? user.username : "Log In",
|
label: user ? (
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
<Button onClick={() => store.dispatch(logOut())}>Log out</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{user.username}
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
"Log In"
|
||||||
|
),
|
||||||
key: "login",
|
key: "login",
|
||||||
icon: <UserOutlined />,
|
icon: <UserOutlined />,
|
||||||
onClick: () => setAuthModalOpen(true),
|
onClick: () => !user && setAuthModalOpen(true),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AuthModal open={authModalOpen} setOpen={setAuthModalOpen} />
|
<AuthModal open={authModalOpen} setOpen={setAuthModalOpen} />
|
||||||
<Header style={{ display: "flex", alignItems: "center" }}>
|
<Header>
|
||||||
<div className="demo-logo" />
|
<div className="demo-logo" />
|
||||||
<Menu
|
<Menu
|
||||||
theme="dark"
|
theme="dark"
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { store, updateUser } from "../config/store";
|
|||||||
const authProvider = () => {
|
const authProvider = () => {
|
||||||
const { data, isFetching, isError } = useGetUserQuery({});
|
const { data, isFetching, isError } = useGetUserQuery({});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(data);
|
|
||||||
if (!isFetching && !isError) {
|
if (!isFetching && !isError) {
|
||||||
store.dispatch(updateUser(data));
|
store.dispatch(updateUser(data));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,42 +1,37 @@
|
|||||||
import { configureStore, createAction, createReducer } from "@reduxjs/toolkit";
|
import {
|
||||||
|
ReducerType,
|
||||||
|
configureStore,
|
||||||
|
createAction,
|
||||||
|
createReducer,
|
||||||
|
} from "@reduxjs/toolkit";
|
||||||
import { setupListeners } from "@reduxjs/toolkit/query";
|
import { setupListeners } from "@reduxjs/toolkit/query";
|
||||||
import { AuthApi, User } from "../slice/AuthApi";
|
import { AuthApi, User } from "../slice/AuthApi";
|
||||||
|
|
||||||
export type authState = {
|
export type AuthDataType = {
|
||||||
token: string | null;
|
token: string | null;
|
||||||
user: { name: string | null; username: string } | null;
|
user: { name: string | null; username: string } | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialAuthState: authState = {
|
const initialAuthDataState: AuthDataType = {
|
||||||
token: null,
|
token: null,
|
||||||
user: null,
|
user: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type StorePrototype = {
|
||||||
|
AuthApi: ReducerType;
|
||||||
|
auth: AuthDataType;
|
||||||
|
};
|
||||||
|
|
||||||
export const updateToken = createAction<string>("auth/updateToken");
|
export const updateToken = createAction<string>("auth/updateToken");
|
||||||
export const getLocalToken = createAction("auth/getLocalToken");
|
export const getLocalToken = createAction("auth/getLocalToken");
|
||||||
export const updateUser = createAction<User>("auth/updateUser");
|
export const updateUser = createAction<User>("auth/updateUser");
|
||||||
|
export const logOut = createAction("auth/logOut");
|
||||||
const parseJwt = (token: string): { sub: string; exp: number } => {
|
|
||||||
const base64Url = token.split(".")[1];
|
|
||||||
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
|
|
||||||
const jsonPayload = decodeURIComponent(
|
|
||||||
window
|
|
||||||
.atob(base64)
|
|
||||||
.split("")
|
|
||||||
.map(function (c) {
|
|
||||||
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
|
|
||||||
})
|
|
||||||
.join("")
|
|
||||||
);
|
|
||||||
|
|
||||||
return JSON.parse(jsonPayload);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
// Add the generated reducer as a specific top-level slice
|
// Add the generated reducer as a specific top-level slice
|
||||||
[AuthApi.reducerPath]: AuthApi.reducer,
|
[AuthApi.reducerPath]: AuthApi.reducer,
|
||||||
auth: createReducer(initialAuthState, (builder) => {
|
auth: createReducer(initialAuthDataState, (builder) => {
|
||||||
builder.addCase(updateToken, (state, action) => {
|
builder.addCase(updateToken, (state, action) => {
|
||||||
state.token = action.payload;
|
state.token = action.payload;
|
||||||
localStorage.setItem("token", action.payload);
|
localStorage.setItem("token", action.payload);
|
||||||
@ -50,6 +45,11 @@ export const store = configureStore({
|
|||||||
builder.addCase(updateUser, (state, action) => {
|
builder.addCase(updateUser, (state, action) => {
|
||||||
state.user = action.payload;
|
state.user = action.payload;
|
||||||
});
|
});
|
||||||
|
builder.addCase(logOut, (state) => {
|
||||||
|
localStorage.removeItem("token");
|
||||||
|
state.token = null;
|
||||||
|
state.user = null;
|
||||||
|
});
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
// Adding the api middleware enables caching, invalidation, polling,
|
// Adding the api middleware enables caching, invalidation, polling,
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
export const theme = {
|
import { ThemeConfig } from "antd";
|
||||||
|
|
||||||
|
export const theme: ThemeConfig = {
|
||||||
components: {
|
components: {
|
||||||
Modal: {
|
Modal: {
|
||||||
contentBg: "#001529",
|
contentBg: "#001529",
|
||||||
@ -11,6 +13,10 @@ export const theme = {
|
|||||||
},
|
},
|
||||||
Button: {
|
Button: {
|
||||||
primaryColor: "#001529",
|
primaryColor: "#001529",
|
||||||
|
defaultHoverBg: "#001529",
|
||||||
|
defaultHoverColor: "white",
|
||||||
|
colorPrimaryBgHover: "#001529",
|
||||||
|
// colorPrimaryHover: "#001529",
|
||||||
},
|
},
|
||||||
Message: {
|
Message: {
|
||||||
contentBg: "#001c36",
|
contentBg: "#001c36",
|
||||||
|
|||||||
19
frontend/app/src/pages/AppRoutes.tsx
Normal file
19
frontend/app/src/pages/AppRoutes.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||||
|
import MainPage from "./MainPage";
|
||||||
|
import { useGetUserQuery } from "../slice/AuthApi";
|
||||||
|
import { getLocalToken, store, updateUser } from "../config/store";
|
||||||
|
|
||||||
|
const AppRoutes = () => {
|
||||||
|
store.dispatch(getLocalToken());
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BrowserRouter>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" Component={MainPage} />
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppRoutes;
|
||||||
19
frontend/app/src/pages/MainPage.tsx
Normal file
19
frontend/app/src/pages/MainPage.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import React from "react";
|
||||||
|
import "./styles.css";
|
||||||
|
import logoSquare from "../../static/logo-square.png";
|
||||||
|
import { Button } from "antd";
|
||||||
|
|
||||||
|
const MainPage = () => {
|
||||||
|
return (
|
||||||
|
<div className="card main">
|
||||||
|
<img src={logoSquare} alt="logo" className="image" />
|
||||||
|
<p style={{ fontSize: "2rem" }}>Organizing queues were never so simple</p>
|
||||||
|
<div className="button-box">
|
||||||
|
<Button>Join a queue</Button>
|
||||||
|
<Button>Take a tour</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MainPage;
|
||||||
27
frontend/app/src/pages/styles.css
Normal file
27
frontend/app/src/pages/styles.css
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
.card {
|
||||||
|
background: #001529;
|
||||||
|
margin: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
justify-items: center;
|
||||||
|
align-items: center;
|
||||||
|
align-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
height: 10vw;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-box {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-self: center;
|
||||||
|
flex-flow: row;
|
||||||
|
justify-items: center;
|
||||||
|
align-content: space-around;
|
||||||
|
}
|
||||||
BIN
frontend/app/static/logo-square.png
Normal file
BIN
frontend/app/static/logo-square.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Reference in New Issue
Block a user