Bluesky app fork with some witchin' additions 馃挮
at main 282 lines 8.8 kB view raw
1import {View} from 'react-native' 2import {type AppBskyActorDefs} from '@atproto/api' 3import {msg, Trans} from '@lingui/macro' 4import {useLingui} from '@lingui/react' 5 6import {urls} from '#/lib/constants' 7import {getUserDisplayName} from '#/lib/getUserDisplayName' 8import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 9import {useModerationOpts} from '#/state/preferences/moderation-opts' 10import {useProfileQuery} from '#/state/queries/profile' 11import {useSession} from '#/state/session' 12import {atoms as a, useBreakpoints, useTheme} from '#/alf' 13import {Admonition} from '#/components/Admonition' 14import {Button, ButtonIcon, ButtonText} from '#/components/Button' 15import * as Dialog from '#/components/Dialog' 16import {useDialogControl} from '#/components/Dialog' 17import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' 18import {Link} from '#/components/Link' 19import * as ProfileCard from '#/components/ProfileCard' 20import {Text} from '#/components/Typography' 21import {type FullVerificationState} from '#/components/verification' 22import {VerificationRemovePrompt} from '#/components/verification/VerificationRemovePrompt' 23import {useAnalytics} from '#/analytics' 24import type * as bsky from '#/types/bsky' 25 26export {useDialogControl} from '#/components/Dialog' 27 28export function VerificationsDialog({ 29 control, 30 profile, 31 verificationState, 32}: { 33 control: Dialog.DialogControlProps 34 profile: bsky.profile.AnyProfileView 35 verificationState: FullVerificationState 36}) { 37 return ( 38 <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 39 <Dialog.Handle /> 40 <Inner 41 control={control} 42 profile={profile} 43 verificationState={verificationState} 44 /> 45 </Dialog.Outer> 46 ) 47} 48 49function Inner({ 50 profile, 51 control, 52 verificationState: state, 53}: { 54 control: Dialog.DialogControlProps 55 profile: bsky.profile.AnyProfileView 56 verificationState: FullVerificationState 57}) { 58 const t = useTheme() 59 const ax = useAnalytics() 60 const {_} = useLingui() 61 const {gtMobile} = useBreakpoints() 62 63 const userName = getUserDisplayName(profile) 64 const label = state.profile.isViewer 65 ? state.profile.isVerified 66 ? _(msg`You are verified`) 67 : _(msg`Your verifications`) 68 : state.profile.isVerified 69 ? _(msg`${userName} is verified`) 70 : _( 71 msg({ 72 message: `${userName}'s verifications`, 73 comment: `Possessive, meaning "the verifications of {userName}"`, 74 }), 75 ) 76 77 return ( 78 <Dialog.ScrollableInner 79 label={label} 80 style={[ 81 gtMobile ? {width: 'auto', maxWidth: 400, minWidth: 200} : a.w_full, 82 ]}> 83 <View style={[a.gap_sm, a.pb_lg]}> 84 <Text style={[a.text_2xl, a.font_semi_bold, a.pr_4xl, a.leading_tight]}> 85 {label} 86 </Text> 87 <Text style={[a.text_md, a.leading_snug]}> 88 {state.profile.isVerified ? ( 89 <Trans> 90 This account has a checkmark because it's been verified by trusted 91 sources. 92 </Trans> 93 ) : ( 94 <Trans> 95 This account has one or more attempted verifications, but it is 96 not currently verified. 97 </Trans> 98 )} 99 </Text> 100 </View> 101 102 {profile.verification ? ( 103 <View style={[a.pb_xl, a.gap_md]}> 104 <Text style={[a.text_sm, t.atoms.text_contrast_medium]}> 105 <Trans>Verified by:</Trans> 106 </Text> 107 108 <View style={[a.gap_lg]}> 109 {profile.verification.verifications.map(v => ( 110 <VerifierCard 111 key={v.uri} 112 verification={v} 113 subject={profile} 114 outerDialogControl={control} 115 /> 116 ))} 117 </View> 118 119 {profile.verification.verifications.some(v => !v.isValid) && 120 state.profile.isViewer && ( 121 <Admonition type="warning" style={[a.mt_xs]}> 122 <Trans>Some of your verifications are invalid.</Trans> 123 </Admonition> 124 )} 125 </View> 126 ) : null} 127 128 <View 129 style={[ 130 a.w_full, 131 a.gap_sm, 132 a.justify_end, 133 gtMobile 134 ? [a.flex_row, a.flex_row_reverse, a.justify_start] 135 : [a.flex_col], 136 ]}> 137 <Button 138 label={_(msg`Close dialog`)} 139 size="small" 140 variant="solid" 141 color="primary" 142 onPress={() => { 143 control.close() 144 }}> 145 <ButtonText> 146 <Trans>Close</Trans> 147 </ButtonText> 148 </Button> 149 <Link 150 overridePresentation 151 to={urls.website.blog.initialVerificationAnnouncement} 152 label={_( 153 msg({ 154 message: `Learn more about verification on Bluesky`, 155 context: `english-only-resource`, 156 }), 157 )} 158 size="small" 159 variant="solid" 160 color="secondary" 161 style={[a.justify_center]} 162 onPress={() => { 163 ax.metric('verification:learn-more', { 164 location: 'verificationsDialog', 165 }) 166 }}> 167 <ButtonText> 168 <Trans context="english-only-resource">Learn more</Trans> 169 </ButtonText> 170 </Link> 171 </View> 172 173 <Dialog.Close /> 174 </Dialog.ScrollableInner> 175 ) 176} 177 178function VerifierCard({ 179 verification, 180 subject, 181 outerDialogControl, 182}: { 183 verification: AppBskyActorDefs.VerificationView 184 subject: bsky.profile.AnyProfileView 185 outerDialogControl: Dialog.DialogControlProps 186}) { 187 const t = useTheme() 188 const {_, i18n} = useLingui() 189 const {currentAccount} = useSession() 190 const moderationOpts = useModerationOpts() 191 const {data: profile, error} = useProfileQuery({did: verification.issuer}) 192 const verificationRemovePromptControl = useDialogControl() 193 const canAdminister = verification.issuer === currentAccount?.did 194 195 const enableSquareButtons = useEnableSquareButtons() 196 197 return ( 198 <View 199 style={{ 200 opacity: verification.isValid ? 1 : 0.5, 201 }}> 202 <ProfileCard.Outer> 203 <ProfileCard.Header> 204 {error ? ( 205 <> 206 <ProfileCard.AvatarPlaceholder /> 207 <View style={[a.flex_1]}> 208 <Text 209 style={[a.text_md, a.font_semi_bold, a.leading_snug]} 210 numberOfLines={1}> 211 <Trans>Unknown verifier</Trans> 212 </Text> 213 <Text 214 emoji 215 style={[a.leading_snug, t.atoms.text_contrast_medium]} 216 numberOfLines={1}> 217 {verification.issuer} 218 </Text> 219 </View> 220 </> 221 ) : profile && moderationOpts ? ( 222 <> 223 <ProfileCard.Link 224 profile={profile} 225 style={[a.flex_row, a.align_center, a.gap_sm, a.flex_1]} 226 onPress={() => { 227 outerDialogControl.close() 228 }}> 229 <ProfileCard.Avatar 230 profile={profile} 231 moderationOpts={moderationOpts} 232 disabledPreview 233 /> 234 <View style={[a.flex_1]}> 235 <ProfileCard.Name 236 profile={profile} 237 moderationOpts={moderationOpts} 238 /> 239 <Text 240 emoji 241 style={[a.leading_snug, t.atoms.text_contrast_medium]} 242 numberOfLines={1}> 243 {i18n.date(new Date(verification.createdAt), { 244 dateStyle: 'long', 245 })} 246 </Text> 247 </View> 248 </ProfileCard.Link> 249 {canAdminister && ( 250 <View> 251 <Button 252 label={_(msg`Remove verification`)} 253 size="small" 254 variant="outline" 255 color="negative" 256 shape={enableSquareButtons ? 'square' : 'round'} 257 onPress={() => { 258 verificationRemovePromptControl.open() 259 }}> 260 <ButtonIcon icon={TrashIcon} /> 261 </Button> 262 </View> 263 )} 264 </> 265 ) : ( 266 <> 267 <ProfileCard.AvatarPlaceholder /> 268 <ProfileCard.NameAndHandlePlaceholder /> 269 </> 270 )} 271 </ProfileCard.Header> 272 </ProfileCard.Outer> 273 274 <VerificationRemovePrompt 275 control={verificationRemovePromptControl} 276 profile={subject} 277 verifications={[verification]} 278 onConfirm={() => outerDialogControl.close()} 279 /> 280 </View> 281 ) 282}