From e78fa95c3ece29f83ac9bde28dbee8f495a58b6e Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 10 Jun 2025 10:23:32 +0300 Subject: [PATCH] add verification --- bskyogcard/src/components/Post/index.tsx | 22 ++++++++++--- bskyogcard/src/components/PostEmbed.tsx | 2 +- .../src/components/PostEmbed/QuotePost.tsx | 31 +++++++++++++++-- bskyogcard/src/components/StarterPack.tsx | 2 +- .../src/components/VerificationCheck.tsx | 12 +++++++ .../src/components/icons/VerifiedCheck.tsx | 15 +++++++++ .../src/components/icons/VerifierCheck.tsx | 20 +++++++++++ .../src/types/{bsky.ts => bsky/index.ts} | 2 ++ bskyogcard/src/types/bsky/profile.ts | 10 ++++++ bskyogcard/src/util/verificationState.ts | 33 +++++++++++++++++++ 10 files changed, 140 insertions(+), 9 deletions(-) create mode 100644 bskyogcard/src/components/VerificationCheck.tsx create mode 100644 bskyogcard/src/components/icons/VerifiedCheck.tsx create mode 100644 bskyogcard/src/components/icons/VerifierCheck.tsx rename bskyogcard/src/types/{bsky.ts => bsky/index.ts} (97%) create mode 100644 bskyogcard/src/types/bsky/profile.ts create mode 100644 bskyogcard/src/util/verificationState.ts diff --git a/bskyogcard/src/components/Post/index.tsx b/bskyogcard/src/components/Post/index.tsx index 04bd775f76..b0b60b1846 100644 --- a/bskyogcard/src/components/Post/index.tsx +++ b/bskyogcard/src/components/Post/index.tsx @@ -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({ /> - - {post.author.displayName || post.author.handle} - + + + {post.author.displayName || post.author.handle} + + {verification.isVerified && ( + + + + )} + {sanitizeHandle(post.author.handle, '@')} diff --git a/bskyogcard/src/components/PostEmbed.tsx b/bskyogcard/src/components/PostEmbed.tsx index 32768cd6fe..6c87701e62 100644 --- a/bskyogcard/src/components/PostEmbed.tsx +++ b/bskyogcard/src/components/PostEmbed.tsx @@ -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' diff --git a/bskyogcard/src/components/PostEmbed/QuotePost.tsx b/bskyogcard/src/components/PostEmbed/QuotePost.tsx index 925e44568a..e42e640ec9 100644 --- a/bskyogcard/src/components/PostEmbed/QuotePost.tsx +++ b/bskyogcard/src/components/PostEmbed/QuotePost.tsx @@ -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} /> - + {author.displayName || author.handle} - + {verification.isVerified && ( + + + + )} + {sanitizeHandle(author.handle, '@')} diff --git a/bskyogcard/src/components/StarterPack.tsx b/bskyogcard/src/components/StarterPack.tsx index 9ae2663e2a..e07fb6eee9 100644 --- a/bskyogcard/src/components/StarterPack.tsx +++ b/bskyogcard/src/components/StarterPack.tsx @@ -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' diff --git a/bskyogcard/src/components/VerificationCheck.tsx b/bskyogcard/src/components/VerificationCheck.tsx new file mode 100644 index 0000000000..5a6d8bf86b --- /dev/null +++ b/bskyogcard/src/components/VerificationCheck.tsx @@ -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 ? : +} diff --git a/bskyogcard/src/components/icons/VerifiedCheck.tsx b/bskyogcard/src/components/icons/VerifiedCheck.tsx new file mode 100644 index 0000000000..62c138f4bc --- /dev/null +++ b/bskyogcard/src/components/icons/VerifiedCheck.tsx @@ -0,0 +1,15 @@ +import {IconProps} from './types.js' + +export function VerifiedCheck({size, fill}: IconProps) { + return ( + + + + + ) +} diff --git a/bskyogcard/src/components/icons/VerifierCheck.tsx b/bskyogcard/src/components/icons/VerifierCheck.tsx new file mode 100644 index 0000000000..e199ab16b0 --- /dev/null +++ b/bskyogcard/src/components/icons/VerifierCheck.tsx @@ -0,0 +1,20 @@ +import {IconProps} from './types.js' + +export function VerifierCheck({size, fill}: IconProps) { + return ( + + + + + ) +} diff --git a/bskyogcard/src/types/bsky.ts b/bskyogcard/src/types/bsky/index.ts similarity index 97% rename from bskyogcard/src/types/bsky.ts rename to bskyogcard/src/types/bsky/index.ts index a48006ff62..53a2a39116 100644 --- a/bskyogcard/src/types/bsky.ts +++ b/bskyogcard/src/types/bsky/index.ts @@ -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 diff --git a/bskyogcard/src/types/bsky/profile.ts b/bskyogcard/src/types/bsky/profile.ts new file mode 100644 index 0000000000..12c8146ae1 --- /dev/null +++ b/bskyogcard/src/types/bsky/profile.ts @@ -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 diff --git a/bskyogcard/src/util/verificationState.ts b/bskyogcard/src/util/verificationState.ts new file mode 100644 index 0000000000..1fba62a526 --- /dev/null +++ b/bskyogcard/src/util/verificationState.ts @@ -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, + } +}