@happyvertical/smrt-users 0.30.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 (150) hide show
  1. package/AGENTS.md +85 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +459 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/chunks/TerminalAuthService-DoAMQ_yn.js +5118 -0
  8. package/dist/chunks/TerminalAuthService-DoAMQ_yn.js.map +1 -0
  9. package/dist/chunks/index-DkoYIvIu.js +169 -0
  10. package/dist/chunks/index-DkoYIvIu.js.map +1 -0
  11. package/dist/collections/CliAuthRequestCollection.d.ts +19 -0
  12. package/dist/collections/CliAuthRequestCollection.d.ts.map +1 -0
  13. package/dist/collections/GroupCollection.d.ts +17 -0
  14. package/dist/collections/GroupCollection.d.ts.map +1 -0
  15. package/dist/collections/GroupMemberCollection.d.ts +43 -0
  16. package/dist/collections/GroupMemberCollection.d.ts.map +1 -0
  17. package/dist/collections/GroupRoleCollection.d.ts +33 -0
  18. package/dist/collections/GroupRoleCollection.d.ts.map +1 -0
  19. package/dist/collections/MagicLinkTokenCollection.d.ts +26 -0
  20. package/dist/collections/MagicLinkTokenCollection.d.ts.map +1 -0
  21. package/dist/collections/MembershipCollection.d.ts +38 -0
  22. package/dist/collections/MembershipCollection.d.ts.map +1 -0
  23. package/dist/collections/MembershipOverrideCollection.d.ts +55 -0
  24. package/dist/collections/MembershipOverrideCollection.d.ts.map +1 -0
  25. package/dist/collections/PermissionCollection.d.ts +34 -0
  26. package/dist/collections/PermissionCollection.d.ts.map +1 -0
  27. package/dist/collections/RoleCollection.d.ts +29 -0
  28. package/dist/collections/RoleCollection.d.ts.map +1 -0
  29. package/dist/collections/RolePermissionCollection.d.ts +33 -0
  30. package/dist/collections/RolePermissionCollection.d.ts.map +1 -0
  31. package/dist/collections/SessionCollection.d.ts +82 -0
  32. package/dist/collections/SessionCollection.d.ts.map +1 -0
  33. package/dist/collections/TenantCollection.d.ts +119 -0
  34. package/dist/collections/TenantCollection.d.ts.map +1 -0
  35. package/dist/collections/TenantPermissionOverrideCollection.d.ts +111 -0
  36. package/dist/collections/TenantPermissionOverrideCollection.d.ts.map +1 -0
  37. package/dist/collections/UserCollection.d.ts +116 -0
  38. package/dist/collections/UserCollection.d.ts.map +1 -0
  39. package/dist/collections/index.d.ts +19 -0
  40. package/dist/collections/index.d.ts.map +1 -0
  41. package/dist/index.d.ts +5 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +1482 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/manifest.json +5216 -0
  46. package/dist/models/CliAuthRequest.d.ts +25 -0
  47. package/dist/models/CliAuthRequest.d.ts.map +1 -0
  48. package/dist/models/Group.d.ts +34 -0
  49. package/dist/models/Group.d.ts.map +1 -0
  50. package/dist/models/GroupMember.d.ts +29 -0
  51. package/dist/models/GroupMember.d.ts.map +1 -0
  52. package/dist/models/GroupRole.d.ts +29 -0
  53. package/dist/models/GroupRole.d.ts.map +1 -0
  54. package/dist/models/MagicLinkToken.d.ts +22 -0
  55. package/dist/models/MagicLinkToken.d.ts.map +1 -0
  56. package/dist/models/Membership.d.ts +48 -0
  57. package/dist/models/Membership.d.ts.map +1 -0
  58. package/dist/models/MembershipOverride.d.ts +50 -0
  59. package/dist/models/MembershipOverride.d.ts.map +1 -0
  60. package/dist/models/Permission.d.ts +79 -0
  61. package/dist/models/Permission.d.ts.map +1 -0
  62. package/dist/models/Role.d.ts +67 -0
  63. package/dist/models/Role.d.ts.map +1 -0
  64. package/dist/models/RolePermission.d.ts +29 -0
  65. package/dist/models/RolePermission.d.ts.map +1 -0
  66. package/dist/models/Session.d.ts +105 -0
  67. package/dist/models/Session.d.ts.map +1 -0
  68. package/dist/models/Tenant.d.ts +138 -0
  69. package/dist/models/Tenant.d.ts.map +1 -0
  70. package/dist/models/TenantPermissionOverride.d.ts +74 -0
  71. package/dist/models/TenantPermissionOverride.d.ts.map +1 -0
  72. package/dist/models/User.d.ts +72 -0
  73. package/dist/models/User.d.ts.map +1 -0
  74. package/dist/models/index.d.ts +19 -0
  75. package/dist/models/index.d.ts.map +1 -0
  76. package/dist/playground.d.ts +2 -0
  77. package/dist/playground.d.ts.map +1 -0
  78. package/dist/playground.js +139 -0
  79. package/dist/playground.js.map +1 -0
  80. package/dist/services/MagicLinkService.d.ts +84 -0
  81. package/dist/services/MagicLinkService.d.ts.map +1 -0
  82. package/dist/services/OidcLoginService.d.ts +134 -0
  83. package/dist/services/OidcLoginService.d.ts.map +1 -0
  84. package/dist/services/PermissionCatalogService.d.ts +62 -0
  85. package/dist/services/PermissionCatalogService.d.ts.map +1 -0
  86. package/dist/services/PermissionResolver.d.ts +150 -0
  87. package/dist/services/PermissionResolver.d.ts.map +1 -0
  88. package/dist/services/PostgresPermissionPolicies.d.ts +29 -0
  89. package/dist/services/PostgresPermissionPolicies.d.ts.map +1 -0
  90. package/dist/services/SessionPermissionContext.d.ts +43 -0
  91. package/dist/services/SessionPermissionContext.d.ts.map +1 -0
  92. package/dist/services/SessionService.d.ts +139 -0
  93. package/dist/services/SessionService.d.ts.map +1 -0
  94. package/dist/services/TenantService.d.ts +135 -0
  95. package/dist/services/TenantService.d.ts.map +1 -0
  96. package/dist/services/TerminalAuthService.d.ts +189 -0
  97. package/dist/services/TerminalAuthService.d.ts.map +1 -0
  98. package/dist/services/index.d.ts +14 -0
  99. package/dist/services/index.d.ts.map +1 -0
  100. package/dist/smrt-knowledge.json +2744 -0
  101. package/dist/svelte/components/InviteUserModal.svelte +351 -0
  102. package/dist/svelte/components/InviteUserModal.svelte.d.ts +17 -0
  103. package/dist/svelte/components/InviteUserModal.svelte.d.ts.map +1 -0
  104. package/dist/svelte/components/UserAvatar.svelte +105 -0
  105. package/dist/svelte/components/UserAvatar.svelte.d.ts +10 -0
  106. package/dist/svelte/components/UserAvatar.svelte.d.ts.map +1 -0
  107. package/dist/svelte/components/UserCard.svelte +179 -0
  108. package/dist/svelte/components/UserCard.svelte.d.ts +18 -0
  109. package/dist/svelte/components/UserCard.svelte.d.ts.map +1 -0
  110. package/dist/svelte/components/UserForm.svelte +194 -0
  111. package/dist/svelte/components/UserForm.svelte.d.ts +18 -0
  112. package/dist/svelte/components/UserForm.svelte.d.ts.map +1 -0
  113. package/dist/svelte/components/UserList.svelte +107 -0
  114. package/dist/svelte/components/UserList.svelte.d.ts +20 -0
  115. package/dist/svelte/components/UserList.svelte.d.ts.map +1 -0
  116. package/dist/svelte/components/UserMenu.svelte +326 -0
  117. package/dist/svelte/components/UserMenu.svelte.d.ts +33 -0
  118. package/dist/svelte/components/UserMenu.svelte.d.ts.map +1 -0
  119. package/dist/svelte/components/__tests__/InviteUserModal.test.js +54 -0
  120. package/dist/svelte/components/__tests__/UserAvatar.test.js +31 -0
  121. package/dist/svelte/components/__tests__/UserCard.test.js +39 -0
  122. package/dist/svelte/components/__tests__/UserForm.test.js +50 -0
  123. package/dist/svelte/components/__tests__/UserList.test.js +48 -0
  124. package/dist/svelte/components/__tests__/UserMenu.test.js +38 -0
  125. package/dist/svelte/i18n.d.ts +15 -0
  126. package/dist/svelte/i18n.d.ts.map +1 -0
  127. package/dist/svelte/i18n.js +15 -0
  128. package/dist/svelte/index.d.ts +23 -0
  129. package/dist/svelte/index.d.ts.map +1 -0
  130. package/dist/svelte/index.js +27 -0
  131. package/dist/svelte/playground.d.ts +151 -0
  132. package/dist/svelte/playground.d.ts.map +1 -0
  133. package/dist/svelte/playground.js +134 -0
  134. package/dist/sveltekit/index.d.ts +379 -0
  135. package/dist/sveltekit/index.d.ts.map +1 -0
  136. package/dist/sveltekit/resource-list-handler.d.ts +127 -0
  137. package/dist/sveltekit/resource-list-handler.d.ts.map +1 -0
  138. package/dist/sveltekit/types.d.ts +31 -0
  139. package/dist/sveltekit/types.d.ts.map +1 -0
  140. package/dist/sveltekit.d.ts +2 -0
  141. package/dist/sveltekit.d.ts.map +1 -0
  142. package/dist/sveltekit.js +978 -0
  143. package/dist/sveltekit.js.map +1 -0
  144. package/dist/types/index.d.ts +61 -0
  145. package/dist/types/index.d.ts.map +1 -0
  146. package/dist/ui.d.ts +10 -0
  147. package/dist/ui.d.ts.map +1 -0
  148. package/dist/ui.js +75 -0
  149. package/dist/ui.js.map +1 -0
  150. package/package.json +97 -0
