diff --git a/frontend/app/package-lock.json b/frontend/app/package-lock.json index b92bd66..34da32a 100644 --- a/frontend/app/package-lock.json +++ b/frontend/app/package-lock.json @@ -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", diff --git a/frontend/app/package.json b/frontend/app/package.json index 1a0092f..a9e8f08 100644 --- a/frontend/app/package.json +++ b/frontend/app/package.json @@ -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", diff --git a/frontend/app/src/App.css b/frontend/app/src/App.css index 164c0a6..525cd30 100644 --- a/frontend/app/src/App.css +++ b/frontend/app/src/App.css @@ -11,7 +11,6 @@ body { line-height: 1.1; text-align: center; flex-direction: column; - justify-content: center; } .content h1 { diff --git a/frontend/app/src/App.tsx b/frontend/app/src/App.tsx index 12a9cd2..51eb2c0 100644 --- a/frontend/app/src/App.tsx +++ b/frontend/app/src/App.tsx @@ -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 = () => {
{contextHolder} +
diff --git a/frontend/app/src/components/AuthModal.tsx b/frontend/app/src/components/AuthModal.tsx index ca64c86..6ae2abd 100644 --- a/frontend/app/src/components/AuthModal.tsx +++ b/frontend/app/src/components/AuthModal.tsx @@ -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!")); }; diff --git a/frontend/app/src/components/HeaderComponent.tsx b/frontend/app/src/components/HeaderComponent.tsx index 8e9e229..b91ed21 100644 --- a/frontend/app/src/components/HeaderComponent.tsx +++ b/frontend/app/src/components/HeaderComponent.tsx @@ -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: , + // key: "logout", + // icon: , + // onClick: () => store.dispatch(logOut()), + // }, + // ] const items: MenuProps["items"] = [ { - label: user ? user.username : "Log In", + label: user ? ( + store.dispatch(logOut())}>Log out + } + > + {user.username} + + ) : ( + "Log In" + ), key: "login", icon: , - onClick: () => setAuthModalOpen(true), + onClick: () => !user && setAuthModalOpen(true), }, ]; return ( <> -
+
{ const { data, isFetching, isError } = useGetUserQuery({}); useEffect(() => { - console.log(data); if (!isFetching && !isError) { store.dispatch(updateUser(data)); } diff --git a/frontend/app/src/config/store.ts b/frontend/app/src/config/store.ts index b579579..f8487d3 100644 --- a/frontend/app/src/config/store.ts +++ b/frontend/app/src/config/store.ts @@ -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("auth/updateToken"); export const getLocalToken = createAction("auth/getLocalToken"); export const updateUser = createAction("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, diff --git a/frontend/app/src/config/style.ts b/frontend/app/src/config/style.ts index cb47658..90951ee 100644 --- a/frontend/app/src/config/style.ts +++ b/frontend/app/src/config/style.ts @@ -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", diff --git a/frontend/app/src/pages/AppRoutes.tsx b/frontend/app/src/pages/AppRoutes.tsx new file mode 100644 index 0000000..6daa0c2 --- /dev/null +++ b/frontend/app/src/pages/AppRoutes.tsx @@ -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 ( + + + + + + ); +}; + +export default AppRoutes; diff --git a/frontend/app/src/pages/MainPage.tsx b/frontend/app/src/pages/MainPage.tsx new file mode 100644 index 0000000..c695094 --- /dev/null +++ b/frontend/app/src/pages/MainPage.tsx @@ -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 ( +
+ logo +

Organizing queues were never so simple

+
+ + +
+
+ ); +}; + +export default MainPage; diff --git a/frontend/app/src/pages/styles.css b/frontend/app/src/pages/styles.css new file mode 100644 index 0000000..ec491b7 --- /dev/null +++ b/frontend/app/src/pages/styles.css @@ -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; +} \ No newline at end of file diff --git a/frontend/app/static/logo-square.png b/frontend/app/static/logo-square.png new file mode 100644 index 0000000..353be97 Binary files /dev/null and b/frontend/app/static/logo-square.png differ