diff options
Diffstat (limited to 'src/screens')
-rw-r--r-- | src/screens/Messages/components/ChatListItem.tsx | 49 | ||||
-rw-r--r-- | src/screens/Moderation/VerificationSettings.tsx | 96 | ||||
-rw-r--r-- | src/screens/Moderation/index.tsx | 27 | ||||
-rw-r--r-- | src/screens/Profile/Header/EditProfileDialog.tsx | 27 | ||||
-rw-r--r-- | src/screens/Profile/Header/ProfileHeaderStandard.tsx | 34 | ||||
-rw-r--r-- | src/screens/Search/components/AutocompleteResults.tsx | 2 | ||||
-rw-r--r-- | src/screens/Search/components/SearchHistory.tsx | 136 | ||||
-rw-r--r-- | src/screens/Settings/Settings.tsx | 49 | ||||
-rw-r--r-- | src/screens/Settings/components/ChangeHandleDialog.tsx | 23 |
9 files changed, 357 insertions, 86 deletions
diff --git a/src/screens/Messages/components/ChatListItem.tsx b/src/screens/Messages/components/ChatListItem.tsx index 8a760e2c9..09cf2dccd 100644 --- a/src/screens/Messages/components/ChatListItem.tsx +++ b/src/screens/Messages/components/ChatListItem.tsx @@ -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'}), - ]}> - {' '} - · {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'}), + ]}> + · {timeElapsed} + </Text> + )} + </TimeElapsed> + </View> )} {(convo.muted || moderation.blocked) && ( <Text diff --git a/src/screens/Moderation/VerificationSettings.tsx b/src/screens/Moderation/VerificationSettings.tsx new file mode 100644 index 000000000..f9665d6d9 --- /dev/null +++ b/src/screens/Moderation/VerificationSettings.tsx @@ -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> + ) +} diff --git a/src/screens/Moderation/index.tsx b/src/screens/Moderation/index.tsx index 55cc67f8c..78b0a6ae9 100644 --- a/src/screens/Moderation/index.tsx +++ b/src/screens/Moderation/index.tsx @@ -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 diff --git a/src/screens/Profile/Header/EditProfileDialog.tsx b/src/screens/Profile/Header/EditProfileDialog.tsx index 62bb5e00e..a0e24d78a 100644 --- a/src/screens/Profile/Header/EditProfileDialog.tsx +++ b/src/screens/Profile/Header/EditProfileDialog.tsx @@ -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> diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx index 773c296c9..1c4c4d9f3 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -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 && ( diff --git a/src/screens/Search/components/AutocompleteResults.tsx b/src/screens/Search/components/AutocompleteResults.tsx index b3bccd1d4..2824ccc1b 100644 --- a/src/screens/Search/components/AutocompleteResults.tsx +++ b/src/screens/Search/components/AutocompleteResults.tsx @@ -49,7 +49,7 @@ let AutocompleteResults = ({ ? undefined : `/search?q=${encodeURIComponent(searchText)}` } - style={{borderBottomWidth: 1}} + style={a.border_b} /> {autocompleteData?.map(item => ( <SearchProfileCard diff --git a/src/screens/Search/components/SearchHistory.tsx b/src/screens/Search/components/SearchHistory.tsx index 5e62f2cd0..048203ed8 100644 --- a/src/screens/Search/components/SearchHistory.tsx +++ b/src/screens/Search/components/SearchHistory.tsx @@ -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, diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index 956413a55..a723aaa37 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -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> diff --git a/src/screens/Settings/components/ChangeHandleDialog.tsx b/src/screens/Settings/components/ChangeHandleDialog.tsx index b69713a10..a39d958ab 100644 --- a/src/screens/Settings/components/ChangeHandleDialog.tsx +++ b/src/screens/Settings/components/ChangeHandleDialog.tsx @@ -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> |