@@ -0,0 +1,351 @@
1
+ <script lang="ts">
2
+ import { RoleSelector } from '@happyvertical/smrt-ui';
3
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
4
+ import type { Role, Tenant } from '@happyvertical/smrt-users';
5
+ import { M } from '../i18n.js';
6
+
7
+ const { t } = useI18n();
8
+
9
+ export interface Props {
10
+ open: boolean;
11
+ tenant: Tenant;
12
+ roles: Role[];
13
+ onsubmit: (data: {
14
+ email: string;
15
+ roleId: string;
16
+ sendEmail: boolean;
17
+ }) => void;
18
+ onclose: () => void;
19
+ loading?: boolean;
20
+ }
21
+
22
+ const {
23
+ open,
24
+ tenant,
25
+ roles,
26
+ onsubmit,
27
+ onclose,
28
+ loading = false,
29
+ }: Props = $props();
30
+
31
+ let email = $state('');
32
+ let roleId = $state('');
33
+ let sendEmail = $state(true);
34
+ let error = $state('');
35
+
36
+ // Set default role to 'member' if available
37
+ $effect(() => {
38
+ if (roles.length > 0 && !roleId) {
39
+ const memberRole = roles.find((r) => r.slug === 'member');
40
+ roleId = memberRole?.id ?? roles[0].id ?? '';
41
+ }
42
+ });
43
+
44
+ function handleSubmit(e: Event) {
45
+ e.preventDefault();
46
+ error = '';
47
+
48
+ if (!email) {
49
+ error = 'Email is required';
50
+ return;
51
+ }
52
+
53
+ if (!roleId) {
54
+ error = 'Please select a role';
55
+ return;
56
+ }
57
+
58
+ onsubmit({ email, roleId, sendEmail });
59
+ }
60
+
61
+ function handleClose() {
62
+ email = '';
63
+ roleId = '';
64
+ sendEmail = true;
65
+ error = '';
66
+ onclose();
67
+ }
68
+
69
+ function handleBackdrop(e: MouseEvent) {
70
+ if (e.target === e.currentTarget) {
71
+ handleClose();
72
+ }
73
+ }
74
+
75
+ function handleKeydown(e: KeyboardEvent) {
76
+ if (e.key === 'Escape' && open) {
77
+ handleClose();
78
+ }
79
+ }
80
+ </script>
81
+
82
+ <svelte:window onkeydown={handleKeydown} />
83
+
84
+ {#if open}
85
+ <div class="modal-backdrop">
86
+ <button
87
+ type="button"
88
+ class="modal-overlay"
89
+ aria-label={t(M['users.invite_user_modal.close_invite_dialog'])}
90
+ onclick={handleClose}
91
+ ></button>
92
+ <div class="modal" role="dialog" aria-modal="true" tabindex="-1">
93
+ <div class="header">
94
+ <h2>{t(M['users.invite_user_modal.title'], { tenantName: tenant.name })}</h2>
95
+ <button type="button" class="close-btn" onclick={handleClose} aria-label={t(M['users.invite_user_modal.close'])}>
96
+ <svg viewBox="0 0 20 20" fill="currentColor">
97
+ <path
98
+ d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
99
+ />
100
+ </svg>
101
+ </button>
102
+ </div>
103
+
104
+ <form onsubmit={handleSubmit}>
105
+ <div class="body">
106
+ {#if error}
107
+ <div class="error">{error}</div>
108
+ {/if}
109
+
110
+ <div class="field">
111
+ <label for="invite-email">{t(M['users.invite_user_modal.email_address'])}</label>
112
+ <input
113
+ id="invite-email"
114
+ type="email"
115
+ bind:value={email}
116
+ placeholder={t(M['users.invite_user_modal.email_placeholder'])}
117
+ disabled={loading}
118
+ required
119
+ />
120
+ </div>
121
+
122
+ <div class="field">
123
+ <label for="invite-role">Role</label>
124
+ <RoleSelector
125
+ {roles}
126
+ value={roleId}
127
+ onchange={(id: string) => (roleId = id)}
128
+ disabled={loading}
129
+ showDescription
130
+ />
131
+ </div>
132
+
133
+ <div class="checkbox-field">
134
+ <input id="send-email" type="checkbox" bind:checked={sendEmail} disabled={loading} />
135
+ <label for="send-email">{t(M['users.invite_user_modal.send_invitation_email'])}</label>
136
+ </div>
137
+
138
+ {#if !sendEmail}
139
+ <div class="hint">
140
+ {t(M['users.invite_user_modal.pending_hint'])}
141
+ </div>
142
+ {/if}
143
+ </div>
144
+
145
+ <div class="footer">
146
+ <button type="button" class="btn-secondary" onclick={handleClose} disabled={loading}>
147
+ Cancel
148
+ </button>
149
+ <button type="submit" class="btn-primary" disabled={loading}>
150
+ {#if loading}
151
+ {t(M['users.invite_user_modal.sending'])}
152
+ {:else}
153
+ {t(M['users.invite_user_modal.send_invite'])}
154
+ {/if}
155
+ </button>
156
+ </div>
157
+ </form>
158
+ </div>
159
+ </div>
160
+ {/if}
161
+
162
+ <style>
163
+ .modal-backdrop {
164
+ position: fixed;
165
+ inset: 0;
166
+ display: flex;
167
+ align-items: center;
168
+ justify-content: center;
169
+ padding: 1rem;
170
+ z-index: var(--smrt-z-index-dialog, 1300);
171
+ }
172
+
173
+ .modal-overlay {
174
+ position: absolute;
175
+ inset: 0;
176
+ border: none;
177
+ background: var(--smrt-color-scrim, rgba(0, 0, 0, 0.5));
178
+ cursor: pointer;
179
+ }
180
+
181
+ .modal {
182
+ position: relative;
183
+ background: var(--smrt-color-surface, white);
184
+ border-radius: var(--smrt-radius-large, 0.5rem);
185
+ box-shadow: var(--smrt-elevation-3, 0 20px 25px -5px rgba(0, 0, 0, 0.1));
186
+ width: 100%;
187
+ max-width: 28rem;
188
+ max-height: 90vh;
189
+ overflow: hidden;
190
+ z-index: 1;
191
+ }
192
+
193
+ .header {
194
+ display: flex;
195
+ align-items: center;
196
+ justify-content: space-between;
197
+ padding: var(--smrt-spacing-md, 1rem) var(--smrt-spacing-lg, 1.5rem);
198
+ border-bottom: 1px solid var(--smrt-color-outline-variant, #c4c6cf);
199
+ }
200
+
201
+ h2 {
202
+ margin: 0;
203
+ font: var(--smrt-typography-title-large-font, 600 1.125rem / 1.25 sans-serif);
204
+ color: var(--smrt-color-on-surface, #1a1c1e);
205
+ }
206
+
207
+ .close-btn {
208
+ display: flex;
209
+ align-items: center;
210
+ justify-content: center;
211
+ width: 2rem;
212
+ height: 2rem;
213
+ background: none;
214
+ border: none;
215
+ border-radius: var(--smrt-radius-medium, 0.5rem);
216
+ cursor: pointer;
217
+ color: var(--smrt-color-on-surface-variant, #43474e);
218
+ transition: background-color var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease);
219
+ }
220
+
221
+ .close-btn:hover {
222
+ background: var(--smrt-color-surface-container, #f3f4f6);
223
+ color: var(--smrt-color-on-surface, #1a1c1e);
224
+ }
225
+
226
+ .close-btn svg {
227
+ width: 1.25rem;
228
+ height: 1.25rem;
229
+ }
230
+
231
+ .body {
232
+ padding: var(--smrt-spacing-lg, 1.5rem);
233
+ display: flex;
234
+ flex-direction: column;
235
+ gap: var(--smrt-spacing-md, 1rem);
236
+ }
237
+
238
+ .error {
239
+ padding: var(--smrt-spacing-sm, 0.75rem);
240
+ background: var(--smrt-color-error-container, #ffdad6);
241
+ border: 1px solid var(--smrt-color-error, #ba1a1a);
242
+ border-radius: var(--smrt-radius-medium, 0.5rem);
243
+ color: var(--smrt-color-on-error-container, #410002);
244
+ font: var(--smrt-typography-body-medium-font, 0.875rem / 1.25 sans-serif);
245
+ }
246
+
247
+ .field {
248
+ display: flex;
249
+ flex-direction: column;
250
+ gap: 0.375rem;
251
+ }
252
+
253
+ label {
254
+ font: var(--smrt-typography-body-medium-font, 500 0.875rem / 1.25 sans-serif);
255
+ color: var(--smrt-color-on-surface-variant, #43474e);
256
+ }
257
+
258
+ input[type='email'] {
259
+ padding: var(--smrt-spacing-sm, 0.5rem) var(--smrt-spacing-md, 0.75rem);
260
+ border: 1px solid var(--smrt-color-outline-variant, #c4c6cf);
261
+ border-radius: var(--smrt-radius-medium, 0.5rem);
262
+ font: var(--smrt-typography-body-medium-font, 0.875rem / 1.25 sans-serif);
263
+ transition: border-color var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease);
264
+ }
265
+
266
+ input[type='email']:focus {
267
+ outline: none;
268
+ border-color: var(--smrt-color-primary, #005ac1);
269
+ box-shadow: 0 0 0 3px var(--smrt-color-primary-container, rgba(0, 90, 193, 0.1));
270
+ }
271
+
272
+ input[type='email']:disabled {
273
+ background: var(--smrt-color-surface-container, #f3f4f6);
274
+ cursor: not-allowed;
275
+ }
276
+
277
+ .checkbox-field {
278
+ display: flex;
279
+ align-items: center;
280
+ gap: var(--smrt-spacing-sm, 0.5rem);
281
+ }
282
+
283
+ .checkbox-field input {
284
+ width: 1rem;
285
+ height: 1rem;
286
+ accent-color: var(--smrt-color-primary, #005ac1);
287
+ }
288
+
289
+ .checkbox-field label {
290
+ font-weight: var(--smrt-typography-weight-normal, 400);
291
+ }
292
+
293
+ .hint {
294
+ font: var(--smrt-typography-body-small-font, 0.75rem / 1.25 sans-serif);
295
+ color: var(--smrt-color-on-surface-variant, #43474e);
296
+ padding: var(--smrt-spacing-sm, 0.5rem);
297
+ background: var(--smrt-color-surface-container-low, #f9fafb);
298
+ border-radius: var(--smrt-radius-small, 0.25rem);
299
+ }
300
+
301
+ .footer {
302
+ display: flex;
303
+ justify-content: flex-end;
304
+ gap: var(--smrt-spacing-sm, 0.75rem);
305
+ padding: var(--smrt-spacing-md, 1rem) var(--smrt-spacing-lg, 1.5rem);
306
+ background: var(--smrt-color-surface-container-low, #f9fafb);
307
+ border-top: 1px solid var(--smrt-color-outline-variant, #c4c6cf);
308
+ }
309
+
310
+ button {
311
+ padding: var(--smrt-spacing-sm, 0.5rem) var(--smrt-spacing-md, 1rem);
312
+ border-radius: var(--smrt-radius-medium, 0.5rem);
313
+ font: var(--smrt-typography-label-large-font, 500 0.875rem / 1.25 sans-serif);
314
+ cursor: pointer;
315
+ transition: all var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease);
316
+ }
317
+
318
+ button:disabled {
319
+ opacity: 0.6;
320
+ cursor: not-allowed;
321
+ }
322
+
323
+ .btn-primary {
324
+ background: var(--smrt-color-primary, #005ac1);
325
+ color: var(--smrt-color-on-primary, #ffffff);
326
+ border: none;
327
+ }
328
+
329
+ .btn-primary:hover:not(:disabled) {
330
+ background: var(--smrt-color-primary-container, #005ac1);
331
+ opacity: 0.9;
332
+ }
333
+
334
+ .btn-secondary {
335
+ background: var(--smrt-color-surface, white);
336
+ color: var(--smrt-color-on-surface-variant, #43474e);
337
+ border: 1px solid var(--smrt-color-outline-variant, #c4c6cf);
338
+ }
339
+
340
+ .btn-secondary:hover:not(:disabled) {
341
+ background: var(--smrt-color-surface-container-low, #f9fafb);
342
+ }
343
+
344
+ @media (prefers-reduced-motion: reduce) {
345
+ button,
346
+ input[type='email'],
347
+ .close-btn {
348
+ transition: none;
349
+ }
350
+ }
351
+ </style>
@@ -0,0 +1,17 @@
1
+ import type { Role, Tenant } from '@happyvertical/smrt-users';
2
+ export interface Props {
3
+ open: boolean;
4
+ tenant: Tenant;
5
+ roles: Role[];
6
+ onsubmit: (data: {
7
+ email: string;
8
+ roleId: string;
9
+ sendEmail: boolean;
10
+ }) => void;
11
+ onclose: () => void;
12
+ loading?: boolean;
13
+ }
14
+ declare const InviteUserModal: import("svelte").Component<Props, {}, "">;
15
+ type InviteUserModal = ReturnType<typeof InviteUserModal>;
16
+ export default InviteUserModal;
17
+ //# sourceMappingURL=InviteUserModal.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InviteUserModal.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InviteUserModal.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AAI9D,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,QAAQ,EAAE,CAAC,IAAI,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,OAAO,CAAC;KACpB,KAAK,IAAI,CAAC;IACX,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAsID,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
@@ -0,0 +1,105 @@
1
+ <script lang="ts">
2
+ import type { Profile } from '@happyvertical/smrt-profiles';
3
+
4
+ export interface Props {
5
+ profile: Profile;
6
+ size?: 'sm' | 'md' | 'lg' | 'xl';
7
+ showName?: boolean;
8
+ }
9
+
10
+ const { profile, size = 'md', showName = false }: Props = $props();
11
+
12
+ function getInitials(name: string): string {
13
+ return name
14
+ .split(' ')
15
+ .map((part) => part[0])
16
+ .join('')
17
+ .toUpperCase()
18
+ .slice(0, 2);
19
+ }
20
+
21
+ // Deterministic per-name avatar color (identicon-style): a fixed decorative
22
+ // palette hashed by name for at-a-glance user differentiation. These are
23
+ // intentionally NOT --smrt-color-* tokens — a variety palette, not themeable
24
+ // semantic roles (cf. the channel-brand avatars in smrt-messages). Applied via
25
+ // an inline style binding, so the color-literal ratchet does not scan them.
26
+ function getColor(name: string): string {
27
+ const colors = [
28
+ '#ef4444',
29
+ '#f97316',
30
+ '#f59e0b',
31
+ '#84cc16',
32
+ '#22c55e',
33
+ '#14b8a6',
34
+ '#06b6d4',
35
+ '#0ea5e9',
36
+ '#3b82f6',
37
+ '#6366f1',
38
+ '#8b5cf6',
39
+ '#a855f7',
40
+ '#d946ef',
41
+ '#ec4899',
42
+ '#f43f5e',
43
+ ];
44
+ let hash = 0;
45
+ for (let i = 0; i < name.length; i++) {
46
+ hash = name.charCodeAt(i) + ((hash << 5) - hash);
47
+ }
48
+ return colors[Math.abs(hash) % colors.length];
49
+ }
50
+ </script>
51
+
52
+ <div class="user-avatar" class:sm={size === 'sm'} class:lg={size === 'lg'} class:xl={size === 'xl'}>
53
+ <span class="avatar" style:background-color={getColor(profile.name ?? '')}>
54
+ {getInitials(profile.name ?? 'U')}
55
+ </span>
56
+ {#if showName}
57
+ <span class="name">{profile.name}</span>
58
+ {/if}
59
+ </div>
60
+
61
+ <style>
62
+ .user-avatar {
63
+ display: inline-flex;
64
+ align-items: center;
65
+ gap: var(--smrt-spacing-sm, 0.5rem);
66
+ }
67
+
68
+ .avatar {
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ width: 2.5rem;
73
+ height: 2.5rem;
74
+ border-radius: var(--smrt-radius-full, 9999px);
75
+ color: var(--smrt-color-on-primary, white);
76
+ font: var(--smrt-typography-label-large-font, 600 0.875rem / 1.25 sans-serif);
77
+ }
78
+
79
+ .sm .avatar {
80
+ width: 2rem;
81
+ height: 2rem;
82
+ font: var(--smrt-typography-label-medium-font, 0.75rem / 1 sans-serif);
83
+ }
84
+
85
+ .lg .avatar {
86
+ width: 3rem;
87
+ height: 3rem;
88
+ font: var(--smrt-typography-title-medium-font, 1rem / 1.5 sans-serif);
89
+ }
90
+
91
+ .xl .avatar {
92
+ width: 4rem;
93
+ height: 4rem;
94
+ font: var(--smrt-typography-title-large-font, 1.25rem / 1.25 sans-serif);
95
+ }
96
+
97
+ .name {
98
+ font: var(--smrt-typography-body-medium-font, 500 0.875rem / 1.25 sans-serif);
99
+ }
100
+
101
+ .lg .name,
102
+ .xl .name {
103
+ font: var(--smrt-typography-body-large-font, 1rem / 1.5 sans-serif);
104
+ }
105
+ </style>
@@ -0,0 +1,10 @@
1
+ import type { Profile } from '@happyvertical/smrt-profiles';
2
+ export interface Props {
3
+ profile: Profile;
4
+ size?: 'sm' | 'md' | 'lg' | 'xl';
5
+ showName?: boolean;
6
+ }
7
+ declare const UserAvatar: import("svelte").Component<Props, {}, "">;
8
+ type UserAvatar = ReturnType<typeof UserAvatar>;
9
+ export default UserAvatar;
10
+ //# sourceMappingURL=UserAvatar.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserAvatar.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/UserAvatar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AAG5D,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA4DD,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,179 @@
1
+ <script lang="ts">
2
+ /**
3
+ * UserCard - Compact user information display
4
+ * refactored for Material 3
5
+ */
6
+ import type { Profile } from '@happyvertical/smrt-profiles';
7
+ import { ripple } from '@happyvertical/smrt-ui';
8
+ import type { User } from '@happyvertical/smrt-users';
9
+ import UserAvatar from './UserAvatar.svelte';
10
+
11
+ export interface Props {
12
+ user: User;
13
+ profile: Profile;
14
+ role?: string;
15
+ status?: string;
16
+ onclick?: () => void;
17
+ selected?: boolean;
18
+ }
19
+
20
+ const {
21
+ user,
22
+ profile,
23
+ role,
24
+ status,
25
+ onclick,
26
+ selected = false,
27
+ }: Props = $props();
28
+
29
+ const statusClass = $derived.by(() => {
30
+ switch (status) {
31
+ case 'active':
32
+ return 'status-active';
33
+ case 'pending':
34
+ return 'status-pending';
35
+ case 'suspended':
36
+ return 'status-error';
37
+ case 'deactivated':
38
+ return 'status-disabled';
39
+ default:
40
+ return '';
41
+ }
42
+ });
43
+ </script>
44
+
45
+ <button
46
+ type="button"
47
+ class="user-card"
48
+ class:selected
49
+ class:clickable={!!onclick}
50
+ onclick={onclick}
51
+ disabled={!onclick}
52
+ use:ripple
53
+ >
54
+ <UserAvatar {profile} size="md" />
55
+
56
+ <div class="info">
57
+ <div class="name">{profile.name}</div>
58
+ <div class="email">{user.email}</div>
59
+ </div>
60
+
61
+ <div class="meta">
62
+ {#if role}
63
+ <span class="role">{role}</span>
64
+ {/if}
65
+ {#if status}
66
+ <span class="status {statusClass}">{status}</span>
67
+ {/if}
68
+ </div>
69
+ </button>
70
+
71
+ <style>
72
+ .user-card {
73
+ display: flex;
74
+ align-items: center;
75
+ gap: var(--smrt-spacing-4, 16px);
76
+ padding: var(--smrt-spacing-3, 12px) var(--smrt-spacing-4, 16px);
77
+ background-color: var(--smrt-color-surface-container-low);
78
+ border-radius: var(--smrt-radius-lg, 12px);
79
+ width: 100%;
80
+ text-align: left;
81
+ cursor: default;
82
+ border: none;
83
+ transition: all 200ms cubic-bezier(0.2, 0, 0, 1);
84
+ position: relative;
85
+ overflow: hidden;
86
+ color: var(--smrt-color-on-surface);
87
+ box-shadow: var(--smrt-elevation-1);
88
+ }
89
+
90
+ .user-card.clickable {
91
+ cursor: pointer;
92
+ }
93
+
94
+ .user-card.clickable:hover {
95
+ background-color: var(--smrt-color-surface-container-high);
96
+ box-shadow: var(--smrt-elevation-2);
97
+ }
98
+
99
+ .user-card.selected {
100
+ background-color: var(--smrt-color-secondary-container);
101
+ color: var(--smrt-color-on-secondary-container);
102
+ }
103
+
104
+ .info {
105
+ flex: 1;
106
+ min-width: 0;
107
+ }
108
+
109
+ .name {
110
+ font: var(--smrt-typography-title-small-font);
111
+ font-weight: var(--smrt-typography-weight-semibold, 600);
112
+ white-space: nowrap;
113
+ overflow: hidden;
114
+ text-overflow: ellipsis;
115
+ }
116
+
117
+ .email {
118
+ font: var(--smrt-typography-body-small-font);
119
+ color: var(--smrt-color-on-surface-variant);
120
+ white-space: nowrap;
121
+ overflow: hidden;
122
+ text-overflow: ellipsis;
123
+ }
124
+
125
+ .selected .email {
126
+ color: var(--smrt-color-on-secondary-container);
127
+ opacity: 0.8;
128
+ }
129
+
130
+ .meta {
131
+ display: flex;
132
+ align-items: center;
133
+ gap: var(--smrt-spacing-2, 8px);
134
+ flex-shrink: 0;
135
+ }
136
+
137
+ .role {
138
+ font: var(--smrt-typography-label-small-font);
139
+ padding: 0 var(--smrt-spacing-2, 8px);
140
+ height: 20px;
141
+ display: inline-flex;
142
+ align-items: center;
143
+ background-color: var(--smrt-color-surface-container-highest);
144
+ color: var(--smrt-color-on-surface-variant);
145
+ border-radius: var(--smrt-radius-lg, 12px);
146
+ text-transform: capitalize;
147
+ }
148
+
149
+ .status {
150
+ font: var(--smrt-typography-label-small-font);
151
+ padding: 0 var(--smrt-spacing-2, 8px);
152
+ height: 20px;
153
+ display: inline-flex;
154
+ align-items: center;
155
+ border-radius: var(--smrt-radius-lg, 12px);
156
+ text-transform: capitalize;
157
+ font-weight: var(--smrt-typography-weight-semibold, 600);
158
+ }
159
+
160
+ .status-active {
161
+ background-color: var(--smrt-color-primary-container);
162
+ color: var(--smrt-color-on-primary-container);
163
+ }
164
+
165
+ .status-pending {
166
+ background-color: var(--smrt-color-secondary-container);
167
+ color: var(--smrt-color-on-secondary-container);
168
+ }
169
+
170
+ .status-error {
171
+ background-color: var(--smrt-color-error-container);
172
+ color: var(--smrt-color-on-error-container);
173
+ }
174
+
175
+ .status-disabled {
176
+ background-color: var(--smrt-color-surface-variant);
177
+ color: var(--smrt-color-on-surface-variant);
178
+ }
179
+ </style>
@@ -0,0 +1,18 @@
1
+ /**
2
+ * UserCard - Compact user information display
3
+ * refactored for Material 3
4
+ */
5
+ import type { Profile } from '@happyvertical/smrt-profiles';
6
+ import type { User } from '@happyvertical/smrt-users';
7
+ export interface Props {
8
+ user: User;
9
+ profile: Profile;
10
+ role?: string;
11
+ status?: string;
12
+ onclick?: () => void;
13
+ selected?: boolean;
14
+ }
15
+ declare const UserCard: import("svelte").Component<Props, {}, "">;
16
+ type UserCard = ReturnType<typeof UserCard>;
17
+ export default UserCard;
18
+ //# sourceMappingURL=UserCard.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserCard.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/UserCard.svelte.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AAE5D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AAItD,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAuDD,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}