Skip to content

Commit b0d576a

Browse files
Fix mobile profile page (#13591)
**1. Fixed crash on mobile profile page load** - **Issue**: Profile page was crashing with `TypeError: Cannot read properties of undefined (reading 'current')` when accessing tab refs - **Root cause**: `interpolateScale` function in `useTabs` hook was accessing `.current` on potentially undefined refs when tabs array was empty during loading - **Fix**: Added optional chaining (`?.`) when accessing refs in `interpolateScale` function - **File**: `packages/web/src/hooks/useTabs/useTabs.tsx` **2. Fixed empty tabs content on mobile profile page** - **Issue**: After fixing the crash, tabs were rendering but showing no content - **Root cause**: `profileTabs` and `profileElements` were being computed after `useTabs` hook was called, so the hook received empty arrays. React hooks don't react to variable assignments that happen after the hook call. - **Fix**: Moved tab and element computation into `useMemo` hook that runs before `useTabs`, matching the pattern used in the desktop version - **File**: `packages/web/src/pages/profile-page/components/mobile/ProfilePage.tsx` ### UI Improvements **3. Added spacing between coin icon and tier text in ProfilePageBadge** - Added `gap: 12px` to `.isCompact` container to create proper spacing between the coin icon and "TIER 3 GOLD" text - **File**: `packages/web/src/components/user-badges/ProfilePageBadge.module.css` **4. Added spacing between "Show More" link and tip button** - Added `margin-bottom: 16px` to `.expandDescription` class to improve visual separation - **File**: `packages/web/src/pages/profile-page/components/mobile/ProfileHeader.module.css` ### Testing - Verified mobile profile page loads without crashing - Confirmed tabs display content correctly for both artist and user profiles - Verified spacing improvements are visible in the UI
1 parent 3b22708 commit b0d576a

4 files changed

Lines changed: 116 additions & 90 deletions

File tree

packages/web/src/components/user-badges/ProfilePageBadge.module.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
border-radius: 8px;
2020
border: 1px solid var(--harmony-n-100);
2121
justify-content: center;
22+
gap: 12px;
2223
}
2324

2425
.isCompact.container svg {
2526
height: 28px;
2627
width: 28px;
27-
margin-right: 11px;
2828
}
2929

3030
.isCompact.container .text {
@@ -76,4 +76,4 @@
7676
width: 1px;
7777
height: 32px;
7878
margin: 0px 6px;
79-
}
79+
}

