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_semi_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={_(
151 msg({
152 message: `Learn more about verification on Bluesky`,
153 context: `english-only-resource`,
154 }),
155 )}
156 size="small"
157 variant="solid"
158 color="secondary"
159 style={[a.justify_center]}
160 onPress={() => {
161 logger.metric(
162 'verification:learn-more',
163 {
164 location: 'verificationsDialog',
165 },
166 {statsig: true},
167 )
168 }}>
169 <ButtonText>
170 <Trans context="english-only-resource">Learn more</Trans>
171 </ButtonText>
172 </Link>
173 </View>
174
175 <Dialog.Close />
176 </Dialog.ScrollableInner>
177 )
178}
179
180function VerifierCard({
181 verification,
182 subject,
183 outerDialogControl,
184}: {
185 verification: AppBskyActorDefs.VerificationView
186 subject: bsky.profile.AnyProfileView
187 outerDialogControl: Dialog.DialogControlProps
188}) {
189 const t = useTheme()
190 const {_, i18n} = useLingui()
191 const {currentAccount} = useSession()
192 const moderationOpts = useModerationOpts()
193 const {data: profile, error} = useProfileQuery({did: verification.issuer})
194 const verificationRemovePromptControl = useDialogControl()
195 const canAdminister = verification.issuer === currentAccount?.did
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="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}