Skip to content

Commit 08e7573

Browse files
committed
Better error message
1 parent 4a38e39 commit 08e7573

3 files changed

Lines changed: 229 additions & 61 deletions

File tree

src/routes/(protected)/rbac/entitlements/create/+page.svelte

Lines changed: 93 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script lang="ts">
22
import { goto } from "$app/navigation";
33
import { page } from "$app/stores";
4-
import { User, KeyRound } from "@lucide/svelte";
4+
import { User, KeyRound, Building2, Globe } from "@lucide/svelte";
55
import { toast } from "$lib/utils/toastService";
66
import { trackedFetch } from "$lib/utils/trackedFetch";
77
import PageRoleCheck from "$lib/components/PageRoleCheck.svelte";
@@ -33,27 +33,29 @@
3333
let bankId = $state(currentBank.bankId);
3434
let isSubmitting = $state(false);
3535
36-
$effect(() => {
37-
bankId = currentBank.bankId;
36+
// Determine if selected role requires bank_id
37+
let selectedRoleRequiresBank = $derived.by(() => {
38+
if (roleName) {
39+
const role = roles.find((r: any) => r.role === roleName);
40+
if (role) return role.requires_bank_id;
41+
}
42+
// When no role selected yet, use scope as hint
43+
return roleScope === "bank";
3844
});
3945
40-
function handleUserSelect(user: any) {
41-
userId = user.user_id;
42-
username = user.username;
43-
}
44-
45-
// Clear bankId when switching to system scope
46+
// Sync bankId based on whether the role needs a bank
4647
$effect(() => {
47-
console.log("roleScope changed to:", roleScope);
48-
if (roleScope === "system") {
48+
if (selectedRoleRequiresBank) {
49+
bankId = currentBank.bankId;
50+
} else {
4951
bankId = "";
5052
}
5153
});
5254
53-
// Debug: log when roleName changes
54-
$effect(() => {
55-
console.log("roleName changed to:", roleName, "roleScope is:", roleScope);
56-
});
55+
function handleUserSelect(user: any) {
56+
userId = user.user_id;
57+
username = user.username;
58+
}
5759
5860
async function handleSubmit(event: Event) {
5961
event.preventDefault();
@@ -97,11 +99,6 @@
9799
"Entitlement Created",
98100
`Successfully granted ${roleName} to user ${userId}`,
99101
);
100-
101-
// Redirect to user detail page after short delay
102-
setTimeout(() => {
103-
goto(`/users/${userId.trim()}`);
104-
}, 1000);
105102
} catch (error) {
106103
const errorMessage =
107104
error instanceof Error ? error.message : "Failed to create entitlement";
@@ -184,6 +181,32 @@
184181
/>
185182
</div>
186183

184+
<!-- Bank ID Field (shown for bank-level roles) -->
185+
{#if selectedRoleRequiresBank}
186+
<div class="form-group">
187+
<label for="bank-id-input" class="form-label">
188+
<Building2 size={18} />
189+
Bank ID
190+
</label>
191+
<input
192+
type="text"
193+
id="bank-id-input"
194+
class="form-input"
195+
bind:value={bankId}
196+
placeholder="Enter bank ID"
197+
disabled={isSubmitting}
198+
/>
199+
<div class="form-hint">
200+
The bank to scope this entitlement to
201+
</div>
202+
</div>
203+
{:else if roleName}
204+
<div class="scope-info">
205+
<Globe size={16} />
206+
<span>This is a system-wide role — no bank ID required</span>
207+
</div>
208+
{/if}
209+
187210
<!-- Form Actions -->
188211
<div class="form-actions">
189212
<button
@@ -345,6 +368,56 @@
345368
color: var(--color-surface-400);
346369
}
347370
371+
.form-input {
372+
width: 100%;
373+
padding: 0.625rem 0.75rem;
374+
border: 1px solid #d1d5db;
375+
border-radius: 6px;
376+
font-size: 0.875rem;
377+
transition: all 0.2s;
378+
}
379+
380+
.form-input:focus {
381+
outline: none;
382+
border-color: #667eea;
383+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
384+
}
385+
386+
.form-input:disabled {
387+
background: #f9fafb;
388+
cursor: not-allowed;
389+
opacity: 0.6;
390+
}
391+
392+
:global([data-mode="dark"]) .form-input {
393+
background: rgb(var(--color-surface-700));
394+
border-color: rgb(var(--color-surface-600));
395+
color: var(--color-surface-100);
396+
}
397+
398+
:global([data-mode="dark"]) .form-input:focus {
399+
border-color: rgb(var(--color-primary-500));
400+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
401+
}
402+
403+
.scope-info {
404+
display: flex;
405+
align-items: center;
406+
gap: 0.5rem;
407+
padding: 0.75rem 1rem;
408+
background: #f0f9ff;
409+
border: 1px solid #bae6fd;
410+
border-radius: 6px;
411+
font-size: 0.875rem;
412+
color: #0369a1;
413+
}
414+
415+
:global([data-mode="dark"]) .scope-info {
416+
background: rgba(14, 165, 233, 0.1);
417+
border-color: rgba(14, 165, 233, 0.3);
418+
color: rgb(var(--color-primary-300));
419+
}
420+
348421
.form-actions {
349422
display: flex;
350423
gap: 1rem;

src/routes/(protected)/user/entitlements/+page.server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ export const actions = {
108108

109109
} catch (error) {
110110
logger.error("Error adding entitlement:", error);
111-
return fail(500, {entitlement: entitlement, error: 'Failed to add entitlement.', ...(bank_id ? { bank_id: bank_id } : {})});
111+
const errorMessage = error instanceof Error ? error.message : 'Failed to add entitlement.';
112+
return fail(500, {entitlement: entitlement, error: errorMessage, ...(bank_id ? { bank_id: bank_id } : {})});
112113
}
113114
}
114115
} satisfies Actions

src/routes/(protected)/user/entitlements/+page.svelte

Lines changed: 134 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<script lang="ts">
22
import { currentBank } from "$lib/stores/currentBank.svelte";
3+
import { Building2, Globe, KeyRound } from "@lucide/svelte";
4+
import RoleSearchWidget from "$lib/components/RoleSearchWidget.svelte";
35
46
const { data, form } = $props();
57
const userEntitlements = data.userEntitlements;
@@ -33,19 +35,27 @@
3335
);
3436
3537
let selectedEntitlementRole = $state("");
38+
let roleScope = $state<"all" | "system" | "bank">("all");
3639
let selectedBankId = $state(currentBank.bankId);
3740
38-
$effect(() => {
39-
selectedBankId = currentBank.bankId;
41+
// Determine if selected role requires bank_id
42+
let selectedRoleRequiresBank = $derived.by(() => {
43+
if (selectedEntitlementRole) {
44+
const role = allEntitlements.find((r: any) => r.role === selectedEntitlementRole);
45+
if (role) return role.requires_bank_id;
46+
}
47+
// When no role selected yet, use scope as hint
48+
return roleScope === "bank";
4049
});
4150
42-
// Derived state to get the full entitlement object
43-
let selectedEntitlement = $derived(
44-
allEntitlements.find((ent) => ent.role === selectedEntitlementRole) || {
45-
role: "",
46-
requires_bank_id: false,
47-
},
48-
);
51+
// Sync bankId based on whether the role needs a bank
52+
$effect(() => {
53+
if (selectedRoleRequiresBank) {
54+
selectedBankId = currentBank.bankId;
55+
} else {
56+
selectedBankId = "";
57+
}
58+
});
4959
5060
// Pre-select entitlement if form data exists (on validation errors)
5161
if (form?.entitlement && !form?.success) {
@@ -60,10 +70,6 @@
6070
if (form?.success) {
6171
selectedEntitlementRole = "";
6272
}
63-
64-
// console.debug('User Entitlements:', userEntitlements);
65-
// console.debug('All Entitlements:', allEntitlements);
66-
console.log("Can Create Entitlements:", canCreateEntitlements);
6773
</script>
6874

6975
<h2 class="mb-4 text-xl font-semibold">Your Entitlements</h2>
@@ -148,46 +154,134 @@
148154
<form
149155
method="POST"
150156
action="?/create"
151-
class="mx-auto w-full max-w-md space-y-4"
157+
class="w-full max-w-2xl space-y-4"
152158
>
153-
<label class="label">
154-
<span class="label-text">Select Entitlement</span>
155-
<select
156-
class="select"
157-
name="entitlement"
158-
bind:value={selectedEntitlementRole}
159-
>
160-
<option value="" disabled>Select an entitlement</option>
161-
{#each allEntitlements as ent}
162-
<option value={ent.role}>{ent.role}</option>
163-
{/each}
164-
</select>
165-
</label>
159+
<!-- Hidden inputs for form submission -->
160+
<input type="hidden" name="entitlement" value={selectedEntitlementRole} />
161+
{#if selectedRoleRequiresBank}
162+
<input type="hidden" name="bank_id" value={selectedBankId} />
163+
{/if}
164+
165+
<div class="form-group">
166+
<label class="form-label">
167+
<KeyRound size={18} />
168+
<span>Select Role</span>
169+
</label>
170+
<RoleSearchWidget
171+
roles={allEntitlements}
172+
bind:selectedRole={selectedEntitlementRole}
173+
bind:roleScope
174+
/>
175+
</div>
166176

167177
{#if form?.missing}<p class="text-error-500 text-xs">
168178
Please select an entitlement to add.
169179
</p>{/if}
170180
{#if form?.error}<p class="text-error-500 text-xs">{form.error}</p>{/if}
171181

172-
{#if selectedEntitlement.requires_bank_id}
173-
<input type="hidden" name="bank_id" value={selectedBankId} />
174-
<div class="label">
175-
<span class="label-text">Bank</span>
176-
{#if selectedBankId}
177-
<div class="rounded border border-gray-300 bg-gray-50 px-3 py-2 text-sm text-gray-700 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300">
178-
{selectedBankId}
179-
</div>
180-
{:else}
181-
<p class="text-sm text-amber-600 dark:text-amber-400">
182-
Please select a bank in <a href="/user" class="underline">My Account</a> first.
183-
</p>
184-
{/if}
182+
<!-- Bank ID Field (shown for bank-level roles) -->
183+
{#if selectedRoleRequiresBank}
184+
<div class="form-group">
185+
<label for="bank-id-input" class="form-label">
186+
<Building2 size={18} />
187+
<span>Bank ID</span>
188+
</label>
189+
<input
190+
type="text"
191+
id="bank-id-input"
192+
class="form-input"
193+
bind:value={selectedBankId}
194+
placeholder="Enter bank ID"
195+
/>
196+
<div class="form-hint">
197+
The bank to scope this entitlement to
198+
</div>
199+
</div>
200+
{:else if selectedEntitlementRole}
201+
<div class="scope-info">
202+
<Globe size={16} />
203+
<span>This is a system-wide role — no bank ID required</span>
185204
</div>
186205
{/if}
206+
187207
<button class="btn preset-outlined-tertiary-500" type="submit"
188208
>Add Entitlement</button
189209
>
190210
</form>
191211
{:else if !canCreateEntitlements}
192212
<h2 class="mt-8 mb-4 text-xl font-semibold">Request Entitlement</h2>
193213
{/if}
214+
215+
<style>
216+
.form-group {
217+
display: flex;
218+
flex-direction: column;
219+
gap: 0.5rem;
220+
}
221+
222+
.form-label {
223+
display: flex;
224+
align-items: center;
225+
gap: 0.5rem;
226+
font-size: 0.875rem;
227+
font-weight: 600;
228+
color: #374151;
229+
}
230+
231+
:global([data-mode="dark"]) .form-label {
232+
color: var(--color-surface-200);
233+
}
234+
235+
.form-input {
236+
width: 100%;
237+
padding: 0.625rem 0.75rem;
238+
border: 1px solid #d1d5db;
239+
border-radius: 6px;
240+
font-size: 0.875rem;
241+
transition: all 0.2s;
242+
}
243+
244+
.form-input:focus {
245+
outline: none;
246+
border-color: #667eea;
247+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
248+
}
249+
250+
:global([data-mode="dark"]) .form-input {
251+
background: rgb(var(--color-surface-700));
252+
border-color: rgb(var(--color-surface-600));
253+
color: var(--color-surface-100);
254+
}
255+
256+
:global([data-mode="dark"]) .form-input:focus {
257+
border-color: rgb(var(--color-primary-500));
258+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
259+
}
260+
261+
.form-hint {
262+
font-size: 0.75rem;
263+
color: #6b7280;
264+
}
265+
266+
:global([data-mode="dark"]) .form-hint {
267+
color: var(--color-surface-400);
268+
}
269+
270+
.scope-info {
271+
display: flex;
272+
align-items: center;
273+
gap: 0.5rem;
274+
padding: 0.75rem 1rem;
275+
background: #f0f9ff;
276+
border: 1px solid #bae6fd;
277+
border-radius: 6px;
278+
font-size: 0.875rem;
279+
color: #0369a1;
280+
}
281+
282+
:global([data-mode="dark"]) .scope-info {
283+
background: rgba(14, 165, 233, 0.1);
284+
border-color: rgba(14, 165, 233, 0.3);
285+
color: rgb(var(--color-primary-300));
286+
}
287+
</style>

0 commit comments

Comments
 (0)