diff options
Diffstat (limited to 'src/state/queries')
-rw-r--r-- | src/state/queries/notifications/util.ts | 26 | ||||
-rw-r--r-- | src/state/queries/nuxs/definitions.ts | 6 | ||||
-rw-r--r-- | src/state/queries/preferences/const.ts | 7 | ||||
-rw-r--r-- | src/state/queries/preferences/index.ts | 30 | ||||
-rw-r--r-- | src/state/queries/profile.ts | 31 | ||||
-rw-r--r-- | src/state/queries/useCurrentAccountProfile.tsx | 9 | ||||
-rw-r--r-- | src/state/queries/verification/useUpdateProfileVerificationCache.ts | 35 | ||||
-rw-r--r-- | src/state/queries/verification/useVerificationCreateMutation.tsx | 53 | ||||
-rw-r--r-- | src/state/queries/verification/useVerificationsRemoveMutation.tsx | 63 |
9 files changed, 229 insertions, 31 deletions
diff --git a/src/state/queries/notifications/util.ts b/src/state/queries/notifications/util.ts index f6f53f58f..6bbf9b250 100644 --- a/src/state/queries/notifications/util.ts +++ b/src/state/queries/notifications/util.ts @@ -1,22 +1,26 @@ import { - AppBskyFeedDefs, + type AppBskyFeedDefs, AppBskyFeedLike, AppBskyFeedPost, AppBskyFeedRepost, - AppBskyGraphDefs, + type AppBskyGraphDefs, AppBskyGraphStarterpack, - AppBskyNotificationListNotifications, - BskyAgent, + type AppBskyNotificationListNotifications, + type BskyAgent, moderateNotification, - ModerationOpts, + type ModerationOpts, } from '@atproto/api' -import {QueryClient} from '@tanstack/react-query' +import {type QueryClient} from '@tanstack/react-query' import chunk from 'lodash.chunk' import {labelIsHideableOffense} from '#/lib/moderation' import * as bsky from '#/types/bsky' import {precacheProfile} from '../profile' -import {FeedNotification, FeedPage, NotificationType} from './types' +import { + type FeedNotification, + type FeedPage, + type NotificationType, +} from './types' const GROUPABLE_REASONS = ['like', 'repost', 'follow'] const MS_1HR = 1e3 * 60 * 60 @@ -155,14 +159,14 @@ export function groupNotifications( const type = toKnownType(notif) if (type !== 'starterpack-joined') { groupedNotifs.push({ - _reactKey: `notif-${notif.uri}`, + _reactKey: `notif-${notif.uri}-${notif.reason}`, type, notification: notif, subjectUri: getSubjectUri(type, notif), }) } else { groupedNotifs.push({ - _reactKey: `notif-${notif.uri}`, + _reactKey: `notif-${notif.uri}-${notif.reason}`, type: 'starterpack-joined', notification: notif, subjectUri: notif.uri, @@ -238,7 +242,9 @@ function toKnownType( notif.reason === 'reply' || notif.reason === 'quote' || notif.reason === 'follow' || - notif.reason === 'starterpack-joined' + notif.reason === 'starterpack-joined' || + notif.reason === 'verified' || + notif.reason === 'unverified' ) { return notif.reason as NotificationType } diff --git a/src/state/queries/nuxs/definitions.ts b/src/state/queries/nuxs/definitions.ts index 8eb53a0a4..a44ffa4c5 100644 --- a/src/state/queries/nuxs/definitions.ts +++ b/src/state/queries/nuxs/definitions.ts @@ -5,6 +5,7 @@ import {type BaseNux} from '#/state/queries/nuxs/types' export enum Nux { NeueTypography = 'NeueTypography', ExploreInterestsCard = 'ExploreInterestsCard', + InitialVerificationAnnouncement = 'InitialVerificationAnnouncement', } export const nuxNames = new Set(Object.values(Nux)) @@ -18,9 +19,14 @@ export type AppNux = BaseNux< id: Nux.ExploreInterestsCard data: undefined } + | { + id: Nux.InitialVerificationAnnouncement + data: undefined + } > export const NuxSchemas: Record<Nux, zod.ZodObject<any> | undefined> = { [Nux.NeueTypography]: undefined, [Nux.ExploreInterestsCard]: undefined, + [Nux.InitialVerificationAnnouncement]: undefined, } diff --git a/src/state/queries/preferences/const.ts b/src/state/queries/preferences/const.ts index 3c1fead5e..84b208a9f 100644 --- a/src/state/queries/preferences/const.ts +++ b/src/state/queries/preferences/const.ts @@ -1,7 +1,7 @@ import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation' import { - ThreadViewPreferences, - UsePreferencesQueryResponse, + type ThreadViewPreferences, + type UsePreferencesQueryResponse, } from '#/state/queries/preferences/types' export const DEFAULT_HOME_FEED_PREFS: UsePreferencesQueryResponse['feedViewPrefs'] = @@ -43,4 +43,7 @@ export const DEFAULT_LOGGED_OUT_PREFERENCES: UsePreferencesQueryResponse = { threadgateAllowRules: undefined, postgateEmbeddingRules: [], }, + verificationPrefs: { + hideBadges: false, + }, } diff --git a/src/state/queries/preferences/index.ts b/src/state/queries/preferences/index.ts index 81b3dd086..daab5eca3 100644 --- a/src/state/queries/preferences/index.ts +++ b/src/state/queries/preferences/index.ts @@ -1,7 +1,7 @@ import { - AppBskyActorDefs, - BskyFeedViewPreference, - LabelPreference, + type AppBskyActorDefs, + type BskyFeedViewPreference, + type LabelPreference, } from '@atproto/api' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' @@ -16,8 +16,8 @@ import { DEFAULT_THREAD_VIEW_PREFS, } from '#/state/queries/preferences/const' import { - ThreadViewPreferences, - UsePreferencesQueryResponse, + type ThreadViewPreferences, + type UsePreferencesQueryResponse, } from '#/state/queries/preferences/types' import {useAgent} from '#/state/session' import {saveLabelers} from '#/state/session/agent-config' @@ -407,3 +407,23 @@ export function useSetActiveProgressGuideMutation() { }, }) } + +export function useSetVerificationPrefsMutation() { + const queryClient = useQueryClient() + const agent = useAgent() + + return useMutation<void, unknown, AppBskyActorDefs.VerificationPrefs>({ + mutationFn: async prefs => { + await agent.setVerificationPrefs(prefs) + if (prefs.hideBadges) { + logger.metric('verification:settings:hideBadges', {}) + } else { + logger.metric('verification:settings:unHideBadges', {}) + } + // triggers a refetch + await queryClient.invalidateQueries({ + queryKey: preferencesQueryKey, + }) + }, + }) +} diff --git a/src/state/queries/profile.ts b/src/state/queries/profile.ts index 2cf144d3a..609a62e25 100644 --- a/src/state/queries/profile.ts +++ b/src/state/queries/profile.ts @@ -1,18 +1,18 @@ import {useCallback} from 'react' -import {Image as RNImage} from 'react-native-image-crop-picker' +import {type Image as RNImage} from 'react-native-image-crop-picker' import { - AppBskyActorDefs, - AppBskyActorGetProfile, - AppBskyActorGetProfiles, - AppBskyActorProfile, + type AppBskyActorDefs, + type AppBskyActorGetProfile, + type AppBskyActorGetProfiles, + type AppBskyActorProfile, AtUri, - BskyAgent, - ComAtprotoRepoUploadBlob, - Un$Typed, + type BskyAgent, + type ComAtprotoRepoUploadBlob, + type Un$Typed, } from '@atproto/api' import { keepPreviousData, - QueryClient, + type QueryClient, useMutation, useQuery, useQueryClient, @@ -21,16 +21,17 @@ import { import {uploadBlob} from '#/lib/api' import {until} from '#/lib/async/until' import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue' -import {logEvent, LogEvents, toClout} from '#/lib/statsig/statsig' -import {Shadow} from '#/state/cache/types' +import {logEvent, type LogEvents, toClout} from '#/lib/statsig/statsig' +import {type Shadow} from '#/state/cache/types' import {STALE} from '#/state/queries' import {resetProfilePostsQueries} from '#/state/queries/post-feed' import { unstableCacheProfileView, useUnstableProfileViewCache, } from '#/state/queries/unstable-profile-cache' +import {useUpdateProfileVerificationCache} from '#/state/queries/verification/useUpdateProfileVerificationCache' import * as userActionHistory from '#/state/userActionHistory' -import * as bsky from '#/types/bsky' +import type * as bsky from '#/types/bsky' import {updateProfileShadow} from '../cache/profile-shadow' import {useAgent, useSession} from '../session' import { @@ -50,7 +51,7 @@ export const precacheProfile = unstableCacheProfileView const RQKEY_ROOT = 'profile' export const RQKEY = (did: string) => [RQKEY_ROOT, did] -const profilesQueryKeyRoot = 'profiles' +export const profilesQueryKeyRoot = 'profiles' export const profilesQueryKey = (handles: string[]) => [ profilesQueryKeyRoot, handles, @@ -137,6 +138,7 @@ interface ProfileUpdateParams { export function useProfileUpdateMutation() { const queryClient = useQueryClient() const agent = useAgent() + const updateProfileVerificationCache = useUpdateProfileVerificationCache() return useMutation<void, Error, ProfileUpdateParams>({ mutationFn: async ({ profile, @@ -223,7 +225,7 @@ export function useProfileUpdateMutation() { }), ) }, - onSuccess(data, variables) { + async onSuccess(_, variables) { // invalidate cache queryClient.invalidateQueries({ queryKey: RQKEY(variables.profile.did), @@ -231,6 +233,7 @@ export function useProfileUpdateMutation() { queryClient.invalidateQueries({ queryKey: [profilesQueryKeyRoot, [variables.profile.did]], }) + await updateProfileVerificationCache({profile: variables.profile}) }, }) } diff --git a/src/state/queries/useCurrentAccountProfile.tsx b/src/state/queries/useCurrentAccountProfile.tsx new file mode 100644 index 000000000..d1f562efc --- /dev/null +++ b/src/state/queries/useCurrentAccountProfile.tsx @@ -0,0 +1,9 @@ +import {useMaybeProfileShadow} from '#/state/cache/profile-shadow' +import {useProfileQuery} from '#/state/queries/profile' +import {useSession} from '#/state/session' + +export function useCurrentAccountProfile() { + const {currentAccount} = useSession() + const {data: profile} = useProfileQuery({did: currentAccount?.did}) + return useMaybeProfileShadow(profile) +} diff --git a/src/state/queries/verification/useUpdateProfileVerificationCache.ts b/src/state/queries/verification/useUpdateProfileVerificationCache.ts new file mode 100644 index 000000000..f5ccf1458 --- /dev/null +++ b/src/state/queries/verification/useUpdateProfileVerificationCache.ts @@ -0,0 +1,35 @@ +import {useCallback} from 'react' +import {useQueryClient} from '@tanstack/react-query' + +import {logger} from '#/logger' +import {updateProfileShadow} from '#/state/cache/profile-shadow' +import {useAgent} from '#/state/session' +import type * as bsky from '#/types/bsky' + +/** + * Fetches a fresh verification state from the app view and updates our profile + * cache. This state is computed using a variety of factors on the server, so + * we need to get this data from the server. + */ +export function useUpdateProfileVerificationCache() { + const qc = useQueryClient() + const agent = useAgent() + + return useCallback( + async ({profile}: {profile: bsky.profile.AnyProfileView}) => { + try { + const {data: updated} = await agent.getProfile({ + actor: profile.did ?? '', + }) + updateProfileShadow(qc, profile.did, { + verification: updated.verification, + }) + } catch (e) { + logger.error(`useUpdateProfileVerificationCache failed`, { + safeMessage: e, + }) + } + }, + [agent, qc], + ) +} diff --git a/src/state/queries/verification/useVerificationCreateMutation.tsx b/src/state/queries/verification/useVerificationCreateMutation.tsx new file mode 100644 index 000000000..1048eb9d2 --- /dev/null +++ b/src/state/queries/verification/useVerificationCreateMutation.tsx @@ -0,0 +1,53 @@ +import {type AppBskyActorGetProfile} from '@atproto/api' +import {useMutation} from '@tanstack/react-query' + +import {until} from '#/lib/async/until' +import {logger} from '#/logger' +import {useUpdateProfileVerificationCache} from '#/state/queries/verification/useUpdateProfileVerificationCache' +import {useAgent, useSession} from '#/state/session' +import type * as bsky from '#/types/bsky' + +export function useVerificationCreateMutation() { + const agent = useAgent() + const {currentAccount} = useSession() + const updateProfileVerificationCache = useUpdateProfileVerificationCache() + + return useMutation({ + async mutationFn({profile}: {profile: bsky.profile.AnyProfileView}) { + if (!currentAccount) { + throw new Error('User not logged in') + } + + const {uri} = await agent.app.bsky.graph.verification.create( + {repo: currentAccount.did}, + { + subject: profile.did, + createdAt: new Date().toISOString(), + handle: profile.handle, + displayName: profile.displayName || '', + }, + ) + + await until( + 5, + 1e3, + ({data: profile}: AppBskyActorGetProfile.Response) => { + if ( + profile.verification && + profile.verification.verifications.find(v => v.uri === uri) + ) { + return true + } + return false + }, + () => { + return agent.getProfile({actor: profile.did ?? ''}) + }, + ) + }, + async onSuccess(_, {profile}) { + logger.metric('verification:create', {}) + await updateProfileVerificationCache({profile}) + }, + }) +} diff --git a/src/state/queries/verification/useVerificationsRemoveMutation.tsx b/src/state/queries/verification/useVerificationsRemoveMutation.tsx new file mode 100644 index 000000000..936c786c9 --- /dev/null +++ b/src/state/queries/verification/useVerificationsRemoveMutation.tsx @@ -0,0 +1,63 @@ +import { + type AppBskyActorDefs, + type AppBskyActorGetProfile, + AtUri, +} from '@atproto/api' +import {useMutation} from '@tanstack/react-query' + +import {until} from '#/lib/async/until' +import {logger} from '#/logger' +import {useUpdateProfileVerificationCache} from '#/state/queries/verification/useUpdateProfileVerificationCache' +import {useAgent, useSession} from '#/state/session' +import type * as bsky from '#/types/bsky' + +export function useVerificationsRemoveMutation() { + const agent = useAgent() + const {currentAccount} = useSession() + const updateProfileVerificationCache = useUpdateProfileVerificationCache() + + return useMutation({ + async mutationFn({ + profile, + verifications, + }: { + profile: bsky.profile.AnyProfileView + verifications: AppBskyActorDefs.VerificationView[] + }) { + if (!currentAccount) { + throw new Error('User not logged in') + } + + const uris = verifications.map(v => v.uri) + + await Promise.all( + uris.map(uri => { + return agent.app.bsky.graph.verification.delete({ + repo: currentAccount.did, + rkey: new AtUri(uri).rkey, + }) + }), + ) + + await until( + 5, + 1e3, + ({data: profile}: AppBskyActorGetProfile.Response) => { + if ( + !profile.verification?.verifications.some(v => uris.includes(v.uri)) + ) { + return true + } + return false + }, + () => { + return agent.getProfile({actor: profile.did ?? ''}) + }, + ) + }, + async onSuccess(_, {profile}) { + logger.metric('verification:revoke', {}) + await updateProfileVerificationCache({profile}) + }, + }) +} |