@svadmin/ui 0.38.7 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/app.css +0 -4
  2. package/dist/app.theme.css +0 -4
  3. package/dist/assets/nft/nft-cover-01.jpg +0 -0
  4. package/dist/assets/nft/nft-cover-02.jpg +0 -0
  5. package/dist/assets/nft/nft-cover-03.jpg +0 -0
  6. package/dist/assets/nft/nft-cover-04.jpg +0 -0
  7. package/dist/assets/nft/nft-cover-05.jpg +0 -0
  8. package/dist/assets/nft/nft-cover-06.jpg +0 -0
  9. package/dist/assets/nft/nft-cover-07.jpg +0 -0
  10. package/dist/assets/nft/nft-cover-08.jpg +0 -0
  11. package/dist/assets/nft/nft-cover-09.jpg +0 -0
  12. package/dist/components/AdminApp.svelte +29 -12
  13. package/dist/components/ApiSettings.svelte +86 -1
  14. package/dist/components/AutoTable.svelte +40 -13
  15. package/dist/components/Breadcrumbs.svelte +39 -6
  16. package/dist/components/Breadcrumbs.svelte.d.ts +5 -1
  17. package/dist/components/ChatDialog.svelte +4 -2
  18. package/dist/components/ChatDialog.svelte.d.ts +2 -0
  19. package/dist/components/DevTools.svelte +3 -1
  20. package/dist/components/DevTools.svelte.d.ts +4 -1
  21. package/dist/components/ForgotPasswordPage.svelte +5 -8
  22. package/dist/components/Header.svelte +5 -3
  23. package/dist/components/Header.svelte.d.ts +2 -0
  24. package/dist/components/Layout.svelte +10 -1
  25. package/dist/components/LoginPage.svelte +5 -8
  26. package/dist/components/RegisterPage.svelte +5 -8
  27. package/dist/components/SettingsPage.svelte +14 -2
  28. package/dist/components/Sidebar.svelte +10 -4
  29. package/dist/components/SvadminLogo.svelte +16 -0
  30. package/dist/components/SvadminLogo.svelte.d.ts +7 -0
  31. package/dist/components/SystemPageShell.svelte +34 -0
  32. package/dist/components/SystemPageShell.svelte.d.ts +8 -0
  33. package/dist/components/UpdatePasswordPage.svelte +5 -8
  34. package/dist/components/account/CompanyProfilePage.svelte +56 -1
  35. package/dist/components/account/ImportMembersPage.svelte +31 -0
  36. package/dist/components/account/SettingsEnterprisePage.svelte +172 -19
  37. package/dist/components/account/SettingsPlainPage.svelte +121 -17
  38. package/dist/components/account/SettingsSidebarPage.svelte +282 -70
  39. package/dist/components/account/TeamMembersPage.svelte +95 -4
  40. package/dist/components/account/UserProfilePage.svelte +206 -0
  41. package/dist/components/account/UserProfilePage.svelte.d.ts +4 -0
  42. package/dist/components/network/TeamCrewTablePage.svelte +37 -5
  43. package/dist/components/network/UserCardsNFTPage.svelte +26 -16
  44. package/dist/components/profile/ActivityTimeline.svelte +24 -2
  45. package/dist/components/profile/ActivityTimeline.svelte.d.ts +1 -0
  46. package/dist/components/profile/ProfileVariantSections.svelte +452 -0
  47. package/dist/components/profile/ProfileVariantSections.svelte.d.ts +8 -0
  48. package/dist/components/profile/ProjectsGrid.svelte +38 -8
  49. package/dist/components/profile/ProjectsGrid.svelte.d.ts +1 -0
  50. package/dist/components/profile/PublicProfilePage.svelte +47 -26
  51. package/dist/components/profile/PublicProfilePage.svelte.d.ts +1 -0
  52. package/dist/components/profile/TeamsShowcase.svelte +31 -12
  53. package/dist/components/profile/TeamsShowcase.svelte.d.ts +1 -0
  54. package/dist/components/ui/alert/alert.svelte.d.ts +14 -14
  55. package/dist/components/ui/avatar/avatar.svelte.d.ts +14 -14
  56. package/dist/components/ui/badge/badge.svelte.d.ts +20 -20
  57. package/dist/components/ui/button/button.svelte.d.ts +44 -44
  58. package/dist/index.d.ts +3 -0
  59. package/dist/index.js +3 -0
  60. package/dist/router-state.svelte.js +4 -0
  61. package/package.json +15 -15
