diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/course.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/course.tsx index d7d16a7b..d3f1189a 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/course.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/course.tsx @@ -13,6 +13,7 @@ import { ArrowRight, Check, File, Sparkles, Star, Video } from "lucide-react"; import Avvvatars from "avvvatars-react"; import { getUser } from "@services/users/users"; import { useOrg } from "@components/Contexts/OrgContext"; +import UserAvatar from "@components/Objects/UserAvatar"; const CourseClient = (props: any) => { const [user, setUser] = useState({}); @@ -192,9 +193,7 @@ const CourseClient = (props: any) => {
{user &&
-
- -
+
Author
diff --git a/apps/web/components/Dashboard/UI/LeftMenu.tsx b/apps/web/components/Dashboard/UI/LeftMenu.tsx index e55225f9..bd780e3e 100644 --- a/apps/web/components/Dashboard/UI/LeftMenu.tsx +++ b/apps/web/components/Dashboard/UI/LeftMenu.tsx @@ -4,12 +4,12 @@ import { useSession } from '@components/Contexts/SessionContext'; import ToolTip from '@components/StyledElements/Tooltip/Tooltip' import LearnHouseDashboardLogo from '@public/dashLogo.png'; import { logout } from '@services/auth/auth'; -import Avvvatars from 'avvvatars-react'; import { ArrowLeft, Book, BookCopy, Home, LogOut, School, Settings } from 'lucide-react' import Image from 'next/image'; import Link from 'next/link' import { useRouter } from 'next/navigation'; import React, { use, useEffect } from 'react' +import UserAvatar from '../../Objects/UserAvatar'; function LeftMenu() { const org = useOrg() as any; @@ -72,10 +72,10 @@ function LeftMenu() {
- -
- -
+ +
+ +
diff --git a/apps/web/components/Dashboard/User/UserEditGeneral/UserEditGeneral.tsx b/apps/web/components/Dashboard/User/UserEditGeneral/UserEditGeneral.tsx index 165e34d7..8c5726ca 100644 --- a/apps/web/components/Dashboard/User/UserEditGeneral/UserEditGeneral.tsx +++ b/apps/web/components/Dashboard/User/UserEditGeneral/UserEditGeneral.tsx @@ -2,11 +2,33 @@ import { updateProfile } from '@services/settings/profile'; import React, { useEffect } from 'react' import { Formik, Form, Field, ErrorMessage } from 'formik'; import { useSession } from '@components/Contexts/SessionContext'; +import { ArrowBigUpDash, Check, FileWarning, Info, UploadCloud } from 'lucide-react'; +import UserAvatar from '@components/Objects/UserAvatar'; +import { updateUserAvatar } from '@services/users/users'; function UserEditGeneral() { const session = useSession() as any; + const [localAvatar, setLocalAvatar] = React.useState(null) as any; + const [isLoading, setIsLoading] = React.useState(false) as any; + const [error, setError] = React.useState() as any; + const [success, setSuccess] = React.useState('') as any; + + const handleFileChange = async (event: any) => { + const file = event.target.files[0]; + setLocalAvatar(file); + setIsLoading(true); + const res = await updateUserAvatar(session.user.user_uuid, file) + // wait for 1 second to show loading animation + await new Promise(r => setTimeout(r, 1500)); + if (res.success === false) { + setError(res.HTTPmessage); + } else { + setIsLoading(false); + setError(''); + setSuccess('Avatar Updated'); + } + }; - useEffect(() => { } , [session, session.user]) @@ -15,83 +37,142 @@ function UserEditGeneral() {
{session.user && ( { - setTimeout(() => { + enableReinitialize + initialValues={{ + username: session.user.username, + first_name: session.user.first_name, + last_name: session.user.last_name, + email: session.user.email, + bio: session.user.bio, + }} + onSubmit={(values, { setSubmitting }) => { + setTimeout(() => { - setSubmitting(false); - updateProfile(values,session.user.id) - }, 400); - }} - > - {({ isSubmitting }) => ( -
- - + setSubmitting(false); + updateProfile(values, session.user.id) + }, 400); + }} + > + {({ isSubmitting }) => ( +
+ + + + + - - + + - + - + - + - + - + - - - + /> + + +
+ + {error && ( +
+ +
{error}
+
+ )} + {success && ( +
+ +
{success}
+
+ )} +
+ +
+ +
+ + {localAvatar ? ( + - )} - + ) : ( + + )} +
+ {isLoading ? (
+ +
+ + Uploading +
+
) : ( +
+ + +
)} +
+
+

Recommended size 100x100

+
+
+
+ +
+ + )} +
)}
) diff --git a/apps/web/components/Objects/Activities/AI/AIActivityAsk.tsx b/apps/web/components/Objects/Activities/AI/AIActivityAsk.tsx index a1fcff3d..82299393 100644 --- a/apps/web/components/Objects/Activities/AI/AIActivityAsk.tsx +++ b/apps/web/components/Objects/Activities/AI/AIActivityAsk.tsx @@ -1,6 +1,6 @@ import { useSession } from '@components/Contexts/SessionContext' import { sendActivityAIChatMessage, startActivityAIChatSession } from '@services/ai/ai'; -import { AlertTriangle, BadgeInfo, NotebookTabs } from 'lucide-react'; +import { AlertTriangle, BadgeInfo, NotebookTabs, User } from 'lucide-react'; import Avvvatars from 'avvvatars-react'; import { motion, AnimatePresence } from 'framer-motion'; import { FlaskConical, Keyboard, MessageCircle, MessageSquareIcon, Sparkle, Sparkles, X } from 'lucide-react' @@ -13,6 +13,7 @@ import { AIChatBotStateTypes, useAIChatBot, useAIChatBotDispatch } from '@compon import FeedbackModal from '@components/Objects/Modals/Feedback/Feedback'; import Modal from '@components/StyledElements/Modal/Modal'; import useGetAIFeatures from '../../../AI/Hooks/useGetAIFeatures'; +import UserAvatar from '@components/Objects/UserAvatar'; type AIActivityAskProps = { @@ -204,7 +205,7 @@ function ActivityChatMessageBox(props: ActivityChatMessageBoxProps) { }
- +
@@ -235,7 +236,11 @@ function AIMessage(props: AIMessageProps) { return (
- + {props.message.sender == 'ai' ? ( + + ) : ( + + )}

@@ -277,7 +282,8 @@ const AIMessagePlaceHolder = (props: { activity_uuid: string, sendMessage: any }

Hello - + + {session.user.username}, how can we help today ? diff --git a/apps/web/components/Objects/Editor/Editor.tsx b/apps/web/components/Objects/Editor/Editor.tsx index 586f78e0..4e369682 100644 --- a/apps/web/components/Objects/Editor/Editor.tsx +++ b/apps/web/components/Objects/Editor/Editor.tsx @@ -42,6 +42,7 @@ import { CourseProvider } from "@components/Contexts/CourseContext"; import { useSession } from "@components/Contexts/SessionContext"; import AIEditorToolkit from "./AI/AIEditorToolkit"; import useGetAIFeatures from "@components/AI/Hooks/useGetAIFeatures"; +import UserAvatar from "../UserAvatar"; interface Editor { @@ -207,7 +208,7 @@ function Editor(props: Editor) { {!session.isAuthenticated && Loading} - {session.isAuthenticated && } + {session.isAuthenticated && } diff --git a/apps/web/components/Objects/Menu/ProfileArea.tsx b/apps/web/components/Objects/Menu/ProfileArea.tsx index bffc5e9f..e96f3945 100644 --- a/apps/web/components/Objects/Menu/ProfileArea.tsx +++ b/apps/web/components/Objects/Menu/ProfileArea.tsx @@ -9,6 +9,7 @@ import { usePathname } from "next/navigation"; import { useRouter } from "next/router"; import path from "path"; import { Settings } from "lucide-react"; +import UserAvatar from "@components/Objects/UserAvatar"; export interface Auth { access_token: string; @@ -91,7 +92,7 @@ function ProfileArea() {

{auth.userInfo.user_object.username}
- +
diff --git a/apps/web/components/Objects/UserAvatar.tsx b/apps/web/components/Objects/UserAvatar.tsx new file mode 100644 index 00000000..7b1d6bd3 --- /dev/null +++ b/apps/web/components/Objects/UserAvatar.tsx @@ -0,0 +1,63 @@ +import { useSession } from '@components/Contexts/SessionContext'; +import emptyAvatar from '@public/empty_avatar.png'; +import aiAvatar from '@public/ai_avatar.png'; +import Image from 'next/image'; +import React, { use, useEffect } from 'react' +import { getUriWithOrg } from '@services/config/config'; +import { useOrg } from '@components/Contexts/OrgContext'; +import { useParams } from 'next/navigation'; +import { getUserAvatarMediaDirectory } from '@services/media/media'; + +type UserAvatarProps = { + width?: number + avatar_url?: string + use_with_session?: boolean + rounded?: 'rounded-md' | 'rounded-xl' | 'rounded-lg' | 'rounded-full' | 'rounded' + border?: 'border-2' | 'border-4' | 'border-8' + predefined_avatar?: 'ai' +} + +function UserAvatar(props: UserAvatarProps) { + const session = useSession() as any; + const params = useParams() as any; + + const predefinedAvatar = props.predefined_avatar === 'ai' ? getUriWithOrg(params.orgslug,'/ai_avatar.png') : null; + const emptyAvatar = getUriWithOrg(params.orgslug,'/empty_avatar.png') as any; + const uploadedAvatar = getUserAvatarMediaDirectory(session.user.user_uuid,session.user.avatar_image) as any; + + const useAvatar = () => { + if (props.predefined_avatar) { + return predefinedAvatar + } else { + if (props.avatar_url) { + return props.avatar_url + } + else { + if (session.user.avatar_image) { + return uploadedAvatar + } + else { + return emptyAvatar + } + } + } + } + + + useEffect(() => { + console.log('params', params) + } + , [session]) + + return ( + User Avatar + ) +} + +export default UserAvatar \ No newline at end of file diff --git a/apps/web/components/Security/HeaderProfileBox.tsx b/apps/web/components/Security/HeaderProfileBox.tsx index 5d1e5464..dabf6212 100644 --- a/apps/web/components/Security/HeaderProfileBox.tsx +++ b/apps/web/components/Security/HeaderProfileBox.tsx @@ -6,6 +6,7 @@ import Avvvatars from "avvvatars-react"; import { GearIcon } from "@radix-ui/react-icons"; import { Settings } from "lucide-react"; import { useSession } from "@components/Contexts/SessionContext"; +import UserAvatar from "@components/Objects/UserAvatar"; export const HeaderProfileBox = () => { const session = useSession() as any; @@ -33,9 +34,7 @@ export const HeaderProfileBox = () => {
{session.user.username}
-
- -
+
@@ -51,7 +50,6 @@ const AccountArea = styled.div` img { width: 29px; - border-radius: 19px; } `; diff --git a/apps/web/public/ai_avatar.png b/apps/web/public/ai_avatar.png new file mode 100644 index 00000000..6c1dea2f Binary files /dev/null and b/apps/web/public/ai_avatar.png differ diff --git a/apps/web/public/empty_avatar.png b/apps/web/public/empty_avatar.png new file mode 100644 index 00000000..80fc9f32 Binary files /dev/null and b/apps/web/public/empty_avatar.png differ diff --git a/apps/web/services/media/media.ts b/apps/web/services/media/media.ts index b17191a2..dfa728fe 100644 --- a/apps/web/services/media/media.ts +++ b/apps/web/services/media/media.ts @@ -9,38 +9,43 @@ function getMediaUrl() { } } -export function getCourseThumbnailMediaDirectory(orgId: string, courseId: string, fileId: string) { - let uri = `${getMediaUrl()}content/${orgId}/courses/${courseId}/thumbnails/${fileId}`; +export function getCourseThumbnailMediaDirectory(orgUUID: string, courseId: string, fileId: string) { + let uri = `${getMediaUrl()}content/orgs/${orgUUID}/courses/${courseId}/thumbnails/${fileId}`; return uri; } -export function getActivityBlockMediaDirectory(orgId: string, courseId: string, activityId: string, blockId: any, fileId: any, type: string) { +export function getUserAvatarMediaDirectory(userUUID: string, fileId: string) { + let uri = `${getMediaUrl()}content/users/${userUUID}/avatars/${fileId}`; + return uri; +} + +export function getActivityBlockMediaDirectory(orgUUID: string, courseId: string, activityId: string, blockId: any, fileId: any, type: string) { if (type == "pdfBlock") { - let uri = `${getMediaUrl()}content/${orgId}/courses/${courseId}/activities/${activityId}/dynamic/blocks/pdfBlock/${blockId}/${fileId}`; + let uri = `${getMediaUrl()}content/${orgUUID}/courses/${courseId}/activities/${activityId}/dynamic/blocks/pdfBlock/${blockId}/${fileId}`; return uri; } if (type == "videoBlock") { - let uri = `${getMediaUrl()}content/${orgId}/courses/${courseId}/activities/${activityId}/dynamic/blocks/videoBlock/${blockId}/${fileId}`; + let uri = `${getMediaUrl()}content/${orgUUID}/courses/${courseId}/activities/${activityId}/dynamic/blocks/videoBlock/${blockId}/${fileId}`; return uri; } if (type == "imageBlock") { - let uri = `${getMediaUrl()}content/${orgId}/courses/${courseId}/activities/${activityId}/dynamic/blocks/imageBlock/${blockId}/${fileId}`; + let uri = `${getMediaUrl()}content/${orgUUID}/courses/${courseId}/activities/${activityId}/dynamic/blocks/imageBlock/${blockId}/${fileId}`; return uri; } } -export function getActivityMediaDirectory(orgId: string, courseId: string, activityId: string, fileId: string, activityType: string) { +export function getActivityMediaDirectory(orgUUID: string, courseId: string, activityId: string, fileId: string, activityType: string) { if (activityType == "video") { - let uri = `${getMediaUrl()}content/${orgId}/courses/${courseId}/activities/${activityId}/video/${fileId}`; + let uri = `${getMediaUrl()}content/${orgUUID}/courses/${courseId}/activities/${activityId}/video/${fileId}`; return uri; } if (activityType == "documentpdf") { - let uri = `${getMediaUrl()}content/${orgId}/courses/${courseId}/activities/${activityId}/documentpdf/${fileId}`; + let uri = `${getMediaUrl()}content/${orgUUID}/courses/${courseId}/activities/${activityId}/documentpdf/${fileId}`; return uri; } } -export function getOrgLogoMediaDirectory(orgId: string, fileId: string) { - let uri = `${getMediaUrl()}content/${orgId}/logos/${fileId}`; +export function getOrgLogoMediaDirectory(orgUUID: string, fileId: string) { + let uri = `${getMediaUrl()}content/${orgUUID}/logos/${fileId}`; return uri; } diff --git a/apps/web/services/users/users.ts b/apps/web/services/users/users.ts index 798079ab..b267412b 100644 --- a/apps/web/services/users/users.ts +++ b/apps/web/services/users/users.ts @@ -1,8 +1,16 @@ import { getAPIUrl } from "@services/config/config"; -import { RequestBody, errorHandling } from "@services/utils/ts/requests"; +import { RequestBody, RequestBodyForm, errorHandling, getResponseMetadata } from "@services/utils/ts/requests"; export async function getUser(user_id: string) { const result = await fetch(`${getAPIUrl()}users/user_id/${user_id}`, RequestBody("GET", null, null)); const res = await errorHandling(result); return res; +} + +export async function updateUserAvatar(user_uuid: any, avatar_file: any) { + const formData = new FormData(); + formData.append("avatar_file", avatar_file); + const result: any = await fetch(`${getAPIUrl()}users/update_avatar/${user_uuid}`, RequestBodyForm("PUT", formData, null)); + const res = await getResponseMetadata(result); + return res; } \ No newline at end of file