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