@@ -7,6 +7,7 @@
7
7
  import CommandPalette from './CommandPalette.svelte';
8
8
  import KeyboardShortcuts from './KeyboardShortcuts.svelte';
9
9
  import ChatDialog from './ChatDialog.svelte';
10
+ import DevTools from './DevTools.svelte';
10
11
  import { useTranslation } from '@svadmin/core/i18n';
11
12
 
12
13
  import { captureAdminContext, getAuthProvider, getTaskProvider } from '@svadmin/core';
@@ -121,6 +122,9 @@
121
122
  </div>
122
123
  </div>
123
124
  </div>
125
+ <div class="hidden" aria-hidden="true">
126
+ <DevTools docked />
127
+ </div>
124
128
  {:else}
125
129
  <div class="flex h-screen bg-background" in:fade={{ duration: 200, delay: 50 }}>
126
130
  <!-- Desktop sidebar -->
@@ -145,6 +149,7 @@
145
149
  <!-- Header with mobile hamburger -->
146
150
  <Header
147
151
  {siteUrl}
152
+ {menu}
148
153
  showSearch={true}
149
154
  onSearchClick={() => { commandOpen = true; }}
150
155
  >
@@ -182,6 +187,11 @@
182
187
  {/key}
183
188
  </div>
184
189
  </main>
190
+
191
+ <footer class="flex min-h-14 shrink-0 items-center justify-end gap-2 border-t border-border/60 bg-background px-4 empty:hidden">
192
+ <DevTools docked />
193
+ <ChatDialog docked />
194
+ </footer>
185
195
  </div>
186
196
  </div>
187
197
  <CommandPalette
@@ -193,5 +203,4 @@
193
203
  }}
194
204
  />
195
205
  <KeyboardShortcuts bind:open={shortcutsOpen} />
196
- <ChatDialog />
197
206
  {/if}
@@ -6,8 +6,9 @@
6
6
  import { Label } from './ui/label/index.js';
7
7
  import * as Alert from './ui/alert/index.js';
8
8
  import PasswordInput from './PasswordInput.svelte';
9
+ import SvadminLogo from './SvadminLogo.svelte';
9
10
  import { Separator } from './ui/separator/index.js';
10
- import { User, Loader2, AlertCircle, Shield } from '@lucide/svelte';
11
+ import { User, Loader2, AlertCircle } from '@lucide/svelte';
11
12
 
12
13
  const i18n = useTranslation();
13
14
 
@@ -77,9 +78,7 @@
77
78
 
78
79
  <div class="relative z-10 flex flex-col justify-between p-10 xl:p-12 w-full">
79
80
  <div class="flex items-center gap-3">
80
- <div class="flex h-11 w-11 items-center justify-center rounded-xl bg-white/15 backdrop-blur-sm">
81
- <Shield class="h-5 w-5 text-white" />
82
- </div>
81
+ <SvadminLogo size={44} onDark />
83
82
  <span class="text-xl font-semibold text-white tracking-tight">{title}</span>
84
83
  </div>
85
84
 
@@ -102,10 +101,8 @@
102
101
 
103
102
  <div class="flex-1 flex items-center justify-center bg-background p-6 sm:p-10 lg:p-14">
104
103
  <div class="w-full max-w-[460px] space-y-8">
105
- <div class="flex items-center gap-3 mb-8 lg:hidden">
106
- <div class="flex h-11 w-11 items-center justify-center rounded-xl bg-primary/10 text-primary">
107
- <Shield class="h-5 w-5" />
108
- </div>
104
+ <div class="mb-8 flex items-center gap-3 lg:hidden">
105
+ <SvadminLogo size={44} />
109
106
  <span class="text-xl font-semibold tracking-tight text-foreground">{title}</span>
