Skip to content

Commit 8b4c065

Browse files
committed
refactor(web): add container for loading & error states
1 parent 027806a commit 8b4c065

File tree

2 files changed

+60
-19
lines changed

2 files changed

+60
-19
lines changed

web/components/Loading/Error.vue

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<script setup lang="ts">
2+
import { ShieldExclamationIcon } from '@heroicons/vue/24/solid';
3+
import { cn } from '~/components/shadcn/utils';
4+
5+
/**
6+
* A default container for displaying loading and error states.
7+
*
8+
* By default, this component will expand to full height and display contents
9+
* in the center of the container.
10+
*
11+
* Any slot/child will only render when a loading/error state isn't displayed.
12+
*
13+
* Exposes a 'retry' event (user-triggered during error state).
14+
*
15+
* @example
16+
* <LoadingError @retry="retryFunction" :loading="loading" :error="error" />
17+
*
18+
* <LoadingError :loading="loading" :error="error">
19+
* <p>Only displayed when both loading and error are false.</p>
20+
* </LoadingError>
21+
*/
22+
const props = withDefaults(
23+
defineProps<{
24+
class?: string;
25+
/** hasdfsa */
26+
loading: boolean;
27+
error: Error | null | undefined;
28+
}>(),
29+
{ class: '' }
30+
);
31+
32+
defineEmits(['retry']);
33+
</script>
34+
<template>
35+
<div :class="cn('h-full flex flex-col items-center justify-center gap-3', props.class)">
36+
<!-- Loading State -->
37+
<div v-if="loading" class="contents">
38+
<LoadingSpinner />
39+
<p>Loading Notifications...</p>
40+
</div>
41+
<!-- Error State -->
42+
<div v-else-if="error" class="space-y-3">
43+
<div class="flex justify-center">
44+
<ShieldExclamationIcon class="h-10 text-unraid-red" />
45+
</div>
46+
<div>
47+
<h3 class="font-bold">{{ `Error` }}</h3>
48+
<p>{{ error.message }}</p>
49+
</div>
50+
<Button type="button" class="w-full" @click="$emit('retry')">Try Again</Button>
51+
</div>
52+
<!-- Default state -->
53+
<slot v-else />
54+
</div>
55+
</template>

web/components/Notifications/List.vue

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { CheckIcon, ShieldExclamationIcon } from '@heroicons/vue/24/solid';
2+
import { CheckIcon } from '@heroicons/vue/24/solid';
33
import { useQuery } from '@vue/apollo-composable';
44
import { vInfiniteScroll } from '@vueuse/components';
55
import { useFragment } from '~/composables/gql/fragment-masking';
@@ -28,7 +28,7 @@ watch(props, () => {
2828
canLoadMore.value = true;
2929
});
3030
31-
const { result, error, loading, fetchMore } = useQuery(getNotifications, () => ({
31+
const { result, error, loading, fetchMore, refetch } = useQuery(getNotifications, () => ({
3232
filter: {
3333
offset: 0,
3434
limit: props.pageSize,
@@ -85,24 +85,10 @@ async function onLoadMore() {
8585
</div>
8686
</div>
8787

88-
<div v-else class="h-full flex flex-col items-center justify-center gap-3">
89-
<!-- Loading State -->
90-
<div v-if="loading" class="contents">
91-
<LoadingSpinner />
92-
<p>Loading Notifications...</p>
93-
</div>
94-
<!-- Error State -->
95-
<div v-else-if="error" class="flex gap-3">
96-
<ShieldExclamationIcon class="h-10 text-unraid-red translate-y-1" />
97-
<div>
98-
<h3 class="font-bold">{{ `Error` }}</h3>
99-
<p>{{ error.message }}</p>
100-
</div>
101-
</div>
102-
<!-- Empty State -->
103-
<div v-else-if="notifications?.length === 0" class="contents">
88+
<LoadingError v-else :loading="loading" :error="error" @retry="refetch">
89+
<div v-if="notifications?.length === 0" class="contents">
10490
<CheckIcon class="h-10 text-green-600 translate-y-3" />
10591
{{ `No ${props.importance?.toLowerCase() ?? ''} notifications to see here!` }}
10692
</div>
107-
</div>
93+
</LoadingError>
10894
</template>

0 commit comments

Comments
 (0)