@svadmin/ui 0.34.10 → 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 (70) hide show
  1. package/dist/app.css +372 -0
  2. package/dist/component-registry.svelte.d.ts +1 -0
  3. package/dist/components/AdminApp.svelte +94 -15
  4. package/dist/components/AdminApp.svelte.d.ts +32 -2
  5. package/dist/components/ApiSettings.svelte +191 -0
  6. package/dist/components/ApiSettings.svelte.d.ts +3 -0
  7. package/dist/components/AutoForm.svelte +48 -43
  8. package/dist/components/AutoTable.svelte +27 -22
  9. package/dist/components/ErrorPage.svelte +58 -0
  10. package/dist/components/ErrorPage.svelte.d.ts +8 -0
  11. package/dist/components/FieldRenderer.svelte +2 -2
  12. package/dist/components/ForgotPasswordPage.svelte +111 -68
  13. package/dist/components/Header.svelte +6 -7
  14. package/dist/components/IntegrationsSettings.svelte +70 -0
  15. package/dist/components/IntegrationsSettings.svelte.d.ts +4 -0
  16. package/dist/components/Layout.svelte +8 -8
  17. package/dist/components/LoginPage.svelte +207 -105
  18. package/dist/components/LoginPage.svelte.d.ts +3 -15
  19. package/dist/components/NotificationsSettings.svelte +115 -0
  20. package/dist/components/NotificationsSettings.svelte.d.ts +3 -0
  21. package/dist/components/PageHeader.svelte +9 -7
  22. package/dist/components/RegisterPage.svelte +103 -65
  23. package/dist/components/ResourceOperationsPage.svelte +153 -0
  24. package/dist/components/ResourceOperationsPage.svelte.d.ts +37 -0
  25. package/dist/components/SecuritySettings.svelte +179 -0
  26. package/dist/components/SecuritySettings.svelte.d.ts +4 -0
  27. package/dist/components/SettingsPage.svelte +121 -54
  28. package/dist/components/SettingsPage.svelte.d.ts +25 -1
  29. package/dist/components/ShowPage.svelte +9 -9
  30. package/dist/components/Sidebar.svelte +96 -48
  31. package/dist/components/SidebarItem.svelte +37 -12
  32. package/dist/components/TwoFactorAuthPage.svelte +247 -0
  33. package/dist/components/TwoFactorAuthPage.svelte.d.ts +3 -0
  34. package/dist/components/UpdatePasswordPage.svelte +86 -48
  35. package/dist/components/account/CompanyProfilePage.svelte +99 -0
  36. package/dist/components/account/CompanyProfilePage.svelte.d.ts +3 -0
  37. package/dist/components/account/GetStartedPage.svelte +123 -0
  38. package/dist/components/account/GetStartedPage.svelte.d.ts +4 -0
  39. package/dist/components/account/ImportMembersPage.svelte +143 -0
  40. package/dist/components/account/ImportMembersPage.svelte.d.ts +3 -0
  41. package/dist/components/account/MembersStarterPage.svelte +104 -0
  42. package/dist/components/account/MembersStarterPage.svelte.d.ts +3 -0
  43. package/dist/components/account/SecurityLogPage.svelte +108 -0
  44. package/dist/components/account/SecurityLogPage.svelte.d.ts +3 -0
  45. package/dist/components/account/SettingsEnterprisePage.svelte +131 -0
  46. package/dist/components/account/SettingsEnterprisePage.svelte.d.ts +3 -0
  47. package/dist/components/account/SettingsPlainPage.svelte +81 -0
  48. package/dist/components/account/SettingsPlainPage.svelte.d.ts +3 -0
  49. package/dist/components/account/SettingsSidebarPage.svelte +98 -0
  50. package/dist/components/account/SettingsSidebarPage.svelte.d.ts +3 -0
  51. package/dist/components/account/TeamMembersPage.svelte +98 -0
  52. package/dist/components/account/TeamMembersPage.svelte.d.ts +3 -0
  53. package/dist/components/network/TeamCrewTablePage.svelte +122 -0
  54. package/dist/components/network/TeamCrewTablePage.svelte.d.ts +3 -0
  55. package/dist/components/network/UserCardsNFTPage.svelte +134 -0
  56. package/dist/components/network/UserCardsNFTPage.svelte.d.ts +3 -0
  57. package/dist/components/profile/ActivityTimeline.svelte +77 -0
  58. package/dist/components/profile/ActivityTimeline.svelte.d.ts +16 -0
  59. package/dist/components/profile/ProfileCard.svelte +216 -0
  60. package/dist/components/profile/ProfileCard.svelte.d.ts +27 -0
  61. package/dist/components/profile/ProjectsGrid.svelte +91 -0
  62. package/dist/components/profile/ProjectsGrid.svelte.d.ts +17 -0
  63. package/dist/components/profile/PublicProfilePage.svelte +179 -0
  64. package/dist/components/profile/PublicProfilePage.svelte.d.ts +10 -0
  65. package/dist/components/profile/TeamsShowcase.svelte +98 -0
  66. package/dist/components/profile/TeamsShowcase.svelte.d.ts +19 -0
  67. package/dist/index.d.ts +23 -0
  68. package/dist/index.js +27 -0
  69. package/dist/router-state.svelte.js +17 -0
  70. package/package.json +2 -2