110
107
  </div>
111
108
 
@@ -6,7 +6,8 @@
6
6
  import { Label } from './ui/label/index.js';
7
7
  import * as Alert from './ui/alert/index.js';
8
8
  import PasswordInput from './PasswordInput.svelte';
9
- import { User, Loader2, AlertCircle, Shield } from '@lucide/svelte';
9
+ import SvadminLogo from './SvadminLogo.svelte';
10
+ import { User, Loader2, AlertCircle } from '@lucide/svelte';
10
11
 
11
12
  const i18n = useTranslation();
12
13
 
@@ -53,9 +54,7 @@
53
54
 
54
55
  <div class="relative z-10 flex flex-col justify-between p-10 xl:p-12 w-full">
55
56
  <div class="flex items-center gap-3">
56
- <div class="flex h-9 w-9 items-center justify-center rounded-lg bg-white/15 backdrop-blur-sm">
57
- <Shield class="h-5 w-5 text-white" />
58
- </div>
57
+ <SvadminLogo size={36} onDark />
59
58
  <span class="text-lg font-semibold text-white tracking-tight">{title}</span>
60
59
  </div>
61
60
 
@@ -79,10 +78,8 @@
79
78
  <!-- Right form panel -->
80
79
  <div class="flex-1 flex items-center justify-center bg-background p-6 sm:p-8 lg:p-12">
81
80
  <div class="w-full max-w-[400px]">
82
- <div class="flex items-center gap-3 mb-8 lg:hidden">
83
- <div class="flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10 text-primary">
84
- <Shield class="h-5 w-5" />
85
- </div>
81
+ <div class="mb-8 flex items-center gap-3 lg:hidden">
82
+ <SvadminLogo size={36} />
86
83
  <span class="text-lg font-semibold tracking-tight">{title}</span>
87
84
  </div>
88
85
 
@@ -88,10 +88,22 @@
88
88
  },
89
89
  ];
90
90
 
91
- const resolvedSections = $derived(customSections ?? defaultSections);
91
+ const resolvedSections = $derived.by(() => {
92
+ if (customSections) return customSections;
93
+ const accountPath = getPath().startsWith('/account/');
94
+ if (!accountPath) return defaultSections;
95
+
96
+ return defaultSections.map((section) => ({
97
+ ...section,
98
+ items: section.items.map((item) => ({
99
+ ...item,
100
+ path: `/account/${item.key === 'api' ? 'api-keys' : item.key}`,
101
+ })),
102
+ }));
103
+ });
92
104
  const sectionKeys = $derived(new Set(resolvedSections.flatMap((section) => section.items.map((item) => item.key))));
93
105
 
94
- import { getParams, getRoute } from "../router-state.svelte.js";
106
+ import { getParams, getPath, getRoute } from "../router-state.svelte.js";
95
107
 
96
108
  const i18n = useTranslation();
97
109
 
@@ -9,6 +9,7 @@
9
9
  import TooltipButton from './TooltipButton.svelte';
10
10
  import * as Tooltip from './ui/tooltip/index.js';
11
11
  import SidebarItem from './SidebarItem.svelte';
12
+ import SvadminLogo from './SvadminLogo.svelte';
12
13
 
13
14
  import { ScrollArea } from './ui/scroll-area/index.js';
14
15
  import * as Collapsible from './ui/collapsible/index.js';
@@ -218,13 +219,18 @@
218
219
  >
219
220
  <div class="flex h-[70px] items-center shrink-0" class:px-5={!collapsed} class:justify-center={collapsed}>
