diff --git a/app/assets/main.css b/app/assets/main.css index bce110f018..56e50038b1 100644 --- a/app/assets/main.css +++ b/app/assets/main.css @@ -401,3 +401,55 @@ html:has(dialog:modal) { .vue-ui-pen-and-paper-group-actions { background-color: transparent !important; } + +/* Like button heart, shared by package and collection likes */ +@keyframes heart-spring { + 0% { + transform: scale(1); + } + 15% { + transform: scale(0.78); + } + 45% { + transform: scale(1.55); + } + 65% { + transform: scale(0.93); + } + 80% { + transform: scale(1.1); + } + 100% { + transform: scale(1); + } +} + +@keyframes heart-unlike { + 0% { + transform: scale(1); + } + 30% { + transform: scale(0.85); + } + 60% { + transform: scale(1.05); + } + 100% { + transform: scale(1); + } +} + +@media (prefers-reduced-motion: reduce) { + @keyframes heart-spring { + from, + to { + transform: scale(1); + } + } + @keyframes heart-unlike { + from, + to { + transform: scale(1); + } + } +} diff --git a/app/components/Collection/FollowButton.vue b/app/components/Collection/FollowButton.vue new file mode 100644 index 0000000000..1d08b3a355 --- /dev/null +++ b/app/components/Collection/FollowButton.vue @@ -0,0 +1,37 @@ + + + diff --git a/app/components/Collection/LikeButton.vue b/app/components/Collection/LikeButton.vue new file mode 100644 index 0000000000..5561826618 --- /dev/null +++ b/app/components/Collection/LikeButton.vue @@ -0,0 +1,57 @@ + + + diff --git a/app/components/Package/Likes.vue b/app/components/Package/Likes.vue index e48f543e62..371886775f 100644 --- a/app/components/Package/Likes.vue +++ b/app/components/Package/Likes.vue @@ -251,56 +251,3 @@ const likeAction = async () => { } } - - diff --git a/app/composables/useCollectionReaction.ts b/app/composables/useCollectionReaction.ts new file mode 100644 index 0000000000..a9ce406bcd --- /dev/null +++ b/app/composables/useCollectionReaction.ts @@ -0,0 +1,46 @@ +import type { Ref } from 'vue' +import type { ReactionState } from '#shared/types/social' +import { toggleCollectionReaction, type Reaction } from '~/utils/atproto/collections' + +/** Optimistic like or follow toggle for a collection; opens sign-in when signed out. */ +export function useCollectionReaction( + state: Ref, + reaction: Reaction, + target: () => { identifier: string; rkey: string }, +) { + const { user } = useAtproto() + const authModal = useModal('auth-modal') + const isPending = shallowRef(false) + + async function toggle() { + const handle = user.value?.handle + if (handle == null) { + authModal.open() + return + } + if (isPending.value) return + + const previous = state.value + state.value = { + total: previous.total + (previous.viewerHas ? -1 : 1), + viewerHas: !previous.viewerHas, + } + isPending.value = true + try { + const { identifier, rkey } = target() + state.value = await toggleCollectionReaction( + identifier, + rkey, + reaction, + previous.viewerHas, + handle, + ) + } catch { + state.value = previous + } finally { + isPending.value = false + } + } + + return { toggle } +} diff --git a/app/pages/collections/[identifier]/[rkey].stories.ts b/app/pages/collections/[identifier]/[rkey].stories.ts index ea8b0976b0..7a328bec42 100644 --- a/app/pages/collections/[identifier]/[rkey].stories.ts +++ b/app/pages/collections/[identifier]/[rkey].stories.ts @@ -4,6 +4,8 @@ import { pageDecorator } from '../../../../.storybook/decorators' import { mockCollectionHandle, mockCollectionHandler, + mockCollectionFollowHandlers, + mockCollectionLikeHandlers, mockCollectionRkey, mockPackageCardHandlers, } from '../../../storybook/mocks/handlers/collections' @@ -17,7 +19,13 @@ const meta = { `/collections/${mockCollectionHandle}/${mockCollectionRkey}`, ), beforeEach({ msw }) { - msw.use(mockCollectionHandler, ...mockPackageCardHandlers, mockAuthSessionHandler()) + msw.use( + mockCollectionHandler, + ...mockPackageCardHandlers, + ...mockCollectionLikeHandlers, + ...mockCollectionFollowHandlers, + mockAuthSessionHandler(), + ) }, parameters: { layout: 'fullscreen' }, decorators: [pageDecorator], diff --git a/app/pages/collections/[identifier]/[rkey].vue b/app/pages/collections/[identifier]/[rkey].vue index 8c24691d2b..36be808ef2 100644 --- a/app/pages/collections/[identifier]/[rkey].vue +++ b/app/pages/collections/[identifier]/[rkey].vue @@ -1,5 +1,5 @@