From 36be68cdcc83b57b92224e1a8047792df167d0d8 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Tue, 25 Apr 2023 15:27:39 -0400 Subject: [PATCH] Improve fallback of Group Avatars not loading --- app/soapbox/components/group-card.tsx | 18 ++----- app/soapbox/components/still-image.tsx | 2 +- app/soapbox/components/ui/avatar/avatar.tsx | 34 ++++++++++--- .../group/components/group-header-image.tsx | 50 +++++++++++++++++++ .../components/discover/group-grid-item.tsx | 12 ++--- 5 files changed, 88 insertions(+), 28 deletions(-) create mode 100644 app/soapbox/features/group/components/group-header-image.tsx diff --git a/app/soapbox/components/group-card.tsx b/app/soapbox/components/group-card.tsx index 2aee2bc7f..84e771902 100644 --- a/app/soapbox/components/group-card.tsx +++ b/app/soapbox/components/group-card.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { defineMessages, useIntl } from 'react-intl'; +import GroupHeaderImage from 'soapbox/features/group/components/group-header-image'; import GroupMemberCount from 'soapbox/features/group/components/group-member-count'; import GroupPrivacy from 'soapbox/features/group/components/group-privacy'; import GroupRelationship from 'soapbox/features/group/components/group-relationship'; @@ -10,17 +10,11 @@ import { HStack, Stack, Text } from './ui'; import type { Group as GroupEntity } from 'soapbox/types/entities'; -const messages = defineMessages({ - groupHeader: { id: 'group.header.alt', defaultMessage: 'Group header' }, -}); - interface IGroupCard { group: GroupEntity } const GroupCard: React.FC = ({ group }) => { - const intl = useIntl(); - return ( = ({ group }) => { > {/* Group Cover Image */} - {group.header && ( - {intl.formatMessage(messages.groupHeader)} - )} + {/* Group Avatar */} diff --git a/app/soapbox/components/still-image.tsx b/app/soapbox/components/still-image.tsx index 8632d3c5d..cdebaf359 100644 --- a/app/soapbox/components/still-image.tsx +++ b/app/soapbox/components/still-image.tsx @@ -3,7 +3,7 @@ import React, { useRef } from 'react'; import { useSettings } from 'soapbox/hooks'; -interface IStillImage { +export interface IStillImage { /** Image alt text. */ alt?: string /** Extra class names for the outer
container. */ diff --git a/app/soapbox/components/ui/avatar/avatar.tsx b/app/soapbox/components/ui/avatar/avatar.tsx index aef3c7147..762939776 100644 --- a/app/soapbox/components/ui/avatar/avatar.tsx +++ b/app/soapbox/components/ui/avatar/avatar.tsx @@ -1,34 +1,54 @@ import clsx from 'clsx'; -import React from 'react'; +import React, { useState } from 'react'; -import StillImage from 'soapbox/components/still-image'; +import StillImage, { IStillImage } from 'soapbox/components/still-image'; + +import Icon from '../icon/icon'; const AVATAR_SIZE = 42; -interface IAvatar { - /** URL to the avatar image. */ - src: string +interface IAvatar extends Pick { /** Width and height of the avatar in pixels. */ size?: number - /** Extra class names for the div surrounding the avatar image. */ - className?: string } /** Round profile avatar for accounts. */ const Avatar = (props: IAvatar) => { const { src, size = AVATAR_SIZE, className } = props; + const [isAvatarMissing, setIsAvatarMissing] = useState(false); + + const handleLoadFailure = () => setIsAvatarMissing(true); + const style: React.CSSProperties = React.useMemo(() => ({ width: size, height: size, }), [size]); + if (isAvatarMissing) { + return ( +
+ +
+ ); + } + return ( ); }; diff --git a/app/soapbox/features/group/components/group-header-image.tsx b/app/soapbox/features/group/components/group-header-image.tsx new file mode 100644 index 000000000..f40749536 --- /dev/null +++ b/app/soapbox/features/group/components/group-header-image.tsx @@ -0,0 +1,50 @@ +import clsx from 'clsx'; +import React, { useState } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; + +import { Icon } from 'soapbox/components/ui'; + +import type { Group } from 'soapbox/schemas'; + +const messages = defineMessages({ + header: { id: 'group.header.alt', defaultMessage: 'Group header' }, +}); + +interface IGroupHeaderImage { + group?: Group | false | null + className?: string +} + +const GroupHeaderImage: React.FC = ({ className, group }) => { + const intl = useIntl(); + + const [isHeaderMissing, setIsHeaderMissing] = useState(false); + + if (!group || !group.header) { + return null; + } + + if (isHeaderMissing) { + return ( +
+ +
+ ); + } + + return ( + {intl.formatMessage(messages.header)} setIsHeaderMissing(true)} + /> + ); +}; + +export default GroupHeaderImage; diff --git a/app/soapbox/features/groups/components/discover/group-grid-item.tsx b/app/soapbox/features/groups/components/discover/group-grid-item.tsx index da8f7542c..c30055971 100644 --- a/app/soapbox/features/groups/components/discover/group-grid-item.tsx +++ b/app/soapbox/features/groups/components/discover/group-grid-item.tsx @@ -4,6 +4,7 @@ import { Link } from 'react-router-dom'; import GroupAvatar from 'soapbox/components/groups/group-avatar'; import { HStack, Stack, Text } from 'soapbox/components/ui'; import GroupActionButton from 'soapbox/features/group/components/group-action-button'; +import GroupHeaderImage from 'soapbox/features/group/components/group-header-image'; import GroupMemberCount from 'soapbox/features/group/components/group-member-count'; import GroupPrivacy from 'soapbox/features/group/components/group-privacy'; @@ -31,13 +32,10 @@ const GroupGridItem = forwardRef((props: IGroup, ref: React.ForwardedRef - {group.header && ( - Group cover - )} +