@makolabs/ripple 3.0.9 → 3.0.11

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 (30) hide show
  1. package/dist/drawer/Drawer.svelte +33 -12
  2. package/dist/drawer/drawer-types.d.ts +13 -5
  3. package/dist/drawer/drawer.d.ts +45 -0
  4. package/dist/drawer/drawer.js +14 -5
  5. package/dist/funcs/mock-user-management.d.ts +40 -0
  6. package/dist/funcs/mock-user-management.js +85 -1
  7. package/dist/index.d.ts +5 -2
  8. package/dist/index.js +4 -1
  9. package/dist/modal/ConfirmDialog.svelte +6 -9
  10. package/dist/modal/Modal.svelte +18 -14
  11. package/dist/modal/modal-types.d.ts +9 -5
  12. package/dist/modal/modal.d.ts +42 -0
  13. package/dist/modal/modal.js +18 -8
  14. package/dist/user-management/ApiKeyField.svelte +165 -0
  15. package/dist/user-management/ApiKeyField.svelte.d.ts +32 -0
  16. package/dist/user-management/RoleCard.svelte +73 -0
  17. package/dist/user-management/RoleCard.svelte.d.ts +16 -0
  18. package/dist/user-management/UserApproveModal.svelte +115 -0
  19. package/dist/user-management/UserApproveModal.svelte.d.ts +4 -0
  20. package/dist/user-management/UserIdentityCard.svelte +53 -0
  21. package/dist/user-management/UserIdentityCard.svelte.d.ts +11 -0
  22. package/dist/user-management/UserManagement.svelte +191 -20
  23. package/dist/user-management/UserModal.svelte +203 -439
  24. package/dist/user-management/UserTable.svelte +48 -55
  25. package/dist/user-management/UserViewModal.svelte +87 -221
  26. package/dist/user-management/UserViewModal.svelte.d.ts +1 -1
  27. package/dist/user-management/user-management-types.d.ts +52 -3
  28. package/package.json +1 -1
  29. package/dist/modal/ModalFooter.svelte +0 -35
  30. package/dist/modal/ModalFooter.svelte.d.ts +0 -11
@@ -1,5 +1,7 @@
1
1
  <script lang="ts">
2
2
  import {
3
+ Button,
4
+ Color,
3
5
  Table,
4
6
  type TableColumn,
5
7
  type User,
@@ -21,6 +23,8 @@
21
23
  onview,
22
24
  onedit,
23
25
  ondelete,
26
+ onapprove,
27
+ onreject,
24
28
  class: className = ''
25
29
  }: UserTableProps = $props();
26
30
 
@@ -82,45 +86,6 @@
82
86
  ];
83
87
  </script>
84
88
 
