add verification

This commit is contained in:
Samuel Newman
2025-06-10 10:23:32 +03:00
committed by Eric Bailey
parent 25b56c40d2
commit e78fa95c3e
10 changed files with 140 additions and 9 deletions
+18 -4
View File
@@ -7,11 +7,12 @@ import {
import {ModeratorData} from '../../data/getModeratorData.js'
import {PostData} from '../../data/getPostData.js'
import {atoms as a, theme as t} from '../../theme/index.js'
import * as bsky from '../../types/bsky.js'
import * as bsky from '../../types/bsky/index.js'
import {formatCount} from '../../util/formatCount.js'
import {formatDate} from '../../util/formatDate.js'
import {moderatePost} from '../../util/moderatePost.js'
import {sanitizeHandle} from '../../util/sanitizeHandle.js'
import {getVerificationState} from '../../util/verificationState.js'
import {Avatar} from '../Avatar.js'
import {Box} from '../Box.js'
import {Heart} from '../icons/Heart.js'
@@ -21,6 +22,7 @@ import {Repost} from '../icons/Repost.js'
import {PostEmbed} from '../PostEmbed.js'
import {RichText} from '../RichText.js'
import {Text} from '../Text.js'
import {VerificationCheck} from '../VerificationCheck.js'
export function Post({
post,
@@ -52,6 +54,7 @@ export function Post({
: undefined
const hasInteractions = post.likeCount > 0 || post.repostCount > 0
const moderation = moderatePost(post, moderatorData.moderationOptions)
const verification = getVerificationState({profile: post.author})
const bgTop = avatar?.colors?.muted ?? '#1083fe'
const bgBottom = avatar?.colors?.lightMuted ?? '#67b0fe'
@@ -91,9 +94,20 @@ export function Post({
/>
</Box>
<Box cx={[a.flex_col, a.flex_1]}>
<Text cx={[a.text_md, a.font_heavy, a.pb_2xs, a.line_clamp_2]}>
{post.author.displayName || post.author.handle}
</Text>
<Box cx={[a.flex_1, a.flex_row]}>
<Text cx={[a.text_md, a.font_heavy, a.pb_2xs, a.line_clamp_2]}>
{post.author.displayName || post.author.handle}
</Text>
{verification.isVerified && (
<Box cx={[a.pl_xs, a.pt_2xs]}>
<VerificationCheck
size={12}
verifier={verification.role === 'verifier'}
fill={t.palette.primary_500}
/>
</Box>
)}
</Box>
<Text
cx={[a.text_sm, t.atoms.text_contrast_medium, a.line_clamp_1]}>
{sanitizeHandle(post.author.handle, '@')}
+1 -1
View File
@@ -7,7 +7,7 @@ import {
import {ModeratorData} from '../data/getModeratorData.js'
import {Image as ImageSource, PostData} from '../data/getPostData.js'
import {atoms as a, theme as t} from '../theme/index.js'
import * as bsky from '../types/bsky.js'
import * as bsky from '../types/bsky/index.js'
import {getModerationCauseInfo} from '../util/getModerationCauseInfo.js'
import {getStarterPackImageUri} from '../util/getStarterPackImageUri.js'
import {Embed, EmbedType, parseEmbed} from '../util/parseEmbed.js'
@@ -7,10 +7,11 @@ import {
import {ModeratorData} from '../../data/getModeratorData.js'
import {PostData} from '../../data/getPostData.js'
import {atoms as a, theme as t} from '../../theme/index.js'
import * as bsky from '../../types/bsky.js'
import * as bsky from '../../types/bsky/index.js'
import {getModerationCauseInfo} from '../../util/getModerationCauseInfo.js'
import {moderatePost} from '../../util/moderatePost.js'
import {sanitizeHandle} from '../../util/sanitizeHandle.js'
import {getVerificationState} from '../../util/verificationState.js'
import {viewRecordToPostView} from '../../util/viewRecordToPostView.js'
import {Avatar} from '../Avatar.js'
import {Box} from '../Box.js'
@@ -19,6 +20,7 @@ import {ModeratedEmbed} from '../ModeratedEmbed.js'
import {PostEmbed} from '../PostEmbed.js'
import {RichText} from '../RichText.js'
import {Text} from '../Text.js'
import {VerificationCheck} from '../VerificationCheck.js'
export function QuotePost({
embed,
@@ -43,6 +45,7 @@ export function QuotePost({
: undefined
const postView = viewRecordToPostView(embed)
const moderation = moderatePost(postView, moderatorData.moderationOptions)
const verification = getVerificationState({profile: embed.author})
const mod = moderation.ui('contentView')
const info = getModerationCauseInfo({
@@ -79,10 +82,32 @@ export function QuotePost({
moderatorData={moderatorData}
/>
<Box cx={[a.flex_row, a.align_center, a.gap_xs, a.pt_2xs]}>
<Text cx={[a.text_sm, a.font_bold]}>
<Text
cx={[
a.text_sm,
a.font_bold,
a.flex_shrink_0,
{maxWidth: '70%'},
a.line_clamp_1,
]}>
{author.displayName || author.handle}
</Text>
<Text cx={[a.text_xs, t.atoms.text_contrast_medium]}>
{verification.isVerified && (
<Box cx={[{marginTop: -2}]}>
<VerificationCheck
size={12}
verifier={verification.role === 'verifier'}
fill={t.palette.primary_500}
/>
</Box>
)}
<Text
cx={[
a.text_xs,
t.atoms.text_contrast_medium,
{flexShrink: 10},
a.line_clamp_1,
]}>
{sanitizeHandle(author.handle, '@')}
</Text>
</Box>
+1 -1
View File
@@ -1,6 +1,6 @@
import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api'
import * as bsky from '../types/bsky.js'
import * as bsky from '../types/bsky/index.js'
import {Butterfly} from './Butterfly.js'
import {Img} from './Img.js'
@@ -0,0 +1,12 @@
import {type IconProps} from './icons/types.js'
import {VerifiedCheck} from './icons/VerifiedCheck.js'
import {VerifierCheck} from './icons/VerifierCheck.js'
export function VerificationCheck({
verifier,
...rest
}: IconProps & {
verifier?: boolean
}) {
return verifier ? <VerifierCheck {...rest} /> : <VerifiedCheck {...rest} />
}
@@ -0,0 +1,15 @@
import {IconProps} from './types.js'
export function VerifiedCheck({size, fill}: IconProps) {
return (
<svg fill="none" viewBox="0 0 24 24" width={size} height={size}>
<circle cx="12" cy="12" r="11.5" fill={fill} />
<path
fill="#fff"
fillRule="evenodd"
clipRule="evenodd"
d="M17.659 8.175a1.361 1.361 0 0 1 0 1.925l-6.224 6.223a1.361 1.361 0 0 1-1.925 0L6.4 13.212a1.361 1.361 0 0 1 1.925-1.925l2.149 2.148 5.26-5.26a1.361 1.361 0 0 1 1.925 0Z"
/>
</svg>
)
}
@@ -0,0 +1,20 @@
import {IconProps} from './types.js'
export function VerifierCheck({size, fill}: IconProps) {
return (
<svg fill="none" viewBox="0 0 24 24" width={size} height={size}>
<path
fill={fill}
fillRule="evenodd"
clipRule="evenodd"
d="M8.792 1.615a4.154 4.154 0 0 1 6.416 0 4.154 4.154 0 0 0 3.146 1.515 4.154 4.154 0 0 1 4 5.017 4.154 4.154 0 0 0 .777 3.404 4.154 4.154 0 0 1-1.427 6.255 4.153 4.153 0 0 0-2.177 2.73 4.154 4.154 0 0 1-5.781 2.784 4.154 4.154 0 0 0-3.492 0 4.154 4.154 0 0 1-5.78-2.784 4.154 4.154 0 0 0-2.178-2.73A4.154 4.154 0 0 1 .87 11.551a4.154 4.154 0 0 0 .776-3.404A4.154 4.154 0 0 1 5.646 3.13a4.154 4.154 0 0 0 3.146-1.515Z"
/>
<path
fill="#fff"
fillRule="evenodd"
clipRule="evenodd"
d="M17.861 8.26a1.438 1.438 0 0 1 0 2.033l-6.571 6.571a1.437 1.437 0 0 1-2.033 0L5.97 13.58a1.438 1.438 0 0 1 2.033-2.033l2.27 2.269 5.554-5.555a1.437 1.437 0 0 1 2.033 0Z"
/>
</svg>
)
}
@@ -1,5 +1,7 @@
import {ValidationResult} from '@atproto/lexicon'
export * as profile from './profile.js'
/**
* Fast type checking without full schema validation, for use with data we
* trust, or for non-critical path use cases. Why? Our SDK's `is*` identity
+10
View File
@@ -0,0 +1,10 @@
import {type AppBskyActorDefs, type ChatBskyActorDefs} from '@atproto/api'
/**
* Matches any profile view exported by our SDK
*/
export type AnyProfileView =
| AppBskyActorDefs.ProfileViewBasic
| AppBskyActorDefs.ProfileView
| AppBskyActorDefs.ProfileViewDetailed
| ChatBskyActorDefs.ProfileViewBasic
+33
View File
@@ -0,0 +1,33 @@
import {useMemo} from 'react'
import * as bsky from '../types/bsky/index.js'
export type VerificationState = {
role: 'default' | 'verifier'
isVerified: boolean
}
export function getVerificationState({
profile,
}: {
profile?: bsky.profile.AnyProfileView
}): VerificationState {
if (!profile || !profile.verification) {
return {
role: 'default',
isVerified: false,
}
}
const {verifiedStatus, trustedVerifierStatus} = profile.verification
const isVerifiedUser = ['valid', 'invalid'].includes(verifiedStatus)
const isVerifierUser = ['valid', 'invalid'].includes(trustedVerifierStatus)
const isVerified =
(isVerifiedUser && verifiedStatus === 'valid') ||
(isVerifierUser && trustedVerifierStatus === 'valid')
return {
role: isVerifierUser ? 'verifier' : 'default',
isVerified,
}
}