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