220
221
  {#if !collapsed}
221
- <a href={formatSidebarLink('/')} class="flex items-center gap-2.5 group" onclick={(e) => { e.preventDefault(); adminContext.navigate('/'); }}>
222
- <div class="w-[34px] h-[34px] rounded-lg bg-gradient-to-tr from-primary to-purple-500 shrink-0 transition-transform group-hover:scale-105" style="box-shadow: 0 4px 12px oklch(0.488 0.243 264.376 / 20%);"></div>
222
+ <a href={formatSidebarLink('/')} class="group flex items-center gap-2.5" onclick={(e) => { e.preventDefault(); adminContext.navigate('/'); }}>
223
+ <SvadminLogo />
223
224
  <span class="font-semibold text-[15px] tracking-[-0.01em] text-sidebar-foreground">{title}</span>
224
225
  </a>
225
226
  {:else}
226
- <a href={formatSidebarLink('/')} class="group" aria-label={title} onclick={(e) => { e.preventDefault(); adminContext.navigate('/'); }}>
227
- <div class="w-[34px] h-[34px] rounded-lg bg-gradient-to-tr from-primary to-purple-500 shrink-0 transition-transform group-hover:scale-105" style="box-shadow: 0 4px 12px oklch(0.488 0.243 264.376 / 20%);"></div>
227
+ <a
228
+ href={formatSidebarLink('/')}
229
+ class="group"
230
+ aria-label={title}
231
+ onclick={(e) => { e.preventDefault(); adminContext.navigate('/'); }}
232
+ >
233
+ <SvadminLogo />
228
234
  </a>
229
235
  {/if}
230
236
  </div>
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ let {
3
+ size = 34,
4
+ onDark = false,
5
+ }: {
6
+ size?: number;
7
+ onDark?: boolean;
8
+ } = $props();
9
+ </script>
10
+
11
+ <span
12
+ aria-hidden="true"
13
+ class="inline-block shrink-0 rounded-lg bg-gradient-to-tr from-primary to-purple-500 shadow-sm {onDark ? 'ring-1 ring-white/35' : ''}"
14
+ style:width={`${size}px`}
15
+ style:height={`${size}px`}
16
+ ></span>
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ size?: number;
3
+ onDark?: boolean;
4
+ };
5
+ declare const SvadminLogo: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type SvadminLogo = ReturnType<typeof SvadminLogo>;
7
+ export default SvadminLogo;
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { captureAdminContext } from '@svadmin/core';
4
+ import { useTranslation } from '@svadmin/core/i18n';
5
+ import { ArrowLeft } from '@lucide/svelte';
6
+ import SvadminLogo from './SvadminLogo.svelte';
7
+ import { Button } from './ui/button/index.js';
8
+
9
+ let { title = 'svadmin', children }: { title?: string; children: Snippet } = $props();
10
+
11
+ const adminContext = captureAdminContext();
12
+ const i18n = useTranslation();
13
+ </script>
14
+
15
+ <div class="min-h-screen bg-background text-foreground">
16
+ <header class="flex h-[70px] items-center justify-between border-b border-border/60 px-4 sm:px-6">
17
+ <button
18
+ type="button"
19
+ class="inline-flex items-center gap-2.5 text-sm font-semibold"
20
+ onclick={() => adminContext.navigate('/')}
21
+ >
22
+ <SvadminLogo size={30} />
23
+ <span>{title}</span>
24
+ </button>
25
+ <Button variant="ghost" size="sm" onclick={() => adminContext.back()}>
26
+ <ArrowLeft class="h-4 w-4" />
27
+ <span class="hidden sm:inline">{i18n.t('common.back')}</span>
28
+ </Button>
29
+ </header>
30
+
31
+ <main class="min-h-[calc(100vh-70px)] bg-muted/20 px-3 py-5 sm:px-6 sm:py-8">
32
+ {@render children()}
33
+ </main>
34
+ </div>
@@ -0,0 +1,8 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ title?: string;
4
+ children: Snippet;
5
+ };
6
+ declare const SystemPageShell: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type SystemPageShell = ReturnType<typeof SystemPageShell>;
8
+ export default SystemPageShell;
@@ -5,7 +5,8 @@
5
5
  import { Button } from './ui/button/index.js';
6
6
  import * as Alert from './ui/alert/index.js';
