From 591dae95674af7e95dacd8bf29d256ea45f24251 Mon Sep 17 00:00:00 2001 From: Pau Costa Date: Thu, 8 Feb 2024 16:56:47 +0100 Subject: [PATCH] :building_construction: Views and routes boilerplate Signed-off-by: Pau Costa --- client/src/app/loginSlice.ts | 37 ++++++++++---- client/src/app/postSlice.ts | 68 ++++++++++++++++++++++++++ client/src/app/store.ts | 2 + client/src/components/bottomAppBar.tsx | 29 +++++++++-- client/src/components/postListItem.tsx | 19 +++++++ client/src/components/sideAppBar.tsx | 24 +++++++-- client/src/components/topAppBar.tsx | 37 +++++++++----- client/src/index.tsx | 35 +++++++++++++ client/src/routes/newPost.tsx | 3 ++ client/src/routes/notification.tsx | 3 ++ client/src/routes/post.tsx | 3 ++ client/src/routes/postList.tsx | 41 ++++++++++++++++ client/src/routes/profile.tsx | 12 +++++ client/src/routes/search.tsx | 3 ++ 14 files changed, 287 insertions(+), 29 deletions(-) create mode 100644 client/src/app/postSlice.ts create mode 100644 client/src/components/postListItem.tsx create mode 100644 client/src/routes/newPost.tsx create mode 100644 client/src/routes/notification.tsx create mode 100644 client/src/routes/post.tsx create mode 100644 client/src/routes/postList.tsx create mode 100644 client/src/routes/profile.tsx create mode 100644 client/src/routes/search.tsx diff --git a/client/src/app/loginSlice.ts b/client/src/app/loginSlice.ts index 35b5d6b..0d2285e 100644 --- a/client/src/app/loginSlice.ts +++ b/client/src/app/loginSlice.ts @@ -5,9 +5,9 @@ import { AuthSignupPostRequest, AuthenticationApi, Configuration, + UsersApi, } from "../api"; import { AppThunk } from "./store"; -import { Axios, AxiosError } from "axios"; interface loginState { loggedIn: boolean; @@ -15,6 +15,7 @@ interface loginState { error: string | null; userInfo: { firstName: string; + lastName: string; jwt: string; }; } @@ -25,6 +26,7 @@ const initialState: loginState = { error: null, userInfo: { firstName: "", + lastName: "", jwt: "", }, }; @@ -35,7 +37,9 @@ export const loginSlice = createSlice({ reducers: { login: (state, action) => { state.loggedIn = true; - state.userInfo.jwt = action.payload; + state.userInfo.jwt = action.payload.jwt; + state.userInfo.firstName = action.payload.firstName; + state.userInfo.lastName = action.payload.lastName; }, logoff: (state) => { state.loggedIn = false; @@ -50,7 +54,7 @@ export const loginSlice = createSlice({ }, }); -const api = new AuthenticationApi( +const authApi = new AuthenticationApi( new Configuration({ basePath: process.env.REACT_APP_BACKEND_URL, }) @@ -59,15 +63,30 @@ const api = new AuthenticationApi( export const postLogin = (params: AuthLoginPostRequest): AppThunk => async (dispatch) => { - let response; + let response, userResponse; try { dispatch(setStatus(Status.loading)); - response = await api.authLoginPost(params); + response = await authApi.authLoginPost(params); - dispatch(login(response.data.token)); await addJWT(response.data.token || ""); - dispatch(setError("")); + // Get user info + const userApi = new UsersApi( + new Configuration({ + basePath: process.env.REACT_APP_BACKEND_URL, + accessToken: response.data.token, + }) + ); + + userResponse = await userApi.usersMeGet(); + + dispatch( + login({ + jwt: response.data.token, + firstName: userResponse.data.firstName, + lastName: userResponse.data.lastName, + }) + ); dispatch(setStatus(Status.succeeded)); } catch (error) { dispatch(setStatus(Status.failed)); @@ -83,7 +102,7 @@ export const postSignup = console.log(params); try { dispatch(setStatus(Status.loading)); - response = await api.authSignupPost(params); + response = await authApi.authSignupPost(params); dispatch(postLogin({ email: params.email, password: params.password })); } catch (error) { @@ -97,7 +116,7 @@ export const postLogout = (): AppThunk => async (dispatch) => { localStorage.removeItem("jwt"); sessionStorage.removeItem("jwt"); dispatch(logoff()); - await api.authLogoutGet(); + await authApi.authLogoutGet(); }; const addJWT = async (token: string) => { diff --git a/client/src/app/postSlice.ts b/client/src/app/postSlice.ts new file mode 100644 index 0000000..b3cb600 --- /dev/null +++ b/client/src/app/postSlice.ts @@ -0,0 +1,68 @@ +import { Store, createSlice } from "@reduxjs/toolkit"; +import { Configuration, Post, PostsApi } from "../api"; +import { Status } from "../util/types"; +import { store } from "./store"; + +interface postSlice { + status: Status; + followedPosts: Post[]; + globalPosts: Post[]; +} + +const initialState: postSlice = { + status: Status.idle, + followedPosts: [], + globalPosts: [], +}; + +export const postSlice = createSlice({ + name: "post", + initialState, + reducers: { + setStatus: (state, action) => { + state.status = action.payload; + }, + setFollowedPosts: (state, action) => { + state.followedPosts = action.payload; + }, + setGlobalPosts: (state, action) => { + state.globalPosts = action.payload; + }, + }, +}); + +export const fetchFollowedPosts = () => async (dispatch: any) => { + const postApi = createApi(store); + + dispatch(setStatus(Status.loading)); + const response = await postApi.getFollowedPosts(); + dispatch(setFollowedPosts(response.data)); + dispatch(setStatus(Status.idle)); +}; + +export const fetchGlobalPosts = () => async (dispatch: any) => { + const postApi = createApi(store); + + dispatch(setStatus(Status.loading)); + const response = await postApi.getAllPosts(); + dispatch(setGlobalPosts(response.data)); + dispatch(setStatus(Status.idle)); +}; + +export const { setFollowedPosts, setGlobalPosts, setStatus } = + postSlice.actions; + +export default postSlice.reducer; + +export const selectFollowedPosts = (state: any) => state.post.followedPosts; +export const selectAllPosts = (state: any) => state.post.globalPosts; +export const selectStatus = (state: any) => state.post.status; + +function createApi(store: Store) { + return new PostsApi( + new Configuration({ + basePath: process.env.REACT_APP_BACKEND_URL, + accessToken: store.getState().login.userInfo.jwt, + }) + ); +} diff --git a/client/src/app/store.ts b/client/src/app/store.ts index 68ccd64..089d197 100644 --- a/client/src/app/store.ts +++ b/client/src/app/store.ts @@ -1,10 +1,12 @@ import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit"; import { useDispatch } from "react-redux"; import loginReducer from "./loginSlice"; +import postReducer from "./postSlice"; export const store = configureStore({ reducer: { login: loginReducer, + post: postReducer, }, }); diff --git a/client/src/components/bottomAppBar.tsx b/client/src/components/bottomAppBar.tsx index 101898f..a94ff78 100644 --- a/client/src/components/bottomAppBar.tsx +++ b/client/src/components/bottomAppBar.tsx @@ -4,10 +4,15 @@ import FeedIcon from "@mui/icons-material/Feed"; import GlobalIcon from "@mui/icons-material/Public"; import AddIcon from "@mui/icons-material/Add"; import SearchIcon from "@mui/icons-material/Search"; +import { useNavigate } from "react-router-dom"; export default function BottomAppBar() { const [value, setValue] = useState(0); + const navigate = useNavigate(); + const handleClick = (to: string) => () => { + navigate(to); + }; return ( - } /> - } /> - } /> - } /> + } + onClick={handleClick("feed")} + /> + } + onClick={handleClick("global")} + /> + } + onClick={handleClick("newpost")} + /> + } + onClick={handleClick("search")} + /> ); diff --git a/client/src/components/postListItem.tsx b/client/src/components/postListItem.tsx new file mode 100644 index 0000000..1e77f2c --- /dev/null +++ b/client/src/components/postListItem.tsx @@ -0,0 +1,19 @@ +import { ListItem, ListItemText } from "@mui/material"; +import { Post } from "../api"; + +interface PostListItemProps { + post: Post; +} + +export default function PostListItem(props: PostListItemProps) { + console.log(props.post); + + return ( + + + + ); +} diff --git a/client/src/components/sideAppBar.tsx b/client/src/components/sideAppBar.tsx index a9c82d6..9b095cc 100644 --- a/client/src/components/sideAppBar.tsx +++ b/client/src/components/sideAppBar.tsx @@ -13,6 +13,7 @@ import FeedIcon from "@mui/icons-material/Feed"; import GlobalIcon from "@mui/icons-material/Public"; import AddIcon from "@mui/icons-material/Add"; import SearchIcon from "@mui/icons-material/Search"; +import { Link, useNavigate } from "react-router-dom"; export interface SideAppBarProps { drawerWidth: number; @@ -20,6 +21,12 @@ export interface SideAppBarProps { export default function SideAppBar(props: SideAppBarProps) { const { drawerWidth } = props; + const navigate = useNavigate(); + + const handleClick = (to: string) => () => { + navigate(to); + }; + return ( - + @@ -46,7 +53,11 @@ export default function SideAppBar(props: SideAppBarProps) { - + @@ -55,7 +66,11 @@ export default function SideAppBar(props: SideAppBarProps) { - + @@ -64,7 +79,7 @@ export default function SideAppBar(props: SideAppBarProps) { - + @@ -72,7 +87,6 @@ export default function SideAppBar(props: SideAppBarProps) { - ); diff --git a/client/src/components/topAppBar.tsx b/client/src/components/topAppBar.tsx index 02eff6b..4d13a55 100644 --- a/client/src/components/topAppBar.tsx +++ b/client/src/components/topAppBar.tsx @@ -11,18 +11,21 @@ import Tooltip from "@mui/material/Tooltip"; import MenuItem from "@mui/material/MenuItem"; import NotificationBell from "./notificationBell"; import {useSelector} from "react-redux"; -import {selectUserInfo} from "../app/loginSlice"; +import { postLogout, selectUserInfo } from "../app/loginSlice"; +import { useAppDispatch } from "../app/store"; +import { useNavigate } from "react-router-dom"; const settings = ["Profile", "Account", "Dashboard", "Logout"]; function TopAppBar() { - - const userInfo = useSelector(selectUserInfo); - const [anchorElUser, setAnchorElUser] = React.useState( + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + const userInfo = useSelector(selectUserInfo); + const [anchorElUser, setAnchorElUser] = React.useState( null ); - console.log(userInfo) + console.log(userInfo); const handleOpenUserMenu = (event: React.MouseEvent) => { setAnchorElUser(event.currentTarget); @@ -32,6 +35,11 @@ function TopAppBar() { setAnchorElUser(null); }; + const handleLogout = () => { + dispatch(postLogout()); + setAnchorElUser(null); + }; + return ( @@ -67,7 +75,10 @@ function TopAppBar() { - + - {settings.map((setting) => ( - - {setting} - - ))} + + {"Profile"} + + + {"Settings"} + + + {"Logout"} + diff --git a/client/src/index.tsx b/client/src/index.tsx index 13737ff..1d23b5f 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -12,6 +12,11 @@ import Register from "./routes/Auth/register"; import AuthRoot from "./routes/Auth/authRoot"; import { Provider } from "react-redux"; import { store } from "./app/store"; +import PostList from "./routes/postList"; +import Profile from "./routes/profile"; +import Post from "./routes/post"; +import NewPost from "./routes/newPost"; +import Search from "./routes/search"; const root = ReactDOM.createRoot( document.getElementById("root") as HTMLElement @@ -28,6 +33,36 @@ const router = createBrowserRouter([ path: "/", element: , errorElement: , + children: [ + { + path: "feed", + element: , + }, + { + path: "global", + element: , + }, + { + path: "me", + element: , + }, + { + path: "user/:id", + element: , + }, + { + path: "post/:id", + element: , + }, + { + path: "search", + element: , + }, + { + path: "newpost", + element: , + }, + ], }, { path: "/auth", diff --git a/client/src/routes/newPost.tsx b/client/src/routes/newPost.tsx new file mode 100644 index 0000000..46ac5ce --- /dev/null +++ b/client/src/routes/newPost.tsx @@ -0,0 +1,3 @@ +export default function NewPost() { + return <>New Post; +} diff --git a/client/src/routes/notification.tsx b/client/src/routes/notification.tsx new file mode 100644 index 0000000..9e98dfc --- /dev/null +++ b/client/src/routes/notification.tsx @@ -0,0 +1,3 @@ +export default function Notification() { + return <>Notification; +} diff --git a/client/src/routes/post.tsx b/client/src/routes/post.tsx new file mode 100644 index 0000000..b0a457c --- /dev/null +++ b/client/src/routes/post.tsx @@ -0,0 +1,3 @@ +export default function Post() { + return <>Post; +} diff --git a/client/src/routes/postList.tsx b/client/src/routes/postList.tsx new file mode 100644 index 0000000..a6f98dc --- /dev/null +++ b/client/src/routes/postList.tsx @@ -0,0 +1,41 @@ +import { useSelector } from "react-redux"; +import { + fetchFollowedPosts, + fetchGlobalPosts, + selectAllPosts, + selectFollowedPosts, +} from "../app/postSlice"; +import { store, useAppDispatch } from "../app/store"; +import { useEffect } from "react"; +import { List, ListItem, ListItemText } from "@mui/material"; +import { Post } from "../api"; +import PostListItem from "../components/postListItem"; + +interface PostListProps { + type: "all" | "user"; +} + +export default function PostList(props: PostListProps) { + const dispatch = useAppDispatch(); + const followedPosts = useSelector(selectFollowedPosts); + const globalPosts = useSelector(selectAllPosts); + + useEffect(() => { + if (props.type === "all") dispatch(fetchGlobalPosts()); + if (props.type === "user") dispatch(fetchFollowedPosts()); + }, []); + + console.log(globalPosts); + return ( + <> + {props.type === "all" && ( + + {globalPosts.map((post: Post) => ( + + ))} + + )} + {props.type === "user" && } + + ); +} diff --git a/client/src/routes/profile.tsx b/client/src/routes/profile.tsx new file mode 100644 index 0000000..ba4f76f --- /dev/null +++ b/client/src/routes/profile.tsx @@ -0,0 +1,12 @@ +import { useParams } from "react-router-dom"; + +export interface ProfileProps { + selfProfile?: boolean; +} + +export default function Profile(props: ProfileProps) { + const { selfProfile } = props; + const userId = useParams<{ userId: string }>().userId; + + return <>Profile; +} diff --git a/client/src/routes/search.tsx b/client/src/routes/search.tsx new file mode 100644 index 0000000..935588d --- /dev/null +++ b/client/src/routes/search.tsx @@ -0,0 +1,3 @@ +export default function Search() { + return <>Search; +}