diff --git a/web/build/index.html b/web/build/index.html
index e0a6034..271c044 100644
--- a/web/build/index.html
+++ b/web/build/index.html
@@ -4,32 +4,35 @@
-
-
-
-
+
+
+
+
-
+
+
+
+
-
+
-
+
+
+
+
{title}
+ {#if description}
+
{description}
+ {/if}
+ {#if children}
+
{@render children()}
+ {/if}
+
diff --git a/web/src/lib/components/Pagination.svelte b/web/src/lib/components/Pagination.svelte
new file mode 100644
index 0000000..7f60b10
--- /dev/null
+++ b/web/src/lib/components/Pagination.svelte
@@ -0,0 +1,47 @@
+
+
+{#if totalPages > 1}
+
+
+ Showing {(page - 1) * perPage + 1}–{Math.min(page * perPage, total)} of {total}
+
+
+
+ {#each Array(Math.min(totalPages, 5)) as _, i}
+ {@const pageNum = page <= 3 ? i + 1 : page - 2 + i}
+ {#if pageNum <= totalPages}
+
+ {/if}
+ {/each}
+
+
+
+{/if}
diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte
new file mode 100644
index 0000000..7e63004
--- /dev/null
+++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte
@@ -0,0 +1,27 @@
+
+
+
diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte
new file mode 100644
index 0000000..a4ce03c
--- /dev/null
+++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte
@@ -0,0 +1,27 @@
+
+
+
diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte
new file mode 100644
index 0000000..17b4c6b
--- /dev/null
+++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte
@@ -0,0 +1,32 @@
+
+
+
+
+
+
diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte
new file mode 100644
index 0000000..1af63c0
--- /dev/null
+++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte
new file mode 100644
index 0000000..4d133b7
--- /dev/null
+++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte
@@ -0,0 +1,23 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte
new file mode 100644
index 0000000..fecf4d2
--- /dev/null
+++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-media.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-media.svelte
new file mode 100644
index 0000000..a5f8bc8
--- /dev/null
+++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-media.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte
new file mode 100644
index 0000000..3c1d3a2
--- /dev/null
+++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte
new file mode 100644
index 0000000..f0a19a8
--- /dev/null
+++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte
new file mode 100644
index 0000000..a801f8d
--- /dev/null
+++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte
new file mode 100644
index 0000000..b22d1d5
--- /dev/null
+++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog.svelte
new file mode 100644
index 0000000..7ea78bb
--- /dev/null
+++ b/web/src/lib/components/ui/alert-dialog/alert-dialog.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/alert-dialog/index.ts b/web/src/lib/components/ui/alert-dialog/index.ts
new file mode 100644
index 0000000..f009c26
--- /dev/null
+++ b/web/src/lib/components/ui/alert-dialog/index.ts
@@ -0,0 +1,40 @@
+import Action from "./alert-dialog-action.svelte";
+import Cancel from "./alert-dialog-cancel.svelte";
+import Content from "./alert-dialog-content.svelte";
+import Description from "./alert-dialog-description.svelte";
+import Footer from "./alert-dialog-footer.svelte";
+import Header from "./alert-dialog-header.svelte";
+import Media from "./alert-dialog-media.svelte";
+import Overlay from "./alert-dialog-overlay.svelte";
+import Portal from "./alert-dialog-portal.svelte";
+import Title from "./alert-dialog-title.svelte";
+import Trigger from "./alert-dialog-trigger.svelte";
+import Root from "./alert-dialog.svelte";
+
+export {
+ Root,
+ Title,
+ Action,
+ Cancel,
+ Portal,
+ Footer,
+ Header,
+ Trigger,
+ Overlay,
+ Content,
+ Description,
+ Media,
+ //
+ Root as AlertDialog,
+ Title as AlertDialogTitle,
+ Action as AlertDialogAction,
+ Cancel as AlertDialogCancel,
+ Portal as AlertDialogPortal,
+ Footer as AlertDialogFooter,
+ Header as AlertDialogHeader,
+ Trigger as AlertDialogTrigger,
+ Overlay as AlertDialogOverlay,
+ Content as AlertDialogContent,
+ Description as AlertDialogDescription,
+ Media as AlertDialogMedia,
+};
diff --git a/web/src/lib/components/ui/button/button.svelte b/web/src/lib/components/ui/button/button.svelte
index c38862e..4066c00 100644
--- a/web/src/lib/components/ui/button/button.svelte
+++ b/web/src/lib/components/ui/button/button.svelte
@@ -1,17 +1,17 @@
+
+
diff --git a/web/src/lib/components/ui/sheet/sheet-content.svelte b/web/src/lib/components/ui/sheet/sheet-content.svelte
new file mode 100644
index 0000000..1ab6772
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet-content.svelte
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+ {@render children?.()}
+ {#if showCloseButton}
+
+ {#snippet child({ props })}
+
+ {/snippet}
+
+ {/if}
+
+
diff --git a/web/src/lib/components/ui/sheet/sheet-description.svelte b/web/src/lib/components/ui/sheet/sheet-description.svelte
new file mode 100644
index 0000000..2f663a8
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet-description.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/web/src/lib/components/ui/sheet/sheet-footer.svelte b/web/src/lib/components/ui/sheet/sheet-footer.svelte
new file mode 100644
index 0000000..ad9e07b
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet-footer.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/sheet/sheet-header.svelte b/web/src/lib/components/ui/sheet/sheet-header.svelte
new file mode 100644
index 0000000..ebeb288
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet-header.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/sheet/sheet-overlay.svelte b/web/src/lib/components/ui/sheet/sheet-overlay.svelte
new file mode 100644
index 0000000..8d33847
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet-overlay.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/web/src/lib/components/ui/sheet/sheet-portal.svelte b/web/src/lib/components/ui/sheet/sheet-portal.svelte
new file mode 100644
index 0000000..f3085a3
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet-portal.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/sheet/sheet-title.svelte b/web/src/lib/components/ui/sheet/sheet-title.svelte
new file mode 100644
index 0000000..60c8d5b
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet-title.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/web/src/lib/components/ui/sheet/sheet-trigger.svelte b/web/src/lib/components/ui/sheet/sheet-trigger.svelte
new file mode 100644
index 0000000..e266975
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet-trigger.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/sheet/sheet.svelte b/web/src/lib/components/ui/sheet/sheet.svelte
new file mode 100644
index 0000000..5bf9783
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/sonner/index.ts b/web/src/lib/components/ui/sonner/index.ts
new file mode 100644
index 0000000..1ad9f4a
--- /dev/null
+++ b/web/src/lib/components/ui/sonner/index.ts
@@ -0,0 +1 @@
+export { default as Toaster } from "./sonner.svelte";
diff --git a/web/src/lib/components/ui/sonner/sonner.svelte b/web/src/lib/components/ui/sonner/sonner.svelte
new file mode 100644
index 0000000..33161a6
--- /dev/null
+++ b/web/src/lib/components/ui/sonner/sonner.svelte
@@ -0,0 +1,34 @@
+
+
+
+ {#snippet loadingIcon()}
+
+ {/snippet}
+ {#snippet successIcon()}
+
+ {/snippet}
+ {#snippet errorIcon()}
+
+ {/snippet}
+ {#snippet infoIcon()}
+
+ {/snippet}
+ {#snippet warningIcon()}
+
+ {/snippet}
+
diff --git a/web/src/lib/utils.ts b/web/src/lib/utils.ts
index e7f844f..6a4d274 100644
--- a/web/src/lib/utils.ts
+++ b/web/src/lib/utils.ts
@@ -10,6 +10,8 @@ export function cn(...inputs: ClassValue[]) {
}
export type WithoutChildren
= T extends { children?: Snippet } ? Omit : T;
+export type WithoutChild = T extends { child?: Snippet } ? Omit : T;
+export type WithoutChildrenOrChild = WithoutChildren>;
export type WithElementRef = WithoutChildren & {
ref?: U | null;
children?: Snippet;
diff --git a/web/src/routes/(dashboard)/+layout.svelte b/web/src/routes/(dashboard)/+layout.svelte
index 19051e5..b499c8b 100644
--- a/web/src/routes/(dashboard)/+layout.svelte
+++ b/web/src/routes/(dashboard)/+layout.svelte
@@ -4,6 +4,7 @@
import { getAuthStore } from '$lib/stores/auth.svelte';
import { destroyWsClient } from '$lib/ws';
import type { UserInfo } from '$lib/api';
+ import * as Sheet from '$lib/components/ui/sheet/index.js';
interface Props {
title?: string;
@@ -44,7 +45,33 @@
- {#if mobileMenuOpen}
-