Verification (#8226)

* WIP

* Alignment with icon

* Add create/remove prompts

* Fill out check dialog a bit

* Reorg

* Handle was verified state

* Add warning to edit profile

* Add warning to handle dialog

* Decent alignment in posts on all platforms

* Refactor alignment for posts, chatlist, hover card

* Disable on profile

* Convo header

* Compute simple verification state

* Add other icon, rename, integrate

* Swap in simple state for profile edits

* Clean up utility hooks

* Add verifications UI to dialog

* Add edu nux

* Revert change

* Fix wrapping of check on profile

* Rename

* Fix gap under PostMeta

* Update check dialogs

* Handle takendown verifiers in check dialog

* alf composer reply to

* Refactor verification state

* Add create/remove mutations, non-functional for now

* Fix up post-rebase

* Add check to first author noty

* Do cache updates after mutations

* DRY up hook, add to profile updates too

* Add to drawer

* Update account list

* Adapt to new types

* Hook up mutations

* Use profile shadow in feeds

* Add to settings

* Shadow currentAccountProfile

* Add invalid state to verifications

* Fix alignment and overflow in Settings and Drawer

* Re-integrate post rebase

* Remove debug code

* Update copy

* Add unverified notification support

* Remove link

* Make sure dialog closes

* Update URL

* Add settings screen

* Integrate new setting into verification states

* Add metrics, bump package, fix bad import

* NUX fixes

* Update copy

* Fixes

* Update types

* fix search autocomplete

* fix lint

* add display name warning to new dialog

* update default prefs

* Add parsing support for notifications

* Bump pkg

* Tweak noty styles

* Adjust check alignment

* Tweak check alignment

* Fix badge for verifier

* Modify copy

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Co-authored-by: Paul Frazee <pfrazee@gmail.com>
This commit is contained in:
Eric Bailey
2025-04-18 21:15:32 -05:00
committed by GitHub
parent f1e44ee12e
commit 0ac15920a4
63 changed files with 2332 additions and 395 deletions
@@ -43,6 +43,8 @@ import {Link} from '#/components/Link'
import {useMenuControl} from '#/components/Menu'
import {PostAlerts} from '#/components/moderation/PostAlerts'
import {Text} from '#/components/Typography'
import {useSimpleVerificationState} from '#/components/verification'
import {VerificationCheck} from '#/components/verification/VerificationCheck'
import type * as bsky from '#/types/bsky'
export let ChatListItem = ({
@@ -106,6 +108,9 @@ function ChatListItemReady({
const playHaptic = useHaptics()
const queryClient = useQueryClient()
const isUnread = convo.unreadCount > 0
const verification = useSimpleVerificationState({
profile,
})
const blockInfo = useMemo(() => {
const modui = moderation.ui('profileView')
@@ -385,11 +390,10 @@ function ChatListItemReady({
<View
style={[a.flex_1, a.justify_center, web({paddingRight: 45})]}>
<View style={[a.w_full, a.flex_row, a.align_end, a.pb_2xs]}>
<Text
numberOfLines={1}
style={[{maxWidth: '85%'}, web([a.leading_normal])]}>
<View style={[a.flex_shrink]}>
<Text
emoji
numberOfLines={1}
style={[
a.text_md,
t.atoms.text,
@@ -399,22 +403,31 @@ function ChatListItemReady({
]}>
{displayName}
</Text>
</Text>
</View>
{verification.showBadge && (
<View style={[a.pl_xs, a.self_center]}>
<VerificationCheck
width={14}
verifier={verification.role === 'verifier'}
/>
</View>
)}
{lastMessageSentAt && (
<TimeElapsed timestamp={lastMessageSentAt}>
{({timeElapsed}) => (
<Text
style={[
a.text_sm,
{lineHeight: 21},
t.atoms.text_contrast_medium,
web({whiteSpace: 'preserve nowrap'}),
]}>
{' '}
&middot; {timeElapsed}
</Text>
)}
</TimeElapsed>
<View style={[a.pl_xs]}>
<TimeElapsed timestamp={lastMessageSentAt}>
{({timeElapsed}) => (
<Text
style={[
a.text_sm,
{lineHeight: 21},
t.atoms.text_contrast_medium,
web({whiteSpace: 'preserve nowrap'}),
]}>
&middot; {timeElapsed}
</Text>
)}
</TimeElapsed>
</View>
)}
{(convo.muted || moderation.blocked) && (
<Text
@@ -0,0 +1,96 @@
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {urls} from '#/lib/constants'
import {logger} from '#/logger'
import {
usePreferencesQuery,
type UsePreferencesQueryResponse,
} from '#/state/queries/preferences'
import {useSetVerificationPrefsMutation} from '#/state/queries/preferences'
import * as SettingsList from '#/screens/Settings/components/SettingsList'
import {atoms as a, useGutters} from '#/alf'
import {Admonition} from '#/components/Admonition'
import * as Toggle from '#/components/forms/Toggle'
import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheck} from '#/components/icons/CircleCheck'
import * as Layout from '#/components/Layout'
import {InlineLinkText} from '#/components/Link'
import {Loader} from '#/components/Loader'
export function Screen() {
const {_} = useLingui()
const gutters = useGutters(['base'])
const {data: preferences} = usePreferencesQuery()
return (
<Layout.Screen testID="ModerationVerificationSettingsScreen">
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Verification Settings</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
<SettingsList.Container>
<SettingsList.Item>
<Admonition type="tip" style={[a.flex_1]}>
<Trans>
Verifications on Bluesky work differently than on other
platforms.{' '}
<InlineLinkText
overridePresentation
to={urls.website.blog.initialVerificationAnnouncement}
label={_(msg`Learn more`)}
onPress={() => {
logger.metric('verification:learn-more', {
location: 'verificationSettings',
})
}}>
Learn more here.
</InlineLinkText>
</Trans>
</Admonition>
</SettingsList.Item>
{preferences ? (
<Inner preferences={preferences} />
) : (
<View style={[gutters, a.justify_center, a.align_center]}>
<Loader size="xl" />
</View>
)}
</SettingsList.Container>
</Layout.Content>
</Layout.Screen>
)
}
function Inner({preferences}: {preferences: UsePreferencesQueryResponse}) {
const {_} = useLingui()
const {hideBadges} = preferences.verificationPrefs
const {mutate: setVerificationPrefs, isPending} =
useSetVerificationPrefsMutation()
return (
<Toggle.Item
type="checkbox"
name="hideBadges"
label={_(msg`Hide verification badges`)}
value={hideBadges}
disabled={isPending}
onChange={value => {
setVerificationPrefs({hideBadges: value})
}}>
<SettingsList.Item>
<SettingsList.ItemIcon icon={CircleCheck} />
<SettingsList.ItemText>
<Trans>Hide verification badges</Trans>
</SettingsList.ItemText>
<Toggle.Platform />
</SettingsList.Item>
</Toggle.Item>
)
}
+23 -4
View File
@@ -6,19 +6,22 @@ import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native'
import {getLabelingServiceTitle} from '#/lib/moderation'
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
import {
type CommonNavigatorParams,
type NativeStackScreenProps,
} from '#/lib/routes/types'
import {logger} from '#/logger'
import {isIOS} from '#/platform/detection'
import {
useMyLabelersQuery,
usePreferencesQuery,
UsePreferencesQueryResponse,
type UsePreferencesQueryResponse,
usePreferencesSetAdultContentMutation,
} from '#/state/queries/preferences'
import {isNonConfigurableModerationAuthority} from '#/state/session/additional-moderation-authorities'
import {useSetMinimalShellMode} from '#/state/shell'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {atoms as a, useBreakpoints, useTheme, ViewStyleProp} from '#/alf'
import {atoms as a, useBreakpoints, useTheme, type ViewStyleProp} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings'
@@ -27,7 +30,8 @@ import {Divider} from '#/components/Divider'
import * as Toggle from '#/components/forms/Toggle'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign'
import {Props as SVGIconProps} from '#/components/icons/common'
import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheck} from '#/components/icons/CircleCheck'
import {type Props as SVGIconProps} from '#/components/icons/common'
import {EditBig_Stroke2_Corner0_Rounded as EditBig} from '#/components/icons/EditBig'
import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter'
import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
@@ -274,6 +278,21 @@ export function ModerationScreenInner({
/>
)}
</Link>
<Divider />
<Link
label={_(msg`Manage verification settings`)}
testID="verificationSettingsBtn"
to="/moderation/verification-settings">
{state => (
<SubItem
title={_(msg`Verification settings`)}
icon={CircleCheck}
style={[
(state.hovered || state.pressed) && [t.atoms.bg_contrast_50],
]}
/>
)}
</Link>
</View>
<Text
@@ -1,10 +1,11 @@
import {useCallback, useEffect, useState} from 'react'
import {Dimensions, View} from 'react-native'
import {Image as RNImage} from 'react-native-image-crop-picker'
import {AppBskyActorDefs} from '@atproto/api'
import {type Image as RNImage} from 'react-native-image-crop-picker'
import {type AppBskyActorDefs} from '@atproto/api'
import {msg, Plural, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {urls} from '#/lib/constants'
import {compressIfNeeded} from '#/lib/media/manip'
import {cleanError} from '#/lib/strings/errors'
import {useWarnMaxGraphemeCount} from '#/lib/strings/helpers'
@@ -16,10 +17,13 @@ import * as Toast from '#/view/com/util/Toast'
import {EditableUserAvatar} from '#/view/com/util/UserAvatar'
import {UserBanner} from '#/view/com/util/UserBanner'
import {atoms as a, useTheme} from '#/alf'
import {Admonition} from '#/components/Admonition'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import * as TextField from '#/components/forms/TextField'
import {InlineLinkText} from '#/components/Link'
import * as Prompt from '#/components/Prompt'
import {useSimpleVerificationState} from '#/components/verification'
const DISPLAY_NAME_MAX_GRAPHEMES = 64
const DESCRIPTION_MAX_GRAPHEMES = 256
@@ -102,6 +106,9 @@ function DialogInner({
const {_} = useLingui()
const t = useTheme()
const control = Dialog.useDialogContext()
const verification = useSimpleVerificationState({
profile,
})
const {
mutateAsync: updateProfileMutation,
error: updateProfileError,
@@ -342,6 +349,22 @@ function DialogInner({
)}
</View>
{verification.isVerified &&
verification.role === 'default' &&
displayName !== initialDisplayName && (
<Admonition type="error">
<Trans>
You are verified. You will lose your verification status if you
change your display name.{' '}
<InlineLinkText
label={_(msg`Learn more`)}
to={urls.website.blog.initialVerificationAnnouncement}>
<Trans>Learn more.</Trans>
</InlineLinkText>
</Trans>
</Admonition>
)}
<View>
<TextField.LabelText>
<Trans>Description</Trans>
@@ -10,6 +10,7 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {logger} from '#/logger'
import {isIOS, isWeb} from '#/platform/detection'
import {useProfileShadow} from '#/state/cache/profile-shadow'
@@ -22,7 +23,7 @@ import {
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} from '#/alf'
import {atoms as a, platform, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog'
import {MessageProfileButton} from '#/components/dms/MessageProfileButton'
@@ -33,7 +34,8 @@ import {
} from '#/components/KnownFollowers'
import * as Prompt from '#/components/Prompt'
import {RichText} from '#/components/RichText'
import {ProfileHeaderDisplayName} from './DisplayName'
import {Text} from '#/components/Typography'
import {VerificationCheckButton} from '#/components/verification/VerificationCheckButton'
import {EditProfileDialog} from './EditProfileDialog'
import {ProfileHeaderHandle} from './Handle'
import {ProfileHeaderMetrics} from './Metrics'
@@ -54,6 +56,8 @@ let ProfileHeaderStandard = ({
hideBackButton = false,
isPlaceholderProfile,
}: Props): React.ReactNode => {
const t = useTheme()
const {gtMobile} = useBreakpoints()
const profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> =
useProfileShadow(profileUnshadowed)
const {currentAccount, hasSession} = useSession()
@@ -238,7 +242,31 @@ let ProfileHeaderStandard = ({
<ProfileMenu profile={profile} />
</View>
<View style={[a.flex_col, a.gap_2xs, a.pt_2xs, a.pb_sm]}>
<ProfileHeaderDisplayName profile={profile} moderation={moderation} />
<View style={[a.flex_row, a.align_center, a.gap_xs, a.flex_1]}>
<Text
emoji
testID="profileHeaderDisplayName"
style={[
t.atoms.text,
gtMobile ? a.text_4xl : a.text_3xl,
a.self_start,
a.font_heavy,
]}>
{sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'),
)}
<View
style={[
a.pl_xs,
{
marginTop: platform({ios: 2}),
},
]}>
<VerificationCheckButton profile={profile} size="lg" />
</View>
</Text>
</View>
<ProfileHeaderHandle profile={profile} />
</View>
{!isPlaceholderProfile && !isBlockedUser && (
@@ -49,7 +49,7 @@ let AutocompleteResults = ({
? undefined
: `/search?q=${encodeURIComponent(searchText)}`
}
style={{borderBottomWidth: 1}}
style={a.border_b}
/>
{autocompleteData?.map(item => (
<SearchProfileCard
+94 -42
View File
@@ -1,18 +1,23 @@
import {Pressable, ScrollView, StyleSheet, View} from 'react-native'
import {moderateProfile, type ModerationOpts} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {createHitslop, HITSLOP_10} from '#/lib/constants'
import {makeProfileLink} from '#/lib/routes/links'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {Link} from '#/view/com/util/Link'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture'
import {atoms as a, tokens, useBreakpoints, useTheme} from '#/alf'
import {atoms as a, tokens, useBreakpoints, useTheme, web} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
import * as Layout from '#/components/Layout'
import {Text} from '#/components/Typography'
import {useSimpleVerificationState} from '#/components/verification'
import {VerificationCheck} from '#/components/verification/VerificationCheck'
import type * as bsky from '#/types/bsky'
export function SearchHistory({
@@ -31,8 +36,8 @@ export function SearchHistory({
onRemoveProfileClick: (profile: bsky.profile.AnyProfileView) => void
}) {
const {gtMobile} = useBreakpoints()
const t = useTheme()
const {_} = useLingui()
const moderationOpts = useModerationOpts()
return (
<Layout.Content
@@ -54,53 +59,25 @@ export function SearchHistory({
<ScrollView
horizontal
keyboardShouldPersistTaps="handled"
showsHorizontalScrollIndicator={false}
style={[
a.flex_row,
a.flex_nowrap,
{marginHorizontal: tokens.space._2xl * -1},
]}
contentContainerStyle={[a.px_2xl, a.border_0]}>
{selectedProfiles.slice(0, 5).map((profile, index) => (
<View
key={index}
style={[
styles.profileItem,
!gtMobile && styles.profileItemMobile,
]}>
<Link
href={makeProfileLink(profile)}
title={profile.handle}
asAnchor
anchorNoUnderline
onBeforePress={() => onProfileClick(profile)}
style={[a.align_center, a.w_full]}>
<UserAvatar
avatar={profile.avatar}
type={profile.associated?.labeler ? 'labeler' : 'user'}
size={60}
{moderationOpts &&
selectedProfiles
.slice(0, 5)
.map(profile => (
<RecentProfileItem
key={profile.did}
profile={profile}
moderationOpts={moderationOpts}
onPress={() => onProfileClick(profile)}
onRemove={() => onRemoveProfileClick(profile)}
/>
<Text
emoji
style={[a.text_xs, a.text_center, styles.profileName]}
numberOfLines={1}>
{sanitizeDisplayName(
profile.displayName || profile.handle,
)}
</Text>
</Link>
<Pressable
accessibilityRole="button"
accessibilityLabel={_(msg`Remove profile`)}
accessibilityHint={_(
msg`Removes profile from search history`,
)}
onPress={() => onRemoveProfileClick(profile)}
hitSlop={createHitslop(6)}
style={styles.profileRemoveBtn}>
<XIcon size="xs" style={t.atoms.text_contrast_low} />
</Pressable>
</View>
))}
))}
</ScrollView>
</BlockDrawerGesture>
</View>
@@ -134,6 +111,81 @@ export function SearchHistory({
)
}
function RecentProfileItem({
profile,
moderationOpts,
onPress,
onRemove,
}: {
profile: bsky.profile.AnyProfileView
moderationOpts: ModerationOpts
onPress: () => void
onRemove: () => void
}) {
const {_} = useLingui()
const {gtMobile} = useBreakpoints()
const t = useTheme()
const moderation = moderateProfile(profile, moderationOpts)
const name = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'),
)
const verification = useSimpleVerificationState({profile})
return (
<View style={[styles.profileItem, !gtMobile && styles.profileItemMobile]}>
<Link
href={makeProfileLink(profile)}
title={profile.handle}
asAnchor
anchorNoUnderline
onBeforePress={onPress}
style={[a.align_center, a.w_full]}>
<UserAvatar
avatar={profile.avatar}
type={profile.associated?.labeler ? 'labeler' : 'user'}
size={60}
moderation={moderation.ui('avatar')}
/>
<View style={styles.profileName}>
<View
style={[
a.flex_row,
a.align_center,
a.justify_center,
web([a.flex_1]),
]}>
<Text
emoji
style={[a.text_xs, a.leading_snug, a.self_start]}
numberOfLines={1}>
{name}
</Text>
{verification.showBadge && (
<View style={[a.pl_xs]}>
<VerificationCheck
width={12}
verifier={verification.role === 'verifier'}
/>
</View>
)}
</View>
</View>
</Link>
<Pressable
accessibilityRole="button"
accessibilityLabel={_(msg`Remove profile`)}
accessibilityHint={_(msg`Removes profile from search history`)}
hitSlop={createHitslop(6)}
style={styles.profileRemoveBtn}
onPress={onRemove}>
<XIcon size="xs" style={t.atoms.text_contrast_low} />
</Pressable>
</View>
)
}
const styles = StyleSheet.create({
selectedProfilesContainer: {
marginTop: 10,
+35 -14
View File
@@ -29,7 +29,7 @@ import {useCloseAllActiveElements} from '#/state/util'
import * as Toast from '#/view/com/util/Toast'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import * as SettingsList from '#/screens/Settings/components/SettingsList'
import {atoms as a, tokens, useBreakpoints, useTheme} from '#/alf'
import {atoms as a, platform, tokens, useBreakpoints, useTheme} from '#/alf'
import {AvatarStackWithFetch} from '#/components/AvatarStack'
import {useDialogControl} from '#/components/Dialog'
import {SwitchAccountDialog} from '#/components/dialogs/SwitchAccount'
@@ -55,6 +55,11 @@ import {Loader} from '#/components/Loader'
import * as Menu from '#/components/Menu'
import * as Prompt from '#/components/Prompt'
import {Text} from '#/components/Typography'
import {useFullVerificationState} from '#/components/verification'
import {
shouldShowVerificationCheckButton,
VerificationCheckButton,
} from '#/components/verification/VerificationCheckButton'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'>
export function SettingsScreen({}: Props) {
@@ -278,6 +283,9 @@ function ProfilePreview({
const {gtMobile} = useBreakpoints()
const shadow = useProfileShadow(profile)
const moderationOpts = useModerationOpts()
const verificationState = useFullVerificationState({
profile: shadow,
})
if (!moderationOpts) return null
@@ -292,20 +300,33 @@ function ProfilePreview({
type={shadow.associated?.labeler ? 'labeler' : 'user'}
/>
<Text
emoji
testID="profileHeaderDisplayName"
style={[
a.pt_sm,
t.atoms.text,
gtMobile ? a.text_4xl : a.text_3xl,
a.font_heavy,
]}>
{sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'),
<View style={[a.flex_row, a.gap_xs, a.align_center]}>
<Text
emoji
testID="profileHeaderDisplayName"
numberOfLines={1}
style={[
a.pt_sm,
t.atoms.text,
gtMobile ? a.text_4xl : a.text_3xl,
a.font_heavy,
]}>
{sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'),
)}
</Text>
{shouldShowVerificationCheckButton(verificationState) && (
<View
style={[
{
marginTop: platform({web: 8, ios: 8, android: 10}),
},
]}>
<VerificationCheckButton profile={shadow} size="lg" />
</View>
)}
</Text>
</View>
<Text style={[a.text_md, a.leading_snug, t.atoms.text_contrast_medium]}>
{sanitizeHandle(profile.handle, '@')}
</Text>
@@ -10,18 +10,19 @@ import Animated, {
SlideOutLeft,
SlideOutRight,
} from 'react-native-reanimated'
import {ComAtprotoServerDescribeServer} from '@atproto/api'
import {type ComAtprotoServerDescribeServer} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useMutation, useQueryClient} from '@tanstack/react-query'
import {HITSLOP_10} from '#/lib/constants'
import {HITSLOP_10, urls} from '#/lib/constants'
import {cleanError} from '#/lib/strings/errors'
import {createFullHandle, validateServiceHandle} from '#/lib/strings/handles'
import {sanitizeHandle} from '#/lib/strings/handles'
import {useFetchDid, useUpdateHandleMutation} from '#/state/queries/handle'
import {RQKEY as RQKEY_PROFILE} from '#/state/queries/profile'
import {useServiceQuery} from '#/state/queries/service'
import {useCurrentAccountProfile} from '#/state/queries/useCurrentAccountProfile'
import {useAgent, useSession} from '#/state/session'
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
import {atoms as a, native, useBreakpoints, useTheme} from '#/alf'
@@ -40,6 +41,7 @@ import {SquareBehindSquare4_Stroke2_Corner0_Rounded as CopyIcon} from '#/compone
import {InlineLinkText} from '#/components/Link'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
import {useSimpleVerificationState} from '#/components/verification'
import {CopyButton} from './CopyButton'
export function ChangeHandleDialog({
@@ -152,6 +154,10 @@ function ProvidedHandlePage({
const control = Dialog.useDialogContext()
const {currentAccount} = useSession()
const queryClient = useQueryClient()
const profile = useCurrentAccountProfile()
const verification = useSimpleVerificationState({
profile,
})
const {
mutate: changeHandle,
@@ -197,6 +203,19 @@ function ProvidedHandlePage({
<Animated.View
layout={native(LinearTransition)}
style={[a.flex_1, a.gap_md]}>
{verification.isVerified && verification.role === 'default' && (
<Admonition type="error">
<Trans>
You are verified. You will lose your verification status if you
change your handle.{' '}
<InlineLinkText
label={_(msg`Learn more`)}
to={urls.website.blog.initialVerificationAnnouncement}>
<Trans>Learn more.</Trans>
</InlineLinkText>
</Trans>
</Admonition>
)}
<View>
<TextField.LabelText>
<Trans>New handle</Trans>