From d58433dd4cc4a2a00c4f5bf59befad5e81144830 Mon Sep 17 00:00:00 2001 From: Zayd Krunz <70227235+ShrootBuck@users.noreply.github.com> Date: Sat, 22 Feb 2025 16:14:24 -0700 Subject: [PATCH 1/3] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9329f6d3..71f71c97 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ We prioritize issues depending on the most requested features from our users, pl [🚢 LearnHouse General Roadmap](https://www.learnhouse.app/roadmap) -[👨‍💻 Detailed Roadmap](https://github.com/orgs/learnhouse/projects/4/views/1) +[👨‍💻 Detailed Roadmap](https://github.com/orgs/learnhouse/projects/4) ## Overview From 77bc14d842911c19d3d7f19ceff601274ce693a8 Mon Sep 17 00:00:00 2001 From: swve Date: Mon, 24 Feb 2025 18:25:49 +0100 Subject: [PATCH 2/3] refactor: Improve UserAvatar component with more robust avatar URL handling --- apps/web/components/Objects/UserAvatar.tsx | 94 +++++++++++----------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/apps/web/components/Objects/UserAvatar.tsx b/apps/web/components/Objects/UserAvatar.tsx index 62d38ffc..f7abe357 100644 --- a/apps/web/components/Objects/UserAvatar.tsx +++ b/apps/web/components/Objects/UserAvatar.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react' +import React from 'react' import { getUriWithOrg } from '@services/config/config' import { useParams } from 'next/navigation' import { getUserAvatarMediaDirectory } from '@services/media/media' @@ -8,12 +8,7 @@ type UserAvatarProps = { width?: number avatar_url?: string use_with_session?: boolean - rounded?: - | 'rounded-md' - | 'rounded-xl' - | 'rounded-lg' - | 'rounded-full' - | 'rounded' + rounded?: 'rounded-md' | 'rounded-xl' | 'rounded-lg' | 'rounded-full' | 'rounded' border?: 'border-2' | 'border-4' | 'border-8' borderColor?: string predefined_avatar?: 'ai' | 'empty' @@ -23,59 +18,62 @@ function UserAvatar(props: UserAvatarProps) { const session = useLHSession() as any const params = useParams() as any - function checkUrlProtocol(url: string): boolean { - return url.startsWith('https://') || url.startsWith('http://'); + const isValidUrl = (url: string): boolean => { + try { + new URL(url) + return true + } catch { + return false + } } - const predefinedAvatarFunc = () => { - if (props.predefined_avatar === 'ai') { - return getUriWithOrg(params.orgslug, '/ai_avatar.png') + const getAvatarUrl = (): string => { + // If avatar_url prop is provided and is a valid URL, use it directly + if (props.avatar_url && isValidUrl(props.avatar_url)) { + return props.avatar_url } - if (props.predefined_avatar === 'empty') { - return getUriWithOrg(params.orgslug, '/empty_avatar.png') + + // If user has an avatar in session and it's a valid URL, use it directly + if (session?.data?.user?.avatar_image && isValidUrl(session.data.user.avatar_image)) { + return session.data.user.avatar_image } - return null - } - const predefinedAvatar = predefinedAvatarFunc() - const emptyAvatar = getUriWithOrg(params.orgslug, '/empty_avatar.png') as any - const uploadedAvatar = (session.status == 'authenticated') && (checkUrlProtocol(session?.data?.user?.avatar_image)) ? session?.data?.user?.avatar_image : getUserAvatarMediaDirectory( - session?.data?.user?.user_uuid, - session?.data?.user?.avatar_image - ) - - const useAvatar = () => { + // If predefined avatar is specified if (props.predefined_avatar) { - return predefinedAvatar - } else { - if (props.avatar_url) { - return props.avatar_url - } else { - if (session?.data?.user?.avatar_image) { - return uploadedAvatar - } else { - return emptyAvatar - } - } + const avatarType = props.predefined_avatar === 'ai' ? 'ai_avatar.png' : 'empty_avatar.png' + return getUriWithOrg(params.orgslug, `/${avatarType}`) } + + // If avatar_url prop is provided but not a URL, process it + if (props.avatar_url) { + return props.avatar_url + } + + // If user has an avatar in session but not a URL, process it + if (session?.data?.user?.avatar_image) { + return getUserAvatarMediaDirectory(session.data.user.user_uuid, session.data.user.avatar_image) + } + + // Fallback to empty avatar + return getUriWithOrg(params.orgslug, '/empty_avatar.png') } - useEffect(() => { - - - - }, [session.status]) - return ( User Avatar ) } From f8974da088acc0f7f00a833c9ea7173213353bc1 Mon Sep 17 00:00:00 2001 From: swve Date: Mon, 24 Feb 2025 18:52:21 +0100 Subject: [PATCH 3/3] fix: userAvatar external image issues --- apps/web/components/Objects/UserAvatar.tsx | 48 +++++++++++++--------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/apps/web/components/Objects/UserAvatar.tsx b/apps/web/components/Objects/UserAvatar.tsx index f7abe357..ff62c3de 100644 --- a/apps/web/components/Objects/UserAvatar.tsx +++ b/apps/web/components/Objects/UserAvatar.tsx @@ -18,40 +18,50 @@ function UserAvatar(props: UserAvatarProps) { const session = useLHSession() as any const params = useParams() as any - const isValidUrl = (url: string): boolean => { - try { - new URL(url) - return true - } catch { - return false + const isExternalUrl = (url: string): boolean => { + return url.startsWith('http://') || url.startsWith('https://') + } + + const extractExternalUrl = (url: string): string | null => { + // Check if the URL contains an embedded external URL + const matches = url.match(/avatars\/(https?:\/\/[^/]+.*$)/) + if (matches && matches[1]) { + return matches[1] } + return null } const getAvatarUrl = (): string => { - // If avatar_url prop is provided and is a valid URL, use it directly - if (props.avatar_url && isValidUrl(props.avatar_url)) { - return props.avatar_url - } - - // If user has an avatar in session and it's a valid URL, use it directly - if (session?.data?.user?.avatar_image && isValidUrl(session.data.user.avatar_image)) { - return session.data.user.avatar_image - } - // If predefined avatar is specified if (props.predefined_avatar) { const avatarType = props.predefined_avatar === 'ai' ? 'ai_avatar.png' : 'empty_avatar.png' return getUriWithOrg(params.orgslug, `/${avatarType}`) } - // If avatar_url prop is provided but not a URL, process it + // If avatar_url prop is provided if (props.avatar_url) { + // Check if it's a malformed URL (external URL processed through getUserAvatarMediaDirectory) + const extractedUrl = extractExternalUrl(props.avatar_url) + if (extractedUrl) { + return extractedUrl + } + // If it's a direct external URL + if (isExternalUrl(props.avatar_url)) { + return props.avatar_url + } + // Otherwise use as is return props.avatar_url } - // If user has an avatar in session but not a URL, process it + // If user has an avatar in session if (session?.data?.user?.avatar_image) { - return getUserAvatarMediaDirectory(session.data.user.user_uuid, session.data.user.avatar_image) + const avatarUrl = session.data.user.avatar_image + // If it's an external URL (e.g., from Google, Facebook, etc.), use it directly + if (isExternalUrl(avatarUrl)) { + return avatarUrl + } + // Otherwise, get the local avatar URL + return getUserAvatarMediaDirectory(session.data.user.user_uuid, avatarUrl) } // Fallback to empty avatar