Hook up shadow and mutation

This commit is contained in:
Eric Bailey
2025-08-25 14:32:32 -05:00
parent 5782ce7a0e
commit 2950b3b8c0
5 changed files with 106 additions and 9 deletions
+46 -6
View File
@@ -1,28 +1,68 @@
import {memo} from 'react'
import {type AppBskyFeedDefs, type AppBskyFeedPost} from '@atproto/api'
import {memo, useCallback} from 'react'
import {type AppBskyFeedDefs} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import type React from 'react'
import {useCleanError} from '#/lib/hooks/useCleanError'
import {type Shadow} from '#/state/cache/post-shadow'
import {Bookmark} from '#/components/icons/Bookmark'
import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation'
import {useTheme} from '#/alf'
import {Bookmark, BookmarkFilled} from '#/components/icons/Bookmark'
import {PostControlButton, PostControlButtonIcon} from './PostControlButton'
export const BookmarkButton = memo(function BookmarkButton({
post,
big,
}: {
post: Shadow<AppBskyFeedDefs.PostView>
big?: boolean
record: AppBskyFeedPost.Record
}): React.ReactNode {
const t = useTheme()
const {_} = useLingui()
const {mutateAsync: bookmark} = useBookmarkMutation()
const cleanError = useCleanError()
const {uri, cid, viewer} = post
const isBookmarked = !!viewer?.bookmarked
const onHandlePress = useCallback(async () => {
try {
if (isBookmarked) {
await bookmark({
action: 'delete',
uri,
})
// TODO toast
} else {
await bookmark({
action: 'create',
uri,
cid,
})
// TODO toast
}
} catch (e) {
const {raw, clean} = cleanError(e)
console.log(clean || raw || e)
// TODO toast
}
}, [uri, cid, isBookmarked, bookmark, cleanError])
return (
<PostControlButton
testID="postBookmarkBtn"
big={big}
label={_(msg`Bookmark this post`)}>
<PostControlButtonIcon icon={Bookmark} />
label={
isBookmarked
? _(msg`Remove this post from your bookmarks`)
: _(msg`Save this post to your bookmarks`)
}
onPress={onHandlePress}>
<PostControlButtonIcon
fill={isBookmarked ? t.palette.primary_500 : undefined}
icon={isBookmarked ? BookmarkFilled : Bookmark}
/>
</PostControlButton>
)
})
@@ -102,12 +102,20 @@ export function PostControlButton({
export function PostControlButtonIcon({
icon: Comp,
}: {
style,
...rest
}: SVGIconProps & {
icon: React.ComponentType<SVGIconProps>
}) {
const {big, color} = useContext(PostControlContext)
return <Comp style={[color, a.pointer_events_none]} width={big ? 22 : 18} />
return (
<Comp
style={[color, a.pointer_events_none, style]}
{...rest}
width={big ? 22 : 18}
/>
)
}
export function PostControlButtonText({style, ...props}: TextProps) {
+1 -1
View File
@@ -284,7 +284,7 @@ let PostControls = ({
a.flex_1,
big ? a.gap_sm : a.gap_xs,
]}>
<BookmarkButton post={post} big={big} record={record} />
<BookmarkButton post={post} big={big} />
<ShareMenuButton
testID="postShareBtn"
post={post}
+3
View File
@@ -25,6 +25,7 @@ export interface PostShadow {
embed: AppBskyEmbedRecord.View | AppBskyEmbedRecordWithMedia.View | undefined
pinned: boolean
optimisticReplyCount: number | undefined
bookmarked: boolean | undefined
}
export const POST_TOMBSTONE = Symbol('PostTombstone')
@@ -132,6 +133,8 @@ function mergeShadow(
like: 'likeUri' in shadow ? shadow.likeUri : post.viewer?.like,
repost: 'repostUri' in shadow ? shadow.repostUri : post.viewer?.repost,
pinned: 'pinned' in shadow ? shadow.pinned : post.viewer?.pinned,
bookmarked:
'bookmarked' in shadow ? shadow.bookmarked : post.viewer?.bookmarked,
},
})
}
@@ -0,0 +1,46 @@
import {useMutation, useQueryClient} from '@tanstack/react-query'
import {isNetworkError} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {updatePostShadow} from '#/state/cache/post-shadow'
import {useAgent} from '#/state/session'
type MutationArgs =
| {action: 'create'; uri: string; cid: string}
| {action: 'delete'; uri: string}
export function useBookmarkMutation() {
const qc = useQueryClient()
const agent = useAgent()
return useMutation({
async mutationFn(args: MutationArgs) {
if (args.action === 'create') {
updatePostShadow(qc, args.uri, {bookmarked: true})
await agent.app.bsky.bookmark.createBookmark({
uri: args.uri,
cid: args.cid,
})
} else if (args.action === 'delete') {
updatePostShadow(qc, args.uri, {bookmarked: false})
await agent.app.bsky.bookmark.deleteBookmark({
uri: args.uri,
})
}
},
onError(e, args) {
if (args.action === 'create') {
updatePostShadow(qc, args.uri, {bookmarked: false})
} else if (args.action === 'delete') {
updatePostShadow(qc, args.uri, {bookmarked: true})
}
if (!isNetworkError(e)) {
logger.error('bookmark mutation failed', {
bookmarkAction: args.action,
safeMessage: e,
})
}
},
})
}