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",
|
||||
"react": "^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": {
|
||||
"@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": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-0.5.4.tgz",
|
||||
@ -4012,6 +4021,36 @@
|
||||
"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": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||
|
||||
@ -13,7 +13,8 @@
|
||||
"antd": "^5.15.4",
|
||||
"react": "^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": {
|
||||
"@rsbuild/core": "^0.5.4",
|
||||
|
||||
@ -11,7 +11,6 @@ body {
|
||||
line-height: 1.1;
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.content h1 {
|
||||
|
||||
@ -6,6 +6,7 @@ import { theme } from "./config/style";
|
||||
import { Provider } from "react-redux";
|
||||
import { store } from "./config/store";
|
||||
import { MessageInstance } from "antd/es/message/interface";
|
||||
import AppRoutes from "./pages/AppRoutes";
|
||||
|
||||
export const MessageContext = createContext({} as MessageInstance);
|
||||
|
||||
@ -21,6 +22,7 @@ const App = () => {
|
||||
<div className="content">
|
||||
{contextHolder}
|
||||
<HeaderComponent />
|
||||
<AppRoutes />
|
||||
</div>
|
||||
</MessageContext.Provider>
|
||||
</ConfigProvider>
|
||||
|
||||
@ -17,17 +17,16 @@ const AuthModal = (props: {
|
||||
const [loginForm] = 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 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 [registerUser, { isLoading: isRegistering }] = useRegisterMutation();
|
||||
|
||||
@ -41,6 +40,7 @@ const AuthModal = (props: {
|
||||
.then((data) => {
|
||||
store.dispatch(updateToken(data.access_token));
|
||||
})
|
||||
.then(() => refetch())
|
||||
.then(() => props.setOpen(false))
|
||||
.catch(() => messageApi.error("Login failed!"));
|
||||
};
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { UserOutlined } from "@ant-design/icons";
|
||||
import { Layout, Menu, MenuProps } from "antd";
|
||||
import { LogoutOutlined, UserOutlined } from "@ant-design/icons";
|
||||
import { Button, Layout, Menu, MenuProps, Tooltip } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import AuthModal from "./AuthModal";
|
||||
import "./styles.css";
|
||||
import { getLocalToken, store, updateUser } from "../config/store";
|
||||
import { User, useGetUserQuery } from "../slice/AuthApi";
|
||||
import { StorePrototype, logOut, store } from "../config/store";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const { Header } = Layout;
|
||||
|
||||
@ -12,31 +12,43 @@ type NullableUser = { name: string | null; username: string } | null;
|
||||
|
||||
const HeaderComponent = () => {
|
||||
const [authModalOpen, setAuthModalOpen] = useState(false);
|
||||
store.dispatch(getLocalToken());
|
||||
const user: NullableUser = store.getState().auth.user;
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log(data);
|
||||
// if (!isFetching && !isError) {
|
||||
// store.dispatch(updateUser(data));
|
||||
// }
|
||||
// }, [data, isFetching, useGetUserQuery]);
|
||||
const user: NullableUser = useSelector(
|
||||
(state: StorePrototype) => state.auth.user
|
||||
);
|
||||
|
||||
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"] = [
|
||||
{
|
||||
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",
|
||||
icon: <UserOutlined />,
|
||||
onClick: () => setAuthModalOpen(true),
|
||||
onClick: () => !user && setAuthModalOpen(true),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<AuthModal open={authModalOpen} setOpen={setAuthModalOpen} />
|
||||
<Header style={{ display: "flex", alignItems: "center" }}>
|
||||
<Header>
|
||||
<div className="demo-logo" />
|
||||
<Menu
|
||||
theme="dark"
|
||||
|
||||
@ -5,7 +5,6 @@ import { store, updateUser } from "../config/store";
|
||||
const authProvider = () => {
|
||||
const { data, isFetching, isError } = useGetUserQuery({});
|
||||
useEffect(() => {
|
||||
console.log(data);
|
||||
if (!isFetching && !isError) {
|
||||
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 { AuthApi, User } from "../slice/AuthApi";
|
||||
|
||||
export type authState = {
|
||||
export type AuthDataType = {
|
||||
token: string | null;
|
||||
user: { name: string | null; username: string } | null;
|
||||
};
|
||||
|
||||
const initialAuthState: authState = {
|
||||
const initialAuthDataState: AuthDataType = {
|
||||
token: null,
|
||||
user: null,
|
||||
};
|
||||
|
||||
export type StorePrototype = {
|
||||
AuthApi: ReducerType;
|
||||
auth: AuthDataType;
|
||||
};
|
||||
|
||||
export const updateToken = createAction<string>("auth/updateToken");
|
||||
export const getLocalToken = createAction("auth/getLocalToken");
|
||||
export const updateUser = createAction<User>("auth/updateUser");
|
||||
|
||||
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 logOut = createAction("auth/logOut");
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
// Add the generated reducer as a specific top-level slice
|
||||
[AuthApi.reducerPath]: AuthApi.reducer,
|
||||
auth: createReducer(initialAuthState, (builder) => {
|
||||
auth: createReducer(initialAuthDataState, (builder) => {
|
||||
builder.addCase(updateToken, (state, action) => {
|
||||
state.token = action.payload;
|
||||
localStorage.setItem("token", action.payload);
|
||||
@ -50,6 +45,11 @@ export const store = configureStore({
|
||||
builder.addCase(updateUser, (state, action) => {
|
||||
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,
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export const theme = {
|
||||
import { ThemeConfig } from "antd";
|
||||
|
||||
export const theme: ThemeConfig = {
|
||||
components: {
|
||||
Modal: {
|
||||
contentBg: "#001529",
|
||||
@ -11,6 +13,10 @@ export const theme = {
|
||||
},
|
||||
Button: {
|
||||
primaryColor: "#001529",
|
||||
defaultHoverBg: "#001529",
|
||||
defaultHoverColor: "white",
|
||||
colorPrimaryBgHover: "#001529",
|
||||
// colorPrimaryHover: "#001529",
|
||||
},
|
||||
Message: {
|
||||
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