packages/web/src/hooks/useTabs/useTabs.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,8 @@ const TabBar = memo(
222222
// Ask Michael to explain this if necessary.
223223
const interpolateScale = (x: number) => {
224224
const [first, second] = [
225-
refsArr.current[0].current,
226-
refsArr.current[1].current
225+
refsArr.current[0]?.current,
226+
refsArr.current[1]?.current
227227
]
228228
if (!(first && second)) {
229229
return 'scale(1, 1)'

packages/web/src/pages/profile-page/components/mobile/ProfileHeader.module.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@
188188
.expandDescription {
189189
width: 100%;
190190
margin-top: 8px;
191+
margin-bottom: 16px;
191192
text-align: left;
192193
color: var(--harmony-primary);
193194
font-size: 14px;

packages/web/src/pages/profile-page/components/mobile/ProfilePage.tsx

Lines changed: 111 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useContext, RefObject } from 'react'
1+
import { useEffect, useContext, RefObject, useMemo } from 'react'
22

33
import { Status, User } from '@audius/common/models'
44
import {
@@ -182,99 +182,26 @@ const ProfilePage = ({ containerRef }: ProfilePageProps) => {
182182
setHeader(null)
183183
}, [setHeader])
184184

185-
const messages = getMessages({ name, isOwner })
186-
let content
187-
let profileTabs
188-
let profileElements
189185
const isLoading = status === Status.LOADING
190186
const isEditing = mode === 'editing'
191187

192-
// Set Nav-Bar Menu
193-
const { setLeft, setCenter, setRight } = useContext(NavContext)!
194-
useEffect(() => {
195-
let leftNav
196-
let rightNav
197-
if (isEditing) {
198-
leftNav = (
199-
<TextElement text='Cancel' type={Type.SECONDARY} onClick={onCancel} />
200-
)
201-
rightNav = (
202-
<TextElement
203-
text='Save'
204-
type={Type.PRIMARY}
205-
isEnabled={hasMadeEdit}
206-
onClick={onSave}
207-
/>
208-
)
209-
} else {
210-
leftNav = isOwner ? LeftPreset.SETTINGS : LeftPreset.BACK
211-
rightNav = <ShareUserButton userId={userId} />
188+
// Compute tabs and elements before calling useTabs
189+
const { profileTabs, profileElements } = useMemo(() => {
190+
if (!profile || isLoading || isEditing) {
191+
return { profileTabs: [], profileElements: [] }
212192
}
213-
if (userId) {
214-
setLeft(leftNav)
215-
setRight(rightNav)
216-
setCenter(CenterPreset.LOGO)
217-
}
218-
}, [
219-
setLeft,
220-
setCenter,
221-
setRight,
222-
userId,
223-
isOwner,
224-
isEditing,
225-
onCancel,
226-
onSave,
227-
hasMadeEdit
228-
])
229-
230-
const { tabs, body } = useTabs({
231-
didChangeTabsFrom,
232-
tabs: isLoading ? [] : profileTabs || [],
233-
elements: isLoading ? [] : profileElements || [],
234-
initialTab: activeTab || undefined,
235-
pathname: profilePage(handle)
236-
})
237193

238-
if (!profile) {
239-
return null
240-
}
194+
const tabMessages = getMessages({ name, isOwner })
241195

242-
const coverPhotoSizes = profile.cover_photo ?? null
243-
244-
if (isLoading) {
245-
content = null
246-
} else if (isEditing) {
247-
content = (
248-
<EditProfile
249-
name={name}
250-
bio={bio}
251-
location={location}
252-
xHandle={xHandle}
253-
instagramHandle={instagramHandle}
254-
tikTokHandle={tikTokHandle}
255-
twitterVerified={twitterVerified}
256-
instagramVerified={instagramVerified}
257-
tikTokVerified={tikTokVerified}
258-
website={website}
259-
onUpdateName={updateName}
260-
onUpdateBio={updateBio}
261-
onUpdateLocation={updateLocation}
262-
onUpdateXHandle={updateXHandle}
263-
onUpdateInstagramHandle={updateInstagramHandle}
264-
onUpdateTikTokHandle={updateTikTokHandle}
265-
onUpdateWebsite={updateWebsite}
266-
/>
267-
)
268-
} else {
269196
if (isArtist) {
270-
profileTabs = artistTabs
271-
profileElements = [
197+
const tabs = artistTabs
198+
const elements = [
272199
<div className={styles.tracksLineupContainer} key='artistTracks'>
273200
{profile.track_count === 0 ? (
274201
<EmptyTab
275202
message={
276203
<>
277-
{messages.emptyTracks}
204+
{tabMessages.emptyTracks}
278205
<i
279206
className={cn('emoji', 'face-with-monocle', styles.emoji)}
280207
/>
@@ -305,7 +232,7 @@ const ProfilePage = ({ containerRef }: ProfilePageProps) => {
305232
<EmptyTab
306233
message={
307234
<>
308-
{messages.emptyReposts}
235+
{tabMessages.emptyReposts}
309236
<i
310237
className={cn('emoji', 'face-with-monocle', styles.emoji)}
311238
/>
@@ -324,15 +251,16 @@ const ProfilePage = ({ containerRef }: ProfilePageProps) => {
324251
)}
325252
</div>
326253
]
254+
return { profileTabs: tabs, profileElements: elements }
327255
} else {
328-
profileTabs = userTabs
329-
profileElements = [
256+
const tabs = userTabs
257+
const elements = [
330258
<div className={styles.tracksLineupContainer} key='tracks'>
331259
{profile.repost_count === 0 ? (
332260
<EmptyTab
333261
message={
334262
<>
335-
{messages.emptyReposts}
263+
{tabMessages.emptyReposts}
336264
<i
337265
className={cn('emoji', 'face-with-monocle', styles.emoji)}
338266
/>
@@ -354,7 +282,104 @@ const ProfilePage = ({ containerRef }: ProfilePageProps) => {
354282
<PlaylistsTab isOwner={isOwner} profile={profile} userId={userId} />
355283
</div>
356284
]
285+
return { profileTabs: tabs, profileElements: elements }
286+
}
287+
}, [
288+
profile,
289+
isLoading,
290+
isEditing,
291+
isArtist,
292+
isOwner,
293+
userId,
294+
name,
295+
artistTracks,
296+
userFeed,
297+
getLineupProps,
298+
loadMoreArtistTracks,
299+
loadMoreUserFeed,
300+
playArtistTrack,
301+
pauseArtistTrack,
302+
playUserFeedTrack,
303+
pauseUserFeedTrack
304+
])
305+
306+
// Set Nav-Bar Menu
307+
const { setLeft, setCenter, setRight } = useContext(NavContext)!
308+
useEffect(() => {
309+
let leftNav
310+
let rightNav
311+
if (isEditing) {
312+
leftNav = (
313+
<TextElement text='Cancel' type={Type.SECONDARY} onClick={onCancel} />
314+
)
315+
rightNav = (
316+
<TextElement
317+
text='Save'
318+
type={Type.PRIMARY}
319+
isEnabled={hasMadeEdit}
320+
onClick={onSave}
321+
/>
322+
)
323+
} else {
324+
leftNav = isOwner ? LeftPreset.SETTINGS : LeftPreset.BACK
325+
rightNav = <ShareUserButton userId={userId} />
326+
}
327+
if (userId) {
328+
setLeft(leftNav)
329+
setRight(rightNav)
330+
setCenter(CenterPreset.LOGO)
357331
}
332+
}, [
333+
setLeft,
334+
setCenter,
335+
setRight,
336+
userId,
337+
isOwner,
338+
isEditing,
339+
onCancel,
340+
onSave,
341+
hasMadeEdit
342+
])
343+
344+
const { tabs, body } = useTabs({
345+
didChangeTabsFrom,
346+
tabs: profileTabs,
347+
elements: profileElements,
348+
initialTab: activeTab || undefined,
349+
pathname: profilePage(handle)
350+
})
351+
352+
if (!profile) {
353+
return null
354+
}
355+
356+
const coverPhotoSizes = profile.cover_photo ?? null
357+
358+
let content
359+
if (isLoading) {
360+
content = null
361+
} else if (isEditing) {
362+
content = (
363+
<EditProfile
364+
name={name}
365+
bio={bio}
366+
location={location}
367+
xHandle={xHandle}
368+
instagramHandle={instagramHandle}
369+
tikTokHandle={tikTokHandle}
370+
twitterVerified={twitterVerified}
371+
instagramVerified={instagramVerified}
372+
tikTokVerified={tikTokVerified}
373+
website={website}
374+
onUpdateName={updateName}
375+
onUpdateBio={updateBio}
376+
onUpdateLocation={updateLocation}
377+
onUpdateXHandle={updateXHandle}
378+
onUpdateInstagramHandle={updateInstagramHandle}
379+
onUpdateTikTokHandle={updateTikTokHandle}
380+
onUpdateWebsite={updateWebsite}
381+
/>
382+
)
358383
}
359384

360385
if (profile.is_deactivated) {

0 commit comments

Comments
 (0)