@svadmin/ui 0.34.9 → 0.35.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 (123) hide show
  1. package/dist/app.css +372 -0
  2. package/dist/component-registry.svelte.d.ts +1 -0
  3. package/dist/component-registry.svelte.js +1 -0
  4. package/dist/components/AICommandBar.svelte +3 -3
  5. package/dist/components/AboutSettings.svelte +2 -2
  6. package/dist/components/AdminApp.svelte +96 -18
  7. package/dist/components/AdminApp.svelte.d.ts +32 -2
  8. package/dist/components/AnomalyBadge.svelte +1 -1
  9. package/dist/components/ApiSettings.svelte +191 -0
  10. package/dist/components/ApiSettings.svelte.d.ts +3 -0
  11. package/dist/components/AppearanceSettings.svelte +5 -6
  12. package/dist/components/AuditLogDrawer.svelte +1 -1
  13. package/dist/components/AuditLogViewer.svelte +1 -1
  14. package/dist/components/AutoForm.svelte +50 -44
  15. package/dist/components/AutoTable.svelte +44 -39
  16. package/dist/components/Breadcrumbs.svelte +1 -0
  17. package/dist/components/ChatDialog.svelte +5 -4
  18. package/dist/components/ComboboxField.svelte +3 -3
  19. package/dist/components/CommandPalette.svelte +3 -3
  20. package/dist/components/CopilotPanel.svelte +2 -2
  21. package/dist/components/CreatePage.svelte +0 -1
  22. package/dist/components/DevTools.svelte +4 -3
  23. package/dist/components/DraggableHeader.svelte +2 -1
  24. package/dist/components/EditPage.svelte +1 -1
  25. package/dist/components/EmptyState.svelte +1 -1
  26. package/dist/components/ErrorPage.svelte +58 -0
  27. package/dist/components/ErrorPage.svelte.d.ts +8 -0
  28. package/dist/components/FieldRenderer.svelte +9 -8
  29. package/dist/components/ForgotPasswordPage.svelte +111 -68
  30. package/dist/components/Header.svelte +7 -7
  31. package/dist/components/InferencerPanel.svelte +4 -3
  32. package/dist/components/InfiniteList.svelte +3 -4
  33. package/dist/components/IntegrationsSettings.svelte +70 -0
  34. package/dist/components/IntegrationsSettings.svelte.d.ts +4 -0
  35. package/dist/components/KeyboardShortcuts.svelte +3 -3
  36. package/dist/components/Layout.svelte +13 -13
  37. package/dist/components/LoginPage.svelte +207 -105
  38. package/dist/components/LoginPage.svelte.d.ts +3 -15
  39. package/dist/components/MarkdownRenderer.svelte +3 -5
  40. package/dist/components/ModalForm.svelte +1 -3
  41. package/dist/components/NotificationsSettings.svelte +115 -0
  42. package/dist/components/NotificationsSettings.svelte.d.ts +3 -0
  43. package/dist/components/PageHeader.svelte +9 -7
  44. package/dist/components/PageSkeleton.svelte +4 -4
  45. package/dist/components/PasswordInput.svelte +1 -2
  46. package/dist/components/PermissionMatrix.svelte +5 -5
  47. package/dist/components/ProfilePage.svelte +1 -1
  48. package/dist/components/RegisterPage.svelte +103 -65
  49. package/dist/components/ResourceOperationsPage.svelte +153 -0
  50. package/dist/components/ResourceOperationsPage.svelte.d.ts +37 -0
  51. package/dist/components/RolesSettings.svelte +1 -2
  52. package/dist/components/SecuritySettings.svelte +179 -0
  53. package/dist/components/SecuritySettings.svelte.d.ts +4 -0
  54. package/dist/components/SettingsPage.svelte +121 -55
  55. package/dist/components/SettingsPage.svelte.d.ts +25 -1
  56. package/dist/components/ShowPage.svelte +13 -15
  57. package/dist/components/Sidebar.svelte +107 -58
  58. package/dist/components/SidebarItem.svelte +38 -14
  59. package/dist/components/StepsForm.svelte +1 -1
  60. package/dist/components/TaskList.svelte +1 -1
  61. package/dist/components/TaskQueueDrawer.svelte +2 -1
  62. package/dist/components/TenantSwitcher.svelte +1 -2
  63. package/dist/components/Toast.svelte +1 -0
  64. package/dist/components/TooltipButton.svelte +1 -0
  65. package/dist/components/TwoFactorAuthPage.svelte +247 -0
  66. package/dist/components/TwoFactorAuthPage.svelte.d.ts +3 -0
  67. package/dist/components/UpdatePasswordPage.svelte +86 -48
  68. package/dist/components/VirtualTable.svelte +1 -1
  69. package/dist/components/VoiceInput.svelte +1 -0
  70. package/dist/components/account/CompanyProfilePage.svelte +99 -0
  71. package/dist/components/account/CompanyProfilePage.svelte.d.ts +3 -0
  72. package/dist/components/account/GetStartedPage.svelte +123 -0
  73. package/dist/components/account/GetStartedPage.svelte.d.ts +4 -0
  74. package/dist/components/account/ImportMembersPage.svelte +143 -0
  75. package/dist/components/account/ImportMembersPage.svelte.d.ts +3 -0
  76. package/dist/components/account/MembersStarterPage.svelte +104 -0
  77. package/dist/components/account/MembersStarterPage.svelte.d.ts +3 -0
  78. package/dist/components/account/SecurityLogPage.svelte +108 -0
  79. package/dist/components/account/SecurityLogPage.svelte.d.ts +3 -0
  80. package/dist/components/account/SettingsEnterprisePage.svelte +131 -0
  81. package/dist/components/account/SettingsEnterprisePage.svelte.d.ts +3 -0
  82. package/dist/components/account/SettingsPlainPage.svelte +81 -0
  83. package/dist/components/account/SettingsPlainPage.svelte.d.ts +3 -0
  84. package/dist/components/account/SettingsSidebarPage.svelte +98 -0
  85. package/dist/components/account/SettingsSidebarPage.svelte.d.ts +3 -0
  86. package/dist/components/account/TeamMembersPage.svelte +98 -0
  87. package/dist/components/account/TeamMembersPage.svelte.d.ts +3 -0
  88. package/dist/components/buttons/ImportButton.svelte +1 -1
  89. package/dist/components/buttons/ListButton.svelte +1 -1
  90. package/dist/components/charts/BarChart.svelte +1 -1
  91. package/dist/components/charts/LineChart.svelte +3 -3
  92. package/dist/components/charts/PieChart.svelte +2 -2
  93. package/dist/components/fieldComponentMap.js +2 -1
  94. package/dist/components/fields/ArrayField.svelte +1 -1
  95. package/dist/components/fields/MarkdownField.svelte +1 -0
  96. package/dist/components/fields/MultiSelectField.svelte +1 -1
  97. package/dist/components/fields/TagField.svelte +1 -1
  98. package/dist/components/network/TeamCrewTablePage.svelte +122 -0
  99. package/dist/components/network/TeamCrewTablePage.svelte.d.ts +3 -0
  100. package/dist/components/network/UserCardsNFTPage.svelte +134 -0
  101. package/dist/components/network/UserCardsNFTPage.svelte.d.ts +3 -0
  102. package/dist/components/profile/ActivityTimeline.svelte +77 -0
  103. package/dist/components/profile/ActivityTimeline.svelte.d.ts +16 -0
  104. package/dist/components/profile/ProfileCard.svelte +216 -0
  105. package/dist/components/profile/ProfileCard.svelte.d.ts +27 -0
  106. package/dist/components/profile/ProjectsGrid.svelte +91 -0
  107. package/dist/components/profile/ProjectsGrid.svelte.d.ts +17 -0
  108. package/dist/components/profile/PublicProfilePage.svelte +179 -0
  109. package/dist/components/profile/PublicProfilePage.svelte.d.ts +10 -0
  110. package/dist/components/profile/TeamsShowcase.svelte +98 -0
  111. package/dist/components/profile/TeamsShowcase.svelte.d.ts +19 -0
  112. package/dist/components/ui/alert/index.js +1 -0
  113. package/dist/components/ui/badge/index.js +1 -0
  114. package/dist/components/ui/button/button.svelte +1 -0
  115. package/dist/components/ui/button/index.js +1 -0
  116. package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +1 -0
  117. package/dist/components/ui/dropdown-menu/dropdown-menu.svelte +1 -1
  118. package/dist/components/ui/sheet/sheet.svelte +1 -0
  119. package/dist/components/ui/tabs/tabs.svelte +1 -0
  120. package/dist/index.d.ts +23 -0
  121. package/dist/index.js +27 -0
  122. package/dist/router-state.svelte.js +17 -0
  123. package/package.json +13 -13
