diff --git a/src/lib/components/EmptyState.svelte b/src/lib/components/EmptyState.svelte new file mode 100644 index 00000000..b3dbbbb0 --- /dev/null +++ b/src/lib/components/EmptyState.svelte @@ -0,0 +1,43 @@ + + + + + + + + {title} + {#if description} + + {description} + + {/if} + + {#if children} + + {@render children()} + + {/if} + diff --git a/src/lib/components/ui/empty/empty-content.svelte b/src/lib/components/ui/empty/empty-content.svelte new file mode 100644 index 00000000..520ac878 --- /dev/null +++ b/src/lib/components/ui/empty/empty-content.svelte @@ -0,0 +1,23 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/empty/empty-description.svelte b/src/lib/components/ui/empty/empty-description.svelte new file mode 100644 index 00000000..e6d97cde --- /dev/null +++ b/src/lib/components/ui/empty/empty-description.svelte @@ -0,0 +1,23 @@ + + +
a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4", + className + )} + {...restProps} +> + {@render children?.()} +
diff --git a/src/lib/components/ui/empty/empty-header.svelte b/src/lib/components/ui/empty/empty-header.svelte new file mode 100644 index 00000000..9085f955 --- /dev/null +++ b/src/lib/components/ui/empty/empty-header.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/empty/empty-media.svelte b/src/lib/components/ui/empty/empty-media.svelte new file mode 100644 index 00000000..6bc65c0f --- /dev/null +++ b/src/lib/components/ui/empty/empty-media.svelte @@ -0,0 +1,41 @@ + + + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/empty/empty-title.svelte b/src/lib/components/ui/empty/empty-title.svelte new file mode 100644 index 00000000..3641794b --- /dev/null +++ b/src/lib/components/ui/empty/empty-title.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/empty/empty.svelte b/src/lib/components/ui/empty/empty.svelte new file mode 100644 index 00000000..e5f37460 --- /dev/null +++ b/src/lib/components/ui/empty/empty.svelte @@ -0,0 +1,23 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/empty/index.ts b/src/lib/components/ui/empty/index.ts new file mode 100644 index 00000000..ae4c106e --- /dev/null +++ b/src/lib/components/ui/empty/index.ts @@ -0,0 +1,22 @@ +import Root from "./empty.svelte"; +import Header from "./empty-header.svelte"; +import Media from "./empty-media.svelte"; +import Title from "./empty-title.svelte"; +import Description from "./empty-description.svelte"; +import Content from "./empty-content.svelte"; + +export { + Root, + Header, + Media, + Title, + Description, + Content, + // + Root as Empty, + Header as EmptyHeader, + Media as EmptyMedia, + Title as EmptyTitle, + Description as EmptyDescription, + Content as EmptyContent, +}; diff --git a/src/routes/(app)/admin/users/[userId]/+page.svelte b/src/routes/(app)/admin/users/[userId]/+page.svelte index 2658a0b9..5c9928ed 100644 --- a/src/routes/(app)/admin/users/[userId]/+page.svelte +++ b/src/routes/(app)/admin/users/[userId]/+page.svelte @@ -3,14 +3,18 @@ import { adminGetUsers } from '$lib/api'; import type { AdminUsersView, AdminUsersViewPaginated } from '$lib/api'; import Container from '$lib/components/Container.svelte'; + import EmptyState from '$lib/components/EmptyState.svelte'; + import { Spinner } from '$lib/components/ui/spinner'; import { handleApiError } from '$lib/errorhandling/apiErrorHandling'; import { registerBreadcrumbs } from '$lib/state/breadcrumbs-state.svelte'; + import UserX from '@lucide/svelte/icons/user-x'; let user = $state(null); + let loading = $state(true); registerBreadcrumbs(() => [ { label: 'Users', href: '/admin/users' }, - { label: user?.name ?? 'Loading...' }, + { label: user?.name ?? (loading ? 'Loading...' : 'Not found') }, ]); function handleResponse(page: AdminUsersViewPaginated) { @@ -23,12 +27,23 @@ } $effect(() => { + loading = true; adminGetUsers({ query: { $filter: 'id eq ' + page.params.userId } }) .then(handleResponse) - .catch(handleApiError); + .catch(handleApiError) + .finally(() => (loading = false)); }); - {user?.name ?? 'Loading...'} + {#if loading} +
+ + Loading user... +
+ {:else if !user} + + {:else} + {user.name} + {/if}
diff --git a/src/routes/(app)/settings/connections/+page.svelte b/src/routes/(app)/settings/connections/+page.svelte index b182939d..2611de00 100644 --- a/src/routes/(app)/settings/connections/+page.svelte +++ b/src/routes/(app)/settings/connections/+page.svelte @@ -10,6 +10,7 @@ import Container from '$lib/components/Container.svelte'; import { Button } from '$lib/components/ui/button'; import * as Card from '$lib/components/ui/card'; + import EmptyState from '$lib/components/EmptyState.svelte'; import * as Dropdown from '$lib/components/ui/dropdown-menu'; import * as Separator from '$lib/components/ui/separator'; import { handleApiError } from '$lib/errorhandling/apiErrorHandling'; @@ -111,7 +112,7 @@ - +
@@ -162,12 +163,11 @@ {/each}
{:else if connections.length === 0} -
-
No connections yet
-
- Choose “Link new provider” above to connect available providers. -
-
+ {:else}
{#each backendMetadata.state!.oAuthProviders as p (p)} diff --git a/src/routes/(app)/shares/public/+page.svelte b/src/routes/(app)/shares/public/+page.svelte index 039af1c6..1addd18e 100644 --- a/src/routes/(app)/shares/public/+page.svelte +++ b/src/routes/(app)/shares/public/+page.svelte @@ -11,6 +11,7 @@ import type { OwnPublicShareResponse } from '$lib/api'; import Container from '$lib/components/Container.svelte'; import CopyInput from '$lib/components/CopyInput.svelte'; + import EmptyState from '$lib/components/EmptyState.svelte'; import { Spinner } from '$lib/components/ui/spinner'; import Button from '$lib/components/ui/button/button.svelte'; import { handleApiError } from '$lib/errorhandling/apiErrorHandling'; @@ -93,19 +94,16 @@
{:else if sortedShares.length === 0} -
- -
-

No public shares yet

-

Create a link that anyone can use to control your shockers.

-
- -
+
{:else}
-
+
{@render children?.()}
diff --git a/src/routes/(app)/shares/user/incoming/+page.svelte b/src/routes/(app)/shares/user/incoming/+page.svelte index a7d77a10..9d6bcc23 100644 --- a/src/routes/(app)/shares/user/incoming/+page.svelte +++ b/src/routes/(app)/shares/user/incoming/+page.svelte @@ -1,8 +1,10 @@ + let invitesPromise = $state(Promise.all([refreshOutgoingInvites(), refreshIncomingInvites()])); -

Outgoing

+ let bothEmpty = $derived( + userSharesState.outgoingInvites.length === 0 && userSharesState.incomingInvites.length === 0 + ); + -{#await outgoingInvitesPromise} +{#await invitesPromise}
{:then} -
- - - {#each userSharesState.outgoingInvites as invite (invite.id)} - - {/each} - - -
-{:catch error} -
Failed to load outgoing invites: {error.message}
-{/await} - -

Incoming

+ {#if bothEmpty} + + {:else} +

Outgoing

+ {#if userSharesState.outgoingInvites.length === 0} + + {:else} +
+ + + {#each userSharesState.outgoingInvites as invite (invite.id)} + + {/each} + + +
+ {/if} -{#await incomingInvitesPromise} -
- -
-{:then} -
- - - {#each userSharesState.incomingInvites as invite (invite.id)} - - {/each} - - -
+

Incoming

+ {#if userSharesState.incomingInvites.length === 0} + + {:else} +
+ + + {#each userSharesState.incomingInvites as invite (invite.id)} + + {/each} + + +
+ {/if} + {/if} {:catch error} -
Failed to load incomding invites: {error.message}
+
Failed to load invites: {error.message}
{/await} diff --git a/src/routes/(app)/shares/user/outgoing/+page.svelte b/src/routes/(app)/shares/user/outgoing/+page.svelte index b98eaf0c..41efb4ec 100644 --- a/src/routes/(app)/shares/user/outgoing/+page.svelte +++ b/src/routes/(app)/shares/user/outgoing/+page.svelte @@ -1,8 +1,10 @@