85
- {#snippet EyeIcon()}
86
- <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
87
- <path
88
- stroke-linecap="round"
89
- stroke-linejoin="round"
90
- stroke-width="2"
91
- d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
92
- ></path>
93
- <path
94
- stroke-linecap="round"
95
- stroke-linejoin="round"
96
- stroke-width="2"
97
- d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
98
- ></path>
99
- </svg>
100
- {/snippet}
101
-
102
- {#snippet EditIcon()}
103
- <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
104
- <path
105
- stroke-linecap="round"
106
- stroke-linejoin="round"
107
- stroke-width="2"
108
- d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
109
- ></path>
110
- </svg>
111
- {/snippet}
112
-
113
- {#snippet TrashIcon()}
114
- <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
115
- <path
116
- stroke-linecap="round"
117
- stroke-linejoin="round"
118
- stroke-width="2"
119
- d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
120
- ></path>
121
- </svg>
122
- {/snippet}
123
-
124
89
  {#snippet UserCell(user: User)}
125
90
  <div class="flex items-center">
126
91
  <div class="h-8 w-8 shrink-0">
@@ -173,36 +138,64 @@
173
138
  {/snippet}
174
139
 
175
140
  {#snippet ActionsCell(user: User)}
176
- <div class="flex items-center justify-end space-x-1">
141
+ <div class="flex items-center justify-end gap-2">
177
142
  {#if onview}
178
- <button
143
+ <Button
144
+ size="sm"
145
+ variant="outline"
179
146
  onclick={() => onview(user)}
180
- class="inline-flex rounded p-1 text-blue-600 transition-colors hover:bg-blue-50 hover:text-blue-900"
181
- title="View User"
182
147
  aria-label="View User"
148
+ testId="view-user-{user.id}"
183
149
  >
184
- {@render EyeIcon()}
185
- </button>
150
+ View
151
+ </Button>
186
152
  {/if}
187
153
  {#if onedit}
188
- <button
154
+ <Button
155
+ size="sm"
156
+ variant="outline"
157
+ color={Color.PRIMARY}
189
158
  onclick={() => onedit(user)}
190
- class="inline-flex rounded p-1 text-green-600 transition-colors hover:bg-green-50 hover:text-green-900"
191
- title="Edit User"
192
159
  aria-label="Edit User"
160
+ testId="edit-user-{user.id}"
193
161
  >
194
- {@render EditIcon()}
195
- </button>
162
+ Edit
163
+ </Button>
196
164
  {/if}
197
165
  {#if ondelete}
198
- <button
166
+ <Button
167
+ size="sm"
168
+ variant="outline"
169
+ color={Color.DANGER}
199
170
  onclick={() => ondelete(user.id)}
200
- class="text-danger-600 hover:bg-danger-50 hover:text-danger-900 inline-flex rounded p-1 transition-colors"
201
- title="Delete User"
202
171
  aria-label="Delete User"
172
+ testId="delete-user-{user.id}"
173
+ >
174
+ Delete
175
+ </Button>
176
+ {/if}
177
+ {#if onapprove}
178
+ <Button
179
+ size="sm"
180
+ color={Color.SUCCESS}
181
+ onclick={() => onapprove(user)}
182
+ aria-label="Approve User"
183
+ testId="approve-user-{user.id}"
184
+ >
185
+ Approve
186
+ </Button>
187
+ {/if}
188
+ {#if onreject}
189
+ <Button
190
+ size="sm"
191
+ variant="outline"
192
+ color={Color.DANGER}
193
+ onclick={() => onreject(user)}
194
+ aria-label="Reject User"
195
+ testId="reject-user-{user.id}"
203
196
  >
204
- {@render TrashIcon()}
205
- </button>
197
+ Reject
198
+ </Button>
206
199
  {/if}
207
200
  </div>
208
201
  {/snippet}
@@ -1,5 +1,9 @@
1
1
  <script lang="ts">
2
- import { Modal, Button, cn, type UserViewModalProps, getUserDisplayName } from '../index.js';
2
+ import { Modal, Button, Color, cn } from '../index.js';
3
+ import RoleCard from './RoleCard.svelte';
4
+ import ApiKeyField from './ApiKeyField.svelte';
5
+ import UserIdentityCard from './UserIdentityCard.svelte';
6
+ import type { Role, UserViewModalProps } from '../index.js';
3
7
  import { SvelteDate } from 'svelte/reactivity';
4
8
 
5
9
  let {
@@ -12,25 +16,17 @@
12
16
  class: className
13
17
  }: UserViewModalProps = $props();
14
18
 
15
- // Local state
16
- let showApiKey = $state(false);
17
19
  let regeneratingApiKey = $state(false);
18
20
  let apiKeyError = $state<string | null>(null);
19
21
 
20
22
  function handleClose() {
21
23
  open = false;
22
- showApiKey = false;
24
+ apiKeyError = null;
23
25
  if (onclose) onclose();
24
26
  }
25
27
 
26
28
  function handleEdit() {
27
- if (onedit && user) {
28
- onedit(user);
29
- }
30
- }
31
-
32
- function getModalTitle() {
33
- return getUserDisplayName(user);
29
+ if (onedit && user) onedit(user);
34
30
  }
35
31
 
36
32
  function formatDate(timestamp?: number) {
@@ -42,13 +38,8 @@
42
38
  });
43
39
  }
44
40
 
45
- function toggleApiKeyVisibility() {
46
- showApiKey = !showApiKey;
47
- }
48
-
49
41
  async function handleRegenerateApiKey() {
50
42
  if (!user?.id || !onregenerateapikey) return;
51
-
52
43
  try {
53
44
  regeneratingApiKey = true;
54
45
  apiKeyError = null;
@@ -61,7 +52,6 @@
61
52
  }
62
53
  }
63
54
 
64
- // Get API key from user's private_metadata
65
55
  const apiKey = $derived(
66
56
  user?.private_metadata &&
67
57
  typeof user.private_metadata === 'object' &&
@@ -70,233 +60,109 @@
70
60
  : ''
71
61
  );
72
62
 
73
- // Mask API key for display
74
- const maskedApiKey = $derived(apiKey ? '•'.repeat(Math.min(apiKey.length, 40)) : '');
75
-
76
- // Find the current role object
77
- const currentRole = $derived(
78
- user?.role && roles.length > 0 ? roles.find((r) => r.value === user.role) : null
63
+ const currentRole = $derived<Role | null>(
64
+ user?.role && roles.length > 0 ? (roles.find((r) => r.value === user.role) ?? null) : null
79
65
  );
80
66
  </script>
81
67
 
68
+ {#snippet ReadOnlyField(label: string, value: string | undefined)}
69
+ <div>
70
+ <p class="text-default-700 mb-1 text-sm font-medium">{label}</p>
71
+ <div
72
+ class="border-default-300 bg-default-50 text-default-900 rounded-lg border px-3 py-2 text-sm"
73
+ >
74
+ {value || '-'}
75
+ </div>
76
+ </div>
77
+ {/snippet}
78
+
82
79
  <Modal
83
80
  {open}
84
81
  onclose={handleClose}
85
- title={getModalTitle()}
86
- contentClass="max-w-4xl"
82
+ title="User details"
83
+ footerAlign="end"
84
+ contentClass="max-w-3xl"
87
85
  bodyClass="flex flex-col"
88
86
  class={cn(className)}
89
87
  >
90
- <div class="flex gap-6">
91
- <!-- Left Column: Profile Information -->
92
- <div class="min-w-0 flex-1 space-y-4">
93
- <div class="border-default-200 border-b pb-3">
94
- <h3 class="text-default-900 text-lg font-semibold">Profile Information</h3>
95
- </div>
96
-
97
- <!-- First Name -->
98
- <div>
99
- <p class="text-default-700 mb-1 text-sm font-medium">First Name</p>
100
- <div
101
- class="border-default-300 bg-default-50 text-default-900 rounded-lg border px-3 py-2 text-sm"
102
- >
103
- {user?.first_name || '-'}
104
- </div>
105
- </div>
106
-
107
- <!-- Last Name -->
108
- <div>
109
- <p class="text-default-700 mb-1 text-sm font-medium">Last Name</p>
110
- <div
111
- class="border-default-300 bg-default-50 text-default-900 rounded-lg border px-3 py-2 text-sm"
112
- >
113
- {user?.last_name || '-'}
114
- </div>
115
- </div>
116
-
117
- <!-- Email Address -->
118
- <div>
119
- <p class="text-default-700 mb-1 text-sm font-medium">Email Address</p>
120
- <div
121
- class="border-default-300 bg-default-50 text-default-900 rounded-lg border px-3 py-2 text-sm"
122
- >
123
- {user?.email_addresses?.[0]?.email_address || '-'}
124
- </div>
125
- </div>
126
-
127
- <!-- Mako API Key -->
128
- {#if apiKey || onregenerateapikey}
129
- <div>
130
- <div class="mb-2 flex items-center justify-between">
131
- <p class="text-default-700 text-sm font-medium">Mako API Key</p>
132
- {#if onregenerateapikey}
133
- <button
134
- type="button"
135
- onclick={handleRegenerateApiKey}
136
- disabled={regeneratingApiKey}
137
- class="disabled:text-default-400 inline-flex items-center gap-1 text-sm text-blue-600 transition-colors hover:text-blue-700 hover:underline disabled:cursor-not-allowed"
138
- aria-label="Regenerate API key"
139
- >
140
- <svg
141
- class="h-4 w-4 {regeneratingApiKey ? 'animate-spin' : ''}"
142
- fill="none"
143
- stroke="currentColor"
144
- viewBox="0 0 24 24"
145
- >
146
- <path
147
- stroke-linecap="round"
148
- stroke-linejoin="round"
149
- stroke-width="2"
150
- d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
151
- ></path>
152
- </svg>
153
- {apiKey ? 'Regenerate' : 'Generate'}
154
- </button>
155
- {/if}
156
- </div>
157
- <div class="relative flex-1">
158
- <div
159
- class="scrollbar-hide border-default-300 bg-default-50 text-default-900 w-full overflow-x-auto overflow-y-hidden rounded-lg border px-3 py-2 pr-12 font-mono text-sm"
160
- >
161
- <div class="whitespace-nowrap">
162
- {showApiKey ? apiKey || 'No API key' : apiKey ? maskedApiKey : 'No API key'}
163
- </div>
164
- </div>
165
- {#if apiKey}
166
- <button
167
- type="button"
168
- onclick={toggleApiKeyVisibility}
169
- class="bg-default-50 text-default-500 hover:text-default-700 absolute top-1/2 right-3 -translate-y-1/2 cursor-pointer"
170
- aria-label={showApiKey ? 'Hide API key' : 'Show API key'}
171
- >
172
- {#if showApiKey}
173
- <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
174
- <path
175
- stroke-linecap="round"
176
- stroke-linejoin="round"
177
- stroke-width="2"
178
- d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.29 3.29m0 0A9.966 9.966 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
179
- ></path>
180
- </svg>
181
- {:else}
182
- <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
183
- <path
184
- stroke-linecap="round"
185
- stroke-linejoin="round"
186
- stroke-width="2"
187
- d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
188
- ></path>
189
- <path
190
- stroke-linecap="round"
191
- stroke-linejoin="round"
192
- stroke-width="2"
193
- d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
194
- ></path>
195
- </svg>
196
- {/if}
197
- </button>
198
- {/if}
199
- </div>
200
- {#if apiKeyError}
201
- <p class="text-danger-500 mt-1 text-xs">{apiKeyError}</p>
202
- {:else}
203
- <p class="text-default-500 mt-1 text-xs">
204
- API keys are system-managed and cannot be manually edited
205
- </p>
206
- {/if}
207
- </div>
208
- {/if}
209
- </div>
210
-
211
- <!-- Right Column: Role & Permissions -->
212
- <div class="min-w-0 flex-1 space-y-4">
213
- <div class="border-default-200 border-b pb-3">
214
- <h3 class="text-default-900 text-lg font-semibold">Role & Permissions</h3>
88
+ <div class="flex flex-col gap-6">
89
+ <UserIdentityCard {user} />
90
+
91
+ <!-- Profile -->
92
+ <div class="space-y-4">
93
+ <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
94
+ {@render ReadOnlyField('First name', user?.first_name)}
95
+ {@render ReadOnlyField('Last name', user?.last_name)}
215
96
  </div>
97
+ {@render ReadOnlyField('Email address', user?.email_addresses?.[0]?.email_address)}
216
98
 
217
- <!-- User Role -->
218
- {#if user?.role}
219
- <div>
220
- <p class="text-default-700 mb-3 text-sm font-medium">User Role</p>
221
- <div class="rounded-lg border-2 border-blue-500 bg-blue-50 p-3">
222
- <div class="flex items-center justify-between gap-2">
223
- <div class="min-w-0 flex-1">
224
- <h4 class="text-default-900 text-sm font-semibold">
225
- {currentRole?.label || user.role}
226
- </h4>
227
- {#if currentRole?.description}
228
- <p class="text-default-600 mt-1 line-clamp-2 text-xs">
229
- {currentRole.description}
230
- </p>
231
- {/if}
232
- </div>
233
- <div
234
- class="flex h-5 w-5 shrink-0 items-center justify-center rounded-full border-2 border-blue-500 bg-blue-500"
235
- >
236
- <div class="h-2 w-2 rounded-full bg-white"></div>
237
- </div>
238
- </div>
239
- </div>
240
- </div>
241
- {/if}
242
-
243
- <!-- Permissions -->
244
- {#if user?.permissions && user.permissions.length > 0}
245
- <div>
246
- <h4 class="text-default-700 mb-2 text-sm font-medium">
247
- Permissions ({user.permissions.length})
248
- </h4>
249
- <div class="bg-default-50 max-h-60 overflow-y-auto rounded-lg p-3">
250
- <div class="space-y-2">
251
- {#each user.permissions as permission, index (`${permission}-${index}`)}
252
- <div class="flex items-start gap-2 text-xs">
253
- <div class="mt-1 h-1 w-1 shrink-0 rounded-full bg-blue-500"></div>
254
- <div class="text-default-700 font-mono">{permission}</div>
255
- </div>
256
- {/each}
257
- </div>
258
- </div>
259
- </div>
260
- {/if}
261
-
262
- <!-- Additional Info -->
263
- <div class="space-y-2">
99
+ <div class="flex flex-wrap gap-x-6 gap-y-2 pt-1 text-xs">
264
100
  {#if user?.created_at}
265
101
  <div>
266
- <p class="text-default-500 text-xs">Created</p>
267
- <p class="text-default-900 text-sm font-medium">{formatDate(user.created_at)}</p>
102
+ <span class="text-default-500">Created · </span>
103
+ <span class="text-default-900 font-medium">{formatDate(user.created_at)}</span>
268
104
  </div>
269
105
  {/if}
270
106
  {#if user?.last_sign_in_at}
271
107
  <div>
272
- <p class="text-default-500 text-xs">Last Sign In</p>
273
- <p class="text-default-900 text-sm font-medium">{formatDate(user.last_sign_in_at)}</p>
108
+ <span class="text-default-500">Last sign in · </span>
109
+ <span class="text-default-900 font-medium">{formatDate(user.last_sign_in_at)}</span>
274
110
  </div>
275
111
  {/if}
276
112
  </div>
277
113
  </div>
278
- </div>
279
114
 
280
- <!-- View Mode Actions -->
281
- {#snippet footer()}
282
- <div class="flex items-center justify-between gap-4">
283
- <div></div>
284
- <div class="flex gap-3">
285
- <Button variant="outline" onclick={handleClose}>Close</Button>
286
- {#if onedit}
287
- <Button color="primary" onclick={handleEdit}>
288
- <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
289
- <path
290
- stroke-linecap="round"
291
- stroke-linejoin="round"
292
- stroke-width="2"
293
- d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
294
- ></path>
295
- </svg>
296
- Edit User
297
- </Button>
115
+ <!-- Role + permissions -->
116
+ {#if user?.role}
117
+ <div class="space-y-3">
118
+ <span class="text-default-700 mb-2 block text-sm font-medium">Role</span>
119
+ <RoleCard
120
+ role={currentRole ?? { value: user.role, label: user.role, permissions: [] }}
121
+ selected
122
+ interactive={false}
123
+ />
124
+
125
+ {#if user?.permissions && user.permissions.length > 0}
126
+ <div>
127
+ <span class="text-default-500 mb-1.5 block text-xs font-medium">
128
+ Granted permissions ({user.permissions.length})
129
+ </span>
130
+ <div class="flex flex-wrap gap-1.5">
131
+ {#each user.permissions as permission, index (`${permission}-${index}`)}
132
+ <span
133
+ class="bg-default-100 text-default-700 rounded-md px-2 py-0.5 font-mono text-xs"
134
+ >
135
+ {permission}
136
+ </span>
137
+ {/each}
138
+ </div>
139
+ </div>
298
140
  {/if}
299
141
  </div>
300
- </div>
142
+ {/if}
143
+
144
+ <!-- API key -->
145
+ {#if apiKey || onregenerateapikey}
146
+ <ApiKeyField
147
+ value={apiKey}
148
+ label="Mako API Key"
149
+ error={apiKeyError}
150
+ helperText="API keys are system-managed and cannot be manually edited"
151
+ regenerate={onregenerateapikey
152
+ ? {
153
+ label: apiKey ? 'Regenerate' : 'Generate',
154
+ loading: regeneratingApiKey,
155
+ onclick: handleRegenerateApiKey
156
+ }
157
+ : undefined}
158
+ />
159
+ {/if}
160
+ </div>
161
+
162
+ {#snippet footer()}
163
+ <Button variant="outline" onclick={handleClose}>Close</Button>
164
+ {#if onedit}
165
+ <Button color={Color.PRIMARY} onclick={handleEdit}>Edit user</Button>
166
+ {/if}
301
167
  {/snippet}
302
168
  </Modal>
@@ -1,4 +1,4 @@
1
- import { type UserViewModalProps } from '../index.js';
1
+ import type { UserViewModalProps } from '../index.js';
2
2
  declare const UserViewModal: import("svelte").Component<UserViewModalProps, {}, "open" | "user">;
3
3
  type UserViewModal = ReturnType<typeof UserViewModal>;
4
4
  export default UserViewModal;
@@ -83,11 +83,15 @@ export interface UserTableProps {
83
83
  direction: 'asc' | 'desc' | null;
84
84
  }) => void;
85
85
  /** Fires when the user clicks the View action on a row. */
86
- onview: (user: User) => void;
86
+ onview?: (user: User) => void;
87
87
  /** Fires when the user clicks the Edit action on a row. */
88
- onedit: (user: User) => void;
88
+ onedit?: (user: User) => void;
89
89
  /** Fires when the user confirms a Delete action on a row. */
90
- ondelete: (userId: string) => void;
90
+ ondelete?: (userId: string) => void;
91
+ /** Fires when the user clicks the Approve action on a row (pending users). */
92
+ onapprove?: (user: User) => void;
93
+ /** Fires when the user clicks the Reject action on a row (pending users). */
94
+ onreject?: (user: User) => void;
91
95
  class?: ClassValue;
92
96
  testId?: string;
93
97
  }
@@ -201,6 +205,51 @@ export interface UserManagementAdapter {
201
205
  error?: string;
202
206
  token?: string;
203
207
  }>;
208
+ /**
209
+ * Optional. List users that are registered in your identity provider but
210
+ * haven't been onboarded yet (no org assignment, no API key). When this
211
+ * method is present, `<UserManagement>` shows a "Pending" tab with an
212
+ * approval workflow. When absent, the tab is hidden entirely.
213
+ */
214
+ getPendingUsers?: (options: GetUsersOptions) => PromiseLike<GetUsersResult>;
215
+ /**
216
+ * Optional. Approve a pending user — assigns them to the org with the
217
+ * selected role and generates their first API key. Returns the new key
218
+ * for one-time reveal in the UI.
219
+ */
220
+ approveUser?: (input: {
221
+ userId: string;
222
+ role: string;
223
+ }) => PromiseLike<{
224
+ apiKey: string;
225
+ }>;
226
+ /**
227
+ * Optional. Reject a pending user — permanently deletes them from the
228
+ * identity provider. Pair with a confirmation dialog at the call site.
229
+ */
230
+ rejectUser?: (userId: string) => PromiseLike<void>;
231
+ }
232
+ /**
233
+ * Props for `<UserApproveModal>` — confirms approval of a pending user with
234
+ * a role pick + on-success API key reveal.
235
+ */
236
+ export interface UserApproveModalProps {
237
+ open?: boolean;
238
+ user: User | null;
239
+ roles?: Role[];
240
+ /**
241
+ * Approval callback. Receives the userId and chosen role; should resolve
242
+ * with the newly generated API key for one-time display.
243
+ */
244
+ onapprove: (input: {
245
+ userId: string;
246
+ role: string;
247
+ }) => Promise<{
248
+ apiKey: string;
249
+ }>;
250
+ onclose: () => void;
251
+ class?: ClassValue;
252
+ testId?: string;
204
253
  }
205
254
  /**
206
255
  * Props for `<UserManagement>` — the all-in-one user dashboard
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makolabs/ripple",
3
- "version": "3.0.9",
3
+ "version": "3.0.11",
4
4
  "description": "Simple Svelte 5 powered component library ✨",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {
@@ -1,35 +0,0 @@
1
- <script lang="ts">
2
- import { cn } from '../helper/cls.js';
3
- import { buildTestId } from '../helper/testid.js';
4
- import type { Snippet } from 'svelte';
5
- import type { ClassValue } from 'tailwind-variants';
6
-
7
- type Props = {
8
- class?: ClassValue;
9
- align?: 'start' | 'center' | 'end' | 'between';
10
- children: Snippet;
11
- testId?: string;
12
- };
13
-
14
- let { class: className = '', align = 'end', children, testId }: Props = $props();
15
-
16
- const alignClass = $derived(
17
- {
18
- start: 'justify-start',
19
- center: 'justify-center',
20
- end: 'justify-end',
21
- between: 'justify-between'
22
- }[align]
23
- );
24
- </script>
25
-
26
- <div
27
- class={cn(
28
- 'border-default-200 flex flex-wrap items-center gap-3 border-t px-6 py-4',
29
- alignClass,
30
- className
31
- )}
32
- data-testid={buildTestId('modal-footer', undefined, testId)}
33
- >
34
- {@render children()}
35
- </div>
@@ -1,11 +0,0 @@
1
- import type { Snippet } from 'svelte';
2
- import type { ClassValue } from 'tailwind-variants';
3
- type Props = {
4
- class?: ClassValue;
5
- align?: 'start' | 'center' | 'end' | 'between';
6
- children: Snippet;
7
- testId?: string;
8
- };
9
- declare const ModalFooter: import("svelte").Component<Props, {}, "">;
10
- type ModalFooter = ReturnType<typeof ModalFooter>;
11
- export default ModalFooter;