user profile and AI chat
parent
bb1bf8ef0e
commit
679ee5bf28
151
src/App.tsx
151
src/App.tsx
|
@ -1,95 +1,78 @@
|
|||
import { ConfigProvider, Layout, theme } from "antd";
|
||||
import DashboardLayout from "./core/components/DashboardLayout";
|
||||
import {
|
||||
darkMode,
|
||||
primaryColor,
|
||||
setBannerUrl,
|
||||
setLogoUrl,
|
||||
setPrimaryColor,
|
||||
setUserAuthenticated,
|
||||
userAuthenticated,
|
||||
} 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";
|
||||
import { ConfigProvider, Layout, theme } from 'antd';
|
||||
import DashboardLayout from './core/components/DashboardLayout';
|
||||
import { darkMode, primaryColor, setBannerUrl, setLogoUrl, setPrimaryColor, setUserAuthenticated, userAuthenticated, setUserProfilePictureUrl } 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;
|
||||
|
||||
export default function App() {
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isDarkMode = useSelector(darkMode);
|
||||
const uAuthenticated = useSelector(userAuthenticated);
|
||||
const primColor = useSelector(primaryColor);
|
||||
const isDarkMode = useSelector(darkMode);
|
||||
const uAuthenticated = useSelector(userAuthenticated);
|
||||
const primColor = useSelector(primaryColor);
|
||||
|
||||
console.info(
|
||||
"\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: #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"
|
||||
);
|
||||
console.info(
|
||||
'\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: #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'
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (uAuthenticated) {
|
||||
(async () => {
|
||||
try {
|
||||
const response = await myFetch({
|
||||
url: "/app",
|
||||
method: "GET",
|
||||
});
|
||||
useEffect(() => {
|
||||
if (uAuthenticated) {
|
||||
(async () => {
|
||||
try {
|
||||
const response = await myFetch({
|
||||
url: '/app',
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
if (response) {
|
||||
dispatch(setPrimaryColor(`#${response.Organization.PrimaryColor}`));
|
||||
dispatch(setLogoUrl(response.Organization.LogoUrl));
|
||||
dispatch(setBannerUrl(response.Organization.BannerUrl));
|
||||
if (response) {
|
||||
dispatch(setPrimaryColor(`#${response.Organization.PrimaryColor}`));
|
||||
dispatch(setLogoUrl(response.Organization.LogoUrl));
|
||||
dispatch(setBannerUrl(response.Organization.BannerUrl));
|
||||
dispatch(setUserProfilePictureUrl(response.User.ProfilePictureUrl));
|
||||
dispatch(setUserAuthenticated(true));
|
||||
|
||||
webSocketService.connect();
|
||||
webSocketService.setHandler(WebSocketMessageHandler, dispatch);
|
||||
}
|
||||
} catch (error) {}
|
||||
})();
|
||||
|
||||
return () => {
|
||||
webSocketService.disconnect();
|
||||
};
|
||||
}
|
||||
}, [uAuthenticated]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!localStorage.getItem('session')) {
|
||||
dispatch(setUserAuthenticated(false));
|
||||
} else {
|
||||
dispatch(setUserAuthenticated(true));
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
webSocketService.connect();
|
||||
webSocketService.setHandler(WebSocketMessageHandler, dispatch);
|
||||
}
|
||||
} catch (error) {}
|
||||
})();
|
||||
|
||||
return () => {
|
||||
webSocketService.disconnect();
|
||||
};
|
||||
}
|
||||
}, [uAuthenticated]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!localStorage.getItem("session")) {
|
||||
dispatch(setUserAuthenticated(false));
|
||||
} else {
|
||||
dispatch(setUserAuthenticated(true));
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Layout style={{ minHeight: "100vh" }}>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
|
||||
token: {
|
||||
colorPrimary: primColor,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MessageProvider>
|
||||
{uAuthenticated == null ? (
|
||||
<MyCenteredSpin />
|
||||
) : uAuthenticated ? (
|
||||
<DashboardLayout />
|
||||
) : (
|
||||
<SignIn />
|
||||
)}
|
||||
</MessageProvider>
|
||||
</ConfigProvider>
|
||||
</Layout>
|
||||
);
|
||||
return (
|
||||
<Layout style={{ minHeight: '100vh' }}>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
|
||||
token: {
|
||||
colorPrimary: primColor,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MessageProvider>{uAuthenticated == null ? <MyCenteredSpin /> : uAuthenticated ? <DashboardLayout /> : <SignIn />}</MessageProvider>
|
||||
</ConfigProvider>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,168 +1,131 @@
|
|||
import { Avatar, Dropdown, Flex } from "antd";
|
||||
import {
|
||||
isSideMenuCollapsed,
|
||||
setIsSideMenuCollapsed,
|
||||
} from "../SideMenu/sideMenuSlice";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
EditOutlined,
|
||||
EyeOutlined,
|
||||
LeftOutlined,
|
||||
LogoutOutlined,
|
||||
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";
|
||||
import { Avatar, Dropdown, Flex } from 'antd';
|
||||
import { isSideMenuCollapsed, setIsSideMenuCollapsed } from '../SideMenu/sideMenuSlice';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { EditOutlined, EyeOutlined, LeftOutlined, LogoutOutlined, 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';
|
||||
import { userProfilePictureUrl } from 'core/reducers/appSlice';
|
||||
import MyUserAvatar from 'shared/components/MyUserAvatar';
|
||||
|
||||
type HeaderBarProps = {
|
||||
theme?: "light" | "dark";
|
||||
onView?: () => void;
|
||||
onEdit?: () => void;
|
||||
backTo?: string;
|
||||
theme?: 'light' | 'dark';
|
||||
onView?: () => void;
|
||||
onEdit?: () => void;
|
||||
backTo?: string;
|
||||
};
|
||||
|
||||
export default function HeaderBar(props: HeaderBarProps = { theme: "light" }) {
|
||||
const dispatch = useDispatch();
|
||||
export default function HeaderBar(props: HeaderBarProps = { theme: 'light' }) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isCollpased = useSelector(isSideMenuCollapsed);
|
||||
const isDarkMode = useSelector(darkMode);
|
||||
const isCollpased = useSelector(isSideMenuCollapsed);
|
||||
const isDarkMode = useSelector(darkMode);
|
||||
const profilePictureUrl = useSelector(userProfilePictureUrl);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
justify="space-between"
|
||||
align="center"
|
||||
style={{
|
||||
paddingTop: 12,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
}}
|
||||
>
|
||||
<Flex align="center" gap={16}>
|
||||
<div
|
||||
className={
|
||||
props.theme === "light"
|
||||
? styles.containerLight
|
||||
: styles.containerDark
|
||||
}
|
||||
style={{ borderRadius: 28, padding: 4 }}
|
||||
return (
|
||||
<Flex
|
||||
justify="space-between"
|
||||
align="center"
|
||||
style={{
|
||||
paddingTop: 12,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
}}
|
||||
>
|
||||
{isCollpased ? (
|
||||
<div
|
||||
className={styles.iconContainer}
|
||||
onClick={() => dispatch(setIsSideMenuCollapsed(false))}
|
||||
>
|
||||
<MenuUnfoldOutlined className={styles.icon} />
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={styles.iconContainer}
|
||||
onClick={() => dispatch(setIsSideMenuCollapsed(true))}
|
||||
>
|
||||
<MenuFoldOutlined className={styles.icon} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Flex align="center" gap={16}>
|
||||
<div className={props.theme === 'light' ? styles.containerLight : styles.containerDark} style={{ borderRadius: 28, padding: 4 }}>
|
||||
{isCollpased ? (
|
||||
<div className={styles.iconContainer} onClick={() => dispatch(setIsSideMenuCollapsed(false))}>
|
||||
<MenuUnfoldOutlined className={styles.icon} />
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.iconContainer} onClick={() => dispatch(setIsSideMenuCollapsed(true))}>
|
||||
<MenuFoldOutlined className={styles.icon} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{props.backTo && (
|
||||
<Link to={props.backTo}>
|
||||
<Flex gap={4}>
|
||||
<LeftOutlined />
|
||||
<span>Back</span>
|
||||
{props.backTo && (
|
||||
<Link to={props.backTo}>
|
||||
<Flex gap={4}>
|
||||
<LeftOutlined />
|
||||
<span>Back</span>
|
||||
</Flex>
|
||||
</Link>
|
||||
)}
|
||||
</Flex>
|
||||
</Link>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
align="center"
|
||||
className={
|
||||
props.theme === "light" ? styles.containerLight : styles.containerDark
|
||||
}
|
||||
style={{
|
||||
borderRadius: 28,
|
||||
paddingLeft: 6,
|
||||
paddingRight: 6,
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
}}
|
||||
gap={8}
|
||||
>
|
||||
{props.onView && (
|
||||
<div className={styles.iconContainer} onClick={props.onView}>
|
||||
<EyeOutlined className={styles.icon} />
|
||||
</div>
|
||||
)}
|
||||
<Flex
|
||||
align="center"
|
||||
className={props.theme === 'light' ? styles.containerLight : styles.containerDark}
|
||||
style={{
|
||||
borderRadius: 28,
|
||||
paddingLeft: 6,
|
||||
paddingRight: 6,
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
}}
|
||||
gap={8}
|
||||
>
|
||||
{props.onView && (
|
||||
<div className={styles.iconContainer} onClick={props.onView}>
|
||||
<EyeOutlined className={styles.icon} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{props.onEdit && (
|
||||
<div className={styles.iconContainer} onClick={props.onEdit}>
|
||||
<EditOutlined className={styles.icon} />
|
||||
</div>
|
||||
)}
|
||||
{props.onEdit && (
|
||||
<div className={styles.iconContainer} onClick={props.onEdit}>
|
||||
<EditOutlined className={styles.icon} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isDarkMode ? (
|
||||
<div
|
||||
className={styles.iconContainer}
|
||||
onClick={() => dispatch(setDarkMode(false))}
|
||||
>
|
||||
<SunOutlined className={styles.icon} />
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={styles.iconContainer}
|
||||
onClick={() => dispatch(setDarkMode(true))}
|
||||
>
|
||||
<MoonOutlined className={styles.icon} />
|
||||
</div>
|
||||
)}
|
||||
<Dropdown
|
||||
overlayStyle={{ minWidth: 150 }}
|
||||
trigger={["click"]}
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: "1",
|
||||
label: "Profile",
|
||||
icon: <UserOutlined />,
|
||||
onClick: () => navigate(Constants.ROUTE_PATHS.ACCOUNT_SETTINGS),
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
label: "Logout",
|
||||
icon: <LogoutOutlined />,
|
||||
danger: true,
|
||||
onClick: () => {
|
||||
webSocketService.disconnect();
|
||||
window.localStorage.removeItem("session");
|
||||
window.location.href = "/";
|
||||
},
|
||||
},
|
||||
],
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
size="default"
|
||||
icon={<UserOutlined />}
|
||||
style={{ cursor: "pointer" }}
|
||||
/>
|
||||
</Dropdown>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
{isDarkMode ? (
|
||||
<div className={styles.iconContainer} onClick={() => dispatch(setDarkMode(false))}>
|
||||
<SunOutlined className={styles.icon} />
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.iconContainer} onClick={() => dispatch(setDarkMode(true))}>
|
||||
<MoonOutlined className={styles.icon} />
|
||||
</div>
|
||||
)}
|
||||
<Dropdown
|
||||
overlayStyle={{ minWidth: 150 }}
|
||||
trigger={['click']}
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: '1',
|
||||
label: 'Profile',
|
||||
icon: <UserOutlined />,
|
||||
onClick: () => navigate(Constants.ROUTE_PATHS.ACCOUNT_SETTINGS),
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: 'Logout',
|
||||
icon: <LogoutOutlined />,
|
||||
danger: true,
|
||||
onClick: () => {
|
||||
webSocketService.disconnect();
|
||||
window.localStorage.removeItem('session');
|
||||
window.location.href = '/';
|
||||
},
|
||||
},
|
||||
],
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<MyUserAvatar size={34} profilePictureUrl={profilePictureUrl ? profilePictureUrl : ''} />
|
||||
</div>
|
||||
</Dropdown>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
/* return (
|
||||
/* return (
|
||||
<Header
|
||||
style={{
|
||||
position: "sticky",
|
||||
|
|
|
@ -1,47 +1,45 @@
|
|||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
export const appSlice = createSlice({
|
||||
name: "app",
|
||||
initialState: {
|
||||
darkMode: false,
|
||||
userAuthenticated: null,
|
||||
primaryColor: "#111",
|
||||
logoUrl: null,
|
||||
bannerUrl: null,
|
||||
},
|
||||
reducers: {
|
||||
setDarkMode: (state, action) => {
|
||||
state.darkMode = action.payload;
|
||||
name: 'app',
|
||||
initialState: {
|
||||
darkMode: false,
|
||||
userAuthenticated: null,
|
||||
userProfilePictureUrl: null,
|
||||
primaryColor: '#111',
|
||||
logoUrl: null,
|
||||
bannerUrl: null,
|
||||
},
|
||||
setUserAuthenticated: (state, action) => {
|
||||
state.userAuthenticated = action.payload;
|
||||
reducers: {
|
||||
setDarkMode: (state, action) => {
|
||||
state.darkMode = action.payload;
|
||||
},
|
||||
setUserAuthenticated: (state, action) => {
|
||||
state.userAuthenticated = action.payload;
|
||||
},
|
||||
setUserProfilePictureUrl: (state, action) => {
|
||||
state.userProfilePictureUrl = action.payload;
|
||||
},
|
||||
setPrimaryColor: (state, action) => {
|
||||
state.primaryColor = action.payload;
|
||||
},
|
||||
setLogoUrl: (state, action) => {
|
||||
state.logoUrl = action.payload;
|
||||
},
|
||||
setBannerUrl: (state, action) => {
|
||||
state.bannerUrl = action.payload;
|
||||
},
|
||||
},
|
||||
setPrimaryColor: (state, action) => {
|
||||
state.primaryColor = action.payload;
|
||||
selectors: {
|
||||
darkMode: (state) => state.darkMode,
|
||||
userAuthenticated: (state) => state.userAuthenticated,
|
||||
userProfilePictureUrl: (state) => state.userProfilePictureUrl,
|
||||
primaryColor: (state) => state.primaryColor,
|
||||
logoUrl: (state) => state.logoUrl,
|
||||
bannerUrl: (state) => state.bannerUrl,
|
||||
},
|
||||
setLogoUrl: (state, action) => {
|
||||
state.logoUrl = action.payload;
|
||||
},
|
||||
setBannerUrl: (state, action) => {
|
||||
state.bannerUrl = action.payload;
|
||||
},
|
||||
},
|
||||
selectors: {
|
||||
darkMode: (state) => state.darkMode,
|
||||
userAuthenticated: (state) => state.userAuthenticated,
|
||||
primaryColor: (state) => state.primaryColor,
|
||||
logoUrl: (state) => state.logoUrl,
|
||||
bannerUrl: (state) => state.bannerUrl,
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
setDarkMode,
|
||||
setUserAuthenticated,
|
||||
setPrimaryColor,
|
||||
setLogoUrl,
|
||||
setBannerUrl,
|
||||
} = appSlice.actions;
|
||||
export const { setDarkMode, setUserAuthenticated, setUserProfilePictureUrl, setPrimaryColor, setLogoUrl, setBannerUrl } = appSlice.actions;
|
||||
|
||||
export const { darkMode, userAuthenticated, primaryColor, logoUrl, bannerUrl } =
|
||||
appSlice.selectors;
|
||||
export const { darkMode, userAuthenticated, userProfilePictureUrl, primaryColor, logoUrl, bannerUrl } = appSlice.selectors;
|
||||
|
|
|
@ -3,6 +3,7 @@ import { baseQueryWithErrorHandling } from "core/helper/api";
|
|||
import {
|
||||
Lesson,
|
||||
LessonContent,
|
||||
LessonQuestion,
|
||||
LessonSettings,
|
||||
UpdateLessonPreviewThumbnail,
|
||||
} from "core/types/lesson";
|
||||
|
@ -93,6 +94,12 @@ export const lessonsApi = createApi({
|
|||
method: "DELETE",
|
||||
}),
|
||||
}),
|
||||
getQuestions: builder.query<LessonQuestion[], string>({
|
||||
query: (lessonId) => ({
|
||||
url: `lessons/${lessonId}/questions`,
|
||||
method: "GET",
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
|
@ -1,286 +1,255 @@
|
|||
import { Dispatch } from "@reduxjs/toolkit";
|
||||
import { Dispatch } from '@reduxjs/toolkit';
|
||||
import { setBannerUrl, setLogoUrl, setPrimaryColor, setUserProfilePictureUrl } from 'core/reducers/appSlice';
|
||||
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 {
|
||||
setBannerUrl,
|
||||
setLogoUrl,
|
||||
setPrimaryColor,
|
||||
} from "core/reducers/appSlice";
|
||||
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 {
|
||||
addLessonContent,
|
||||
deleteLessonContent,
|
||||
setLessonThumbnailTitle,
|
||||
setLessonThumbnailUrl,
|
||||
setPageEditorLessonState,
|
||||
updateLessonContent,
|
||||
updateLessonContentPosition,
|
||||
} from "features/Lessons/LessonPageEditor/lessonPageEditorSlice";
|
||||
import {
|
||||
addLesson,
|
||||
updateLessonPreviewThumbnail,
|
||||
updateLessonPreviewTitle,
|
||||
updateLessonState,
|
||||
} from "features/Lessons/lessonsSlice";
|
||||
import {
|
||||
addTeamMember,
|
||||
deleteTeamMember,
|
||||
updateTeamMemberRole,
|
||||
} from "features/Team/teamSlice";
|
||||
import { setProfilePictureUrl } from "features/UserProfile/userProfileSlice";
|
||||
addLessonContent,
|
||||
deleteLessonContent,
|
||||
setLessonThumbnailTitle,
|
||||
setLessonThumbnailUrl,
|
||||
setPageEditorLessonState,
|
||||
updateLessonContent,
|
||||
updateLessonContentPosition,
|
||||
} from 'features/Lessons/LessonPageEditor/lessonPageEditorSlice';
|
||||
import { addLesson, updateLessonPreviewThumbnail, updateLessonPreviewTitle, updateLessonState } from 'features/Lessons/lessonsSlice';
|
||||
import { addTeamMember, deleteTeamMember, updateTeamMemberRole } from 'features/Team/teamSlice';
|
||||
import { setProfilePictureUrl } from 'features/UserProfile/userProfileSlice';
|
||||
|
||||
interface WebSocketMessage {
|
||||
Cmd: number;
|
||||
Body: any;
|
||||
Cmd: number;
|
||||
Body: any;
|
||||
}
|
||||
|
||||
class WebSocketService {
|
||||
private url: string;
|
||||
private socket: WebSocket | null = null;
|
||||
private reconnectInterval: number = 2000; // in ms
|
||||
private offlineQueue: WebSocketMessage[] = [];
|
||||
private firstConnect: boolean = true;
|
||||
private url: string;
|
||||
private socket: WebSocket | null = null;
|
||||
private reconnectInterval: number = 2000; // in ms
|
||||
private offlineQueue: WebSocketMessage[] = [];
|
||||
private firstConnect: boolean = true;
|
||||
|
||||
private messageHandler:
|
||||
| ((message: WebSocketMessage, dispatch: Dispatch) => void)
|
||||
| null = null;
|
||||
private messageHandler: ((message: WebSocketMessage, dispatch: Dispatch) => void) | null = null;
|
||||
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
private dispatch: Dispatch | null = null;
|
||||
|
||||
public connect(): void {
|
||||
this.socket = new WebSocket(
|
||||
`${this.url}?auth=${localStorage.getItem(
|
||||
"session"
|
||||
)}&bts=${BrowserTabSession}`
|
||||
);
|
||||
|
||||
this.socket.onopen = () => {
|
||||
console.log("WebSocket connected", this.firstConnect);
|
||||
|
||||
// Send all messages from the offline queue
|
||||
|
||||
this.offlineQueue.forEach((message) => this.send(message));
|
||||
this.offlineQueue = [];
|
||||
|
||||
// Dispatch event to notify that the WebSocket connection is established
|
||||
|
||||
if (!this.firstConnect) {
|
||||
document.dispatchEvent(webSocketConnectionEvent);
|
||||
} else {
|
||||
this.firstConnect = false;
|
||||
}
|
||||
};
|
||||
|
||||
this.socket.onmessage = (event: MessageEvent) => {
|
||||
const data: WebSocketMessage = JSON.parse(event.data);
|
||||
|
||||
if (this.messageHandler) {
|
||||
this.messageHandler(data, this.dispatch!);
|
||||
} else {
|
||||
console.error("No handler defined for WebSocket messages");
|
||||
}
|
||||
};
|
||||
|
||||
this.socket.onclose = () => {
|
||||
console.log("WebSocket disconnected. Reconnecting...");
|
||||
setTimeout(() => this.connect(), this.reconnectInterval);
|
||||
};
|
||||
|
||||
this.socket.onerror = (error: Event) => {
|
||||
console.error("WebSocket error:", error);
|
||||
};
|
||||
}
|
||||
|
||||
public setHandler(
|
||||
handler: (message: WebSocketMessage, dispatch: Dispatch) => void,
|
||||
dispatch: Dispatch
|
||||
): void {
|
||||
this.messageHandler = handler;
|
||||
this.dispatch = dispatch;
|
||||
}
|
||||
|
||||
public send(message: WebSocketMessage): void {
|
||||
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||||
this.socket.send(
|
||||
JSON.stringify({
|
||||
Cmd: message.Cmd,
|
||||
Body: message.Body,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.offlineQueue.push(message);
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
public disconnect(): void {
|
||||
if (this.socket) {
|
||||
this.socket.close();
|
||||
private dispatch: Dispatch | null = null;
|
||||
|
||||
public connect(): void {
|
||||
this.socket = new WebSocket(`${this.url}?auth=${localStorage.getItem('session')}&bts=${BrowserTabSession}`);
|
||||
|
||||
this.socket.onopen = () => {
|
||||
console.log('WebSocket connected', this.firstConnect);
|
||||
|
||||
// Send all messages from the offline queue
|
||||
|
||||
this.offlineQueue.forEach((message) => this.send(message));
|
||||
this.offlineQueue = [];
|
||||
|
||||
// Dispatch event to notify that the WebSocket connection is established
|
||||
|
||||
if (!this.firstConnect) {
|
||||
document.dispatchEvent(webSocketConnectionEvent);
|
||||
} else {
|
||||
this.firstConnect = false;
|
||||
}
|
||||
};
|
||||
|
||||
this.socket.onmessage = (event: MessageEvent) => {
|
||||
const data: WebSocketMessage = JSON.parse(event.data);
|
||||
|
||||
if (this.messageHandler) {
|
||||
this.messageHandler(data, this.dispatch!);
|
||||
} else {
|
||||
console.error('No handler defined for WebSocket messages');
|
||||
}
|
||||
};
|
||||
|
||||
this.socket.onclose = () => {
|
||||
console.log('WebSocket disconnected. Reconnecting...');
|
||||
setTimeout(() => this.connect(), this.reconnectInterval);
|
||||
};
|
||||
|
||||
this.socket.onerror = (error: Event) => {
|
||||
console.error('WebSocket error:', error);
|
||||
};
|
||||
}
|
||||
|
||||
public setHandler(handler: (message: WebSocketMessage, dispatch: Dispatch) => void, dispatch: Dispatch): void {
|
||||
this.messageHandler = handler;
|
||||
this.dispatch = dispatch;
|
||||
}
|
||||
|
||||
public send(message: WebSocketMessage): void {
|
||||
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||||
this.socket.send(
|
||||
JSON.stringify({
|
||||
Cmd: message.Cmd,
|
||||
Body: message.Body,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.offlineQueue.push(message);
|
||||
}
|
||||
}
|
||||
|
||||
public disconnect(): void {
|
||||
if (this.socket) {
|
||||
this.socket.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const webSocketConnectionEventName = "WebSocketConnectionEvent";
|
||||
const webSocketConnectionEventName = 'WebSocketConnectionEvent';
|
||||
|
||||
const webSocketConnectionEvent = new CustomEvent(webSocketConnectionEventName, {
|
||||
detail: "wsReconnect",
|
||||
detail: 'wsReconnect',
|
||||
});
|
||||
|
||||
export function addWebSocketReconnectListener(callback: () => void): void {
|
||||
document.addEventListener(webSocketConnectionEventName, callback);
|
||||
document.addEventListener(webSocketConnectionEventName, callback);
|
||||
}
|
||||
|
||||
export function removeWebSocketReconnectListener(callback: () => void): void {
|
||||
document.removeEventListener(webSocketConnectionEventName, callback);
|
||||
document.removeEventListener(webSocketConnectionEventName, callback);
|
||||
}
|
||||
|
||||
const webSocketService = new WebSocketService(Constants.WS_ADDRESS);
|
||||
export default webSocketService;
|
||||
|
||||
export function WebSocketMessageHandler(
|
||||
message: WebSocketMessage,
|
||||
dispatch: Dispatch
|
||||
) {
|
||||
const { Cmd, Body } = message;
|
||||
export function WebSocketMessageHandler(message: WebSocketMessage, dispatch: Dispatch) {
|
||||
const { Cmd, Body } = message;
|
||||
|
||||
console.log("WebSocketMessageHandler", Cmd, Body);
|
||||
console.log('WebSocketMessageHandler', Cmd, Body);
|
||||
|
||||
switch (Cmd) {
|
||||
case WebSocketReceivedMessagesCmds.SettingsUpdated:
|
||||
dispatch(setPrimaryColor(Body.PrimaryColor));
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.SettingsUpdatedLogo:
|
||||
dispatch(setLogoUrl(Body));
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.SettingsUpdatedBanner:
|
||||
dispatch(setBannerUrl(Body));
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.SettingsUpdatedSubdomain:
|
||||
localStorage.removeItem("session");
|
||||
switch (Cmd) {
|
||||
case WebSocketReceivedMessagesCmds.SettingsUpdated:
|
||||
dispatch(setPrimaryColor(Body.PrimaryColor));
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.SettingsUpdatedLogo:
|
||||
dispatch(setLogoUrl(Body));
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.SettingsUpdatedBanner:
|
||||
dispatch(setBannerUrl(Body));
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.SettingsUpdatedSubdomain:
|
||||
localStorage.removeItem('session');
|
||||
|
||||
window.location.href = `${
|
||||
window.location.protocol
|
||||
}//${Body}.${window.location.hostname.split(".").slice(1).join(".")}`;
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.TeamAddedMember:
|
||||
dispatch(addTeamMember(Body));
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.TeamUpdatedMemberRole:
|
||||
dispatch(updateTeamMemberRole(Body));
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.TeamDeletedMember:
|
||||
dispatch(deleteTeamMember(Body));
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonCreated:
|
||||
dispatch(addLesson(Body));
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonPreviewTitleUpdated:
|
||||
dispatch(updateLessonPreviewTitle(Body));
|
||||
window.location.href = `${window.location.protocol}//${Body}.${window.location.hostname.split('.').slice(1).join('.')}`;
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.TeamAddedMember:
|
||||
dispatch(addTeamMember(Body));
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.TeamUpdatedMemberRole:
|
||||
dispatch(updateTeamMemberRole(Body));
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.TeamDeletedMember:
|
||||
dispatch(deleteTeamMember(Body));
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonCreated:
|
||||
dispatch(addLesson(Body));
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonPreviewTitleUpdated:
|
||||
dispatch(updateLessonPreviewTitle(Body));
|
||||
|
||||
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
|
||||
dispatch(setLessonThumbnailTitle(Body.Title));
|
||||
}
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonPreviewThumbnailUpdated:
|
||||
dispatch(updateLessonPreviewThumbnail(Body));
|
||||
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
|
||||
dispatch(setLessonThumbnailTitle(Body.Title));
|
||||
}
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonPreviewThumbnailUpdated:
|
||||
dispatch(updateLessonPreviewThumbnail(Body));
|
||||
|
||||
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
|
||||
dispatch(setLessonThumbnailUrl(Body.ThumbnailUrl));
|
||||
}
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonStateUpdated:
|
||||
dispatch(updateLessonState(Body));
|
||||
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
|
||||
dispatch(setLessonThumbnailUrl(Body.ThumbnailUrl));
|
||||
}
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonStateUpdated:
|
||||
dispatch(updateLessonState(Body));
|
||||
|
||||
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
|
||||
dispatch(setPageEditorLessonState(Body.State));
|
||||
}
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonAddedContent:
|
||||
if (Body.LessonId === store.getState().lessonPage.currentLessonId) {
|
||||
dispatch(addLessonPageContent(Body));
|
||||
}
|
||||
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
|
||||
dispatch(addLessonContent(Body));
|
||||
}
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonDeletedContent:
|
||||
if (Body.LessonId === store.getState().lessonPage.currentLessonId) {
|
||||
dispatch(deleteLessonPageContent(Body.ContentId));
|
||||
}
|
||||
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
|
||||
dispatch(deleteLessonContent(Body.ContentId));
|
||||
}
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonContentUpdated:
|
||||
if (Body.LessonId === store.getState().lessonPage.currentLessonId) {
|
||||
dispatch(
|
||||
updateLessonPageContent({
|
||||
id: Body.ContentId,
|
||||
data: Body.Data,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
|
||||
dispatch(setPageEditorLessonState(Body.State));
|
||||
}
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonAddedContent:
|
||||
if (Body.LessonId === store.getState().lessonPage.currentLessonId) {
|
||||
dispatch(addLessonPageContent(Body));
|
||||
}
|
||||
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
|
||||
dispatch(addLessonContent(Body));
|
||||
}
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonDeletedContent:
|
||||
if (Body.LessonId === store.getState().lessonPage.currentLessonId) {
|
||||
dispatch(deleteLessonPageContent(Body.ContentId));
|
||||
}
|
||||
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
|
||||
dispatch(deleteLessonContent(Body.ContentId));
|
||||
}
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonContentUpdated:
|
||||
if (Body.LessonId === store.getState().lessonPage.currentLessonId) {
|
||||
dispatch(
|
||||
updateLessonPageContent({
|
||||
id: Body.ContentId,
|
||||
data: Body.Data,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
|
||||
dispatch(
|
||||
updateLessonContent({
|
||||
id: Body.ContentId,
|
||||
data: Body.Data,
|
||||
})
|
||||
);
|
||||
}
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonContentUpdatedPosition:
|
||||
if (Body.LessonId === store.getState().lessonPage.currentLessonId) {
|
||||
dispatch(
|
||||
updateLessonPageContentPosition({
|
||||
contentId: Body.ContentId,
|
||||
position: Body.Position,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
|
||||
dispatch(
|
||||
updateLessonContent({
|
||||
id: Body.ContentId,
|
||||
data: Body.Data,
|
||||
})
|
||||
);
|
||||
}
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonContentUpdatedPosition:
|
||||
if (Body.LessonId === store.getState().lessonPage.currentLessonId) {
|
||||
dispatch(
|
||||
updateLessonPageContentPosition({
|
||||
contentId: Body.ContentId,
|
||||
position: Body.Position,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
|
||||
dispatch(
|
||||
updateLessonContentPosition({
|
||||
contentId: Body.ContentId,
|
||||
position: Body.Position,
|
||||
})
|
||||
);
|
||||
}
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonContentFileUpdated:
|
||||
if (Body.LessonId === store.getState().lessonPage.currentLessonId) {
|
||||
dispatch(
|
||||
updateLessonPageContent({
|
||||
id: Body.ContentId,
|
||||
data: Body.Data,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
|
||||
dispatch(
|
||||
updateLessonContentPosition({
|
||||
contentId: Body.ContentId,
|
||||
position: Body.Position,
|
||||
})
|
||||
);
|
||||
}
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.LessonContentFileUpdated:
|
||||
if (Body.LessonId === store.getState().lessonPage.currentLessonId) {
|
||||
dispatch(
|
||||
updateLessonPageContent({
|
||||
id: Body.ContentId,
|
||||
data: Body.Data,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
|
||||
dispatch(
|
||||
updateLessonContent({
|
||||
id: Body.ContentId,
|
||||
data: Body.Data,
|
||||
})
|
||||
);
|
||||
}
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.UserProfilePictureUpdated:
|
||||
dispatch(setProfilePictureUrl(Body));
|
||||
break;
|
||||
default:
|
||||
console.error("Unknown message type:", Cmd);
|
||||
}
|
||||
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
|
||||
dispatch(
|
||||
updateLessonContent({
|
||||
id: Body.ContentId,
|
||||
data: Body.Data,
|
||||
})
|
||||
);
|
||||
}
|
||||
break;
|
||||
case WebSocketReceivedMessagesCmds.UserProfilePictureUpdated:
|
||||
dispatch(setProfilePictureUrl(Body));
|
||||
dispatch(setUserProfilePictureUrl(Body));
|
||||
break;
|
||||
default:
|
||||
console.error('Unknown message type:', Cmd);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,55 +1,55 @@
|
|||
export interface Lesson {
|
||||
Id: string;
|
||||
State: number;
|
||||
Title: string;
|
||||
ThumbnailUrl: string;
|
||||
CreatorUserId: string;
|
||||
CreatedAt: string;
|
||||
Id: string;
|
||||
State: number;
|
||||
Title: string;
|
||||
ThumbnailUrl: string;
|
||||
CreatorUserId: string;
|
||||
CreatedAt: string;
|
||||
}
|
||||
|
||||
export enum LessonState {
|
||||
Published = 1,
|
||||
Draft = 2,
|
||||
Published = 1,
|
||||
Draft = 2,
|
||||
}
|
||||
|
||||
// used for the preview card on /lessions page and on the lesson editor
|
||||
export interface LessonSettings {
|
||||
Title: string;
|
||||
ThumbnailUrl: string;
|
||||
State?: LessonState;
|
||||
Title: string;
|
||||
ThumbnailUrl: string;
|
||||
State?: LessonState;
|
||||
}
|
||||
|
||||
// used on lesson page and on the lesson editor
|
||||
export interface LessonContent {
|
||||
Id: string;
|
||||
Page: number;
|
||||
Position: number;
|
||||
Type: number;
|
||||
Data: string;
|
||||
Id: string;
|
||||
Page: number;
|
||||
Position: number;
|
||||
Type: number;
|
||||
Data: string;
|
||||
}
|
||||
|
||||
export interface UpdateLessonPreviewThumbnail {
|
||||
lessonId: string;
|
||||
formData: FormData;
|
||||
lessonId: string;
|
||||
formData: FormData;
|
||||
}
|
||||
|
||||
export interface LessonQuestion {
|
||||
Id: string;
|
||||
LessionId: string;
|
||||
Question: string;
|
||||
Likes: number;
|
||||
CreatorUserId: string;
|
||||
CreatedAt: string;
|
||||
UpdatedAt: string;
|
||||
Id: string;
|
||||
LessionId: string;
|
||||
Question: string;
|
||||
Likes: number;
|
||||
CreatorUserId: string;
|
||||
CreatedAt: string;
|
||||
UpdatedAt: string;
|
||||
}
|
||||
|
||||
export interface LessonQuestionReply {
|
||||
Id: string;
|
||||
QuestionId: string;
|
||||
ReplyId?: string;
|
||||
Reply: string;
|
||||
Likes: number;
|
||||
CreatorUserId: string;
|
||||
CreatedAt: string;
|
||||
UpdatedAt: string;
|
||||
Id: string;
|
||||
QuestionId: string;
|
||||
ReplyId?: string;
|
||||
Reply: string;
|
||||
Likes: number;
|
||||
CreatorUserId: string;
|
||||
CreatedAt: string;
|
||||
UpdatedAt: string;
|
||||
}
|
||||
|
|
|
@ -1,32 +1,33 @@
|
|||
export interface TeamMember {
|
||||
Id: string;
|
||||
FirstName: string;
|
||||
LastName: string;
|
||||
Email: string;
|
||||
RoleId: string;
|
||||
Online: boolean;
|
||||
Id: string;
|
||||
FirstName: string;
|
||||
LastName: string;
|
||||
Email: string;
|
||||
RoleId: string;
|
||||
ProfilePictureUrl: string;
|
||||
Online: boolean;
|
||||
}
|
||||
|
||||
export interface OrganizationSettings {
|
||||
Subdomain: string;
|
||||
CompanyName: string;
|
||||
PrimaryColor: string;
|
||||
LogoUrl: string;
|
||||
BannerUrl: string;
|
||||
Subdomain: string;
|
||||
CompanyName: string;
|
||||
PrimaryColor: string;
|
||||
LogoUrl: string;
|
||||
BannerUrl: string;
|
||||
}
|
||||
|
||||
interface RoleUser {
|
||||
FirstName: string;
|
||||
LastName: string;
|
||||
ProfilePictureUrl: string;
|
||||
FirstName: string;
|
||||
LastName: string;
|
||||
ProfilePictureUrl: string;
|
||||
}
|
||||
|
||||
export interface Role {
|
||||
Id: string;
|
||||
Permissions: number[];
|
||||
Users: RoleUser[];
|
||||
Id: string;
|
||||
Permissions: number[];
|
||||
Users: RoleUser[];
|
||||
}
|
||||
|
||||
export interface Roles {
|
||||
Roles: Role[];
|
||||
Roles: Role[];
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from "react";
|
|||
import { FloatButton } from "antd";
|
||||
import { CommentOutlined } from "@ant-design/icons";
|
||||
import { DeepChat } from "deep-chat-react";
|
||||
import { getUserSessionFromLocalStorage } from "core/utils/utils";
|
||||
|
||||
function AiChat() {
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
|
@ -30,20 +31,17 @@ function AiChat() {
|
|||
bottom: 0,
|
||||
position: "absolute",
|
||||
}}
|
||||
history={[
|
||||
{ text: "Show me a modern city", role: "user" },
|
||||
{
|
||||
files: [
|
||||
{
|
||||
src: "https://test.ex.umbach.dev/api/statico/809fe37e-8c41-4a44-98d1-d9247affd531/67c763b6-ea67-4b49-9621-2f78b85eb180.png",
|
||||
type: "image",
|
||||
},
|
||||
],
|
||||
role: "ai",
|
||||
history={[{ text: "Stell mir Fragen :)", role: "ai" }]}
|
||||
connect={{
|
||||
url: "/api/chat/v1/prompt/",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-Authorization": getUserSessionFromLocalStorage() || "",
|
||||
},
|
||||
{ text: "Whats on your mind?", role: "user" },
|
||||
{ text: "Peace and tranquility", role: "ai" },
|
||||
]}
|
||||
}}
|
||||
onMessage={async (message) => {
|
||||
console.log("onMessagee", message);
|
||||
}}
|
||||
></DeepChat>
|
||||
</div>
|
||||
) : null}
|
||||
|
|
|
@ -156,7 +156,7 @@ function GeneralCard({
|
|||
}
|
||||
|
||||
debounceRef.current = setTimeout(() => {
|
||||
dispatch(setPrimaryColor(color.toHexString()));
|
||||
dispatch(setPrimaryColor(color.toHexString().split("#")[1]));
|
||||
}, 600);
|
||||
}}
|
||||
/>
|
||||
|
@ -218,12 +218,9 @@ function MediaCard({
|
|||
<MyUpload
|
||||
action="/organization/file/banner"
|
||||
onChange={(info) => {
|
||||
console.log("Banner updated1!", info.file.status);
|
||||
if (info.file.status === "done" && info.file.response.Data) {
|
||||
dispatch(setBannerUrl(info.file.response.Data));
|
||||
|
||||
console.log("Banner updated!");
|
||||
|
||||
success("Banner updated successfully!");
|
||||
}
|
||||
}}
|
||||
|
@ -337,14 +334,7 @@ function SubdomainCard({
|
|||
</p>
|
||||
</Modal>
|
||||
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
requiredMark={false}
|
||||
onFinish={(values) => {
|
||||
console.log(values);
|
||||
}}
|
||||
>
|
||||
<Form form={form} layout="vertical" requiredMark={false}>
|
||||
<MyMiddleCard
|
||||
title="Subdomain"
|
||||
loading={isLoading}
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
removeWebSocketReconnectListener,
|
||||
} from "core/services/websocketService";
|
||||
import { useMessage } from "core/context/MessageContext";
|
||||
import MyUserAvatar from "shared/components/MyUserAvatar";
|
||||
|
||||
const TeamList: React.FC = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
@ -146,7 +147,17 @@ const TeamList: React.FC = () => {
|
|||
dataTeamMembers.forEach((item) => {
|
||||
items.push({
|
||||
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,
|
||||
email: item.Email,
|
||||
role: tmpRoleNames[item.RoleId],
|
||||
|
|
|
@ -35,6 +35,8 @@ import { tmpRoleNames } from "features/Roles";
|
|||
import MyErrorResult from "shared/components/MyResult";
|
||||
import MyUpload from "shared/components/MyUpload";
|
||||
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 }) {
|
||||
const dispatch = useDispatch();
|
||||
|
@ -122,6 +124,9 @@ export default function UserProfile({ isAdmin }: { isAdmin?: boolean }) {
|
|||
success("Profile picture updated successfully");
|
||||
|
||||
dispatch(setProfilePictureUrl(info.file.response.Data));
|
||||
dispatch(
|
||||
setUserProfilePictureUrl(info.file.response.Data)
|
||||
);
|
||||
}
|
||||
}}
|
||||
imgCropProps={{
|
||||
|
@ -129,16 +134,7 @@ export default function UserProfile({ isAdmin }: { isAdmin?: boolean }) {
|
|||
children: <></>,
|
||||
}}
|
||||
>
|
||||
{dataProfilePictureUrl === "" ? (
|
||||
<Avatar size={56} style={{ backgroundColor: "#1677ff" }}>
|
||||
{dataFirstName.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
) : (
|
||||
<Avatar
|
||||
src={`${Constants.STATIC_CONTENT_ADDRESS}/${dataProfilePictureUrl}`}
|
||||
size={56}
|
||||
/>
|
||||
)}
|
||||
<MyUserAvatar profilePictureUrl={dataProfilePictureUrl} />
|
||||
</MyUpload>
|
||||
}
|
||||
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