From 679ee5bf285f5622ca3497506c8cd0a42281235c Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 9 Sep 2024 22:30:05 +0200 Subject: [PATCH] user profile and AI chat --- src/App.tsx | 151 +++--- src/core/components/Header/index.tsx | 265 +++++------ src/core/reducers/appSlice.tsx | 76 ++- src/core/services/lessons.ts | 7 + src/core/services/websocketService.ts | 469 +++++++++---------- src/core/types/lesson.ts | 66 +-- src/core/types/organization.ts | 37 +- src/features/AiChat/index.tsx | 24 +- src/features/Settings/index.tsx | 14 +- src/features/Team/index.tsx | 13 +- src/features/UserProfile/index.tsx | 16 +- src/shared/components/MyUserAvatar/index.tsx | 34 ++ 12 files changed, 561 insertions(+), 611 deletions(-) create mode 100644 src/shared/components/MyUserAvatar/index.tsx diff --git a/src/App.tsx b/src/App.tsx index aa32e46..264a5c9 100644 --- a/src/App.tsx +++ b/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 ( - - - - {uAuthenticated == null ? ( - - ) : uAuthenticated ? ( - - ) : ( - - )} - - - - ); + return ( + + + {uAuthenticated == null ? : uAuthenticated ? : } + + + ); } diff --git a/src/core/components/Header/index.tsx b/src/core/components/Header/index.tsx index 5024e7c..01efbad 100644 --- a/src/core/components/Header/index.tsx +++ b/src/core/components/Header/index.tsx @@ -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 ( - - -
- {isCollpased ? ( -
dispatch(setIsSideMenuCollapsed(false))} - > - -
- ) : ( -
dispatch(setIsSideMenuCollapsed(true))} - > - -
- )} -
+ +
+ {isCollpased ? ( +
dispatch(setIsSideMenuCollapsed(false))}> + +
+ ) : ( +
dispatch(setIsSideMenuCollapsed(true))}> + +
+ )} +
- {props.backTo && ( - - - - Back + {props.backTo && ( + + + + Back + + + )} - - )} -
- - {props.onView && ( -
- -
- )} + + {props.onView && ( +
+ +
+ )} - {props.onEdit && ( -
- -
- )} + {props.onEdit && ( +
+ +
+ )} - {isDarkMode ? ( -
dispatch(setDarkMode(false))} - > - -
- ) : ( -
dispatch(setDarkMode(true))} - > - -
- )} - , - onClick: () => navigate(Constants.ROUTE_PATHS.ACCOUNT_SETTINGS), - }, - { - key: "2", - label: "Logout", - icon: , - danger: true, - onClick: () => { - webSocketService.disconnect(); - window.localStorage.removeItem("session"); - window.location.href = "/"; - }, - }, - ], - }} - > - } - style={{ cursor: "pointer" }} - /> - -
-
- ); + {isDarkMode ? ( +
dispatch(setDarkMode(false))}> + +
+ ) : ( +
dispatch(setDarkMode(true))}> + +
+ )} + , + onClick: () => navigate(Constants.ROUTE_PATHS.ACCOUNT_SETTINGS), + }, + { + key: '2', + label: 'Logout', + icon: , + danger: true, + onClick: () => { + webSocketService.disconnect(); + window.localStorage.removeItem('session'); + window.location.href = '/'; + }, + }, + ], + }} + > +
+ +
+
+
+
+ ); - /* return ( + /* return (
{ - 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; diff --git a/src/core/services/lessons.ts b/src/core/services/lessons.ts index 260c8fb..479dbcd 100644 --- a/src/core/services/lessons.ts +++ b/src/core/services/lessons.ts @@ -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({ + query: (lessonId) => ({ + url: `lessons/${lessonId}/questions`, + method: "GET", + }), + }), }), }); diff --git a/src/core/services/websocketService.ts b/src/core/services/websocketService.ts index cad25e9..28d3b84 100644 --- a/src/core/services/websocketService.ts +++ b/src/core/services/websocketService.ts @@ -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); + } } diff --git a/src/core/types/lesson.ts b/src/core/types/lesson.ts index 4ed7d68..8ad1a9f 100644 --- a/src/core/types/lesson.ts +++ b/src/core/types/lesson.ts @@ -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; } diff --git a/src/core/types/organization.ts b/src/core/types/organization.ts index 2e3f4dd..5581578 100644 --- a/src/core/types/organization.ts +++ b/src/core/types/organization.ts @@ -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[]; } diff --git a/src/features/AiChat/index.tsx b/src/features/AiChat/index.tsx index d97de09..21a6a76 100644 --- a/src/features/AiChat/index.tsx +++ b/src/features/AiChat/index.tsx @@ -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); + }} > ) : null} diff --git a/src/features/Settings/index.tsx b/src/features/Settings/index.tsx index 351abe5..4a214ad 100644 --- a/src/features/Settings/index.tsx +++ b/src/features/Settings/index.tsx @@ -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({ { - 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({

-
{ - console.log(values); - }} - > + { const dispatch = useDispatch(); @@ -146,7 +147,17 @@ const TeamList: React.FC = () => { dataTeamMembers.forEach((item) => { items.push({ key: item.Id, - firstName: item.FirstName, + firstName: ( + + + {item.FirstName} + + ), lastName: item.LastName, email: item.Email, role: tmpRoleNames[item.RoleId], diff --git a/src/features/UserProfile/index.tsx b/src/features/UserProfile/index.tsx index 2874609..b6a24ff 100644 --- a/src/features/UserProfile/index.tsx +++ b/src/features/UserProfile/index.tsx @@ -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 === "" ? ( - - {dataFirstName.charAt(0).toUpperCase()} - - ) : ( - - )} + } title={`${dataFirstName} ${dataLastName}`} diff --git a/src/shared/components/MyUserAvatar/index.tsx b/src/shared/components/MyUserAvatar/index.tsx new file mode 100644 index 0000000..070971b --- /dev/null +++ b/src/shared/components/MyUserAvatar/index.tsx @@ -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 = ({ 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 ? : undefined; + const avatarSrc = isProfilePictureEmpty ? undefined : `${Constants.STATIC_CONTENT_ADDRESS}/${profilePictureUrl}`; + const avatarStyle = isProfilePictureEmpty ? { ...defaultStyle, backgroundColor: `#${appPrimaryColor}` } : defaultStyle; + + return ( +
+ + {avatarContent} + +
+ ); +}; + +export default MyUserAvatar;