Skip to content

Commit bb44cef

Browse files
committed
fix: show skeleton instead of 0 in like cards while loading
Like cards showed totalLikes: 0 default before the client-side fetch resolved, causing a visible 0 -> X jump. Now shows a pulse skeleton and neutral heart icon during the pending state.
1 parent 036dfc2 commit bb44cef

2 files changed

Lines changed: 30 additions & 5 deletions

File tree

app/components/Package/LikeCard.vue

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const { user } = useAtproto()
1717
1818
const authModal = useModal('auth-modal')
1919
20-
const { data: likesData } = useFetch(() => `/api/social/likes/${name.value}`, {
20+
const { data: likesData, status: likesStatus } = useFetch(() => `/api/social/likes/${name.value}`, {
2121
default: () => ({ totalLikes: 0, userHasLiked: false }),
2222
server: false,
2323
})
@@ -92,14 +92,20 @@ const likeAction = async () => {
9292
>
9393
<span
9494
:class="
95-
likesData?.userHasLiked
96-
? 'i-lucide-heart-minus text-red-500'
97-
: 'i-lucide-heart-plus'
95+
likesStatus === 'pending'
96+
? 'i-lucide-heart'
97+
: likesData?.userHasLiked
98+
? 'i-lucide-heart-minus text-red-500'
99+
: 'i-lucide-heart-plus'
98100
"
99101
class="w-4 h-4"
100102
aria-hidden="true"
101103
/>
102-
<span>{{ compactNumberFormatter.format(likesData?.totalLikes ?? 0) }}</span>
104+
<span
105+
v-if="likesStatus === 'pending'"
106+
class="inline-block w-4 h-4 bg-bg-subtle rounded animate-pulse"
107+
/>
108+
<span v-else>{{ compactNumberFormatter.format(likesData?.totalLikes ?? 0) }}</span>
103109
</button>
104110
</TooltipApp>
105111
</ClientOnly>

test/nuxt/components/PackageLikeCard.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,23 @@ describe('PackageLikeCard', () => {
4444

4545
expect(wrapper.find('span.truncate').text()).toBe('@scope/pkg')
4646
})
47+
48+
it('shows a loading skeleton instead of zero while like data is pending', async () => {
49+
wrapper = await mountLikeCard('https://npmx.dev/package/vue')
50+
51+
const button = wrapper.find('button')
52+
expect(button.text()).not.toContain('0')
53+
54+
const skeleton = button.find('.animate-pulse')
55+
expect(skeleton.exists()).toBe(true)
56+
})
57+
58+
it('shows a neutral heart icon while like data is pending', async () => {
59+
wrapper = await mountLikeCard('https://npmx.dev/package/vue')
60+
61+
const icon = wrapper.find('button span[aria-hidden]')
62+
expect(icon.classes()).toContain('i-lucide-heart')
63+
expect(icon.classes()).not.toContain('i-lucide-heart-plus')
64+
expect(icon.classes()).not.toContain('i-lucide-heart-minus')
65+
})
4766
})

0 commit comments

Comments
 (0)