@@ -5,7 +5,9 @@
5
5
  import SidebarItem from './SidebarItem.svelte';
6
6
  import {
7
7
  LayoutDashboard, FileText, Users, Settings, Home,
8
- ChevronDown, Folder, ExternalLink, type Icon as LucideIcon,
8
+ ChevronDown, Folder, ExternalLink, Repeat, ClipboardCheck, SlidersHorizontal, AlertTriangle,
9
+ Barcode, FolderTree, Building2, Warehouse, BriefcaseBusiness, Handshake, UserRoundCheck,
10
+ CalendarCheck, KeyRound, MapPinned,
9
11
  } from '@lucide/svelte';
10
12
 
11
13
  import { formatLink } from '@svadmin/core/router';
@@ -24,9 +26,23 @@
24
26
  settings: Settings,
25
27
  home: Home,
26
28
  folder: Folder,
29
+ repeat: Repeat,
30
+ 'clipboard-check': ClipboardCheck,
31
+ 'sliders-horizontal': SlidersHorizontal,
32
+ 'alert-triangle': AlertTriangle,
33
+ 'barcode': Barcode,
34
+ 'folder-tree': FolderTree,
35
+ 'building-2': Building2,
36
+ 'warehouse': Warehouse,
37
+ 'briefcase-business': BriefcaseBusiness,
38
+ handshake: Handshake,
39
+ 'user-round-check': UserRoundCheck,
40
+ 'calendar-check': CalendarCheck,
41
+ 'key-round': KeyRound,
42
+ 'map-pinned': MapPinned,
27
43
  };
28
44
 
