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