@@ -0,0 +1,247 @@
1
+ <script lang="ts">
2
+ import { t } from '@svadmin/core/i18n';
3
+ import * as Card from './ui/card/index.js';
4
+ import { Button } from './ui/button/index.js';
5
+ import { Input } from './ui/input/index.js';
6
+ import * as Alert from './ui/alert/index.js';
7
+ import { Badge } from './ui/badge/index.js';
8
+ import { Separator } from './ui/separator/index.js';
9
+ import { Shield, Copy, CheckCircle2, AlertTriangle, ArrowRight, ArrowLeft } from '@lucide/svelte';
10
+
11
+ type Step = 'intro' | 'scan' | 'verify' | 'recovery';
12
+
13
+ let currentStep = $state<Step>('intro');
14
+ let is2faEnabled = $state(false);
15
+ let verifyError = $state('');
16
+ let copiedRecovery = $state(false);
17
+ let codeDigits = $state<string[]>(Array.from({ length: 6 }, () => ''));
18
+
19
+ const verificationCode = $derived(codeDigits.join(''));
20
+ const qrCells = [
21
+ '11111110',
22
+ '10010010',
23
+ '10111010',
24
+ '10100100',
25
+ '11101010',
26
+ '00101110',
27
+ '11010100',
28
+ '01110110',
29
+ ].flatMap(row => [...row].map(cell => cell === '1'));
30
+
31
+ const recoveryCodes = [
32
+ 'A1B2-C3D4', 'E5F6-G7H8', 'I9J0-K1L2',
33
+ 'M3N4-O5P6', 'Q7R8-S9T0', 'U1V2-W3X4',
34
+ 'Y5Z6-A7B8', 'C9D0-E1F2',
35
+ ];
36
+
37
+ function startSetup() {
38
+ currentStep = 'scan';
39
+ }
40
+
41
+ function proceedToVerify() {
42
+ currentStep = 'verify';
43
+ }
44
+
45
+ function handleVerify() {
46
+ verifyError = '';
47
+ if (verificationCode.length !== 6) {
48
+ verifyError = t('auth.twoFactorEnterCode');
49
+ return;
50
+ }
51
+ currentStep = 'recovery';
52
+ }
53
+
54
+ function handleDigitInput(e: Event, index: number) {
55
+ const input = e.currentTarget as HTMLInputElement;
56
+ const digit = input.value.replace(/\D/g, '').slice(-1);
57
+ codeDigits[index] = digit;
58
+ input.value = digit;
59
+ if (digit && index < codeDigits.length - 1) {
60
+ const next = input.parentElement?.children[index + 1] as HTMLInputElement | undefined;
61
+ next?.focus();
62
+ }
63
+ }
64
+
65
+ function completeSetup() {
66
+ is2faEnabled = true;
67
+ currentStep = 'intro';
68
+ }
69
+
70
+ function disable2fa() {
71
+ is2faEnabled = false;
72
+ currentStep = 'intro';
73
+ }
74
+
75
+ function copyRecoveryCodes() {
76
+ navigator.clipboard?.writeText(recoveryCodes.join('\n'));
77
+ copiedRecovery = true;
78
+ setTimeout(() => { copiedRecovery = false; }, 2000);
79
+ }
80
+ </script>
81
+
82
+ <div class="flex min-h-[70vh] items-center justify-center p-4">
83
+ <div class="w-full max-w-lg space-y-6">
84
+ {#if currentStep === 'intro' && !is2faEnabled}
85
+ <!-- Intro -->
86
+ <Card.Card class="border-border/60">
87
+ <Card.CardContent class="space-y-6 p-8 text-center">
88
+ <div class="mx-auto inline-flex h-16 w-16 items-center justify-center rounded-2xl bg-amber-500/10 text-amber-500">
89
+ <Shield class="h-8 w-8" />
90
+ </div>
91
+ <div class="space-y-2">
92
+ <h2 class="text-2xl font-semibold text-foreground">{t('auth.twoFactorSetup')}</h2>
93
+ <p class="text-sm text-muted-foreground">{t('security.twoFactorDescription')}</p>
94
+ </div>
95
+ <div class="space-y-3 text-left">
96
+ <div class="flex items-start gap-3 rounded-xl border p-3">
97
+ <div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-blue-500/10 text-blue-500 text-sm font-bold">1</div>
98
+ <div>
99
+ <p class="text-sm font-medium text-foreground">{t('auth.twoFactorScanQR')}</p>
100
+ <p class="text-xs text-muted-foreground">Google Authenticator, Authy, etc.</p>
101
+ </div>
102
+ </div>
103
+ <div class="flex items-start gap-3 rounded-xl border p-3">
104
+ <div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-blue-500/10 text-blue-500 text-sm font-bold">2</div>
105
+ <div>
106
+ <p class="text-sm font-medium text-foreground">{t('auth.twoFactorEnterCode')}</p>
107
+ <p class="text-xs text-muted-foreground">6-digit code from your authenticator</p>
108
+ </div>
109
+ </div>
110
+ <div class="flex items-start gap-3 rounded-xl border p-3">
111
+ <div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-blue-500/10 text-blue-500 text-sm font-bold">3</div>
112
+ <div>
113
+ <p class="text-sm font-medium text-foreground">{t('auth.twoFactorSaveRecovery')}</p>
114
+ <p class="text-xs text-muted-foreground">{t('auth.twoFactorRecoveryHint')}</p>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ <Button class="w-full" onclick={startSetup}>
119
+ {t('auth.twoFactorSetup')} <ArrowRight class="h-4 w-4 ml-2" />
120
+ </Button>
121
+ </Card.CardContent>
122
+ </Card.Card>
123
+
124
+ {:else if currentStep === 'scan'}
125
+ <!-- QR Code -->
126
+ <Card.Card class="border-border/60">
127
+ <Card.CardContent class="space-y-6 p-8">
128
+ <div class="text-center space-y-2">
129
+ <h2 class="text-xl font-semibold text-foreground">{t('auth.twoFactorScanQR')}</h2>
130
+ <p class="text-sm text-muted-foreground">Use your authenticator app to scan the code below.</p>
131
+ </div>
132
+ <div class="mx-auto flex h-48 w-48 items-center justify-center rounded-xl border-2 border-dashed border-border/60 bg-muted/20">
133
+ <div class="grid grid-cols-8 gap-[2px]">
134
+ {#each qrCells as filled, i (i)}
135
+ <div class="h-3 w-3 rounded-[1px] {filled ? 'bg-foreground' : 'bg-transparent'}"></div>
136
+ {/each}
137
+ </div>
138
+ </div>
139
+ <div class="flex gap-3">
140
+ <Button variant="outline" class="flex-1" onclick={() => currentStep = 'intro'}>
141
+ <ArrowLeft class="h-4 w-4 mr-2" />{t('common.back')}
142
+ </Button>
143
+ <Button class="flex-1" onclick={proceedToVerify}>
144
+ {t('common.next')} <ArrowRight class="h-4 w-4 ml-2" />
145
+ </Button>
146
+ </div>
147
+ </Card.CardContent>
148
+ </Card.Card>
149
+
150
+ {:else if currentStep === 'verify'}
151
+ <!-- Verify -->
152
+ <Card.Card class="border-border/60">
153
+ <Card.CardContent class="space-y-6 p-8">
154
+ <div class="text-center space-y-2">
155
+ <h2 class="text-xl font-semibold text-foreground">{t('auth.twoFactorVerify')}</h2>
156
+ <p class="text-sm text-muted-foreground">{t('auth.twoFactorEnterCode')}</p>
157
+ </div>
158
+
159
+ {#if verifyError}
160
+ <Alert.Root variant="destructive">
161
+ <AlertTriangle class="h-4 w-4" />
162
+ <Alert.Description>{verifyError}</Alert.Description>
163
+ </Alert.Root>
164
+ {/if}
165
+
166
+ <div class="flex justify-center gap-2">
167
+ {#each Array(6) as _, i (i)}
168
+ <Input
169
+ class="h-14 w-12 text-center text-xl font-bold"
170
+ maxlength={1}
171
+ inputmode="numeric"
172
+ pattern="[0-9]*"
173
+ bind:value={codeDigits[i]}
174
+ oninput={(e: Event) => handleDigitInput(e, i)}
175
+ />
176
+ {/each}
177
+ </div>
178
+
179
+ <div class="flex gap-3">
180
+ <Button variant="outline" class="flex-1" onclick={() => currentStep = 'scan'}>
181
+ <ArrowLeft class="h-4 w-4 mr-2" />{t('common.back')}
182
+ </Button>
183
+ <Button class="flex-1" onclick={handleVerify}>
184
+ {t('common.confirm')} <ArrowRight class="h-4 w-4 ml-2" />
185
+ </Button>
186
+ </div>
187
+ </Card.CardContent>
188
+ </Card.Card>
189
+
190
+ {:else if currentStep === 'recovery'}
191
+ <!-- Recovery codes -->
192
+ <Card.Card class="border-border/60">
193
+ <Card.CardContent class="space-y-6 p-8">
194
+ <div class="text-center space-y-2">
195
+ <div class="mx-auto inline-flex h-12 w-12 items-center justify-center rounded-xl bg-green-500/10 text-green-500">
196
+ <CheckCircle2 class="h-6 w-6" />
197
+ </div>
198
+ <h2 class="text-xl font-semibold text-foreground">{t('auth.twoFactorRecovery')}</h2>
199
+ <p class="text-sm text-muted-foreground">{t('auth.twoFactorRecoveryHint')}</p>
200
+ </div>
201
+
202
+ <div class="rounded-xl border bg-muted/20 p-4">
203
+ <div class="grid grid-cols-2 gap-2 font-mono text-sm">
204
+ {#each recoveryCodes as code, i (i)}
205
+ <div class="rounded-lg bg-background px-3 py-1.5 text-center text-foreground">{code}</div>
206
+ {/each}
207
+ </div>
208
+ </div>
209
+
210
+ <div class="flex gap-3">
211
+ <Button variant="outline" class="flex-1" onclick={copyRecoveryCodes}>
212
+ {#if copiedRecovery}
213
+ <CheckCircle2 class="h-4 w-4 mr-2" />{t('common.copied')}
214
+ {:else}
215
+ <Copy class="h-4 w-4 mr-2" />{t('common.copyAll')}
216
+ {/if}
217
+ </Button>
218
+ <Button class="flex-1" onclick={completeSetup}>
219
+ {t('account.completeSetup')} <ArrowRight class="h-4 w-4 ml-2" />
220
+ </Button>
221
+ </div>
222
+ </Card.CardContent>
223
+ </Card.Card>
224
+
225
+ {:else}
226
+ <!-- 2FA enabled state -->
227
+ <Card.Card class="border-border/60">
228
+ <Card.CardContent class="space-y-6 p-8 text-center">
229
+ <div class="mx-auto inline-flex h-16 w-16 items-center justify-center rounded-2xl bg-green-500/10 text-green-500">
230
+ <Shield class="h-8 w-8" />
231
+ </div>
232
+ <div class="space-y-2">
233
+ <h2 class="text-2xl font-semibold text-foreground">{t('auth.twoFactorVerifySuccess')}</h2>
234
+ <p class="text-sm text-muted-foreground">{t('security.twoFactorActive')}</p>
235
+ </div>
236
+ <Badge variant="secondary" class="bg-green-500/10 text-green-600">
237
+ <CheckCircle2 class="h-3 w-3 mr-1" />2FA Active
238
+ </Badge>
239
+ <Separator />
240
+ <Button variant="destructive" onclick={disable2fa}>
241
+ {t('auth.twoFactorDisable')}
242
+ </Button>
243
+ </Card.CardContent>
244
+ </Card.Card>
245
+ {/if}
246
+ </div>
247
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const TwoFactorAuthPage: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type TwoFactorAuthPage = ReturnType<typeof TwoFactorAuthPage>;
3
+ export default TwoFactorAuthPage;
@@ -3,10 +3,9 @@
3
3
  import { t } from '@svadmin/core/i18n';
4
4
  import { navigate } from '@svadmin/core/router';
5
5
  import { Button } from './ui/button/index.js';
6
- import * as Card from './ui/card/index.js';
7
6
  import * as Alert from './ui/alert/index.js';
8
7
  import PasswordInput from './PasswordInput.svelte';
9
- import { ShieldCheck, Loader2, AlertCircle } from '@lucide/svelte';
8
+ import { ShieldCheck, Loader2, AlertCircle, Shield } from '@lucide/svelte';
10
9
 
11
10
  let { title = 'Admin' } = $props<{
12
11
  title?: string;
@@ -32,58 +31,97 @@
32
31
  }
33
32
  </script>
34
33
 
35
- <div class="min-h-screen flex items-center justify-center bg-background p-4">
36
- <div class="w-full max-w-[380px]">
37
- <Card.Card>
38
- <Card.CardHeader class="text-center pb-2">
39
- <div class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10 text-primary mx-auto mb-3">
34
+ <div class="min-h-screen flex flex-col lg:flex-row">
35
+ <!-- Left brand panel -->
36
+ <div class="hidden lg:flex lg:w-[480px] xl:w-[520px] relative overflow-hidden bg-gradient-to-br from-primary/90 via-primary to-primary/80 shrink-0">
37
+ <div class="absolute inset-0">
38
+ <div class="absolute top-[12%] left-[8%] w-28 h-28 rounded-3xl border border-white/10 bg-white/5 rotate-12 backdrop-blur-sm"></div>
39
+ <div class="absolute top-[55%] left-[55%] w-40 h-40 rounded-full border border-white/10 bg-white/5 -rotate-6 backdrop-blur-sm"></div>
40
+ <div class="absolute bottom-[15%] left-[15%] w-24 h-24 rounded-2xl border border-white/10 bg-white/5 -rotate-12 backdrop-blur-sm"></div>
41
+ <div class="absolute top-[30%] right-[10%] w-16 h-16 rounded-xl border border-white/10 bg-white/5 rotate-45 backdrop-blur-sm"></div>
42
+ <div class="absolute inset-0 opacity-[0.03]" style="background-image: radial-gradient(circle, white 1px, transparent 1px); background-size: 24px 24px;"></div>
43
+ </div>
44
+
45
+ <div class="relative z-10 flex flex-col justify-between p-10 xl:p-12 w-full">
46
+ <div class="flex items-center gap-3">
47
+ <div class="flex h-9 w-9 items-center justify-center rounded-lg bg-white/15 backdrop-blur-sm">
48
+ <Shield class="h-5 w-5 text-white" />
49
+ </div>
50
+ <span class="text-lg font-semibold text-white tracking-tight">{title}</span>
51
+ </div>
52
+
53
+ <div class="space-y-4">
54
+ <h2 class="text-3xl xl:text-4xl font-bold text-white leading-tight">
55
+ {t('auth.resetPassword')}
56
+ </h2>
57
+ <p class="text-base text-white/70 leading-relaxed max-w-[320px]">
58
+ {t('auth.resetPasswordDescription')}
59
+ </p>
60
+ </div>
61
+
62
+ <div class="flex items-center gap-3">
63
+ <div class="h-1 w-8 rounded-full bg-white/30"></div>
64
+ <div class="h-1 w-4 rounded-full bg-white/15"></div>
65
+ <div class="h-1 w-2 rounded-full bg-white/10"></div>
66
+ </div>
67
+ </div>
68
+ </div>
69
+
70
+ <!-- Right form panel -->
71
+ <div class="flex-1 flex items-center justify-center bg-background p-6 sm:p-8 lg:p-12">
72
+ <div class="w-full max-w-[400px]">
73
+ <div class="flex items-center gap-3 mb-8 lg:hidden">
74
+ <div class="flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10 text-primary">
40
75
  <ShieldCheck class="h-5 w-5" />
41
76
  </div>
42
- <Card.CardTitle class="text-xl">{t('auth.resetPassword')}</Card.CardTitle>
77
+ <span class="text-lg font-semibold tracking-tight">{title}</span>
78
+ </div>
79
+
80
+ <div class="space-y-1 mb-8">
81
+ <h1 class="text-2xl font-semibold tracking-tight">{t('auth.resetPassword')}</h1>
43
82
  <p class="text-sm text-muted-foreground">{t('auth.resetPasswordDescription')}</p>
44
- </Card.CardHeader>
45
- <Card.CardContent>
46
- <form onsubmit={handleSubmit} class="space-y-4">
47
- {#if error}
48
- <Alert.Root variant="destructive">
49
- <AlertCircle class="h-4 w-4" />
50
- <Alert.Description>{error}</Alert.Description>
51
- </Alert.Root>
83
+ </div>
84
+
85
+ <form onsubmit={handleSubmit} class="space-y-5">
86
+ {#if error}
87
+ <Alert.Root variant="destructive">
88
+ <AlertCircle class="h-4 w-4" />
89
+ <Alert.Description>{error}</Alert.Description>
90
+ </Alert.Root>
91
+ {/if}
92
+
93
+ <PasswordInput
94
+ id="new-password"
95
+ label={t('auth.password')}
96
+ bind:value={password}
97
+ autocomplete="new-password"
98
+ showStrength
99
+ />
100
+
101
+ <PasswordInput
102
+ id="confirm-password"
103
+ label={t('auth.confirmPassword')}
104
+ bind:value={confirmPassword}
105
+ autocomplete="new-password"
106
+ />
107
+
108
+ <Button type="submit" class="w-full h-10" disabled={updatePw.isLoading}>
109
+ {#if updatePw.isLoading}
110
+ <Loader2 class="h-4 w-4 animate-spin mr-2" />
52
111
  {/if}
112
+ {t('auth.resetPassword')}
113
+ </Button>
53
114
 
54
- <PasswordInput
55
- id="new-password"
56
- label={t('auth.password')}
57
- bind:value={password}
58
- autocomplete="new-password"
59
- showStrength
60
- />
61
-
62
- <PasswordInput
63
- id="confirm-password"
64
- label={t('auth.confirmPassword')}
65
- bind:value={confirmPassword}
66
- autocomplete="new-password"
67
- />
68
-
69
- <Button type="submit" class="w-full" disabled={updatePw.isLoading}>
70
- {#if updatePw.isLoading}
71
- <Loader2 class="h-4 w-4 animate-spin mr-2" />
72
- {/if}
73
- {t('auth.resetPassword')}
115
+ <div class="flex items-center justify-center mt-2">
116
+ <Button variant="link" class="text-sm h-auto p-0 font-medium" onclick={() => navigate('/login')}>
117
+ {t('auth.backToLogin')}
74
118
  </Button>
119
+ </div>
120
+ </form>
75
121
 
76
- <div class="flex items-center justify-center mt-2">
77
- <Button variant="link" class="text-sm h-auto p-0 font-medium" onclick={() => navigate('/login')}>
78
- {t('auth.backToLogin')}
79
- </Button>
80
- </div>
81
- </form>
82
- </Card.CardContent>
83
- </Card.Card>
84
-
85
- <p class="text-xs text-muted-foreground mt-4 text-center opacity-60">
86
- Powered by {title}
87
- </p>
122
+ <p class="text-xs text-muted-foreground/60 mt-8 text-center">
123
+ Powered by {title}
124
+ </p>
125
+ </div>
88
126
  </div>
89
127
  </div>
@@ -37,7 +37,7 @@
37
37
  let scrollTop = $state(0);
38
38
  let containerRef = $state<HTMLDivElement | null>(null);
39
39
 
40
- const totalHeight = $derived(items.length * rowHeight);
40
+ const _totalHeight = $derived(items.length * rowHeight);
41
41
 
42
42
  const visibleRange = $derived.by(() => {
43
43
  const start = Math.max(0, Math.floor(scrollTop / rowHeight) - overscan);
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ /* eslint-disable no-undef */
2
3
  import { Mic, MicOff, Loader2 } from '@lucide/svelte';
3
4
  import TooltipButton from './TooltipButton.svelte';
4
5
 
@@ -0,0 +1,99 @@
1
+ <script lang="ts">
2
+ import { t } from '@svadmin/core/i18n';
3
+ import * as Card from '../ui/card/index.js';
4
+ import { Button } from '../ui/button/index.js';
5
+ import { Input } from '../ui/input/index.js';
6
+ import { Label } from '../ui/label/index.js';
7
+ import { Upload, Loader2 } from '@lucide/svelte';
8
+
9
+ let companyName = $state('Acme Corporation');
10
+ let industry = $state('Technology');
11
+ let website = $state('https://acme.com');
12
+ let employeeCount = $state('1,250');
13
+ let foundedYear = $state('2015');
14
+ let description = $state('Building the future of enterprise software with cutting-edge AI and cloud solutions.');
15
+ let saving = $state(false);
16
+
17
+ async function handleSave() {
18
+ saving = true;
19
+ await new Promise(r => setTimeout(r, 800));
20
+ saving = false;
21
+ }
22
+ </script>
23
+
24
+ <div class="space-y-6">
25
+ <div>
26
+ <h2 class="text-xl font-semibold text-foreground">{t('account.companyProfile')}</h2>
27
+ <p class="mt-1 text-sm text-muted-foreground">{t('account.companyProfileDescription')}</p>
28
+ </div>
29
+
30
+ <!-- Company Avatar & Banner -->
31
+ <Card.Card class="border-border/60">
32
+ <Card.CardContent class="p-0">
33
+ <div class="h-32 bg-gradient-to-r from-primary/20 via-primary/10 to-accent/20 relative rounded-t-xl">
34
+ <button class="absolute bottom-2 right-2 flex h-8 w-8 items-center justify-center rounded-lg bg-black/40 text-white hover:bg-black/60 transition-colors">
35
+ <Upload class="h-4 w-4" />
36
+ </button>
37
+ </div>
38
+ <div class="relative px-6 pb-5">
39
+ <div class="-mt-8 flex items-end gap-4">
40
+ <div class="ring-4 ring-card rounded-xl overflow-hidden shrink-0">
41
+ <div class="flex h-16 w-16 items-center justify-center bg-primary/10 text-primary text-xl font-bold">
42
+ {companyName.split(' ').map(n => n[0]).join('').slice(0, 2)}
43
+ </div>
44
+ </div>
45
+ <div class="pb-1">
46
+ <h3 class="text-lg font-semibold text-foreground">{companyName}</h3>
47
+ <p class="text-sm text-muted-foreground">{industry}</p>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ </Card.CardContent>
52
+ </Card.Card>
53
+
54
+ <!-- Company Details Form -->
55
+ <Card.Card class="border-border/60">
56
+ <Card.CardHeader>
57
+ <Card.CardTitle class="text-base">{t('profile.companyName')}</Card.CardTitle>
58
+ </Card.CardHeader>
59
+ <Card.CardContent class="space-y-4">
60
+ <div class="grid gap-4 sm:grid-cols-2">
61
+ <div class="space-y-2">
62
+ <Label for="company-name">{t('profile.companyName')}</Label>
63
+ <Input id="company-name" bind:value={companyName} />
64
+ </div>
65
+ <div class="space-y-2">
66
+ <Label for="company-industry">{t('profile.industry')}</Label>
67
+ <Input id="company-industry" bind:value={industry} />
68
+ </div>
69
+ <div class="space-y-2">
70
+ <Label for="company-website">{t('profile.website')}</Label>
71
+ <Input id="company-website" bind:value={website} type="url" />
72
+ </div>
73
+ <div class="space-y-2">
74
+ <Label for="company-employees">{t('profile.employees')}</Label>
75
+ <Input id="company-employees" bind:value={employeeCount} />
76
+ </div>
77
+ <div class="space-y-2">
78
+ <Label for="company-founded">{t('profile.founded')}</Label>
79
+ <Input id="company-founded" bind:value={foundedYear} />
80
+ </div>
81
+ </div>
82
+ <div class="space-y-2">
83
+ <Label for="company-description">{t('profile.companyDescription')}</Label>
84
+ <textarea
85
+ id="company-description"
86
+ bind:value={description}
87
+ rows={3}
88
+ class="flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
89
+ ></textarea>
90
+ </div>
91
+ <div class="flex justify-end">
92
+ <Button onclick={handleSave} disabled={saving}>
93
+ {#if saving}<Loader2 class="h-4 w-4 animate-spin mr-2" />{/if}
94
+ {t('common.save')}
95
+ </Button>
96
+ </div>
97
+ </Card.CardContent>
98
+ </Card.Card>
99
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const CompanyProfilePage: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type CompanyProfilePage = ReturnType<typeof CompanyProfilePage>;
3
+ export default CompanyProfilePage;
@@ -0,0 +1,123 @@
1
+ <script lang="ts">
2
+ import { t } from '@svadmin/core/i18n';
3
+ import * as Card from '../ui/card/index.js';
4
+ import { Button } from '../ui/button/index.js';
5
+ import { Badge } from '../ui/badge/index.js';
6
+ import { Camera, UserPlus, Puzzle, Users, CheckCircle2, ArrowRight, Sparkles } from '@lucide/svelte';
7
+ import type { Component } from 'svelte';
8
+
9
+ interface Step {
10
+ key: string;
11
+ title: string;
12
+ description: string;
13
+ icon: Component;
14
+ completed: boolean;
15
+ }
16
+
17
+ let currentStep = $state(0);
18
+
19
+ let steps = $state<Step[]>([
20
+ { key: 'avatar', title: t('account.uploadAvatar'), description: t('account.getStartedDescription'), icon: Camera, completed: false },
21
+ { key: 'profile', title: t('account.setupProfile'), description: t('account.getStartedDescription'), icon: UserPlus, completed: false },
22
+ { key: 'connect', title: t('account.connectProvider'), description: t('account.getStartedDescription'), icon: Puzzle, completed: false },
23
+ { key: 'team', title: t('account.setupTeam'), description: t('account.getStartedDescription'), icon: Users, completed: false },
24
+ ]);
25
+
26
+ const completedCount = $derived(steps.filter(s => s.completed).length);
27
+ const progressPercent = $derived(Math.round((completedCount / steps.length) * 100));
28
+ const allCompleted = $derived(completedCount === steps.length);
29
+
30
+ function markComplete(index: number) {
31
+ steps[index] = { ...steps[index], completed: true };
32
+ if (currentStep < steps.length - 1) {
33
+ currentStep = index + 1;
34
+ }
35
+ }
36
+
37
+ function goToStep(index: number) {
38
+ if (index <= completedCount) {
39
+ currentStep = index;
40
+ }
41
+ }
42
+
43
+ function skipStep() {
44
+ if (currentStep < steps.length - 1) {
45
+ currentStep++;
46
+ }
47
+ }
48
+ </script>
49
+
50
+ <div class="space-y-6">
51
+ <div class="flex items-start justify-between gap-4">
52
+ <div>
53
+ <h2 class="text-xl font-semibold text-foreground">{t('account.getStarted')}</h2>
54
+ <p class="mt-1 text-sm text-muted-foreground">{t('account.getStartedDescription')}</p>
55
+ </div>
56
+ <Badge variant="secondary">{progressPercent}%</Badge>
57
+ </div>
58
+
59
+ <!-- Progress bar -->
60
+ <div class="space-y-1.5">
61
+ <div class="h-2 rounded-full bg-muted overflow-hidden">
62
+ <div class="h-full rounded-full bg-primary transition-all duration-500" style="width: {progressPercent}%"></div>
63
+ </div>
64
+ <p class="text-xs text-muted-foreground">{t('account.step', { current: completedCount, total: steps.length })}</p>
65
+ </div>
66
+
67
+ {#if allCompleted}
68
+ <Card.Card class="border-success/30 bg-success/5">
69
+ <Card.CardContent class="flex items-center gap-4 p-6">
70
+ <div class="flex h-12 w-12 items-center justify-center rounded-xl bg-success/10 text-success">
71
+ <Sparkles class="h-6 w-6" />
72
+ </div>
73
+ <div>
74
+ <h3 class="font-semibold text-success">{t('account.completeSetup')}</h3>
75
+ <p class="text-sm text-success/70">{t('account.getStartedDescription')}</p>
76
+ </div>
77
+ </Card.CardContent>
78
+ </Card.Card>
79
+ {:else}
80
+ <!-- Step cards -->
81
+ <div class="space-y-3">
82
+ {#each steps as step, i (i)}
83
+ <Card.Card class="border-border/60 transition-all {currentStep === i ? 'border-primary/40 shadow-sm' : ''} {step.completed ? 'opacity-60' : ''}">
84
+ <Card.CardContent class="p-4">
85
+ <div class="flex items-start gap-4">
86
+ <button
87
+ class="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl transition-colors
88
+ {step.completed ? 'bg-success/10 text-success' : currentStep === i ? 'bg-primary/10 text-primary' : 'bg-muted text-muted-foreground'}"
89
+ onclick={() => goToStep(i)}
90
+ >
91
+ {#if step.completed}
92
+ <CheckCircle2 class="h-5 w-5" />
93
+ {:else}
94
+ <step.icon class="h-5 w-5" />
95
+ {/if}
96
+ </button>
97
+ <div class="flex-1 min-w-0">
98
+ <div class="flex items-center gap-2">
99
+ <h4 class="font-medium text-foreground">{step.title}</h4>
100
+ {#if currentStep === i && !step.completed}
101
+ <Badge variant="outline" class="text-[10px]">{t('account.step', { current: i + 1, total: steps.length })}</Badge>
102
+ {/if}
103
+ </div>
104
+ <p class="text-sm text-muted-foreground mt-0.5">{step.description}</p>
105
+
106
+ {#if currentStep === i && !step.completed}
107
+ <div class="mt-3 flex gap-2">
108
+ <Button size="sm" onclick={() => markComplete(i)}>
109
+ {t('account.completeSetup')} <ArrowRight class="h-3.5 w-3.5 ml-1" />
110
+ </Button>
111
+ <Button size="sm" variant="ghost" onclick={skipStep}>
112
+ {t('account.skipSetup')}
113
+ </Button>
114
+ </div>
115
+ {/if}
116
+ </div>
117
+ </div>
118
+ </Card.CardContent>
119
+ </Card.Card>
120
+ {/each}
121
+ </div>
122
+ {/if}
123
+ </div>
@@ -0,0 +1,4 @@
1
+ import type { Component } from 'svelte';
2
+ declare const GetStartedPage: Component<Record<string, never>, {}, "">;
3
+ type GetStartedPage = ReturnType<typeof GetStartedPage>;
4
+ export default GetStartedPage;