From 6f612064bd47f021f514f2c409b2c7225f4cf162 Mon Sep 17 00:00:00 2001 From: Dylan Audius Date: Wed, 22 Jul 2026 12:36:29 -0700 Subject: [PATCH 1/2] fix(profile): polish the deactivated account screen on web and mobile The deactivated profile page rendered its message pinned to the top of a mostly empty page, with no cover photo and a profile picture stuck on a loading skeleton. - Rebuild the tombstone as a centered empty state (heading + supporting copy + button) using harmony primitives, replacing the CSS module and its negative-margin hack for the desktop left column. - Desktop: pass the real userId to CoverPhoto/ProfilePicture. The image hooks already scrub deactivated accounts' images and return the default placeholders; withholding the id left useImageSize unresolved, so no cover photo rendered and the avatar never left its skeleton. - Mobile web: show the default avatar instead of an empty src. - Native: fill the viewport so the message is vertically centered, and show the default cover photo and avatar. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../DeactivatedProfileTombstone.tsx | 59 +++++++++++++++---- .../DeactivatedProfileTombstone.module.css | 28 --------- .../DeactivatedProfileTombstone.tsx | 49 ++++++++++----- .../components/desktop/ProfilePage.tsx | 15 +++-- .../components/mobile/ProfileHeader.tsx | 9 ++- 5 files changed, 96 insertions(+), 64 deletions(-) delete mode 100644 packages/web/src/pages/profile-page/components/DeactivatedProfileTombstone.module.css diff --git a/packages/mobile/src/screens/profile-screen/DeactivatedProfileTombstone.tsx b/packages/mobile/src/screens/profile-screen/DeactivatedProfileTombstone.tsx index bb8bc3e9406..c7005903b57 100644 --- a/packages/mobile/src/screens/profile-screen/DeactivatedProfileTombstone.tsx +++ b/packages/mobile/src/screens/profile-screen/DeactivatedProfileTombstone.tsx @@ -2,13 +2,25 @@ import { useCallback } from 'react' import { route } from '@audius/common/utils' import { useLinkTo } from '@react-navigation/native' +import { ImageBackground } from 'react-native' -import { Button, Flex, IconArrowRight, Text } from '@audius/harmony-native' +import { + Avatar, + Button, + Flex, + IconArrowRight, + Text +} from '@audius/harmony-native' +import imageCoverPhotoBlank from 'app/assets/images/imageCoverPhotoBlank.jpg' +import imageProfilePicEmpty from 'app/assets/images/imageProfilePicEmpty2X.png' const { FEED_PAGE } = route +const COVER_PHOTO_HEIGHT = 96 + const messages = { helpText: 'This Account No Longer Exists', + description: 'The account you’re looking for has been deleted.', buttonText: 'Take Me Back To The Music' } @@ -20,18 +32,41 @@ export const DeactivatedProfileTombstone = () => { }, [linkTo]) return ( - - - {messages.helpText} - - + + + + {messages.helpText} + + + {messages.description} + + + + ) } diff --git a/packages/web/src/pages/profile-page/components/DeactivatedProfileTombstone.module.css b/packages/web/src/pages/profile-page/components/DeactivatedProfileTombstone.module.css deleted file mode 100644 index 3e1e0e6b656..00000000000 --- a/packages/web/src/pages/profile-page/components/DeactivatedProfileTombstone.module.css +++ /dev/null @@ -1,28 +0,0 @@ -.deactivated { - color: var(--harmony-neutral); - font-size: var(--harmony-font-m); - font-weight: var(--harmony-font-medium); - text-align: center; - - margin-top: 38px; - padding: 3px; - user-select: none; -} -.deactivated:not(.mobile) { - /** Offset to undo desktop left padding **/ - width: calc(100% + 229px); - margin-left: -229px; -} -.deactivatedText { - margin: 42px 0; - padding: 22px 0; -} - -.mobile.deactivated { - margin: 0; - padding: 24px 12px; - background-color: var(--harmony-white); -} -.mobile .deactivatedText { - margin: 24px 0 12px; -} diff --git a/packages/web/src/pages/profile-page/components/DeactivatedProfileTombstone.tsx b/packages/web/src/pages/profile-page/components/DeactivatedProfileTombstone.tsx index f07f16467ed..6db6cb684ca 100644 --- a/packages/web/src/pages/profile-page/components/DeactivatedProfileTombstone.tsx +++ b/packages/web/src/pages/profile-page/components/DeactivatedProfileTombstone.tsx @@ -1,14 +1,12 @@ import { route } from '@audius/common/utils' -import { Button, IconArrowRight } from '@audius/harmony' -import cn from 'classnames' +import { Button, Flex, IconArrowRight, Text } from '@audius/harmony' import { Link } from 'react-router' -import styles from './DeactivatedProfileTombstone.module.css' - const { HOME_PAGE } = route const messages = { helpText: 'This Account No Longer Exists', + description: 'The account you’re looking for has been deleted.', buttonText: 'Take Me Back To The Music' } @@ -18,16 +16,39 @@ export const DeactivatedProfileTombstone = ({ isMobile?: boolean }) => { return ( -
-
{messages.helpText}
- -
+ + + {messages.helpText} + + + {messages.description} + + + + + ) } diff --git a/packages/web/src/pages/profile-page/components/desktop/ProfilePage.tsx b/packages/web/src/pages/profile-page/components/desktop/ProfilePage.tsx index 35d4317e55e..66a91b2c85f 100644 --- a/packages/web/src/pages/profile-page/components/desktop/ProfilePage.tsx +++ b/packages/web/src/pages/profile-page/components/desktop/ProfilePage.tsx @@ -450,8 +450,12 @@ const ProfilePage = ({ containerRef }: ProfilePageProps) => { fromOpacity={1} > + {/* `useCoverPhoto`/`useProfilePicture` already scrub deactivated + accounts' images and return the default placeholders, so pass the + real userId — withholding it leaves the hooks unresolved and the + images stuck on a loading skeleton. */} { > {/* @ts-ignore */} { - - - - {status === Status.SUCCESS && } - - + {status === Status.SUCCESS && } ) : ( diff --git a/packages/web/src/pages/profile-page/components/mobile/ProfileHeader.tsx b/packages/web/src/pages/profile-page/components/mobile/ProfileHeader.tsx index 945cbfa9c90..cd355843cee 100644 --- a/packages/web/src/pages/profile-page/components/mobile/ProfileHeader.tsx +++ b/packages/web/src/pages/profile-page/components/mobile/ProfileHeader.tsx @@ -1,7 +1,10 @@ import { useState, useRef, useCallback, useEffect } from 'react' import { useArtistCreatedFanClub } from '@audius/common/api' -import { imageCoverPhotoBlank } from '@audius/common/assets' +import { + imageCoverPhotoBlank, + imageProfilePicEmpty +} from '@audius/common/assets' import { Name, SquareSizes, @@ -297,7 +300,9 @@ const ProfileHeader = ({ {messages.profilePicAltText} Date: Thu, 23 Jul 2026 13:39:27 -0700 Subject: [PATCH 2/2] fix(profile): fill the viewport on the deactivated profile page (mobile web) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tombstone was capped at `clamp(240px, 32vh, 420px)`, so on mobile web it ended partway down the screen and left roughly half the viewport as dead grey space below the button. Mirror what the native screen already does and let the tombstone flex to fill instead: - Stretch the mobile page container to the scroll area for deactivated profiles (`min-height: 100%`, not `100dvh` — the latter overflows by the height of the announcement banner stacked above it). - Give the tombstone `flex: 1` on mobile web and render it as a direct child of the container so the flex line applies. Desktop keeps the fixed minimum, since that page is sized by its content. - Stop `.headerContainer` (`flex: 1`) from growing when deactivated. Otherwise the header and tombstone split the free space and a ~200px blank band opens up between the avatar and the message. Verified on /cyranomusic under iPhone 13 emulation: page height now equals the viewport with zero overflow, and a normal profile is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../components/DeactivatedProfileTombstone.tsx | 11 +++++++++-- .../components/mobile/ProfileHeader.module.css | 10 ++++++++++ .../components/mobile/ProfileHeader.tsx | 6 +++++- .../components/mobile/ProfilePage.module.css | 15 +++++++++++++++ .../components/mobile/ProfilePage.tsx | 12 ++++++------ 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/packages/web/src/pages/profile-page/components/DeactivatedProfileTombstone.tsx b/packages/web/src/pages/profile-page/components/DeactivatedProfileTombstone.tsx index 6db6cb684ca..8e8228839f9 100644 --- a/packages/web/src/pages/profile-page/components/DeactivatedProfileTombstone.tsx +++ b/packages/web/src/pages/profile-page/components/DeactivatedProfileTombstone.tsx @@ -23,8 +23,15 @@ export const DeactivatedProfileTombstone = ({ justifyContent='center' p='xl' // Fill the space below the profile header so the message reads as a - // centered empty state rather than being pinned to the top. - css={{ minHeight: 'clamp(240px, 32vh, 420px)', userSelect: 'none' }} + // centered empty state rather than being pinned to the top. On mobile + // web the page container is stretched to the viewport, so `flex: 1` + // claims everything below the header; on desktop the page is sized by + // its content, so fall back to a fixed minimum. + flex={isMobile ? 1 : undefined} + css={{ + minHeight: isMobile ? undefined : 'clamp(240px, 32vh, 420px)', + userSelect: 'none' + }} > +
{ } if (profile.is_deactivated) { - content = ( -
- -
- ) + // Rendered as a direct child of the page container (a flex column) so the + // tombstone's `flex: 1` can claim the space left below the header. + content = } else if (!isLoading && !isEditing) { content = (
@@ -463,7 +461,9 @@ const ProfilePage = ({ containerRef }: ProfilePageProps) => { structuredData={structuredData} entityType='user' hashId={profile?.user_id ? Id.parse(profile.user_id) : undefined} - containerClassName={styles.container} + containerClassName={cn(styles.container, { + [styles.deactivatedContainer]: profile.is_deactivated + })} >