29
- function getIcon(name?: string | any): typeof LayoutDashboard {
45
+ function getIcon(name?: string | typeof LayoutDashboard): typeof LayoutDashboard {
30
46
  if (!name) return depth === 0 ? Settings : Folder;
31
47
  if (typeof name !== 'string') return name; // if it's already a component
32
48
  return iconMap[name] ?? Settings;
@@ -69,11 +85,13 @@
69
85
  <!-- Parent node with children: render as collapsible group -->
70
86
  <Collapsible.Root bind:open={isOpen}>
71
87
  <Collapsible.Trigger
72
- class="flex w-full items-center justify-between rounded-lg px-3 py-2 text-sm font-medium transition-all duration-200
88
+ class="flex w-full items-center justify-between rounded-xl px-3 py-2 text-sm font-medium transition-all duration-200
73
89
  {childActive
74
- ? 'text-sidebar-foreground bg-sidebar-accent/30'
75
- : 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
90
+ ? 'bg-primary/10 text-primary ring-1 ring-primary/15'
91
+ : 'text-sidebar-foreground/68 hover:bg-sidebar-accent/65 hover:text-sidebar-foreground'}"
76
92
  style="padding-left: {12 + depth * 12}px"
93
+ data-sidebar={depth === 0 ? "menu-button" : "menu-sub-button"}
94
+ data-active={childActive ? "true" : "false"}
77
95
  >
78
96
  <span class="flex items-center gap-3">
79
97
  <Icon class="h-4 w-4 flex-shrink-0" />
@@ -82,7 +100,7 @@
82
100
  <ChevronDown class="h-3.5 w-3.5 transition-transform duration-200 {isOpen ? 'rotate-180' : ''}" />
83
101
  </Collapsible.Trigger>
84
102
  <Collapsible.Content>
85
- <div class="mt-0.5 space-y-0.5" style="padding-left: {depth > 0 ? 0 : 0}px">
103
+ <div class="mt-0.5 space-y-0.5" style="padding-left: {depth > 0 ? 0 : 0}px" data-sidebar="menu-sub">
86
104
  {#each item.children as child, _i (_i)}
87
105
  <SidebarItem item={child} {currentPath} {collapsed} depth={depth + 1} />
88
106
  {/each}
@@ -99,10 +117,12 @@
99
117
  href={finalHref}
100
118
  target={isExternal ? '_blank' : undefined}
101
119
  rel={isExternal ? 'noopener noreferrer' : undefined}
102
- class="flex items-center justify-center rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-200
120
+ class="flex items-center justify-center rounded-xl px-3 py-2.5 text-sm font-medium transition-all duration-200
103
121
  {active
104
- ? 'bg-card text-sidebar-primary shadow-sm ring-1 ring-foreground/5'
105
- : 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
122
+ ? 'bg-primary/10 text-primary shadow-sm shadow-primary/10 ring-1 ring-primary/15'
123
+ : 'text-sidebar-foreground/68 hover:bg-sidebar-accent/65 hover:text-sidebar-foreground'}"
124
+ data-sidebar={depth === 0 ? "menu-button" : "menu-sub-button"}
125
+ data-active={active ? "true" : "false"}
106
126
  >
107
127
  <Icon class="h-4 w-4 flex-shrink-0" />
108
128
  </a>
@@ -118,12 +138,17 @@
118
138
  href={finalHref}
119
139
  target={isExternal ? '_blank' : undefined}
120
140
  rel={isExternal ? 'noopener noreferrer' : undefined}
121
- class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-all duration-200
141
+ class="relative flex items-center gap-3 rounded-xl px-3 py-2 text-sm font-medium transition-all duration-200
122
142
  {active
123
- ? 'bg-card text-sidebar-primary shadow-sm ring-1 ring-foreground/5 font-semibold before:absolute before:left-0 before:top-2 before:bottom-2 before:w-[3px] before:bg-primary before:rounded-r-md overflow-hidden relative'
124
- : 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
143
+ ? 'bg-primary/10 text-primary font-semibold shadow-sm shadow-primary/10 ring-1 ring-primary/15'
144
+ : 'text-sidebar-foreground/68 hover:bg-sidebar-accent/65 hover:text-sidebar-foreground'}"
125
145
  style="padding-left: {12 + depth * 12}px"
146
+ data-sidebar={depth === 0 ? "menu-button" : "menu-sub-button"}
147
+ data-active={active ? "true" : "false"}
126
148
  >
149
+ {#if active && !collapsed}
150
+ <div class="absolute left-0 top-1/2 -translate-y-1/2 h-5 w-1 rounded-r-full bg-primary"></div>
151
+ {/if}
127
152
  <Icon class="h-4 w-4 flex-shrink-0" />
128
153
  <span class="flex-1">{label}</span>
129
154
  {#if isExternal}
@@ -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>
@@ -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;