7
7
  import PasswordInput from './PasswordInput.svelte';
8
- import { ShieldCheck, Loader2, AlertCircle, Shield } from '@lucide/svelte';
8
+ import SvadminLogo from './SvadminLogo.svelte';
9
+ import { Loader2, AlertCircle } from '@lucide/svelte';
9
10
 
10
11
  const i18n = useTranslation();
11
12
 
@@ -47,9 +48,7 @@
47
48
 
48
49
  <div class="relative z-10 flex flex-col justify-between p-10 xl:p-12 w-full">
49
50
  <div class="flex items-center gap-3">
50
- <div class="flex h-9 w-9 items-center justify-center rounded-lg bg-white/15 backdrop-blur-sm">
51
- <Shield class="h-5 w-5 text-white" />
52
- </div>
51
+ <SvadminLogo size={36} onDark />
53
52
  <span class="text-lg font-semibold text-white tracking-tight">{title}</span>
54
53
  </div>
55
54
 
@@ -73,10 +72,8 @@
73
72
  <!-- Right form panel -->
74
73
  <div class="flex-1 flex items-center justify-center bg-background p-6 sm:p-8 lg:p-12">
75
74
  <div class="w-full max-w-[400px]">
76
- <div class="flex items-center gap-3 mb-8 lg:hidden">
77
- <div class="flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10 text-primary">
78
- <ShieldCheck class="h-5 w-5" />
79
- </div>
75
+ <div class="mb-8 flex items-center gap-3 lg:hidden">
76
+ <SvadminLogo size={36} />
80
77
  <span class="text-lg font-semibold tracking-tight">{title}</span>
81
78
  </div>
82
79
 
@@ -4,7 +4,9 @@
4
4
  import { Button } from '../ui/button/index.js';
5
5
  import { Input } from '../ui/input/index.js';
6
6
  import { Label } from '../ui/label/index.js';
7
- import { Upload, Loader2 } from '@lucide/svelte';
7
+ import { Badge } from '../ui/badge/index.js';
8
+ import * as Table from '../ui/table/index.js';
9
+ import { Upload, Loader2, ShieldCheck, ShieldOff } from '@lucide/svelte';
8
10
 
9
11
  const i18n = useTranslation();
10
12
 
@@ -16,6 +18,14 @@
16
18
  let description = $state('Building the future of enterprise software with cutting-edge AI and cloud solutions.');
17
19
  let saving = $state(false);
18
20
 
