parent
fda05c661a
commit
e6f73932fa
|
|
@ -2,11 +2,13 @@ import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import loginReducer from "./loginSlice";
|
import loginReducer from "./loginSlice";
|
||||||
import postReducer from "./postSlice";
|
import postReducer from "./postSlice";
|
||||||
|
import usersReducer from "./usersSlice";
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
login: loginReducer,
|
login: loginReducer,
|
||||||
post: postReducer,
|
post: postReducer,
|
||||||
|
users: usersReducer,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
import { Store, createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { Configuration, User, UserWithRelations, UsersApi } from "../api";
|
||||||
|
import { Status } from "../util/types";
|
||||||
|
import { AppThunk, store } from "./store";
|
||||||
|
|
||||||
|
interface userState {
|
||||||
|
status: Status;
|
||||||
|
error: string | null;
|
||||||
|
users: User[];
|
||||||
|
userWithRelations: UserWithRelations | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: userState = {
|
||||||
|
status: Status.idle,
|
||||||
|
error: null,
|
||||||
|
users: [],
|
||||||
|
userWithRelations: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usersSlice = createSlice({
|
||||||
|
name: "users",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setUsers: (state, action) => {
|
||||||
|
state.users = action.payload;
|
||||||
|
},
|
||||||
|
setStatus: (state, action) => {
|
||||||
|
state.status = action.payload;
|
||||||
|
},
|
||||||
|
setError: (state, action) => {
|
||||||
|
state.error = action.payload;
|
||||||
|
},
|
||||||
|
setUserWithRelations: (state, action) => {
|
||||||
|
state.userWithRelations = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getUsers = (): AppThunk => async (dispatch) => {
|
||||||
|
const api = createApi(store);
|
||||||
|
|
||||||
|
dispatch(setStatus(Status.loading));
|
||||||
|
try {
|
||||||
|
const response = await api.usersGet();
|
||||||
|
dispatch(setUsers(response.data));
|
||||||
|
dispatch(setStatus(Status.idle));
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(setError((error as Error).message));
|
||||||
|
dispatch(setStatus(Status.idle));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUserWithRelations =
|
||||||
|
(userId: number): AppThunk =>
|
||||||
|
async (dispatch) => {
|
||||||
|
const api = createApi(store);
|
||||||
|
|
||||||
|
dispatch(setStatus(Status.loading));
|
||||||
|
try {
|
||||||
|
const response = await api.usersIdGet(userId);
|
||||||
|
dispatch(setUserWithRelations(response));
|
||||||
|
dispatch(setStatus(Status.idle));
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(setError((error as Error).message));
|
||||||
|
dispatch(setStatus(Status.idle));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const followUser =
|
||||||
|
(userId: number): AppThunk =>
|
||||||
|
async (dispatch) => {
|
||||||
|
const api = createApi(store);
|
||||||
|
|
||||||
|
dispatch(setStatus(Status.loading));
|
||||||
|
try {
|
||||||
|
await api.usersIdFollowPost(userId);
|
||||||
|
dispatch(getUsers());
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(setError((error as Error).message));
|
||||||
|
dispatch(setStatus(Status.idle));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const unFollowUser =
|
||||||
|
(userId: number): AppThunk =>
|
||||||
|
async (dispatch) => {
|
||||||
|
const api = createApi(store);
|
||||||
|
|
||||||
|
dispatch(setStatus(Status.loading));
|
||||||
|
try {
|
||||||
|
await api.usersIdFollowDelete(userId);
|
||||||
|
dispatch(getUsers());
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(setError((error as Error).message));
|
||||||
|
dispatch(setStatus(Status.idle));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const { setUsers, setStatus, setError, setUserWithRelations } =
|
||||||
|
usersSlice.actions;
|
||||||
|
|
||||||
|
export default usersSlice.reducer;
|
||||||
|
|
||||||
|
export const selectUsers = (state: { users: userState }) => state.users.users;
|
||||||
|
export const selectAUser = (state: { users: userState }) =>
|
||||||
|
state.users.userWithRelations;
|
||||||
|
export const selectStatus = (state: { users: userState }) => state.users.status;
|
||||||
|
export const selectError = (state: { users: userState }) => state.users.error;
|
||||||
|
|
||||||
|
function createApi(store: Store) {
|
||||||
|
const configuration = new Configuration({
|
||||||
|
basePath: process.env.REACT_APP_BACKEND_URL,
|
||||||
|
accessToken: store.getState().login.userInfo.jwt,
|
||||||
|
});
|
||||||
|
return new UsersApi(configuration);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Avatar } from "@mui/material";
|
||||||
|
import { User, UserWithRelations } from "../api";
|
||||||
|
|
||||||
|
interface AppAvatarProps {
|
||||||
|
user: User | UserWithRelations;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppAvatar(props: AppAvatarProps) {
|
||||||
|
return (
|
||||||
|
<Avatar
|
||||||
|
alt={`${props.user.firstName} ${props.user.lastName}`}
|
||||||
|
src={`/images/${props.user.profilePictureId}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -5,13 +5,13 @@ import Toolbar from "@mui/material/Toolbar";
|
||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import Menu from "@mui/material/Menu";
|
import Menu from "@mui/material/Menu";
|
||||||
import Avatar from "@mui/material/Avatar";
|
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import NotificationBell from "./notificationBell";
|
import NotificationBell from "./notificationBell";
|
||||||
import {useSelector} from "react-redux";
|
import {useSelector} from "react-redux";
|
||||||
import { postLogout, selectUserInfo } from "../app/loginSlice";
|
import { postLogout, selectUserInfo } from "../app/loginSlice";
|
||||||
import { useAppDispatch } from "../app/store";
|
import { useAppDispatch } from "../app/store";
|
||||||
|
import { AppAvatar } from "./appAvatar";
|
||||||
|
|
||||||
interface TopAppBarProps {
|
interface TopAppBarProps {
|
||||||
height: number;
|
height: number;
|
||||||
|
|
@ -39,73 +39,73 @@ function TopAppBar(props: TopAppBarProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppBar position="absolute" sx={{zIndex:1600, height: `${props.height}px` }}>
|
<AppBar
|
||||||
<Toolbar disableGutters>
|
position="absolute"
|
||||||
<Typography
|
sx={{ zIndex: 1600, height: `${props.height}px` }}
|
||||||
variant="h6"
|
>
|
||||||
noWrap
|
<Toolbar disableGutters>
|
||||||
component="a"
|
<Typography
|
||||||
sx={{
|
variant="h6"
|
||||||
marginLeft: "1rem",
|
noWrap
|
||||||
mr: 2,
|
component="a"
|
||||||
display: "flex",
|
sx={{
|
||||||
fontFamily: "monospace",
|
marginLeft: "1rem",
|
||||||
fontWeight: 700,
|
mr: 2,
|
||||||
letterSpacing: ".3rem",
|
display: "flex",
|
||||||
color: "inherit",
|
fontFamily: "monospace",
|
||||||
textDecoration: "none",
|
fontWeight: 700,
|
||||||
}}
|
letterSpacing: ".3rem",
|
||||||
>
|
color: "inherit",
|
||||||
DevSpace
|
textDecoration: "none",
|
||||||
</Typography>
|
}}
|
||||||
<Box
|
>
|
||||||
sx={{
|
DevSpace
|
||||||
flexGrow: 1,
|
</Typography>
|
||||||
display: "flex",
|
<Box
|
||||||
justifyContent: "right",
|
sx={{
|
||||||
padding: "1rem",
|
flexGrow: 1,
|
||||||
}}
|
display: "flex",
|
||||||
>
|
justifyContent: "right",
|
||||||
<NotificationBell />
|
padding: "1rem",
|
||||||
</Box>
|
}}
|
||||||
|
>
|
||||||
|
<NotificationBell />
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ flexGrow: 0, mr:"2rem" }}>
|
<Box sx={{ flexGrow: 0, mr: "2rem" }}>
|
||||||
<Tooltip title="Open settings" >
|
<Tooltip title="Open settings">
|
||||||
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
|
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
|
||||||
<Avatar
|
<AppAvatar user={userInfo} />
|
||||||
alt={`${userInfo.firstName} ${userInfo.lastName}`}
|
</IconButton>
|
||||||
src="/static/images/avatar/2.jpg"
|
</Tooltip>
|
||||||
/>
|
<Menu
|
||||||
</IconButton>
|
sx={{ mt: "45px" }}
|
||||||
</Tooltip>
|
id="menu-appbar"
|
||||||
<Menu
|
anchorEl={anchorElUser}
|
||||||
sx={{ mt: "45px" }}
|
anchorOrigin={{
|
||||||
id="menu-appbar"
|
vertical: "top",
|
||||||
anchorEl={anchorElUser}
|
horizontal: "right",
|
||||||
anchorOrigin={{
|
}}
|
||||||
vertical: "top",
|
keepMounted
|
||||||
horizontal: "right",
|
transformOrigin={{
|
||||||
}}
|
vertical: "top",
|
||||||
keepMounted
|
horizontal: "right",
|
||||||
transformOrigin={{
|
}}
|
||||||
vertical: "top",
|
open={Boolean(anchorElUser)}
|
||||||
horizontal: "right",
|
onClose={handleCloseUserMenu}
|
||||||
}}
|
>
|
||||||
open={Boolean(anchorElUser)}
|
<MenuItem key={"profile"} onClick={handleCloseUserMenu}>
|
||||||
onClose={handleCloseUserMenu}
|
<Typography textAlign="center">{"Profile"}</Typography>
|
||||||
>
|
</MenuItem>
|
||||||
<MenuItem key={"profile"} onClick={handleCloseUserMenu}>
|
<MenuItem key={"settings"} onClick={handleCloseUserMenu}>
|
||||||
<Typography textAlign="center">{"Profile"}</Typography>
|
<Typography textAlign="center">{"Settings"}</Typography>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem key={"settings"} onClick={handleCloseUserMenu}>
|
<MenuItem key={"logout"} onClick={handleLogout}>
|
||||||
<Typography textAlign="center">{"Settings"}</Typography>
|
<Typography textAlign="center">{"Logout"}</Typography>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem key={"logout"} onClick={handleLogout}>
|
</Menu>
|
||||||
<Typography textAlign="center">{"Logout"}</Typography>
|
</Box>
|
||||||
</MenuItem>
|
</Toolbar>
|
||||||
</Menu>
|
|
||||||
</Box>
|
|
||||||
</Toolbar>
|
|
||||||
</AppBar>
|
</AppBar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,79 @@
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { getUsers, selectUsers } from "../app/usersSlice";
|
||||||
|
import React, { MouseEventHandler, useEffect } from "react";
|
||||||
|
import { useAppDispatch } from "../app/store";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemText,
|
||||||
|
Paper,
|
||||||
|
TextField,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { AppAvatar } from "../components/appAvatar";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { matchSorter } from "match-sorter";
|
||||||
|
|
||||||
export default function Search() {
|
export default function Search() {
|
||||||
return <>Search</>;
|
const userList = useSelector(selectUsers);
|
||||||
|
const [displayUserList, setDisplayUserList] = React.useState(userList);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(getUsers());
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const searchValue = event.currentTarget.value;
|
||||||
|
if (searchValue === "") {
|
||||||
|
setDisplayUserList(userList);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setDisplayUserList(
|
||||||
|
matchSorter(userList, searchValue, { keys: ["firstName", "lastName"] })
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick: MouseEventHandler = (event) => {
|
||||||
|
const userId = event.currentTarget.id;
|
||||||
|
navigate(`/user/${userId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||||
|
<Paper sx={{ mb: "2rem" }}>
|
||||||
|
<TextField
|
||||||
|
sx={{ width: "100%" }}
|
||||||
|
label="Search for a user..."
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
<Paper>
|
||||||
|
<List>
|
||||||
|
{displayUserList.map((user) => (
|
||||||
|
<>
|
||||||
|
<ListItem key={user.id}>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
sx={{ textAlign: "start" }}
|
||||||
|
id={user.id?.toString()}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<AppAvatar user={user} />
|
||||||
|
<ListItemText
|
||||||
|
sx={{ ml: "2rem" }}
|
||||||
|
primary={`${user.firstName} ${user.lastName}`}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</ListItem>
|
||||||
|
<Divider sx={{ mb: "0.3rem" }} />
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export class User {
|
||||||
@Column()
|
@Column()
|
||||||
password: string;
|
password: string;
|
||||||
|
|
||||||
@Column({ default: true })
|
@Column({ default: false })
|
||||||
isPrivate: boolean;
|
isPrivate: boolean;
|
||||||
|
|
||||||
@Column({ default: null })
|
@Column({ default: null })
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue