Skip to content

Commit e401ab5

Browse files
committed
I think this every thing expect to todos and more testing
1 parent 6045cb7 commit e401ab5

7 files changed

Lines changed: 115 additions & 92 deletions

File tree

app/components/Header/AuthModal.client.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
2-
import { authRedirect, useAtproto } from '~/composables/atproto/useAtproto'
2+
import { useAtproto } from '~/composables/atproto/useAtproto'
3+
import { authRedirect } from '~/utils/atproto/helpers'
34
45
const handleInput = shallowRef('')
56

app/composables/atproto/useAtproto.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,4 @@
11
import type { UserSession } from '#shared/schemas/userSession'
2-
import type { LocationQueryRaw } from 'vue-router'
3-
4-
export async function authRedirect(identifier: string, create: boolean = false) {
5-
let query: LocationQueryRaw = { handle: identifier }
6-
if (create) {
7-
query = { ...query, create: 'true' }
8-
}
9-
await navigateTo(
10-
{
11-
path: '/api/auth/atproto',
12-
query,
13-
},
14-
{ external: true },
15-
)
16-
}
172

183
export function useAtproto() {
194
const { data: user, pending, clear } = useFetch<UserSession | null>('/api/auth/session')

app/composables/atproto/useLikePackage.ts

Lines changed: 0 additions & 34 deletions
This file was deleted.

app/composables/atproto/useUnlikePackage.ts

Lines changed: 0 additions & 34 deletions
This file was deleted.

app/pages/package/[...package].vue

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ import { formatBytes } from '~/utils/formatters'
1414
import { NuxtLink } from '#components'
1515
import { useModal } from '~/composables/useModal'
1616
import { useAtproto } from '~/composables/atproto/useAtproto'
17-
import { useLikePackage } from '~/composables/atproto/useLikePackage'
18-
import { useUnlikePackage } from '~/composables/atproto/useUnlikePackage'
17+
import { togglePackageLike } from '~/utils/atproto/likes'
1918
2019
definePageMeta({
2120
name: 'package',
@@ -361,6 +360,7 @@ const canonicalUrl = computed(() => {
361360
})
362361
363362
//atproto
363+
// TODO: Maybe set this where it's not loaded here every load?
364364
const { user } = useAtproto()
365365
366366
const authModal = useModal('auth-modal')
@@ -370,16 +370,39 @@ const { data: likesData } = useFetch(() => `/api/social/likes/${packageName.valu
370370
server: false,
371371
})
372372
373-
const { mutate: likePackage } = useLikePackage(packageName.value)
374-
const { mutate: unlikePackage } = useUnlikePackage(packageName.value)
373+
const isLikeActionPending = ref(false)
375374
376375
const likeAction = async () => {
377376
if (user.value?.handle == null) {
378377
authModal.open()
378+
return
379+
}
380+
381+
if (isLikeActionPending.value) return
382+
383+
const currentlyLiked = likesData.value?.userHasLiked ?? false
384+
const currentLikes = likesData.value?.totalLikes ?? 0
385+
386+
// Optimistic update
387+
likesData.value = {
388+
totalLikes: currentlyLiked ? currentLikes - 1 : currentLikes + 1,
389+
userHasLiked: !currentlyLiked,
390+
}
391+
392+
isLikeActionPending.value = true
393+
394+
const result = await togglePackageLike(packageName.value, currentlyLiked, user.value?.handle)
395+
396+
isLikeActionPending.value = false
397+
398+
if (result.success) {
399+
// Update with server response
400+
likesData.value = result.data
379401
} else {
380-
const result = likesData.value?.userHasLiked ? await unlikePackage() : await likePackage()
381-
if (result?.totalLikes != null) {
382-
likesData.value = result
402+
// Revert on error
403+
likesData.value = {
404+
totalLikes: currentLikes,
405+
userHasLiked: currentlyLiked,
383406
}
384407
}
385408
}

app/utils/atproto/helpers.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
import type { FetchError } from 'ofetch'
2-
import { authRedirect } from '~/composables/atproto/useAtproto'
2+
import type { LocationQueryRaw } from 'vue-router'
3+
4+
/**
5+
* Redirect user to ATProto authentication
6+
*/
7+
export async function authRedirect(identifier: string, create: boolean = false) {
8+
let query: LocationQueryRaw = { handle: identifier }
9+
if (create) {
10+
query = { ...query, create: 'true' }
11+
}
12+
await navigateTo(
13+
{
14+
path: '/api/auth/atproto',
15+
query,
16+
},
17+
{ external: true },
18+
)
19+
}
320

421
export async function handleAuthError(
522
fetchError: FetchError,

app/utils/atproto/likes.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { FetchError } from 'ofetch'
2+
import { handleAuthError } from '~/utils/atproto/helpers'
3+
4+
// TODO: I need to share a type with the backend likes service
5+
export type PackageLikes = {
6+
totalLikes: number
7+
userHasLiked: boolean
8+
}
9+
10+
export type LikeResult = { success: true; data: PackageLikes } | { success: false; error: Error }
11+
12+
/**
13+
* Like a package via the API
14+
*/
15+
export async function likePackage(
16+
packageName: string,
17+
userHandle?: string | null,
18+
): Promise<LikeResult> {
19+
try {
20+
const result = await $fetch<PackageLikes>('/api/social/like', {
21+
method: 'POST',
22+
body: { packageName },
23+
})
24+
return { success: true, data: result }
25+
} catch (e) {
26+
if (e instanceof FetchError) {
27+
await handleAuthError(e, userHandle)
28+
}
29+
return { success: false, error: e as Error }
30+
}
31+
}
32+
33+
/**
34+
* Unlike a package via the API
35+
*/
36+
export async function unlikePackage(
37+
packageName: string,
38+
userHandle?: string | null,
39+
): Promise<LikeResult> {
40+
try {
41+
const result = await $fetch<PackageLikes>('/api/social/like', {
42+
method: 'DELETE',
43+
body: { packageName },
44+
})
45+
return { success: true, data: result }
46+
} catch (e) {
47+
if (e instanceof FetchError) {
48+
await handleAuthError(e, userHandle)
49+
}
50+
return { success: false, error: e as Error }
51+
}
52+
}
53+
54+
/**
55+
* Toggle like status for a package
56+
*/
57+
export async function togglePackageLike(
58+
packageName: string,
59+
currentlyLiked: boolean,
60+
userHandle?: string | null,
61+
): Promise<LikeResult> {
62+
return currentlyLiked
63+
? unlikePackage(packageName, userHandle)
64+
: likePackage(packageName, userHandle)
65+
}

0 commit comments

Comments
 (0)