From 2902ab9b48984a049358e5fb75e7ac88f203cc31 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 27 Jun 2025 15:22:14 +0300 Subject: [PATCH] integrate api --- .../SubscribeProfileDialog.tsx | 119 +++++++++++++----- 1 file changed, 88 insertions(+), 31 deletions(-) diff --git a/src/components/activity-notifications/SubscribeProfileDialog.tsx b/src/components/activity-notifications/SubscribeProfileDialog.tsx index 6188963b7c..cb0bb6b1c9 100644 --- a/src/components/activity-notifications/SubscribeProfileDialog.tsx +++ b/src/components/activity-notifications/SubscribeProfileDialog.tsx @@ -1,19 +1,32 @@ import {useMemo, useState} from 'react' import {View} from 'react-native' -import {type ModerationOpts} from '@atproto/api' +import {type AppBskyNotificationDefs, type ModerationOpts} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useMutation} from '@tanstack/react-query' +import {sanitizeHandle} from 'bskyogcard/dist/util/sanitizeHandle' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' +import {cleanError} from '#/lib/strings/errors' +import {logger} from '#/logger' import {isWeb} from '#/platform/detection' +import {useAgent} from '#/state/session' +import * as Toast from '#/view/com/util/Toast' import {platform, useTheme, web} from '#/alf' import {atoms as a} from '#/alf' -import {Button, type ButtonProps, ButtonText} from '#/components/Button' +import { + Button, + ButtonIcon, + type ButtonProps, + ButtonText, +} from '#/components/Button' import * as Dialog from '#/components/Dialog' import * as Toggle from '#/components/forms/Toggle' +import {Loader} from '#/components/Loader' import * as ProfileCard from '#/components/ProfileCard' import {Text} from '#/components/Typography' import type * as bsky from '#/types/bsky' +import {Admonition} from '../Admonition' export function SubscribeProfileDialog({ control, @@ -38,11 +51,10 @@ export function SubscribeProfileDialog({ ) } -// TEMP - should be derived from profile view -const initialState = { - posts: false, - replies: false, -} +const defaultState = { + post: false, + reply: false, +} satisfies AppBskyNotificationDefs.ActivitySubscription function DialogInner({ profile, @@ -55,52 +67,93 @@ function DialogInner({ }) { const {_} = useLingui() const t = useTheme() + const agent = useAgent() const control = Dialog.useDialogContext() - + const initialState = profile.viewer?.activitySubscription || defaultState const [state, setState] = useState(initialState) const values = useMemo(() => { - const {posts, replies} = state + const {post, reply} = state const res = [] - if (posts) res.push('posts') - if (replies) res.push('replies') + if (post) res.push('post') + if (reply) res.push('reply') return res }, [state]) const onChange = (newValues: string[]) => { setState(oldValues => { - // ensure you can't have replies without posts - if (!oldValues.replies && newValues.includes('replies')) { + // ensure you can't have reply without post + if (!oldValues.reply && newValues.includes('reply')) { return { - posts: true, - replies: true, + post: true, + reply: true, } } - if (oldValues.posts && !newValues.includes('posts')) { + if (oldValues.post && !newValues.includes('post')) { return { - posts: false, - replies: false, + post: false, + reply: false, } } return { - posts: newValues.includes('posts'), - replies: newValues.includes('replies'), + post: newValues.includes('post'), + reply: newValues.includes('reply'), } }) } + const { + mutate: saveChanges, + isPending: isSaving, + error, + } = useMutation({ + mutationFn: async ( + activitySubscription: AppBskyNotificationDefs.ActivitySubscription, + ) => { + await new Promise(resolve => setTimeout(resolve, 1000)) + await agent.app.bsky.notification.putActivitySubscription({ + subject: profile.did, + activitySubscription, + }) + }, + onSuccess: (_data, variables) => { + control.close() + if (!variables.post && !variables.reply) { + Toast.show( + _( + msg`You will no longer receive notifications for ${sanitizeHandle(profile.handle, '@')}`, + ), + 'check', + ) + } else if (!initialState.post && !initialState) { + Toast.show( + _( + msg`You'll start receiving notifications for ${sanitizeHandle(profile.handle, '@')}!`, + ), + 'check', + ) + } else { + Toast.show(_(msg`Changes saved`), 'check') + } + }, + onError: error => { + logger.error('Could not save activity subscription', {message: error}) + }, + }) + const buttonProps: Omit = useMemo(() => { const isDirty = - state.posts !== initialState.posts || - state.replies !== initialState.replies - const hasAny = state.posts || state.replies + state.post !== initialState.post || state.reply !== initialState.reply + const hasAny = state.post || state.reply if (isDirty) { return { label: _(msg`Save changes`), color: hasAny ? 'primary' : 'negative', + onPress: () => saveChanges(state), + disabled: isSaving, } } else { // on web, a disabled save button feels more natural than a massive close button @@ -114,10 +167,11 @@ function DialogInner({ return { label: _(msg`Cancel`), color: 'secondary', + onPress: () => control.close(), } } } - }, [state, _]) + }, [state, initialState, control, _, isSaving, saveChanges]) const name = createSanitizedDisplayName(profile, false) @@ -156,7 +210,7 @@ function DialogInner({ -