Add minimal header to profiles (#8936)

This commit is contained in:
Samuel Newman
2025-11-20 18:31:41 +02:00
committed by GitHub
parent dfe9f9fa07
commit 13c4ef83f1
5 changed files with 425 additions and 303 deletions
@@ -23,7 +23,6 @@ import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useRequireAuth, useSession} from '#/state/session'
import {ProfileMenu} from '#/view/com/profile/ProfileMenu'
import * as Toast from '#/view/com/util/Toast'
import {atoms as a, tokens, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import {type DialogOuterProps, useDialogControl} from '#/components/Dialog'
@@ -34,6 +33,7 @@ import {
import {Link} from '#/components/Link'
import * as Prompt from '#/components/Prompt'
import {RichText} from '#/components/RichText'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {ProfileHeaderDisplayName} from './DisplayName'
import {EditProfileDialog} from './EditProfileDialog'
@@ -63,28 +63,17 @@ let ProfileHeaderLabeler = ({
const t = useTheme()
const {_} = useLingui()
const {currentAccount, hasSession} = useSession()
const requireAuth = useRequireAuth()
const playHaptic = useHaptics()
const cantSubscribePrompt = Prompt.usePromptControl()
const isSelf = currentAccount?.did === profile.did
const moderation = useMemo(
() => moderateProfile(profile, moderationOpts),
[profile, moderationOpts],
)
const {data: preferences} = usePreferencesQuery()
const {
mutateAsync: toggleSubscription,
variables,
reset,
} = useLabelerSubscriptionMutation()
const isSubscribed =
variables?.subscribe ??
preferences?.moderationPrefs.labelers.find(l => l.did === profile.did)
const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation()
const {mutateAsync: unlikeMod, isPending: isUnlikePending} =
useUnlikeMutation()
const [likeUri, setLikeUri] = useState<string>(labeler.viewer?.like || '')
const [likeUri, setLikeUri] = useState(labeler.viewer?.like || '')
const [likeCount, setLikeCount] = useState(labeler.likeCount || 0)
const onToggleLiked = useCallback(async () => {
@@ -108,56 +97,12 @@ let ProfileHeaderLabeler = ({
_(
msg`There was an issue contacting the server, please check your internet connection and try again.`,
),
'xmark',
{type: 'error'},
)
logger.error(`Failed to toggle labeler like`, {message: e.message})
}
}, [labeler, playHaptic, likeUri, unlikeMod, likeMod, _])
const editProfileControl = useDialogControl()
const onPressSubscribe = useCallback(() => {
requireAuth(async (): Promise<void> => {
playHaptic()
const subscribe = !isSubscribed
try {
await toggleSubscription({
did: profile.did,
subscribe,
})
logger.metric(
subscribe
? 'moderation:subscribedToLabeler'
: 'moderation:unsubscribedFromLabeler',
{},
{statsig: true},
)
} catch (e: any) {
reset()
if (e.message === 'MAX_LABELERS') {
cantSubscribePrompt.open()
return
}
logger.error(`Failed to subscribe to labeler`, {message: e.message})
}
})
}, [
playHaptic,
requireAuth,
toggleSubscription,
isSubscribed,
profile,
cantSubscribePrompt,
reset,
])
const isMe = useMemo(
() => currentAccount?.did === profile.did,
[currentAccount, profile],
)
return (
<ProfileHeaderShell
profile={profile}
@@ -170,75 +115,7 @@ let ProfileHeaderLabeler = ({
<View
style={[a.flex_row, a.justify_end, a.align_center, a.gap_xs, a.pb_lg]}
pointerEvents={isIOS ? 'auto' : 'box-none'}>
{isMe ? (
<>
<Button
testID="profileHeaderEditProfileButton"
size="small"
color="secondary"
variant="solid"
onPress={editProfileControl.open}
label={_(msg`Edit profile`)}
style={a.rounded_full}>
<ButtonText>
<Trans>Edit Profile</Trans>
</ButtonText>
</Button>
<EditProfileDialog
profile={profile}
control={editProfileControl}
/>
</>
) : !isAppLabeler(profile.did) ? (
<>
<Button
testID="toggleSubscribeBtn"
label={
isSubscribed
? _(msg`Unsubscribe from this labeler`)
: _(msg`Subscribe to this labeler`)
}
onPress={onPressSubscribe}>
{state => (
<View
style={[
{
paddingVertical: 9,
paddingHorizontal: 12,
borderRadius: 6,
gap: 6,
backgroundColor: isSubscribed
? state.hovered || state.pressed
? t.palette.contrast_50
: t.palette.contrast_25
: state.hovered || state.pressed
? tokens.color.temp_purple_dark
: tokens.color.temp_purple,
},
]}>
<Text
style={[
{
color: isSubscribed
? t.palette.contrast_700
: t.palette.white,
},
a.font_semi_bold,
a.text_center,
a.leading_tight,
]}>
{isSubscribed ? (
<Trans>Unsubscribe</Trans>
) : (
<Trans>Subscribe to Labeler</Trans>
)}
</Text>
</View>
)}
</Button>
</>
) : null}
<ProfileMenu profile={profile} />
<HeaderLabelerButtons profile={profile} />
</View>
<View style={[a.flex_col, a.gap_2xs, a.pt_2xs, a.pb_md]}>
<ProfileHeaderDisplayName profile={profile} moderation={moderation} />
@@ -265,7 +142,6 @@ let ProfileHeaderLabeler = ({
testID="toggleLikeBtn"
size="small"
color="secondary"
variant="solid"
shape="round"
label={_(msg`Like this labeler`)}
disabled={!hasSession || isLikePending || isUnlikePending}
@@ -318,7 +194,6 @@ let ProfileHeaderLabeler = ({
</>
)}
</View>
<CantSubscribePrompt control={cantSubscribePrompt} />
</ProfileHeaderShell>
)
}
@@ -349,3 +224,132 @@ function CantSubscribePrompt({
</Prompt.Outer>
)
}
export function HeaderLabelerButtons({
profile,
minimal = false,
}: {
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>
/** disable the subscribe button */
minimal?: boolean
}) {
const {_} = useLingui()
const t = useTheme()
const {currentAccount} = useSession()
const requireAuth = useRequireAuth()
const playHaptic = useHaptics()
const editProfileControl = useDialogControl()
const {data: preferences} = usePreferencesQuery()
const {
mutateAsync: toggleSubscription,
variables,
reset,
} = useLabelerSubscriptionMutation()
const isSubscribed =
variables?.subscribe ??
preferences?.moderationPrefs.labelers.find(l => l.did === profile.did)
const cantSubscribePrompt = Prompt.usePromptControl()
const isMe = currentAccount?.did === profile.did
const onPressSubscribe = () =>
requireAuth(async (): Promise<void> => {
playHaptic()
const subscribe = !isSubscribed
try {
await toggleSubscription({
did: profile.did,
subscribe,
})
logger.metric(
subscribe
? 'moderation:subscribedToLabeler'
: 'moderation:unsubscribedFromLabeler',
{},
{statsig: true},
)
} catch (e: any) {
reset()
if (e.message === 'MAX_LABELERS') {
cantSubscribePrompt.open()
return
}
logger.error(`Failed to subscribe to labeler`, {message: e.message})
}
})
return (
<>
{isMe ? (
<>
<Button
testID="profileHeaderEditProfileButton"
size="small"
color="secondary"
onPress={editProfileControl.open}
label={_(msg`Edit profile`)}
style={a.rounded_full}>
<ButtonText>
<Trans>Edit Profile</Trans>
</ButtonText>
</Button>
<EditProfileDialog profile={profile} control={editProfileControl} />
</>
) : !isAppLabeler(profile.did) && !minimal ? (
// hidden in the minimal header, because it's not shadowed so the two buttons
// can get out of sync. if you want to reenable, you'll need to add shadowing
// to the subscribed state -sfn
<Button
testID="toggleSubscribeBtn"
label={
isSubscribed
? _(msg`Unsubscribe from this labeler`)
: _(msg`Subscribe to this labeler`)
}
onPress={onPressSubscribe}>
{state => (
<View
style={[
{
paddingVertical: 9,
paddingHorizontal: 12,
borderRadius: 6,
gap: 6,
backgroundColor: isSubscribed
? state.hovered || state.pressed
? t.palette.contrast_50
: t.palette.contrast_25
: state.hovered || state.pressed
? tokens.color.temp_purple_dark
: tokens.color.temp_purple,
},
]}>
<Text
style={[
{
color: isSubscribed
? t.palette.contrast_700
: t.palette.white,
},
a.font_semi_bold,
a.text_center,
a.leading_tight,
]}>
{isSubscribed ? (
<Trans>Unsubscribe</Trans>
) : (
<Trans>Subscribe to Labeler</Trans>
)}
</Text>
</View>
)}
</Button>
) : null}
<ProfileMenu profile={profile} />
<CantSubscribePrompt control={cantSubscribePrompt} />
</>
)
}