From bae28ef863bda04fc031d88c07adb4c0cc159edb Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 16 Aug 2026 12:44:35 +0200 Subject: [PATCH] feat: add useAsync and implement it on every async mutation method --- src/components/ToastRenderer.vue | 9 ++ src/composables/useAsync.ts | 43 ++++++ src/layouts/docs.vue | 1 + src/layouts/slotOnly.vue | 1 + src/pages/account/forgot-password.vue | 56 +++----- src/pages/account/index.vue | 195 +++++++++++++------------- src/pages/account/login.vue | 11 +- src/pages/account/miieditor/index.vue | 36 ++--- src/pages/account/register.vue | 11 +- src/pages/account/reset-password.vue | 31 ++-- src/pages/account/upgrade.vue | 35 +++-- src/stores/toasts.ts | 3 + 12 files changed, 236 insertions(+), 196 deletions(-) create mode 100644 src/composables/useAsync.ts diff --git a/src/components/ToastRenderer.vue b/src/components/ToastRenderer.vue index 65e9587..5cff845 100644 --- a/src/components/ToastRenderer.vue +++ b/src/components/ToastRenderer.vue @@ -2,6 +2,15 @@ import { Toast } from 'reka-ui/namespaced'; const toastStore = useToasts(); +const route = useRoute(); +const routeKey = computed(() => route.path); + +watch(routeKey, () => { + toastStore.clear(); // Clear toasts when route changes +}); +onUnmounted(() => { + toastStore.clear(); // Clear toasts toast rendered gets removed (route change) +}); function handleOpenUpdate(id: number, open: boolean) { if (!open) { diff --git a/src/composables/useAsync.ts b/src/composables/useAsync.ts new file mode 100644 index 0000000..ef80fa6 --- /dev/null +++ b/src/composables/useAsync.ts @@ -0,0 +1,43 @@ +import { useAsyncState } from '@vueuse/core'; + +export type UseAsyncOptions = { + handler(...args: TArgs): Promise; + onSuccess?: (data: TResult) => void; + onError?: (error: any) => void; + allowParallel?: boolean; +}; + +export type UseAsyncResult = { + state: Ref; + isLoading: Ref; + error: Ref; + execute: (...args: TArgs) => Promise; +}; + +export function useAsync(ops: UseAsyncOptions): UseAsyncResult { + const preventDuringLoading = !ops.allowParallel; + const output = useAsyncState(ops.handler, null, { + immediate: false, + onSuccess(data) { + ops.onSuccess?.(data as any); + }, + onError(err) { + ops.onError?.(err); + } + }); + + async function execute(...args: TArgs) { + if (preventDuringLoading && output.isLoading.value) { + return; + } + + await output.executeImmediate(...args); + } + + return { + state: output.state, + isLoading: output.isLoading, + error: output.error, + execute + }; +} diff --git a/src/layouts/docs.vue b/src/layouts/docs.vue index fddbb0d..8baf24e 100644 --- a/src/layouts/docs.vue +++ b/src/layouts/docs.vue @@ -18,6 +18,7 @@ useHead({ id="root" class="main-body" > +
+
diff --git a/src/pages/account/forgot-password.vue b/src/pages/account/forgot-password.vue index 7f94935..31a8a8f 100644 --- a/src/pages/account/forgot-password.vue +++ b/src/pages/account/forgot-password.vue @@ -2,17 +2,13 @@ import type { ApiAuthForgotPasswordRequest } from '~~/shared/api-types'; const { t } = useI18n(); +const toasts = useToasts(); +const captchaRef = useTemplateRef('captcha'); const form = reactive({ emailOrUsername: '' }); -const errorMessage = ref(); -const successMessage = ref(); -const captchaRef = useTemplateRef('captcha'); - -async function submit() { - errorMessage.value = null; - successMessage.value = null; - try { +const { execute } = useAsync({ + async handler() { let captchaResponse: string | null = null; if (captchaRef.value) { captchaResponse = await captchaRef.value.getToken(); @@ -28,21 +24,21 @@ async function submit() { captchaResponse: captchaResponse ?? undefined } satisfies ApiAuthForgotPasswordRequest }); - - // Success - form.emailOrUsername = ''; - successMessage.value = 'An email has been sent.'; - setTimeout(() => { - successMessage.value = null; - }, 5000); - } catch (error: unknown) { + }, + onSuccess() { + toasts.publish({ + type: 'success', + text: 'An email has been sent.' + }); + }, + onError(error) { const err = getApiError(error); - errorMessage.value = err.code; - setTimeout(() => { - errorMessage.value = null; - }, 5000); + toasts.publish({ + type: 'error', + text: err.message + }); } -} +}); diff --git a/src/pages/account/index.vue b/src/pages/account/index.vue index c0ca83d..e758a63 100644 --- a/src/pages/account/index.vue +++ b/src/pages/account/index.vue @@ -1,30 +1,36 @@