[APP-1403] Profile empty states (#8969)

* update: Empty state component

* update type error fixes

* add empty state icon

* update translations on labels

* update: lint error for empty state

* remove unused prompt

* update type errors

* fix lint errors and update profile followers and profile follows

* updated starterpack

* fix lint errors

* address feedback

* update icons to be a react element

* update icons

* update viewbox for icons

* optimize: icons and update profile lists

* optimize: icons and update profile lists

* lint error fix

* update iconSize from the rebase
This commit is contained in:
Anastasiya Uraleva
2025-11-27 03:22:03 -08:00
committed by GitHub
parent 2d056e5be6
commit d0649e9a9e
44 changed files with 551 additions and 124 deletions
+10 -1
View File
@@ -20,6 +20,7 @@ import {Text} from '#/view/com/util/text/Text'
import * as Toast from '#/view/com/util/Toast'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {ViewSelector} from '#/view/com/util/ViewSelector'
import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag'
import * as Layout from '#/components/Layout'
const MAIN_VIEWS = ['Base', 'Controls', 'Error', 'Notifs']
@@ -333,7 +334,15 @@ function TypographyView() {
}
function EmptyStateView() {
return <EmptyState icon="bars" message="This is an empty state" />
const {_} = useLingui()
return (
<EmptyState
icon={HashtagWideIcon}
iconSize="2xl"
message={_(msg`This is an empty state`)}
/>
)
}
function LoadingPlaceholderView() {
+63 -2
View File
@@ -7,17 +7,19 @@ import {
type ModerationOpts,
RichText as RichTextAPI,
} from '@atproto/api'
import {msg} from '@lingui/macro'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native'
import {useFocusEffect, useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
import {useSetTitle} from '#/lib/hooks/useSetTitle'
import {ComposeIcon2} from '#/lib/icons'
import {
type CommonNavigatorParams,
type NativeStackScreenProps,
type NavigationProp,
} from '#/lib/routes/types'
import {combinedDisplayName} from '#/lib/strings/display-names'
import {cleanError} from '#/lib/strings/errors'
@@ -42,6 +44,11 @@ import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header'
import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed'
import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels'
import {atoms as a} from '#/alf'
import {Circle_And_Square_Stroke1_Corner0_Rounded_Filled as CircleAndSquareIcon} from '#/components/icons/CircleAndSquare'
import {Heart2_Stroke1_Corner0_Rounded as HeartIcon} from '#/components/icons/Heart2'
import {Image_Stroke1_Corner0_Rounded as ImageIcon} from '#/components/icons/Image'
import {Message_Stroke1_Corner0_Rounded_Filled as MessageIcon} from '#/components/icons/Message'
import {VideoClip_Stroke1_Corner0_Rounded as VideoIcon} from '#/components/icons/VideoClip'
import * as Layout from '#/components/Layout'
import {ScreenHider} from '#/components/moderation/ScreenHider'
import {ProfileStarterPacks} from '#/components/StarterPack/ProfileStarterPacks'
@@ -169,6 +176,8 @@ function ProfileScreenLoaded({
const {hasSession, currentAccount} = useSession()
const setMinimalShellMode = useSetMinimalShellMode()
const {openComposer} = useOpenComposer()
const navigation = useNavigation<NavigationProp>()
const requireEmailVerification = useRequireEmailVerification()
const {
data: labelerInfo,
error: labelerError,
@@ -334,6 +343,17 @@ function ProfileScreenLoaded({
scrollSectionToTop(index)
}
const navToWizard = useCallback(() => {
navigation.navigate('StarterPackWizard', {})
}, [navigation])
const wrappedNavToWizard = requireEmailVerification(navToWizard, {
instructions: [
<Trans key="nav">
Before creating a starter pack, you must first verify your email.
</Trans>,
],
})
// rendering
// =
@@ -408,6 +428,14 @@ function ProfileScreenLoaded({
scrollElRef={scrollElRef as ListRef}
ignoreFilterFor={profile.did}
setScrollViewTag={setScrollViewTag}
emptyStateMessage={_(msg`No posts yet`)}
emptyStateButton={{
label: _(msg`Write a post`),
text: _(msg`Write a post`),
onPress: () => openComposer({}),
size: 'small',
color: 'primary',
}}
/>
)
: null}
@@ -421,6 +449,8 @@ function ProfileScreenLoaded({
scrollElRef={scrollElRef as ListRef}
ignoreFilterFor={profile.did}
setScrollViewTag={setScrollViewTag}
emptyStateMessage={_(msg`No replies yet`)}
emptyStateIcon={MessageIcon}
/>
)
: null}
@@ -434,6 +464,15 @@ function ProfileScreenLoaded({
scrollElRef={scrollElRef as ListRef}
ignoreFilterFor={profile.did}
setScrollViewTag={setScrollViewTag}
emptyStateMessage={_(msg`No media yet`)}
emptyStateButton={{
label: _(msg`Post a photo`),
text: _(msg`Post a photo`),
onPress: () => openComposer({}),
size: 'small',
color: 'primary',
}}
emptyStateIcon={ImageIcon}
/>
)
: null}
@@ -447,6 +486,15 @@ function ProfileScreenLoaded({
scrollElRef={scrollElRef as ListRef}
ignoreFilterFor={profile.did}
setScrollViewTag={setScrollViewTag}
emptyStateMessage={_(msg`No video posts yet`)}
emptyStateButton={{
label: _(msg`Post a video`),
text: _(msg`Post a video`),
onPress: () => openComposer({}),
size: 'small',
color: 'primary',
}}
emptyStateIcon={VideoIcon}
/>
)
: null}
@@ -460,6 +508,8 @@ function ProfileScreenLoaded({
scrollElRef={scrollElRef as ListRef}
ignoreFilterFor={profile.did}
setScrollViewTag={setScrollViewTag}
emptyStateMessage={_(msg`No likes yet`)}
emptyStateIcon={HeartIcon}
/>
)
: null}
@@ -485,6 +535,17 @@ function ProfileScreenLoaded({
headerOffset={headerHeight}
enabled={isFocused}
setScrollViewTag={setScrollViewTag}
emptyStateMessage={_(
msg`Starter packs let you share your favorite feeds and people with your friends.`,
)}
emptyStateButton={{
label: _(msg`Create a Starter Pack`),
text: _(msg`Create a Starter Pack`),
onPress: wrappedNavToWizard,
color: 'primary',
size: 'small',
}}
emptyStateIcon={CircleAndSquareIcon}
/>
)
: null}