user avatar
parent
71f0abf37b
commit
b89f40f622
|
@ -2,14 +2,18 @@ import {
|
||||||
AppstoreOutlined,
|
AppstoreOutlined,
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
SnippetsOutlined,
|
SnippetsOutlined,
|
||||||
UserOutlined,
|
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Badge, Divider, Menu } from "antd";
|
import { Badge, Divider, Menu } from "antd";
|
||||||
import Sider from "antd/es/layout/Sider";
|
import Sider from "antd/es/layout/Sider";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { Constants, UseUserSession, WebSocketContext } from "../../utils";
|
import {
|
||||||
|
Constants,
|
||||||
|
MyAvatar,
|
||||||
|
UseUserSession,
|
||||||
|
WebSocketContext,
|
||||||
|
} from "../../utils";
|
||||||
|
|
||||||
export default function SideMenu({ setUserSession }) {
|
export default function SideMenu({ setUserSession }) {
|
||||||
const { userSession } = UseUserSession();
|
const { userSession } = UseUserSession();
|
||||||
|
@ -87,8 +91,16 @@ export default function SideMenu({ setUserSession }) {
|
||||||
key: "/users",
|
key: "/users",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: webSocketContext.User.Username,
|
label: " " + webSocketContext.User.Username,
|
||||||
icon: <UserOutlined />,
|
icon: (
|
||||||
|
<MyAvatar
|
||||||
|
avatar={
|
||||||
|
webSocketContext.AllUsers.find(
|
||||||
|
(user) => user.Id === webSocketContext.User.Id
|
||||||
|
)?.Avatar
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
key: "/user-profile",
|
key: "/user-profile",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
import { Badge, Space, Table } from "antd";
|
import { Button, Card, Form, Input, Space, Table, Upload, message } from "antd";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { Constants, FormatDatetime, WebSocketContext } from "../../utils";
|
import {
|
||||||
|
Constants,
|
||||||
|
FormatDatetime,
|
||||||
|
MyAvatar,
|
||||||
|
WebSocketContext,
|
||||||
|
getConnectionStatusItem,
|
||||||
|
getUserSessionFromLocalStorage,
|
||||||
|
} from "../../utils";
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
|
@ -37,9 +44,7 @@ const columns = [
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"X-Authorization": JSON.parse(
|
"X-Authorization": getUserSessionFromLocalStorage(),
|
||||||
localStorage.getItem("session")
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
@ -68,12 +73,7 @@ export default function UserProfile() {
|
||||||
items.push({
|
items.push({
|
||||||
key: session.IdForDeletion,
|
key: session.IdForDeletion,
|
||||||
userAgent: session.UserAgent,
|
userAgent: session.UserAgent,
|
||||||
connectionStatus:
|
connectionStatus: getConnectionStatusItem(session.ConnectionStatus),
|
||||||
session.ConnectionStatus === 0 ? (
|
|
||||||
<Badge status="error" text="Offline" />
|
|
||||||
) : (
|
|
||||||
<Badge status="success" text="Online" />
|
|
||||||
),
|
|
||||||
lastUsed: FormatDatetime(session.LastUsed),
|
lastUsed: FormatDatetime(session.LastUsed),
|
||||||
expiresAt: FormatDatetime(session.ExpiresAt),
|
expiresAt: FormatDatetime(session.ExpiresAt),
|
||||||
});
|
});
|
||||||
|
@ -82,8 +82,60 @@ export default function UserProfile() {
|
||||||
return items;
|
return items;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const beforeUpload = (file) => {
|
||||||
|
if (file.size > Constants.MAX_AVATAR_SIZE) {
|
||||||
|
message.error(
|
||||||
|
`Image must be smaller than ${
|
||||||
|
Constants.MAX_AVATAR_SIZE / 1024 / 1024
|
||||||
|
} MB`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<h1 style={{ fontWeight: "bold" }}>Your Profile</h1>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<Upload
|
||||||
|
accept={Constants.ACCEPTED_FILE_TYPES.join(",")}
|
||||||
|
action={Constants.API_ADDRESS + "/user/avatar"}
|
||||||
|
maxCount={1}
|
||||||
|
showUploadList={false}
|
||||||
|
beforeUpload={beforeUpload}
|
||||||
|
headers={{
|
||||||
|
"X-Authorization": getUserSessionFromLocalStorage(),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MyAvatar
|
||||||
|
avatar={
|
||||||
|
webSocketContext.AllUsers.find(
|
||||||
|
(user) => user.Id === webSocketContext.User.Id
|
||||||
|
)?.Avatar
|
||||||
|
}
|
||||||
|
avatarWidth={200}
|
||||||
|
/>
|
||||||
|
</Upload>
|
||||||
|
|
||||||
|
<Form layout="vertical">
|
||||||
|
<Form.Item label="Username">
|
||||||
|
<Input value={webSocketContext.User.Username} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="E-Mail">
|
||||||
|
<Input value={webSocketContext.User.Email} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
<h1 style={{ fontWeight: "bold" }}>
|
<h1 style={{ fontWeight: "bold" }}>
|
||||||
Your Sessions ({webSocketContext.User.Sessions.length})
|
Your Sessions ({webSocketContext.User.Sessions.length})
|
||||||
</h1>
|
</h1>
|
||||||
|
|
|
@ -1,5 +1,73 @@
|
||||||
import { Button } from "antd";
|
import { Popover, Table } from "antd";
|
||||||
|
import {
|
||||||
|
MyAvatar,
|
||||||
|
WebSocketContext,
|
||||||
|
getConnectionStatusItem,
|
||||||
|
} from "../../utils";
|
||||||
|
import { useContext } from "react";
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: "Avatar",
|
||||||
|
dataIndex: "avatar",
|
||||||
|
key: "avatar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Username",
|
||||||
|
dataIndex: "username",
|
||||||
|
key: "username",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Connection status",
|
||||||
|
dataIndex: "connectionStatus",
|
||||||
|
key: "connectionStatus",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Last online",
|
||||||
|
dataIndex: "lastOnline",
|
||||||
|
key: "lastOnline",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default function Users() {
|
export default function Users() {
|
||||||
return <Button>Test</Button>;
|
const webSocketContext = useContext(WebSocketContext);
|
||||||
|
|
||||||
|
const getTableItems = () => {
|
||||||
|
let items = [];
|
||||||
|
|
||||||
|
webSocketContext.AllUsers.sort(
|
||||||
|
(a, b) => b.ConnectionStatus - a.ConnectionStatus
|
||||||
|
);
|
||||||
|
|
||||||
|
webSocketContext.AllUsers.forEach((user) => {
|
||||||
|
items.push({
|
||||||
|
key: user.Id,
|
||||||
|
avatar: (
|
||||||
|
<Popover
|
||||||
|
placement="right"
|
||||||
|
trigger={"hover"}
|
||||||
|
content={<MyAvatar avatar={user.Avatar} avatarWidth={256} />}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<MyAvatar avatar={user.Avatar} />
|
||||||
|
</>
|
||||||
|
</Popover>
|
||||||
|
),
|
||||||
|
connectionStatus: getConnectionStatusItem(user.ConnectionStatus),
|
||||||
|
username: user.Username,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1 style={{ fontWeight: "bold" }}>
|
||||||
|
All users ({webSocketContext.AllUsers.length})
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<Table columns={columns} dataSource={getTableItems()} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
59
src/utils.js
59
src/utils.js
|
@ -1,3 +1,5 @@
|
||||||
|
import { UserOutlined } from "@ant-design/icons";
|
||||||
|
import { Avatar, Badge } from "antd";
|
||||||
import { createContext, useEffect, useRef, useState } from "react";
|
import { createContext, useEffect, useRef, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
@ -12,6 +14,7 @@ export const Constants = {
|
||||||
},
|
},
|
||||||
TEXT_EMPTY_PLACEHOLDER: "-/-",
|
TEXT_EMPTY_PLACEHOLDER: "-/-",
|
||||||
API_ADDRESS: "http://localhost:8080/v1",
|
API_ADDRESS: "http://localhost:8080/v1",
|
||||||
|
STATIC_CONTENT_ADDRESS: "http://localhost:8080/",
|
||||||
WS_ADDRESS: "ws://localhost:8080/ws",
|
WS_ADDRESS: "ws://localhost:8080/ws",
|
||||||
ROUTE_PATHS: {
|
ROUTE_PATHS: {
|
||||||
GROUP_TASKS: "/group-tasks",
|
GROUP_TASKS: "/group-tasks",
|
||||||
|
@ -30,6 +33,8 @@ export const Constants = {
|
||||||
MIN_PASSWORD_LENGTH: 6,
|
MIN_PASSWORD_LENGTH: 6,
|
||||||
MAX_PASSWORD_LENGTH: 64,
|
MAX_PASSWORD_LENGTH: 64,
|
||||||
},
|
},
|
||||||
|
MAX_AVATAR_SIZE: 5 * 1024 * 1024,
|
||||||
|
ACCEPTED_FILE_TYPES: ["image/png", "image/jpeg", "image/jpg"],
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,7 +42,7 @@ export const Constants = {
|
||||||
*/
|
*/
|
||||||
export function UseUserSession() {
|
export function UseUserSession() {
|
||||||
const getUserSession = () => {
|
const getUserSession = () => {
|
||||||
return JSON.parse(localStorage.getItem("session"));
|
return getUserSessionFromLocalStorage();
|
||||||
};
|
};
|
||||||
|
|
||||||
const [userSession, setUserSession] = useState(getUserSession());
|
const [userSession, setUserSession] = useState(getUserSession());
|
||||||
|
@ -58,6 +63,10 @@ export function UseUserSession() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getUserSessionFromLocalStorage() {
|
||||||
|
return JSON.parse(localStorage.getItem("session"));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* websocket
|
* websocket
|
||||||
*/
|
*/
|
||||||
|
@ -65,10 +74,12 @@ let l = "loading...";
|
||||||
|
|
||||||
let webSocketContextPreview = {
|
let webSocketContextPreview = {
|
||||||
User: {
|
User: {
|
||||||
|
Id: "",
|
||||||
Username: l,
|
Username: l,
|
||||||
Email: l,
|
Email: l,
|
||||||
Sessions: [],
|
Sessions: [],
|
||||||
},
|
},
|
||||||
|
AllUsers: [],
|
||||||
ConnectionBadgeStatus: "error",
|
ConnectionBadgeStatus: "error",
|
||||||
ConnectedWebSocketUsersCount: 0,
|
ConnectedWebSocketUsersCount: 0,
|
||||||
CategoryGroups: [],
|
CategoryGroups: [],
|
||||||
|
@ -89,6 +100,7 @@ const ReceivedMessagesCommands = {
|
||||||
ReloadingGroupTasks: 7,
|
ReloadingGroupTasks: 7,
|
||||||
GroupTasksReloaded: 8,
|
GroupTasksReloaded: 8,
|
||||||
UpdateUserSessions: 9,
|
UpdateUserSessions: 9,
|
||||||
|
UpdateAllUsersUserAvatar: 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
// commands sent to the backend server
|
// commands sent to the backend server
|
||||||
|
@ -112,6 +124,7 @@ export function WebSocketProvider({
|
||||||
const [connectedWebSocketUsersCount, setConnectedWebSocketUsersCount] =
|
const [connectedWebSocketUsersCount, setConnectedWebSocketUsersCount] =
|
||||||
useState(0);
|
useState(0);
|
||||||
const [user, setUser] = useState(webSocketContextPreview.User);
|
const [user, setUser] = useState(webSocketContextPreview.User);
|
||||||
|
const [allUsers, setAllUsers] = useState(webSocketContextPreview.AllUsers);
|
||||||
const [categoryGroups, setCategoryGroups] = useState(
|
const [categoryGroups, setCategoryGroups] = useState(
|
||||||
webSocketContextPreview.CategoryGroups
|
webSocketContextPreview.CategoryGroups
|
||||||
);
|
);
|
||||||
|
@ -145,12 +158,22 @@ export function WebSocketProvider({
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case ReceivedMessagesCommands.InitUserSocketConnection:
|
case ReceivedMessagesCommands.InitUserSocketConnection:
|
||||||
setUser(body.User);
|
setUser(body.User);
|
||||||
|
setAllUsers(body.AllUsers);
|
||||||
setCategoryGroups(body.CategoryGroups);
|
setCategoryGroups(body.CategoryGroups);
|
||||||
setGroupTasks(body.GroupTasks);
|
setGroupTasks(body.GroupTasks);
|
||||||
setGroupTasksSteps(body.GroupTasksSteps);
|
setGroupTasksSteps(body.GroupTasksSteps);
|
||||||
break;
|
break;
|
||||||
case ReceivedMessagesCommands.UpdateConnectedUsers:
|
case ReceivedMessagesCommands.UpdateConnectedUsers:
|
||||||
setConnectedWebSocketUsersCount(body);
|
setConnectedWebSocketUsersCount(body.WebSocketUsersCount);
|
||||||
|
setAllUsers((arr) => {
|
||||||
|
const newArr = [...arr];
|
||||||
|
|
||||||
|
newArr[
|
||||||
|
arr.findIndex((arr1) => arr1.Id === body.UserId)
|
||||||
|
].ConnectionStatus = body.ConnectionStatus;
|
||||||
|
|
||||||
|
return newArr;
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case ReceivedMessagesCommands.NewGroupTaskStarted:
|
case ReceivedMessagesCommands.NewGroupTaskStarted:
|
||||||
setGroupTasks((arr) => [...arr, body]);
|
setGroupTasks((arr) => [...arr, body]);
|
||||||
|
@ -207,7 +230,17 @@ export function WebSocketProvider({
|
||||||
case ReceivedMessagesCommands.UpdateUserSessions:
|
case ReceivedMessagesCommands.UpdateUserSessions:
|
||||||
setUser((arr) => ({ ...arr, Sessions: body }));
|
setUser((arr) => ({ ...arr, Sessions: body }));
|
||||||
break;
|
break;
|
||||||
case ReceivedMessagesCommands.default:
|
case ReceivedMessagesCommands.UpdateAllUsersUserAvatar:
|
||||||
|
setAllUsers((arr) => {
|
||||||
|
const newArr = [...arr];
|
||||||
|
|
||||||
|
newArr[arr.findIndex((arr1) => arr1.Id === body.UserId)].Avatar =
|
||||||
|
body.Avatar;
|
||||||
|
|
||||||
|
return newArr;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
console.error("unknown command", cmd);
|
console.error("unknown command", cmd);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -257,6 +290,7 @@ export function WebSocketProvider({
|
||||||
CategoryGroups: categoryGroups,
|
CategoryGroups: categoryGroups,
|
||||||
GroupTasks: groupTasks,
|
GroupTasks: groupTasks,
|
||||||
User: user,
|
User: user,
|
||||||
|
AllUsers: allUsers,
|
||||||
SendSocketMessage: SendSocketMessage,
|
SendSocketMessage: SendSocketMessage,
|
||||||
GroupTasksSteps: groupTasksSteps,
|
GroupTasksSteps: groupTasksSteps,
|
||||||
setGroupTasksSteps: setGroupTasksSteps,
|
setGroupTasksSteps: setGroupTasksSteps,
|
||||||
|
@ -318,3 +352,22 @@ export function GetDuration(startTime, endTime) {
|
||||||
|
|
||||||
return result.trim();
|
return result.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getConnectionStatusItem(connectionStatus) {
|
||||||
|
return connectionStatus === 0 ? (
|
||||||
|
<Badge status="error" text="Offline" />
|
||||||
|
) : (
|
||||||
|
<Badge status="success" text="Online" />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MyAvatar({ avatarWidth, avatar }) {
|
||||||
|
return avatar !== undefined && avatar !== "" ? (
|
||||||
|
<Avatar
|
||||||
|
size={avatarWidth}
|
||||||
|
src={Constants.STATIC_CONTENT_ADDRESS + "avatars/" + avatar}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Avatar size={avatarWidth} icon={<UserOutlined />} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue