Hook up shadow and mutation
This commit is contained in:
@@ -1,28 +1,68 @@
|
|||||||
import {memo} from 'react'
|
import {memo, useCallback} from 'react'
|
||||||
import {type AppBskyFeedDefs, type AppBskyFeedPost} from '@atproto/api'
|
import {type AppBskyFeedDefs} from '@atproto/api'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import type React from 'react'
|
import type React from 'react'
|
||||||
|
|
||||||
|
import {useCleanError} from '#/lib/hooks/useCleanError'
|
||||||
import {type Shadow} from '#/state/cache/post-shadow'
|
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'
|
import {PostControlButton, PostControlButtonIcon} from './PostControlButton'
|
||||||
|
|
||||||
export const BookmarkButton = memo(function BookmarkButton({
|
export const BookmarkButton = memo(function BookmarkButton({
|
||||||
|
post,
|
||||||
big,
|
big,
|
||||||
}: {
|
}: {
|
||||||
post: Shadow<AppBskyFeedDefs.PostView>
|
post: Shadow<AppBskyFeedDefs.PostView>
|
||||||
big?: boolean
|
big?: boolean
|
||||||
record: AppBskyFeedPost.Record
|
|
||||||
}): React.ReactNode {
|
}): React.ReactNode {
|
||||||
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
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 (
|
return (
|
||||||
<PostControlButton
|
<PostControlButton
|
||||||
testID="postBookmarkBtn"
|
testID="postBookmarkBtn"
|
||||||
big={big}
|
big={big}
|
||||||
label={_(msg`Bookmark this post`)}>
|
label={
|
||||||
<PostControlButtonIcon icon={Bookmark} />
|
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>
|
</PostControlButton>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -102,12 +102,20 @@ export function PostControlButton({
|
|||||||
|
|
||||||
export function PostControlButtonIcon({
|
export function PostControlButtonIcon({
|
||||||
icon: Comp,
|
icon: Comp,
|
||||||
}: {
|
style,
|
||||||
|
...rest
|
||||||
|
}: SVGIconProps & {
|
||||||
icon: React.ComponentType<SVGIconProps>
|
icon: React.ComponentType<SVGIconProps>
|
||||||
}) {
|
}) {
|
||||||
const {big, color} = useContext(PostControlContext)
|
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) {
|
export function PostControlButtonText({style, ...props}: TextProps) {
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ let PostControls = ({
|
|||||||
a.flex_1,
|
a.flex_1,
|
||||||
big ? a.gap_sm : a.gap_xs,
|
big ? a.gap_sm : a.gap_xs,
|
||||||
]}>
|
]}>
|
||||||
<BookmarkButton post={post} big={big} record={record} />
|
<BookmarkButton post={post} big={big} />
|
||||||
<ShareMenuButton
|
<ShareMenuButton
|
||||||
testID="postShareBtn"
|
testID="postShareBtn"
|
||||||
post={post}
|
post={post}
|
||||||
|
|||||||
Vendored
+3
@@ -25,6 +25,7 @@ export interface PostShadow {
|
|||||||
embed: AppBskyEmbedRecord.View | AppBskyEmbedRecordWithMedia.View | undefined
|
embed: AppBskyEmbedRecord.View | AppBskyEmbedRecordWithMedia.View | undefined
|
||||||
pinned: boolean
|
pinned: boolean
|
||||||
optimisticReplyCount: number | undefined
|
optimisticReplyCount: number | undefined
|
||||||
|
bookmarked: boolean | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export const POST_TOMBSTONE = Symbol('PostTombstone')
|
export const POST_TOMBSTONE = Symbol('PostTombstone')
|
||||||
@@ -132,6 +133,8 @@ function mergeShadow(
|
|||||||
like: 'likeUri' in shadow ? shadow.likeUri : post.viewer?.like,
|
like: 'likeUri' in shadow ? shadow.likeUri : post.viewer?.like,
|
||||||
repost: 'repostUri' in shadow ? shadow.repostUri : post.viewer?.repost,
|
repost: 'repostUri' in shadow ? shadow.repostUri : post.viewer?.repost,
|
||||||
pinned: 'pinned' in shadow ? shadow.pinned : post.viewer?.pinned,
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user