user profile and AI chat
parent
bb1bf8ef0e
commit
679ee5bf28
57
src/App.tsx
57
src/App.tsx
|
@ -1,23 +1,13 @@
|
||||||
import { ConfigProvider, Layout, theme } from "antd";
|
import { ConfigProvider, Layout, theme } from 'antd';
|
||||||
import DashboardLayout from "./core/components/DashboardLayout";
|
import DashboardLayout from './core/components/DashboardLayout';
|
||||||
import {
|
import { darkMode, primaryColor, setBannerUrl, setLogoUrl, setPrimaryColor, setUserAuthenticated, userAuthenticated, setUserProfilePictureUrl } from './core/reducers/appSlice';
|
||||||
darkMode,
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
primaryColor,
|
import SignIn from './features/Auth/SignIn';
|
||||||
setBannerUrl,
|
import { useEffect } from 'react';
|
||||||
setLogoUrl,
|
import { myFetch } from './core/utils/utils';
|
||||||
setPrimaryColor,
|
import MyCenteredSpin from './shared/components/MyCenteredSpin';
|
||||||
setUserAuthenticated,
|
import webSocketService, { WebSocketMessageHandler } from 'core/services/websocketService';
|
||||||
userAuthenticated,
|
import { MessageProvider } from 'core/context/MessageContext';
|
||||||
} from "./core/reducers/appSlice";
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
|
||||||
import SignIn from "./features/Auth/SignIn";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { myFetch } from "./core/utils/utils";
|
|
||||||
import MyCenteredSpin from "./shared/components/MyCenteredSpin";
|
|
||||||
import webSocketService, {
|
|
||||||
WebSocketMessageHandler,
|
|
||||||
} from "core/services/websocketService";
|
|
||||||
import { MessageProvider } from "core/context/MessageContext";
|
|
||||||
|
|
||||||
const { defaultAlgorithm, darkAlgorithm } = theme;
|
const { defaultAlgorithm, darkAlgorithm } = theme;
|
||||||
|
|
||||||
|
@ -29,10 +19,10 @@ export default function App() {
|
||||||
const primColor = useSelector(primaryColor);
|
const primColor = useSelector(primaryColor);
|
||||||
|
|
||||||
console.info(
|
console.info(
|
||||||
"\n %c LMS %c v1.0.0 %c \n",
|
'\n %c LMS %c v1.0.0 %c \n',
|
||||||
"background-color: #555;color: #fff;padding: 3px 2px 3px 3px;border-radius: 3px 0 0 3px;font-family: DejaVu Sans,Verdana,Geneva,sans-serif;text-shadow: 0 1px 0 rgba(1, 1, 1, 0.3)",
|
'background-color: #555;color: #fff;padding: 3px 2px 3px 3px;border-radius: 3px 0 0 3px;font-family: DejaVu Sans,Verdana,Geneva,sans-serif;text-shadow: 0 1px 0 rgba(1, 1, 1, 0.3)',
|
||||||
"background-color: #bc81e0;background-image: linear-gradient(90deg, #e67e22, #9b59b6);color: #fff;padding: 3px 3px 3px 2px;border-radius: 0 3px 3px 0;font-family: DejaVu Sans,Verdana,Geneva,sans-serif;text-shadow: 0 1px 0 rgba(1, 1, 1, 0.3)",
|
'background-color: #bc81e0;background-image: linear-gradient(90deg, #e67e22, #9b59b6);color: #fff;padding: 3px 3px 3px 2px;border-radius: 0 3px 3px 0;font-family: DejaVu Sans,Verdana,Geneva,sans-serif;text-shadow: 0 1px 0 rgba(1, 1, 1, 0.3)',
|
||||||
"background-color: transparent"
|
'background-color: transparent'
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -40,14 +30,15 @@ export default function App() {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const response = await myFetch({
|
const response = await myFetch({
|
||||||
url: "/app",
|
url: '/app',
|
||||||
method: "GET",
|
method: 'GET',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response) {
|
if (response) {
|
||||||
dispatch(setPrimaryColor(`#${response.Organization.PrimaryColor}`));
|
dispatch(setPrimaryColor(`#${response.Organization.PrimaryColor}`));
|
||||||
dispatch(setLogoUrl(response.Organization.LogoUrl));
|
dispatch(setLogoUrl(response.Organization.LogoUrl));
|
||||||
dispatch(setBannerUrl(response.Organization.BannerUrl));
|
dispatch(setBannerUrl(response.Organization.BannerUrl));
|
||||||
|
dispatch(setUserProfilePictureUrl(response.User.ProfilePictureUrl));
|
||||||
dispatch(setUserAuthenticated(true));
|
dispatch(setUserAuthenticated(true));
|
||||||
|
|
||||||
webSocketService.connect();
|
webSocketService.connect();
|
||||||
|
@ -63,7 +54,7 @@ export default function App() {
|
||||||
}, [uAuthenticated]);
|
}, [uAuthenticated]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!localStorage.getItem("session")) {
|
if (!localStorage.getItem('session')) {
|
||||||
dispatch(setUserAuthenticated(false));
|
dispatch(setUserAuthenticated(false));
|
||||||
} else {
|
} else {
|
||||||
dispatch(setUserAuthenticated(true));
|
dispatch(setUserAuthenticated(true));
|
||||||
|
@ -71,7 +62,7 @@ export default function App() {
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout style={{ minHeight: "100vh" }}>
|
<Layout style={{ minHeight: '100vh' }}>
|
||||||
<ConfigProvider
|
<ConfigProvider
|
||||||
theme={{
|
theme={{
|
||||||
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
|
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
|
||||||
|
@ -80,15 +71,7 @@ export default function App() {
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MessageProvider>
|
<MessageProvider>{uAuthenticated == null ? <MyCenteredSpin /> : uAuthenticated ? <DashboardLayout /> : <SignIn />}</MessageProvider>
|
||||||
{uAuthenticated == null ? (
|
|
||||||
<MyCenteredSpin />
|
|
||||||
) : uAuthenticated ? (
|
|
||||||
<DashboardLayout />
|
|
||||||
) : (
|
|
||||||
<SignIn />
|
|
||||||
)}
|
|
||||||
</MessageProvider>
|
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,42 +1,28 @@
|
||||||
import { Avatar, Dropdown, Flex } from "antd";
|
import { Avatar, Dropdown, Flex } from 'antd';
|
||||||
import {
|
import { isSideMenuCollapsed, setIsSideMenuCollapsed } from '../SideMenu/sideMenuSlice';
|
||||||
isSideMenuCollapsed,
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
setIsSideMenuCollapsed,
|
import { EditOutlined, EyeOutlined, LeftOutlined, LogoutOutlined, MenuFoldOutlined, MenuUnfoldOutlined, MoonOutlined, SunOutlined, UserOutlined } from '@ant-design/icons';
|
||||||
} from "../SideMenu/sideMenuSlice";
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { darkMode, setDarkMode, setUserAuthenticated } from '../../reducers/appSlice';
|
||||||
import {
|
import styles from './styles.module.css';
|
||||||
EditOutlined,
|
import { Constants } from 'core/utils/utils';
|
||||||
EyeOutlined,
|
import webSocketService from 'core/services/websocketService';
|
||||||
LeftOutlined,
|
import { userProfilePictureUrl } from 'core/reducers/appSlice';
|
||||||
LogoutOutlined,
|
import MyUserAvatar from 'shared/components/MyUserAvatar';
|
||||||
MenuFoldOutlined,
|
|
||||||
MenuUnfoldOutlined,
|
|
||||||
MoonOutlined,
|
|
||||||
SunOutlined,
|
|
||||||
UserOutlined,
|
|
||||||
} from "@ant-design/icons";
|
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
|
||||||
import {
|
|
||||||
darkMode,
|
|
||||||
setDarkMode,
|
|
||||||
setUserAuthenticated,
|
|
||||||
} from "../../reducers/appSlice";
|
|
||||||
import styles from "./styles.module.css";
|
|
||||||
import { Constants } from "core/utils/utils";
|
|
||||||
import webSocketService from "core/services/websocketService";
|
|
||||||
|
|
||||||
type HeaderBarProps = {
|
type HeaderBarProps = {
|
||||||
theme?: "light" | "dark";
|
theme?: 'light' | 'dark';
|
||||||
onView?: () => void;
|
onView?: () => void;
|
||||||
onEdit?: () => void;
|
onEdit?: () => void;
|
||||||
backTo?: string;
|
backTo?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function HeaderBar(props: HeaderBarProps = { theme: "light" }) {
|
export default function HeaderBar(props: HeaderBarProps = { theme: 'light' }) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const isCollpased = useSelector(isSideMenuCollapsed);
|
const isCollpased = useSelector(isSideMenuCollapsed);
|
||||||
const isDarkMode = useSelector(darkMode);
|
const isDarkMode = useSelector(darkMode);
|
||||||
|
const profilePictureUrl = useSelector(userProfilePictureUrl);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
@ -51,26 +37,13 @@ export default function HeaderBar(props: HeaderBarProps = { theme: "light" }) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex align="center" gap={16}>
|
<Flex align="center" gap={16}>
|
||||||
<div
|
<div className={props.theme === 'light' ? styles.containerLight : styles.containerDark} style={{ borderRadius: 28, padding: 4 }}>
|
||||||
className={
|
|
||||||
props.theme === "light"
|
|
||||||
? styles.containerLight
|
|
||||||
: styles.containerDark
|
|
||||||
}
|
|
||||||
style={{ borderRadius: 28, padding: 4 }}
|
|
||||||
>
|
|
||||||
{isCollpased ? (
|
{isCollpased ? (
|
||||||
<div
|
<div className={styles.iconContainer} onClick={() => dispatch(setIsSideMenuCollapsed(false))}>
|
||||||
className={styles.iconContainer}
|
|
||||||
onClick={() => dispatch(setIsSideMenuCollapsed(false))}
|
|
||||||
>
|
|
||||||
<MenuUnfoldOutlined className={styles.icon} />
|
<MenuUnfoldOutlined className={styles.icon} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div className={styles.iconContainer} onClick={() => dispatch(setIsSideMenuCollapsed(true))}>
|
||||||
className={styles.iconContainer}
|
|
||||||
onClick={() => dispatch(setIsSideMenuCollapsed(true))}
|
|
||||||
>
|
|
||||||
<MenuFoldOutlined className={styles.icon} />
|
<MenuFoldOutlined className={styles.icon} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -88,9 +61,7 @@ export default function HeaderBar(props: HeaderBarProps = { theme: "light" }) {
|
||||||
|
|
||||||
<Flex
|
<Flex
|
||||||
align="center"
|
align="center"
|
||||||
className={
|
className={props.theme === 'light' ? styles.containerLight : styles.containerDark}
|
||||||
props.theme === "light" ? styles.containerLight : styles.containerDark
|
|
||||||
}
|
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 28,
|
borderRadius: 28,
|
||||||
paddingLeft: 6,
|
paddingLeft: 6,
|
||||||
|
@ -113,50 +84,42 @@ export default function HeaderBar(props: HeaderBarProps = { theme: "light" }) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isDarkMode ? (
|
{isDarkMode ? (
|
||||||
<div
|
<div className={styles.iconContainer} onClick={() => dispatch(setDarkMode(false))}>
|
||||||
className={styles.iconContainer}
|
|
||||||
onClick={() => dispatch(setDarkMode(false))}
|
|
||||||
>
|
|
||||||
<SunOutlined className={styles.icon} />
|
<SunOutlined className={styles.icon} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div className={styles.iconContainer} onClick={() => dispatch(setDarkMode(true))}>
|
||||||
className={styles.iconContainer}
|
|
||||||
onClick={() => dispatch(setDarkMode(true))}
|
|
||||||
>
|
|
||||||
<MoonOutlined className={styles.icon} />
|
<MoonOutlined className={styles.icon} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Dropdown
|
<Dropdown
|
||||||
overlayStyle={{ minWidth: 150 }}
|
overlayStyle={{ minWidth: 150 }}
|
||||||
trigger={["click"]}
|
trigger={['click']}
|
||||||
menu={{
|
menu={{
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
key: "1",
|
key: '1',
|
||||||
label: "Profile",
|
label: 'Profile',
|
||||||
icon: <UserOutlined />,
|
icon: <UserOutlined />,
|
||||||
onClick: () => navigate(Constants.ROUTE_PATHS.ACCOUNT_SETTINGS),
|
onClick: () => navigate(Constants.ROUTE_PATHS.ACCOUNT_SETTINGS),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "2",
|
key: '2',
|
||||||
label: "Logout",
|
label: 'Logout',
|
||||||
icon: <LogoutOutlined />,
|
icon: <LogoutOutlined />,
|
||||||
danger: true,
|
danger: true,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
webSocketService.disconnect();
|
webSocketService.disconnect();
|
||||||
window.localStorage.removeItem("session");
|
window.localStorage.removeItem('session');
|
||||||
window.location.href = "/";
|
window.location.href = '/';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar
|
<div>
|
||||||
size="default"
|
<MyUserAvatar size={34} profilePictureUrl={profilePictureUrl ? profilePictureUrl : ''} />
|
||||||
icon={<UserOutlined />}
|
</div>
|
||||||
style={{ cursor: "pointer" }}
|
|
||||||
/>
|
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
export const appSlice = createSlice({
|
export const appSlice = createSlice({
|
||||||
name: "app",
|
name: 'app',
|
||||||
initialState: {
|
initialState: {
|
||||||
darkMode: false,
|
darkMode: false,
|
||||||
userAuthenticated: null,
|
userAuthenticated: null,
|
||||||
primaryColor: "#111",
|
userProfilePictureUrl: null,
|
||||||
|
primaryColor: '#111',
|
||||||
logoUrl: null,
|
logoUrl: null,
|
||||||
bannerUrl: null,
|
bannerUrl: null,
|
||||||
},
|
},
|
||||||
|
@ -16,6 +17,9 @@ export const appSlice = createSlice({
|
||||||
setUserAuthenticated: (state, action) => {
|
setUserAuthenticated: (state, action) => {
|
||||||
state.userAuthenticated = action.payload;
|
state.userAuthenticated = action.payload;
|
||||||
},
|
},
|
||||||
|
setUserProfilePictureUrl: (state, action) => {
|
||||||
|
state.userProfilePictureUrl = action.payload;
|
||||||
|
},
|
||||||
setPrimaryColor: (state, action) => {
|
setPrimaryColor: (state, action) => {
|
||||||
state.primaryColor = action.payload;
|
state.primaryColor = action.payload;
|
||||||
},
|
},
|
||||||
|
@ -29,19 +33,13 @@ export const appSlice = createSlice({
|
||||||
selectors: {
|
selectors: {
|
||||||
darkMode: (state) => state.darkMode,
|
darkMode: (state) => state.darkMode,
|
||||||
userAuthenticated: (state) => state.userAuthenticated,
|
userAuthenticated: (state) => state.userAuthenticated,
|
||||||
|
userProfilePictureUrl: (state) => state.userProfilePictureUrl,
|
||||||
primaryColor: (state) => state.primaryColor,
|
primaryColor: (state) => state.primaryColor,
|
||||||
logoUrl: (state) => state.logoUrl,
|
logoUrl: (state) => state.logoUrl,
|
||||||
bannerUrl: (state) => state.bannerUrl,
|
bannerUrl: (state) => state.bannerUrl,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const {
|
export const { setDarkMode, setUserAuthenticated, setUserProfilePictureUrl, setPrimaryColor, setLogoUrl, setBannerUrl } = appSlice.actions;
|
||||||
setDarkMode,
|
|
||||||
setUserAuthenticated,
|
|
||||||
setPrimaryColor,
|
|
||||||
setLogoUrl,
|
|
||||||
setBannerUrl,
|
|
||||||
} = appSlice.actions;
|
|
||||||
|
|
||||||
export const { darkMode, userAuthenticated, primaryColor, logoUrl, bannerUrl } =
|
export const { darkMode, userAuthenticated, userProfilePictureUrl, primaryColor, logoUrl, bannerUrl } = appSlice.selectors;
|
||||||
appSlice.selectors;
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { baseQueryWithErrorHandling } from "core/helper/api";
|
||||||
import {
|
import {
|
||||||
Lesson,
|
Lesson,
|
||||||
LessonContent,
|
LessonContent,
|
||||||
|
LessonQuestion,
|
||||||
LessonSettings,
|
LessonSettings,
|
||||||
UpdateLessonPreviewThumbnail,
|
UpdateLessonPreviewThumbnail,
|
||||||
} from "core/types/lesson";
|
} from "core/types/lesson";
|
||||||
|
@ -93,6 +94,12 @@ export const lessonsApi = createApi({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
getQuestions: builder.query<LessonQuestion[], string>({
|
||||||
|
query: (lessonId) => ({
|
||||||
|
url: `lessons/${lessonId}/questions`,
|
||||||
|
method: "GET",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,9 @@
|
||||||
import { Dispatch } from "@reduxjs/toolkit";
|
import { Dispatch } from '@reduxjs/toolkit';
|
||||||
import {
|
import { setBannerUrl, setLogoUrl, setPrimaryColor, setUserProfilePictureUrl } from 'core/reducers/appSlice';
|
||||||
setBannerUrl,
|
import { store } from 'core/store/store';
|
||||||
setLogoUrl,
|
import { BrowserTabSession, Constants } from 'core/utils/utils';
|
||||||
setPrimaryColor,
|
import { WebSocketReceivedMessagesCmds } from 'core/utils/webSocket';
|
||||||
} from "core/reducers/appSlice";
|
import { addLessonPageContent, deleteLessonPageContent, updateLessonPageContent, updateLessonPageContentPosition } from 'features/Lessons/LessonPage/lessonPageSlice';
|
||||||
import { store } from "core/store/store";
|
|
||||||
import { BrowserTabSession, Constants } from "core/utils/utils";
|
|
||||||
import { WebSocketReceivedMessagesCmds } from "core/utils/webSocket";
|
|
||||||
import {
|
|
||||||
addLessonPageContent,
|
|
||||||
deleteLessonPageContent,
|
|
||||||
updateLessonPageContent,
|
|
||||||
updateLessonPageContentPosition,
|
|
||||||
} from "features/Lessons/LessonPage/lessonPageSlice";
|
|
||||||
import {
|
import {
|
||||||
addLessonContent,
|
addLessonContent,
|
||||||
deleteLessonContent,
|
deleteLessonContent,
|
||||||
|
@ -21,19 +12,10 @@ import {
|
||||||
setPageEditorLessonState,
|
setPageEditorLessonState,
|
||||||
updateLessonContent,
|
updateLessonContent,
|
||||||
updateLessonContentPosition,
|
updateLessonContentPosition,
|
||||||
} from "features/Lessons/LessonPageEditor/lessonPageEditorSlice";
|
} from 'features/Lessons/LessonPageEditor/lessonPageEditorSlice';
|
||||||
import {
|
import { addLesson, updateLessonPreviewThumbnail, updateLessonPreviewTitle, updateLessonState } from 'features/Lessons/lessonsSlice';
|
||||||
addLesson,
|
import { addTeamMember, deleteTeamMember, updateTeamMemberRole } from 'features/Team/teamSlice';
|
||||||
updateLessonPreviewThumbnail,
|
import { setProfilePictureUrl } from 'features/UserProfile/userProfileSlice';
|
||||||
updateLessonPreviewTitle,
|
|
||||||
updateLessonState,
|
|
||||||
} from "features/Lessons/lessonsSlice";
|
|
||||||
import {
|
|
||||||
addTeamMember,
|
|
||||||
deleteTeamMember,
|
|
||||||
updateTeamMemberRole,
|
|
||||||
} from "features/Team/teamSlice";
|
|
||||||
import { setProfilePictureUrl } from "features/UserProfile/userProfileSlice";
|
|
||||||
|
|
||||||
interface WebSocketMessage {
|
interface WebSocketMessage {
|
||||||
Cmd: number;
|
Cmd: number;
|
||||||
|
@ -47,9 +29,7 @@ class WebSocketService {
|
||||||
private offlineQueue: WebSocketMessage[] = [];
|
private offlineQueue: WebSocketMessage[] = [];
|
||||||
private firstConnect: boolean = true;
|
private firstConnect: boolean = true;
|
||||||
|
|
||||||
private messageHandler:
|
private messageHandler: ((message: WebSocketMessage, dispatch: Dispatch) => void) | null = null;
|
||||||
| ((message: WebSocketMessage, dispatch: Dispatch) => void)
|
|
||||||
| null = null;
|
|
||||||
|
|
||||||
constructor(url: string) {
|
constructor(url: string) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
|
@ -58,14 +38,10 @@ class WebSocketService {
|
||||||
private dispatch: Dispatch | null = null;
|
private dispatch: Dispatch | null = null;
|
||||||
|
|
||||||
public connect(): void {
|
public connect(): void {
|
||||||
this.socket = new WebSocket(
|
this.socket = new WebSocket(`${this.url}?auth=${localStorage.getItem('session')}&bts=${BrowserTabSession}`);
|
||||||
`${this.url}?auth=${localStorage.getItem(
|
|
||||||
"session"
|
|
||||||
)}&bts=${BrowserTabSession}`
|
|
||||||
);
|
|
||||||
|
|
||||||
this.socket.onopen = () => {
|
this.socket.onopen = () => {
|
||||||
console.log("WebSocket connected", this.firstConnect);
|
console.log('WebSocket connected', this.firstConnect);
|
||||||
|
|
||||||
// Send all messages from the offline queue
|
// Send all messages from the offline queue
|
||||||
|
|
||||||
|
@ -87,24 +63,21 @@ class WebSocketService {
|
||||||
if (this.messageHandler) {
|
if (this.messageHandler) {
|
||||||
this.messageHandler(data, this.dispatch!);
|
this.messageHandler(data, this.dispatch!);
|
||||||
} else {
|
} else {
|
||||||
console.error("No handler defined for WebSocket messages");
|
console.error('No handler defined for WebSocket messages');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.socket.onclose = () => {
|
this.socket.onclose = () => {
|
||||||
console.log("WebSocket disconnected. Reconnecting...");
|
console.log('WebSocket disconnected. Reconnecting...');
|
||||||
setTimeout(() => this.connect(), this.reconnectInterval);
|
setTimeout(() => this.connect(), this.reconnectInterval);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.socket.onerror = (error: Event) => {
|
this.socket.onerror = (error: Event) => {
|
||||||
console.error("WebSocket error:", error);
|
console.error('WebSocket error:', error);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public setHandler(
|
public setHandler(handler: (message: WebSocketMessage, dispatch: Dispatch) => void, dispatch: Dispatch): void {
|
||||||
handler: (message: WebSocketMessage, dispatch: Dispatch) => void,
|
|
||||||
dispatch: Dispatch
|
|
||||||
): void {
|
|
||||||
this.messageHandler = handler;
|
this.messageHandler = handler;
|
||||||
this.dispatch = dispatch;
|
this.dispatch = dispatch;
|
||||||
}
|
}
|
||||||
|
@ -129,10 +102,10 @@ class WebSocketService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const webSocketConnectionEventName = "WebSocketConnectionEvent";
|
const webSocketConnectionEventName = 'WebSocketConnectionEvent';
|
||||||
|
|
||||||
const webSocketConnectionEvent = new CustomEvent(webSocketConnectionEventName, {
|
const webSocketConnectionEvent = new CustomEvent(webSocketConnectionEventName, {
|
||||||
detail: "wsReconnect",
|
detail: 'wsReconnect',
|
||||||
});
|
});
|
||||||
|
|
||||||
export function addWebSocketReconnectListener(callback: () => void): void {
|
export function addWebSocketReconnectListener(callback: () => void): void {
|
||||||
|
@ -146,13 +119,10 @@ export function removeWebSocketReconnectListener(callback: () => void): void {
|
||||||
const webSocketService = new WebSocketService(Constants.WS_ADDRESS);
|
const webSocketService = new WebSocketService(Constants.WS_ADDRESS);
|
||||||
export default webSocketService;
|
export default webSocketService;
|
||||||
|
|
||||||
export function WebSocketMessageHandler(
|
export function WebSocketMessageHandler(message: WebSocketMessage, dispatch: Dispatch) {
|
||||||
message: WebSocketMessage,
|
|
||||||
dispatch: Dispatch
|
|
||||||
) {
|
|
||||||
const { Cmd, Body } = message;
|
const { Cmd, Body } = message;
|
||||||
|
|
||||||
console.log("WebSocketMessageHandler", Cmd, Body);
|
console.log('WebSocketMessageHandler', Cmd, Body);
|
||||||
|
|
||||||
switch (Cmd) {
|
switch (Cmd) {
|
||||||
case WebSocketReceivedMessagesCmds.SettingsUpdated:
|
case WebSocketReceivedMessagesCmds.SettingsUpdated:
|
||||||
|
@ -165,11 +135,9 @@ export function WebSocketMessageHandler(
|
||||||
dispatch(setBannerUrl(Body));
|
dispatch(setBannerUrl(Body));
|
||||||
break;
|
break;
|
||||||
case WebSocketReceivedMessagesCmds.SettingsUpdatedSubdomain:
|
case WebSocketReceivedMessagesCmds.SettingsUpdatedSubdomain:
|
||||||
localStorage.removeItem("session");
|
localStorage.removeItem('session');
|
||||||
|
|
||||||
window.location.href = `${
|
window.location.href = `${window.location.protocol}//${Body}.${window.location.hostname.split('.').slice(1).join('.')}`;
|
||||||
window.location.protocol
|
|
||||||
}//${Body}.${window.location.hostname.split(".").slice(1).join(".")}`;
|
|
||||||
break;
|
break;
|
||||||
case WebSocketReceivedMessagesCmds.TeamAddedMember:
|
case WebSocketReceivedMessagesCmds.TeamAddedMember:
|
||||||
dispatch(addTeamMember(Body));
|
dispatch(addTeamMember(Body));
|
||||||
|
@ -279,8 +247,9 @@ export function WebSocketMessageHandler(
|
||||||
break;
|
break;
|
||||||
case WebSocketReceivedMessagesCmds.UserProfilePictureUpdated:
|
case WebSocketReceivedMessagesCmds.UserProfilePictureUpdated:
|
||||||
dispatch(setProfilePictureUrl(Body));
|
dispatch(setProfilePictureUrl(Body));
|
||||||
|
dispatch(setUserProfilePictureUrl(Body));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.error("Unknown message type:", Cmd);
|
console.error('Unknown message type:', Cmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ export interface TeamMember {
|
||||||
LastName: string;
|
LastName: string;
|
||||||
Email: string;
|
Email: string;
|
||||||
RoleId: string;
|
RoleId: string;
|
||||||
|
ProfilePictureUrl: string;
|
||||||
Online: boolean;
|
Online: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from "react";
|
||||||
import { FloatButton } from "antd";
|
import { FloatButton } from "antd";
|
||||||
import { CommentOutlined } from "@ant-design/icons";
|
import { CommentOutlined } from "@ant-design/icons";
|
||||||
import { DeepChat } from "deep-chat-react";
|
import { DeepChat } from "deep-chat-react";
|
||||||
|
import { getUserSessionFromLocalStorage } from "core/utils/utils";
|
||||||
|
|
||||||
function AiChat() {
|
function AiChat() {
|
||||||
const [visible, setVisible] = React.useState(false);
|
const [visible, setVisible] = React.useState(false);
|
||||||
|
@ -30,20 +31,17 @@ function AiChat() {
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
}}
|
}}
|
||||||
history={[
|
history={[{ text: "Stell mir Fragen :)", role: "ai" }]}
|
||||||
{ text: "Show me a modern city", role: "user" },
|
connect={{
|
||||||
{
|
url: "/api/chat/v1/prompt/",
|
||||||
files: [
|
method: "POST",
|
||||||
{
|
headers: {
|
||||||
src: "https://test.ex.umbach.dev/api/statico/809fe37e-8c41-4a44-98d1-d9247affd531/67c763b6-ea67-4b49-9621-2f78b85eb180.png",
|
"X-Authorization": getUserSessionFromLocalStorage() || "",
|
||||||
type: "image",
|
|
||||||
},
|
},
|
||||||
],
|
}}
|
||||||
role: "ai",
|
onMessage={async (message) => {
|
||||||
},
|
console.log("onMessagee", message);
|
||||||
{ text: "Whats on your mind?", role: "user" },
|
}}
|
||||||
{ text: "Peace and tranquility", role: "ai" },
|
|
||||||
]}
|
|
||||||
></DeepChat>
|
></DeepChat>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
@ -156,7 +156,7 @@ function GeneralCard({
|
||||||
}
|
}
|
||||||
|
|
||||||
debounceRef.current = setTimeout(() => {
|
debounceRef.current = setTimeout(() => {
|
||||||
dispatch(setPrimaryColor(color.toHexString()));
|
dispatch(setPrimaryColor(color.toHexString().split("#")[1]));
|
||||||
}, 600);
|
}, 600);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -218,12 +218,9 @@ function MediaCard({
|
||||||
<MyUpload
|
<MyUpload
|
||||||
action="/organization/file/banner"
|
action="/organization/file/banner"
|
||||||
onChange={(info) => {
|
onChange={(info) => {
|
||||||
console.log("Banner updated1!", info.file.status);
|
|
||||||
if (info.file.status === "done" && info.file.response.Data) {
|
if (info.file.status === "done" && info.file.response.Data) {
|
||||||
dispatch(setBannerUrl(info.file.response.Data));
|
dispatch(setBannerUrl(info.file.response.Data));
|
||||||
|
|
||||||
console.log("Banner updated!");
|
|
||||||
|
|
||||||
success("Banner updated successfully!");
|
success("Banner updated successfully!");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -337,14 +334,7 @@ function SubdomainCard({
|
||||||
</p>
|
</p>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Form
|
<Form form={form} layout="vertical" requiredMark={false}>
|
||||||
form={form}
|
|
||||||
layout="vertical"
|
|
||||||
requiredMark={false}
|
|
||||||
onFinish={(values) => {
|
|
||||||
console.log(values);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MyMiddleCard
|
<MyMiddleCard
|
||||||
title="Subdomain"
|
title="Subdomain"
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
removeWebSocketReconnectListener,
|
removeWebSocketReconnectListener,
|
||||||
} from "core/services/websocketService";
|
} from "core/services/websocketService";
|
||||||
import { useMessage } from "core/context/MessageContext";
|
import { useMessage } from "core/context/MessageContext";
|
||||||
|
import MyUserAvatar from "shared/components/MyUserAvatar";
|
||||||
|
|
||||||
const TeamList: React.FC = () => {
|
const TeamList: React.FC = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
@ -146,7 +147,17 @@ const TeamList: React.FC = () => {
|
||||||
dataTeamMembers.forEach((item) => {
|
dataTeamMembers.forEach((item) => {
|
||||||
items.push({
|
items.push({
|
||||||
key: item.Id,
|
key: item.Id,
|
||||||
firstName: item.FirstName,
|
firstName: (
|
||||||
|
<Space>
|
||||||
|
<MyUserAvatar
|
||||||
|
size={42}
|
||||||
|
profilePictureUrl={item.ProfilePictureUrl}
|
||||||
|
firstName={item.FirstName}
|
||||||
|
disableCursorPointer
|
||||||
|
/>
|
||||||
|
<span>{item.FirstName}</span>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
lastName: item.LastName,
|
lastName: item.LastName,
|
||||||
email: item.Email,
|
email: item.Email,
|
||||||
role: tmpRoleNames[item.RoleId],
|
role: tmpRoleNames[item.RoleId],
|
||||||
|
|
|
@ -35,6 +35,8 @@ import { tmpRoleNames } from "features/Roles";
|
||||||
import MyErrorResult from "shared/components/MyResult";
|
import MyErrorResult from "shared/components/MyResult";
|
||||||
import MyUpload from "shared/components/MyUpload";
|
import MyUpload from "shared/components/MyUpload";
|
||||||
import { useMessage } from "core/context/MessageContext";
|
import { useMessage } from "core/context/MessageContext";
|
||||||
|
import MyUserAvatar from "shared/components/MyUserAvatar";
|
||||||
|
import { setUserProfilePictureUrl } from "core/reducers/appSlice";
|
||||||
|
|
||||||
export default function UserProfile({ isAdmin }: { isAdmin?: boolean }) {
|
export default function UserProfile({ isAdmin }: { isAdmin?: boolean }) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
@ -122,6 +124,9 @@ export default function UserProfile({ isAdmin }: { isAdmin?: boolean }) {
|
||||||
success("Profile picture updated successfully");
|
success("Profile picture updated successfully");
|
||||||
|
|
||||||
dispatch(setProfilePictureUrl(info.file.response.Data));
|
dispatch(setProfilePictureUrl(info.file.response.Data));
|
||||||
|
dispatch(
|
||||||
|
setUserProfilePictureUrl(info.file.response.Data)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
imgCropProps={{
|
imgCropProps={{
|
||||||
|
@ -129,16 +134,7 @@ export default function UserProfile({ isAdmin }: { isAdmin?: boolean }) {
|
||||||
children: <></>,
|
children: <></>,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{dataProfilePictureUrl === "" ? (
|
<MyUserAvatar profilePictureUrl={dataProfilePictureUrl} />
|
||||||
<Avatar size={56} style={{ backgroundColor: "#1677ff" }}>
|
|
||||||
{dataFirstName.charAt(0).toUpperCase()}
|
|
||||||
</Avatar>
|
|
||||||
) : (
|
|
||||||
<Avatar
|
|
||||||
src={`${Constants.STATIC_CONTENT_ADDRESS}/${dataProfilePictureUrl}`}
|
|
||||||
size={56}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</MyUpload>
|
</MyUpload>
|
||||||
}
|
}
|
||||||
title={`${dataFirstName} ${dataLastName}`}
|
title={`${dataFirstName} ${dataLastName}`}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { Avatar } from 'antd';
|
||||||
|
import { Constants } from 'core/utils/utils';
|
||||||
|
import { UserOutlined } from '@ant-design/icons';
|
||||||
|
import { primaryColor } from 'core/reducers/appSlice';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
interface MyUserAvatarProps {
|
||||||
|
size?: number;
|
||||||
|
firstName?: string;
|
||||||
|
profilePictureUrl: string;
|
||||||
|
disableCursorPointer?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MyUserAvatar: React.FC<MyUserAvatarProps> = ({ size = 56, firstName, profilePictureUrl, disableCursorPointer }) => {
|
||||||
|
const appPrimaryColor = useSelector(primaryColor);
|
||||||
|
|
||||||
|
const defaultStyle = disableCursorPointer === undefined ? { cursor: 'pointer' } : {};
|
||||||
|
|
||||||
|
const isProfilePictureEmpty = profilePictureUrl === '';
|
||||||
|
const avatarContent = isProfilePictureEmpty && firstName !== undefined ? firstName.charAt(0) : undefined;
|
||||||
|
const iconContent = isProfilePictureEmpty && firstName === undefined ? <UserOutlined /> : undefined;
|
||||||
|
const avatarSrc = isProfilePictureEmpty ? undefined : `${Constants.STATIC_CONTENT_ADDRESS}/${profilePictureUrl}`;
|
||||||
|
const avatarStyle = isProfilePictureEmpty ? { ...defaultStyle, backgroundColor: `#${appPrimaryColor}` } : defaultStyle;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ userSelect: 'none' }}>
|
||||||
|
<Avatar size={size} style={avatarStyle} src={avatarSrc} icon={iconContent}>
|
||||||
|
{avatarContent}
|
||||||
|
</Avatar>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MyUserAvatar;
|
Loading…
Reference in New Issue