add button + dialog

This commit is contained in:
Samuel Newman
2025-05-28 18:10:56 +03:00
parent 6d38a5a64f
commit 6a423fbb61
4 changed files with 170 additions and 1 deletions
@@ -0,0 +1,59 @@
import {useCallback} from 'react'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
import {atoms as a} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog'
import type * as bsky from '#/types/bsky'
import {Bell_Stroke2_Corner0_Rounded as NotificationIcon} from '../icons/Bell'
import {SubscribeProfileDialog} from './SubscribeProfileDialog'
export function SubscribeProfileButton({
profile,
}: {
profile: bsky.profile.AnyProfileView
}) {
const {_} = useLingui()
const requireEmailVerification = useRequireEmailVerification()
const subscribeDialogControl = useDialogControl()
const onPress = useCallback(() => {
subscribeDialogControl.open()
}, [subscribeDialogControl])
const name = createSanitizedDisplayName(profile, true)
const wrappedOnPress = requireEmailVerification(onPress, {
instructions: [
<Trans key="message">
Before you can subscribe to {name}'s posts, you must first verify your
email.
</Trans>,
],
})
return (
<>
<Button
accessibilityRole="button"
testID="dmBtn"
size="small"
color="secondary"
variant="solid"
shape="round"
label={_(msg`Subscribe to ${name}'s posts`)}
style={[a.justify_center]}
onPress={wrappedOnPress}>
<ButtonIcon icon={NotificationIcon} size="md" />
</Button>
<SubscribeProfileDialog
control={subscribeDialogControl}
profile={profile}
/>
</>
)
}
@@ -0,0 +1,96 @@
import {useMemo, useState} from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
import {useTheme, web} from '#/alf'
import {atoms as a} from '#/alf'
import * as Dialog from '#/components/Dialog'
import * as Toggle from '#/components/forms/Toggle'
import {Text} from '#/components/Typography'
import type * as bsky from '#/types/bsky'
export function SubscribeProfileDialog({
control,
profile,
}: {
control: Dialog.DialogControlProps
profile: bsky.profile.AnyProfileView
}) {
return (
<Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
<Dialog.Handle />
<DialogInner profile={profile} />
</Dialog.Outer>
)
}
function DialogInner({profile}: {profile: bsky.profile.AnyProfileView}) {
const {_} = useLingui()
const t = useTheme()
const [state, setState] = useState({posts: false, replies: false})
const values = useMemo(() => {
const {posts, replies} = state
const res = []
if (posts) res.push('posts')
if (replies) res.push('replies')
return res
}, [state])
const onChange = (newValues: string[]) =>
setState({
posts: newValues.includes('posts'),
replies: newValues.includes('replies'),
})
const name = createSanitizedDisplayName(profile, false)
return (
<Dialog.ScrollableInner
style={web({maxWidth: 400})}
label={_(msg`Subscribe to posts`)}>
<View style={[a.gap_md]}>
<View style={[a.gap_xs]}>
<Text style={[a.font_heavy, a.text_2xl]}>
<Trans>Subscribe to {name}</Trans>
</Text>
<Text style={[t.atoms.text_contrast_medium, a.text_md]}>
<Trans>Get notifications for account activity</Trans>
</Text>
</View>
<Toggle.Group
label={_(msg`Subscribe to account`)}
values={values}
onChange={onChange}>
<View style={[a.gap_sm]}>
<Toggle.Item
label={_(msg`Posts`)}
name="posts"
style={[a.justify_between, a.py_xs]}>
<Toggle.LabelText
style={[t.atoms.text, a.font_normal, a.text_md]}>
<Trans>Posts</Trans>
</Toggle.LabelText>
<Toggle.Platform />
</Toggle.Item>
<Toggle.Item
label={_(msg`Replies`)}
name="replies"
style={[a.justify_between, a.py_xs]}>
<Toggle.LabelText
style={[t.atoms.text, a.font_normal, a.text_md]}>
<Trans>Replies</Trans>
</Toggle.LabelText>
<Toggle.Platform />
</Toggle.Item>
</View>
</Toggle.Group>
</View>
<Dialog.Close />
</Dialog.ScrollableInner>
)
}
@@ -30,6 +30,12 @@ export function useGlobalLabelStrings(): GlobalLabelStrings {
msg`This user has requested that their content only be shown to signed-in users.`,
),
},
'!no-subscriptions': {
name: _(msg`No subscriptions allowed`),
description: _(
msg`This user has requested that they cannot be subscribed to for post notifications.`,
),
},
porn: {
name: _(msg`Adult Content`),
description: _(msg`Explicit sexual images.`),
@@ -32,6 +32,7 @@ import {
KnownFollowers,
shouldShowKnownFollowers,
} from '#/components/KnownFollowers'
import {SubscribeProfileButton} from '#/components/post-subscriptions/SubscribeProfileButton'
import * as Prompt from '#/components/Prompt'
import {RichText} from '#/components/RichText'
import {Text} from '#/components/Typography'
@@ -134,13 +135,17 @@ let ProfileHeaderStandard = ({
}
}, [_, queueUnblock])
const isMe = React.useMemo(
const isMe = useMemo(
() => currentAccount?.did === profile.did,
[currentAccount, profile],
)
const {isActive: live} = useActorStatus(profile)
const subscriptionsDisallowed = useMemo(() => {
return !!profile.labels?.find(label => label.val === '!no-subscriptions')
}, [profile])
return (
<ProfileHeaderShell
profile={profile}
@@ -198,6 +203,9 @@ let ProfileHeaderStandard = ({
)
) : !profile.viewer?.blockedBy ? (
<>
{hasSession && !subscriptionsDisallowed && (
<SubscribeProfileButton profile={profile} />
)}
{hasSession && <MessageProfileButton profile={profile} />}
<Button