From 9758c59f64dd0a73cca7e9cc8f6633f6ea1d5e85 Mon Sep 17 00:00:00 2001 From: Echo Date: Thu, 2 Apr 2026 14:33:32 +0200 Subject: [PATCH] [Glitch] Profile redesign: Account featured tab Port 8da2fae6ccd50158171a7caa52a809da2e146c26 to glitch-soc Co-authored-by: diondiondion Signed-off-by: Claire --- .../empty_state/empty_state.module.scss | 7 + .../glitch/components/empty_state/index.tsx | 17 ++- .../components/empty_message.tsx | 120 ++++++++++++++---- .../components/featured_tag.tsx | 52 -------- .../features/account_featured/index.tsx | 48 +------ .../account_timeline/components/tabs.tsx | 11 +- .../flavours/glitch/hooks/useTheme.ts | 23 ++++ 7 files changed, 148 insertions(+), 130 deletions(-) delete mode 100644 app/javascript/flavours/glitch/features/account_featured/components/featured_tag.tsx create mode 100644 app/javascript/flavours/glitch/hooks/useTheme.ts diff --git a/app/javascript/flavours/glitch/components/empty_state/empty_state.module.scss b/app/javascript/flavours/glitch/components/empty_state/empty_state.module.scss index 1707b3bc08..bdab2f2732 100644 --- a/app/javascript/flavours/glitch/components/empty_state/empty_state.module.scss +++ b/app/javascript/flavours/glitch/components/empty_state/empty_state.module.scss @@ -10,6 +10,13 @@ } .content { + svg, + img { + width: 240px; + max-width: 100%; + margin-bottom: 16px; + } + h3 { font-size: 17px; font-weight: 500; diff --git a/app/javascript/flavours/glitch/components/empty_state/index.tsx b/app/javascript/flavours/glitch/components/empty_state/index.tsx index 93f034f3e9..59210bbc87 100644 --- a/app/javascript/flavours/glitch/components/empty_state/index.tsx +++ b/app/javascript/flavours/glitch/components/empty_state/index.tsx @@ -9,10 +9,12 @@ import classes from './empty_state.module.scss'; */ export const EmptyState: React.FC<{ - title?: string | React.ReactElement; - message?: string | React.ReactElement; + image?: React.ReactNode; + title?: React.ReactNode; + message?: React.ReactNode; children?: React.ReactNode; }> = ({ + image, title = ( ), @@ -21,10 +23,13 @@ export const EmptyState: React.FC<{ }) => { return (
-
-

{title}

- {!!message &&

{message}

} -
+ {(title || message || image) && ( +
+ {image} + {!!title &&

{title}

} + {!!message &&

{message}

} +
+ )} {children}
diff --git a/app/javascript/flavours/glitch/features/account_featured/components/empty_message.tsx b/app/javascript/flavours/glitch/features/account_featured/components/empty_message.tsx index 1a38022ea6..301283b520 100644 --- a/app/javascript/flavours/glitch/features/account_featured/components/empty_message.tsx +++ b/app/javascript/flavours/glitch/features/account_featured/components/empty_message.tsx @@ -1,9 +1,15 @@ import { FormattedMessage } from 'react-intl'; import { useParams } from 'react-router'; +import { Link } from 'react-router-dom'; -import { LimitedAccountHint } from 'flavours/glitch/features/account_timeline/components/limited_account_hint'; -import { me } from 'flavours/glitch/initial_state'; +import { EmptyState } from '@/flavours/glitch/components/empty_state'; +import { LimitedAccountHint } from '@/flavours/glitch/features/account_timeline/components/limited_account_hint'; +import { areCollectionsEnabled } from '@/flavours/glitch/features/collections/utils'; +import { useCurrentAccountId } from '@/flavours/glitch/hooks/useAccountId'; +import { useTheme } from '@/flavours/glitch/hooks/useTheme'; +import ElephantDarkImage from '@/images/elephant_ui_dark.svg?react'; +import ElephantLightImage from '@/images/elephant_ui_light.svg?react'; interface EmptyMessageProps { suspended: boolean; @@ -19,21 +25,59 @@ export const EmptyMessage: React.FC = ({ blockedBy, }) => { const { acct } = useParams<{ acct?: string }>(); + const me = useCurrentAccountId(); + const theme = useTheme(); + const ElephantImage = + theme === 'dark' ? ElephantDarkImage : ElephantLightImage; + if (!accountId) { return null; } + let title: React.ReactNode = null; let message: React.ReactNode = null; + const hasCollections = areCollectionsEnabled(); + + const image = ; + if (me === accountId) { - message = ( - - ); + if (hasCollections) { + // Return only here to insert the "Create a collection" button as the action for the empty state. + return ( + + } + > + + + + + ); + } else { + title = ( + + ); + message = ( + + ); + } } else if (suspended) { - message = ( + title = ( = ({ } else if (hidden) { message = ; } else if (blockedBy) { - message = ( + title = ( ); - } else if (acct) { - message = ( - - ); } else { - message = ( + // Standard other account empty state. + title = ( ); + if (hasCollections) { + if (acct) { + message = ( + + ); + } else { + message = ( + + ); + } + } else { + if (acct) { + message = ( + + ); + } else { + message = ( + + ); + } + } } - return ( -
- {message} -
- ); + return ; }; diff --git a/app/javascript/flavours/glitch/features/account_featured/components/featured_tag.tsx b/app/javascript/flavours/glitch/features/account_featured/components/featured_tag.tsx deleted file mode 100644 index 274a4c10eb..0000000000 --- a/app/javascript/flavours/glitch/features/account_featured/components/featured_tag.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { defineMessages, useIntl } from 'react-intl'; - -import type { Map as ImmutableMap } from 'immutable'; - -import { Hashtag } from 'flavours/glitch/components/hashtag'; - -export type TagMap = ImmutableMap< - 'id' | 'name' | 'url' | 'statuses_count' | 'last_status_at' | 'accountId', - string | null ->; - -interface FeaturedTagProps { - tag: TagMap; - account: string; -} - -const messages = defineMessages({ - lastStatusAt: { - id: 'account.featured_tags.last_status_at', - defaultMessage: 'Last post on {date}', - }, - empty: { - id: 'account.featured_tags.last_status_never', - defaultMessage: 'No posts', - }, -}); - -export const FeaturedTag: React.FC = ({ tag, account }) => { - const intl = useIntl(); - const name = tag.get('name') ?? ''; - const count = Number.parseInt(tag.get('statuses_count') ?? ''); - return ( - 0 - ? intl.formatMessage(messages.lastStatusAt, { - date: intl.formatDate(tag.get('last_status_at') ?? '', { - month: 'short', - day: '2-digit', - year: 'numeric', - }), - }) - : intl.formatMessage(messages.empty) - } - /> - ); -}; diff --git a/app/javascript/flavours/glitch/features/account_featured/index.tsx b/app/javascript/flavours/glitch/features/account_featured/index.tsx index eb420097ee..60e9531318 100644 --- a/app/javascript/flavours/glitch/features/account_featured/index.tsx +++ b/app/javascript/flavours/glitch/features/account_featured/index.tsx @@ -8,7 +8,6 @@ import { List as ImmutableList } from 'immutable'; import { useAccount } from '@/flavours/glitch/hooks/useAccount'; import { fetchEndorsedAccounts } from 'flavours/glitch/actions/accounts'; -import { fetchFeaturedTags } from 'flavours/glitch/actions/featured_tags'; import { Account } from 'flavours/glitch/components/account'; import { ColumnBackButton } from 'flavours/glitch/components/column_back_button'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; @@ -33,8 +32,6 @@ import { CollectionListItem } from '../collections/detail/collection_list_item'; import { areCollectionsEnabled } from '../collections/utils'; import { EmptyMessage } from './components/empty_message'; -import { FeaturedTag } from './components/featured_tag'; -import type { TagMap } from './components/featured_tag'; const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({ multiColumn, @@ -55,26 +52,15 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({ useEffect(() => { if (accountId) { - void dispatch(fetchFeaturedTags({ accountId })); void dispatch(fetchEndorsedAccounts({ accountId })); + if (areCollectionsEnabled()) { void dispatch(fetchAccountCollections({ accountId })); } } }, [accountId, dispatch]); - const isLoading = useAppSelector( - (state) => - !accountId || - !!state.user_lists.getIn(['featured_tags', accountId, 'isLoading']), - ); - const featuredTags = useAppSelector( - (state) => - state.user_lists.getIn( - ['featured_tags', accountId, 'items'], - ImmutableList(), - ) as ImmutableList, - ); + const isLoading = !accountId; const featuredAccountIds = useAppSelector( (state) => state.user_lists.getIn( @@ -106,13 +92,7 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({ ); } - const noTags = featuredTags.isEmpty(); - - if ( - noTags && - featuredAccountIds.isEmpty() && - listedCollections.length === 0 - ) { + if (featuredAccountIds.isEmpty() && listedCollections.length === 0) { return ( = ({ )} - {!noTags && ( - <> -

- -

- - {featuredTags.map((tag, index) => ( -
- -
- ))} -
- - )} {!featuredAccountIds.isEmpty() && ( <>

diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/tabs.tsx b/app/javascript/flavours/glitch/features/account_timeline/components/tabs.tsx index 9d6229d8ba..d37c5db812 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/tabs.tsx +++ b/app/javascript/flavours/glitch/features/account_timeline/components/tabs.tsx @@ -8,6 +8,8 @@ import { NavLink } from 'react-router-dom'; import { useAccount } from '@/flavours/glitch/hooks/useAccount'; import { useAccountId } from '@/flavours/glitch/hooks/useAccountId'; +import { areCollectionsEnabled } from '../../collections/utils'; + import classes from './redesign.module.scss'; const isActive: Required['isActive'] = (match, location) => @@ -39,7 +41,14 @@ export const AccountTabs: FC = () => { )} {show_featured && ( - + {areCollectionsEnabled() ? ( + + ) : ( + + )} )} diff --git a/app/javascript/flavours/glitch/hooks/useTheme.ts b/app/javascript/flavours/glitch/hooks/useTheme.ts new file mode 100644 index 0000000000..9c67efaf58 --- /dev/null +++ b/app/javascript/flavours/glitch/hooks/useTheme.ts @@ -0,0 +1,23 @@ +import { useEffect, useState } from 'react'; + +import { isDarkMode } from '../utils/theme'; + +export function useTheme() { + const [darkMode, setDarkMode] = useState(() => isDarkMode()); + + useEffect(() => { + const mutationObserver = new MutationObserver(() => { + setDarkMode(isDarkMode()); + }); + mutationObserver.observe(document.documentElement, { + attributes: true, + attributeFilter: ['data-color-scheme'], + }); + + return () => { + mutationObserver.disconnect(); + }; + }, []); + + return darkMode ? 'dark' : 'light'; +}