21
+ const members = [
22
+ { name: 'Alex Chen', email: 'alex@acme.com', role: 'Admin', twoFactor: true, joined: '2022-03-15' },
23
+ { name: 'Sarah Kim', email: 'sarah@acme.com', role: 'Editor', twoFactor: true, joined: '2022-06-22' },
24
+ { name: 'Mike Johnson', email: 'mike@acme.com', role: 'Viewer', twoFactor: false, joined: '2023-01-10' },
25
+ { name: 'Emma Davis', email: 'emma@acme.com', role: 'Admin', twoFactor: true, joined: '2022-02-14' },
26
+ { name: 'Lisa Wang', email: 'lisa@acme.com', role: 'Editor', twoFactor: false, joined: '2023-04-05' },
27
+ ];
28
+
19
29
  async function handleSave() {
20
30
  saving = true;
21
31
  await new Promise(r => setTimeout(r, 800));
@@ -98,4 +108,49 @@
98
108
  </div>
99
109
  </Card.CardContent>
100
110
  </Card.Card>
111
+
112
+ <!-- Members -->
113
+ <Card.Card class="border-border/60">
114
+ <Card.CardHeader>
115
+ <Card.CardTitle class="text-base">{i18n.t('profileSections.members')}</Card.CardTitle>
116
+ </Card.CardHeader>
117
+ <Card.CardContent class="p-0">
118
+ <Table.Root>
119
+ <Table.Header>
120
+ <Table.Row>
121
+ <Table.Head>{i18n.t('profile.name')}</Table.Head>
122
+ <Table.Head>{i18n.t('account.role')}</Table.Head>
123
+ <Table.Head>2FA</Table.Head>
124
+ <Table.Head class="text-right">{i18n.t('account.joined')}</Table.Head>
125
+ </Table.Row>
126
+ </Table.Header>
127
+ <Table.Body>
128
+ {#each members as member (member.email)}
129
+ <Table.Row>
130
+ <Table.Cell>
131
+ <div class="flex items-center gap-3">
132
+ <div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary text-xs font-semibold">
133
+ {member.name.split(' ').map(n => n[0]).join('').slice(0, 2)}
134
+ </div>
135
+ <div class="min-w-0">
136
+ <p class="truncate text-sm font-medium text-foreground">{member.name}</p>
137
+ <p class="truncate text-xs text-muted-foreground">{member.email}</p>
138
+ </div>
139
+ </div>
140
+ </Table.Cell>
141
+ <Table.Cell><Badge variant="secondary" class="text-[10px]">{member.role}</Badge></Table.Cell>
142
+ <Table.Cell>
143
+ {#if member.twoFactor}
144
+ <ShieldCheck class="h-4 w-4 text-green-500" />
145
+ {:else}
146
+ <ShieldOff class="h-4 w-4 text-muted-foreground/50" />
147
+ {/if}
148
+ </Table.Cell>
149
+ <Table.Cell class="text-right text-muted-foreground">{member.joined}</Table.Cell>
150
+ </Table.Row>
151
+ {/each}
152
+ </Table.Body>
153
+ </Table.Root>
154
+ </Card.CardContent>
155
+ </Card.Card>
101
156
  </div>
@@ -2,6 +2,8 @@
2
2
  import { useTranslation } from '@svadmin/core/i18n';
3
3
  import * as Card from '../ui/card/index.js';
4
4
  import { Button } from '../ui/button/index.js';
5
+ import { Switch } from '../ui/switch/index.js';
6
+ import { Label } from '../ui/label/index.js';
5
7
  import { Upload, Download, FileText, CheckCircle2, Loader2 } from '@lucide/svelte';
6
8
 
7
9
  const i18n = useTranslation();
@@ -12,6 +14,15 @@
12
14
  let progress = $state(0);
13
15
  let importCount = $state(0);
14
16
 
17
+ const importOptions = [
18
+ { key: 'account.importCreateUsers', checked: true },
19
+ { key: 'account.importUpdateUsers', checked: false },
20
+ { key: 'account.importNotifyPassword', checked: false },
21
+ { key: 'account.importExternalIds', checked: false },
22
+ { key: 'account.importWelcomeEmail', checked: true },
23
+ ];
24
+ let optionStates = $state<boolean[]>(importOptions.map(o => o.checked));
25
+
15
26
  function handleDragOver(e: DragEvent) {
16
27
  e.preventDefault();
17
28
  isDragging = true;
@@ -141,5 +152,25 @@
141
152
  </Button>
142
153
  </Card.CardContent>
143
154
  </Card.Card>
155
+
156
+ <!-- Import options -->
157
+ <Card.Card class="border-border/60">
158
+ <Card.CardHeader>
159
+ <Card.CardTitle class="text-base">{i18n.t('account.importOptions')}</Card.CardTitle>
160
+ </Card.CardHeader>
161
+ <Card.CardContent class="space-y-4">
162
+ {#each importOptions as option, i (option.key)}
163
+ <div class="flex items-center justify-between gap-4">
164
+ <Label for="import-option-{i}" class="text-sm font-normal text-foreground">{i18n.t(option.key)}</Label>
165
+ <Switch id="import-option-{i}" bind:checked={optionStates[i]} />
166
+ </div>
167
+ {/each}
168
+ <div class="flex justify-end border-t pt-4">
169
+ <Button onclick={() => { const input = document.getElementById('file-input'); input?.click(); }}>
170
+ {i18n.t('account.startImport')}
171
+ </Button>
172
+ </div>
173
+ </Card.CardContent>
174
+ </Card.Card>
144
175
  {/if}
145
176
  </div>