import { AppBskyActorDefs, BskyFeedViewPreference, LabelPreference, } from '@atproto/api' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import {track} from '#/lib/analytics/analytics' import {PROD_DEFAULT_FEED} from '#/lib/constants' import {replaceEqualDeep} from '#/lib/functions' import {getAge} from '#/lib/strings/time' import {STALE} from '#/state/queries' import { DEFAULT_HOME_FEED_PREFS, DEFAULT_LOGGED_OUT_PREFERENCES, DEFAULT_THREAD_VIEW_PREFS, } from '#/state/queries/preferences/const' import { ThreadViewPreferences, UsePreferencesQueryResponse, } from '#/state/queries/preferences/types' import {useAgent} from '#/state/session' import {saveLabelers} from '#/state/session/agent-config' export * from '#/state/queries/preferences/const' export * from '#/state/queries/preferences/moderation' export * from '#/state/queries/preferences/types' const preferencesQueryKeyRoot = 'getPreferences' export const preferencesQueryKey = [preferencesQueryKeyRoot] export function usePreferencesQuery() { const {getAgent} = useAgent() return useQuery({ staleTime: STALE.SECONDS.FIFTEEN, structuralSharing: replaceEqualDeep, refetchOnWindowFocus: true, queryKey: preferencesQueryKey, queryFn: async () => { const agent = getAgent() if (agent.session?.did === undefined) { return DEFAULT_LOGGED_OUT_PREFERENCES } else { const res = await agent.getPreferences() // save to local storage to ensure there are labels on initial requests saveLabelers( agent.session.did, res.moderationPrefs.labelers.map(l => l.did), ) const preferences: UsePreferencesQueryResponse = { ...res, savedFeeds: res.savedFeeds.filter(f => f.type !== 'unknown'), /** * Special preference, only used for following feed, previously * called `home` */ feedViewPrefs: { ...DEFAULT_HOME_FEED_PREFS, ...(res.feedViewPrefs.home || {}), }, threadViewPrefs: { ...DEFAULT_THREAD_VIEW_PREFS, ...(res.threadViewPrefs ?? {}), }, userAge: res.birthDate ? getAge(res.birthDate) : undefined, } return preferences } }, }) } export function useClearPreferencesMutation() { const queryClient = useQueryClient() const {getAgent} = useAgent() return useMutation({ mutationFn: async () => { await getAgent().app.bsky.actor.putPreferences({preferences: []}) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) }, }) } export function usePreferencesSetContentLabelMutation() { const {getAgent} = useAgent() const queryClient = useQueryClient() return useMutation< void, unknown, {label: string; visibility: LabelPreference; labelerDid: string | undefined} >({ mutationFn: async ({label, visibility, labelerDid}) => { await getAgent().setContentLabelPref(label, visibility, labelerDid) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) }, }) } export function useSetContentLabelMutation() { const queryClient = useQueryClient() const {getAgent} = useAgent() return useMutation({ mutationFn: async ({ label, visibility, labelerDid, }: { label: string visibility: LabelPreference labelerDid?: string }) => { await getAgent().setContentLabelPref(label, visibility, labelerDid) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) }, }) } export function usePreferencesSetAdultContentMutation() { const queryClient = useQueryClient() const {getAgent} = useAgent() return useMutation({ mutationFn: async ({enabled}) => { await getAgent().setAdultContentEnabled(enabled) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) }, }) } export function usePreferencesSetBirthDateMutation() { const queryClient = useQueryClient() const {getAgent} = useAgent() return useMutation({ mutationFn: async ({birthDate}: {birthDate: Date}) => { await getAgent().setPersonalDetails({birthDate: birthDate.toISOString()}) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) }, }) } export function useSetFeedViewPreferencesMutation() { const queryClient = useQueryClient() const {getAgent} = useAgent() return useMutation>({ mutationFn: async prefs => { /* * special handling here, merged into `feedViewPrefs` above, since * following was previously called `home` */ await getAgent().setFeedViewPrefs('home', prefs) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) }, }) } export function useSetThreadViewPreferencesMutation() { const queryClient = useQueryClient() const {getAgent} = useAgent() return useMutation>({ mutationFn: async prefs => { await getAgent().setThreadViewPrefs(prefs) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) }, }) } export function useOverwriteSavedFeedsMutation() { const queryClient = useQueryClient() const {getAgent} = useAgent() return useMutation({ mutationFn: async savedFeeds => { await getAgent().overwriteSavedFeeds(savedFeeds) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) }, }) } export function useAddSavedFeedsMutation() { const queryClient = useQueryClient() const {getAgent} = useAgent() return useMutation< void, unknown, Pick[] >({ mutationFn: async savedFeeds => { await getAgent().addSavedFeeds(savedFeeds) track('CustomFeed:Save') // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) }, }) } export function useRemoveFeedMutation() { const queryClient = useQueryClient() const {getAgent} = useAgent() return useMutation>({ mutationFn: async savedFeed => { await getAgent().removeSavedFeeds([savedFeed.id]) track('CustomFeed:Unsave') // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) }, }) } export function useReplaceForYouWithDiscoverFeedMutation() { const queryClient = useQueryClient() const {getAgent} = useAgent() return useMutation({ mutationFn: async ({ forYouFeedConfig, discoverFeedConfig, }: { forYouFeedConfig: AppBskyActorDefs.SavedFeed | undefined discoverFeedConfig: AppBskyActorDefs.SavedFeed | undefined }) => { if (forYouFeedConfig) { await getAgent().removeSavedFeeds([forYouFeedConfig.id]) } if (!discoverFeedConfig) { await getAgent().addSavedFeeds([ { type: 'feed', value: PROD_DEFAULT_FEED('whats-hot'), pinned: true, }, ]) } else { await getAgent().updateSavedFeeds([ { ...discoverFeedConfig, pinned: true, }, ]) } // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) }, }) } export function useUpdateSavedFeedsMutation() { const queryClient = useQueryClient() const {getAgent} = useAgent() return useMutation({ mutationFn: async feeds => { await getAgent().updateSavedFeeds(feeds) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) }, }) } export function useUpsertMutedWordsMutation() { const queryClient = useQueryClient() const {getAgent} = useAgent() return useMutation({ mutationFn: async (mutedWords: AppBskyActorDefs.MutedWord[]) => { await getAgent().upsertMutedWords(mutedWords) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) }, }) } export function useUpdateMutedWordMutation() { const queryClient = useQueryClient() const {getAgent} = useAgent() return useMutation({ mutationFn: async (mutedWord: AppBskyActorDefs.MutedWord) => { await getAgent().updateMutedWord(mutedWord) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) }, }) } export function useRemoveMutedWordMutation() { const queryClient = useQueryClient() const {getAgent} = useAgent() return useMutation({ mutationFn: async (mutedWord: AppBskyActorDefs.MutedWord) => { await getAgent().removeMutedWord(mutedWord) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) }, }) }