integrate api
This commit is contained in:
@@ -1,19 +1,32 @@
|
|||||||
import {useMemo, useState} from 'react'
|
import {useMemo, useState} from 'react'
|
||||||
import {View} from 'react-native'
|
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 {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
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 {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||||
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
|
import {logger} from '#/logger'
|
||||||
import {isWeb} from '#/platform/detection'
|
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 {platform, useTheme, web} from '#/alf'
|
||||||
import {atoms as a} 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 Dialog from '#/components/Dialog'
|
||||||
import * as Toggle from '#/components/forms/Toggle'
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
|
import {Loader} from '#/components/Loader'
|
||||||
import * as ProfileCard from '#/components/ProfileCard'
|
import * as ProfileCard from '#/components/ProfileCard'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import type * as bsky from '#/types/bsky'
|
import type * as bsky from '#/types/bsky'
|
||||||
|
import {Admonition} from '../Admonition'
|
||||||
|
|
||||||
export function SubscribeProfileDialog({
|
export function SubscribeProfileDialog({
|
||||||
control,
|
control,
|
||||||
@@ -38,11 +51,10 @@ export function SubscribeProfileDialog({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEMP - should be derived from profile view
|
const defaultState = {
|
||||||
const initialState = {
|
post: false,
|
||||||
posts: false,
|
reply: false,
|
||||||
replies: false,
|
} satisfies AppBskyNotificationDefs.ActivitySubscription
|
||||||
}
|
|
||||||
|
|
||||||
function DialogInner({
|
function DialogInner({
|
||||||
profile,
|
profile,
|
||||||
@@ -55,52 +67,93 @@ function DialogInner({
|
|||||||
}) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
|
const agent = useAgent()
|
||||||
const control = Dialog.useDialogContext()
|
const control = Dialog.useDialogContext()
|
||||||
|
const initialState = profile.viewer?.activitySubscription || defaultState
|
||||||
const [state, setState] = useState(initialState)
|
const [state, setState] = useState(initialState)
|
||||||
|
|
||||||
const values = useMemo(() => {
|
const values = useMemo(() => {
|
||||||
const {posts, replies} = state
|
const {post, reply} = state
|
||||||
const res = []
|
const res = []
|
||||||
if (posts) res.push('posts')
|
if (post) res.push('post')
|
||||||
if (replies) res.push('replies')
|
if (reply) res.push('reply')
|
||||||
return res
|
return res
|
||||||
}, [state])
|
}, [state])
|
||||||
|
|
||||||
const onChange = (newValues: string[]) => {
|
const onChange = (newValues: string[]) => {
|
||||||
setState(oldValues => {
|
setState(oldValues => {
|
||||||
// ensure you can't have replies without posts
|
// ensure you can't have reply without post
|
||||||
if (!oldValues.replies && newValues.includes('replies')) {
|
if (!oldValues.reply && newValues.includes('reply')) {
|
||||||
return {
|
return {
|
||||||
posts: true,
|
post: true,
|
||||||
replies: true,
|
reply: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldValues.posts && !newValues.includes('posts')) {
|
if (oldValues.post && !newValues.includes('post')) {
|
||||||
return {
|
return {
|
||||||
posts: false,
|
post: false,
|
||||||
replies: false,
|
reply: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
posts: newValues.includes('posts'),
|
post: newValues.includes('post'),
|
||||||
replies: newValues.includes('replies'),
|
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<ButtonProps, 'children'> = useMemo(() => {
|
const buttonProps: Omit<ButtonProps, 'children'> = useMemo(() => {
|
||||||
const isDirty =
|
const isDirty =
|
||||||
state.posts !== initialState.posts ||
|
state.post !== initialState.post || state.reply !== initialState.reply
|
||||||
state.replies !== initialState.replies
|
const hasAny = state.post || state.reply
|
||||||
const hasAny = state.posts || state.replies
|
|
||||||
|
|
||||||
if (isDirty) {
|
if (isDirty) {
|
||||||
return {
|
return {
|
||||||
label: _(msg`Save changes`),
|
label: _(msg`Save changes`),
|
||||||
color: hasAny ? 'primary' : 'negative',
|
color: hasAny ? 'primary' : 'negative',
|
||||||
|
onPress: () => saveChanges(state),
|
||||||
|
disabled: isSaving,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// on web, a disabled save button feels more natural than a massive close button
|
// on web, a disabled save button feels more natural than a massive close button
|
||||||
@@ -114,10 +167,11 @@ function DialogInner({
|
|||||||
return {
|
return {
|
||||||
label: _(msg`Cancel`),
|
label: _(msg`Cancel`),
|
||||||
color: 'secondary',
|
color: 'secondary',
|
||||||
|
onPress: () => control.close(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [state, _])
|
}, [state, initialState, control, _, isSaving, saveChanges])
|
||||||
|
|
||||||
const name = createSanitizedDisplayName(profile, false)
|
const name = createSanitizedDisplayName(profile, false)
|
||||||
|
|
||||||
@@ -156,7 +210,7 @@ function DialogInner({
|
|||||||
<View style={[a.gap_sm]}>
|
<View style={[a.gap_sm]}>
|
||||||
<Toggle.Item
|
<Toggle.Item
|
||||||
label={_(msg`Posts`)}
|
label={_(msg`Posts`)}
|
||||||
name="posts"
|
name="post"
|
||||||
style={[
|
style={[
|
||||||
a.flex_1,
|
a.flex_1,
|
||||||
a.py_xs,
|
a.py_xs,
|
||||||
@@ -173,7 +227,7 @@ function DialogInner({
|
|||||||
</Toggle.Item>
|
</Toggle.Item>
|
||||||
<Toggle.Item
|
<Toggle.Item
|
||||||
label={_(msg`Replies`)}
|
label={_(msg`Replies`)}
|
||||||
name="replies"
|
name="reply"
|
||||||
style={[
|
style={[
|
||||||
a.flex_1,
|
a.flex_1,
|
||||||
a.py_xs,
|
a.py_xs,
|
||||||
@@ -191,12 +245,15 @@ function DialogInner({
|
|||||||
</View>
|
</View>
|
||||||
</Toggle.Group>
|
</Toggle.Group>
|
||||||
|
|
||||||
<Button
|
{error && (
|
||||||
{...buttonProps}
|
<Admonition type="error">
|
||||||
size="large"
|
<Trans>Could not save changes: {cleanError(error)}</Trans>
|
||||||
variant="solid"
|
</Admonition>
|
||||||
onPress={() => control.close()}>
|
)}
|
||||||
|
|
||||||
|
<Button {...buttonProps} size="large" variant="solid">
|
||||||
<ButtonText>{buttonProps.label}</ButtonText>
|
<ButtonText>{buttonProps.label}</ButtonText>
|
||||||
|
{isSaving && <ButtonIcon icon={Loader} />}
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user