diff --git a/src/components/post-subscriptions/SubscribeProfileButton.tsx b/src/components/post-subscriptions/SubscribeProfileButton.tsx
new file mode 100644
index 0000000000..cfed19a60a
--- /dev/null
+++ b/src/components/post-subscriptions/SubscribeProfileButton.tsx
@@ -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: [
+
+ Before you can subscribe to {name}'s posts, you must first verify your
+ email.
+ ,
+ ],
+ })
+
+ return (
+ <>
+
+
+
+ >
+ )
+}
diff --git a/src/components/post-subscriptions/SubscribeProfileDialog.tsx b/src/components/post-subscriptions/SubscribeProfileDialog.tsx
new file mode 100644
index 0000000000..334d6ace94
--- /dev/null
+++ b/src/components/post-subscriptions/SubscribeProfileDialog.tsx
@@ -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 (
+
+
+
+
+ )
+}
+
+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 (
+
+
+
+
+ Subscribe to {name}
+
+
+ Get notifications for account activity
+
+
+
+
+
+
+
+ Posts
+
+
+
+
+
+ Replies
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/lib/moderation/useGlobalLabelStrings.ts b/src/lib/moderation/useGlobalLabelStrings.ts
index 86ad8e76d6..f272c6bc41 100644
--- a/src/lib/moderation/useGlobalLabelStrings.ts
+++ b/src/lib/moderation/useGlobalLabelStrings.ts
@@ -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.`),
diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx
index 1639abaf0c..18792ec779 100644
--- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx
+++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx
@@ -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 (
+ {hasSession && !subscriptionsDisallowed && (
+
+ )}
{hasSession && }