@jmruthers/pace-core 0.5.140 → 0.5.142

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 (180) hide show
  1. package/README.md +2 -2
  2. package/dist/{DataTable-JXFCA2BJ.js → DataTable-SKCX4SCB.js} +6 -6
  3. package/dist/{EventLogo-rFL_kRjk.d.ts → EventLogo-B3V3otev.d.ts} +307 -1
  4. package/dist/{UnifiedAuthProvider-XIQQ7LVU.js → UnifiedAuthProvider-BMJAP6Z7.js} +3 -3
  5. package/dist/{chunk-22WKWKRX.js → chunk-2AKRP5QZ.js} +4 -4
  6. package/dist/{chunk-4C7EXCAR.js → chunk-CRGFNQ2L.js} +4 -4
  7. package/dist/{chunk-TLT2ZR3L.js → chunk-E6ZCVF4T.js} +4 -4
  8. package/dist/{chunk-INQLMHPF.js → chunk-ERGKJX4D.js} +2 -2
  9. package/dist/{chunk-6LAAY47Q.js → chunk-MSHEVJXS.js} +2 -2
  10. package/dist/{chunk-MA6EPSGZ.js → chunk-PKW27QVS.js} +2 -2
  11. package/dist/{chunk-T6JN6LH6.js → chunk-R53TUSFK.js} +3 -3
  12. package/dist/{chunk-6DXZ6V5Q.js → chunk-SFVL7ZFI.js} +5 -5
  13. package/dist/{chunk-5JMOHWDI.js → chunk-TUJSIWX6.js} +497 -329
  14. package/dist/chunk-TUJSIWX6.js.map +1 -0
  15. package/dist/{chunk-BOOI7GK2.js → chunk-VOJBGZYI.js} +119 -3
  16. package/dist/chunk-VOJBGZYI.js.map +1 -0
  17. package/dist/{chunk-YCWDTTUK.js → chunk-WM26XK7I.js} +22 -8
  18. package/dist/chunk-WM26XK7I.js.map +1 -0
  19. package/dist/components.d.ts +3 -1
  20. package/dist/components.js +20 -8
  21. package/dist/components.js.map +1 -1
  22. package/dist/hooks.js +7 -7
  23. package/dist/index.d.ts +4 -2
  24. package/dist/index.js +25 -11
  25. package/dist/index.js.map +1 -1
  26. package/dist/providers.js +2 -2
  27. package/dist/rbac/index.d.ts +94 -1
  28. package/dist/rbac/index.js +9 -7
  29. package/dist/utils.js +1 -1
  30. package/docs/api/README.md +2 -2
  31. package/docs/api/classes/ColumnFactory.md +1 -1
  32. package/docs/api/classes/ErrorBoundary.md +1 -1
  33. package/docs/api/classes/InvalidScopeError.md +1 -1
  34. package/docs/api/classes/MissingUserContextError.md +1 -1
  35. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  36. package/docs/api/classes/PermissionDeniedError.md +1 -1
  37. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  38. package/docs/api/classes/RBACAuditManager.md +1 -1
  39. package/docs/api/classes/RBACCache.md +1 -1
  40. package/docs/api/classes/RBACEngine.md +1 -1
  41. package/docs/api/classes/RBACError.md +1 -1
  42. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  43. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  44. package/docs/api/classes/StorageUtils.md +1 -1
  45. package/docs/api/enums/FileCategory.md +1 -1
  46. package/docs/api/interfaces/AggregateConfig.md +1 -1
  47. package/docs/api/interfaces/BadgeProps.md +1 -1
  48. package/docs/api/interfaces/ButtonProps.md +1 -1
  49. package/docs/api/interfaces/CalendarProps.md +40 -0
  50. package/docs/api/interfaces/CardProps.md +1 -1
  51. package/docs/api/interfaces/ColorPalette.md +1 -1
  52. package/docs/api/interfaces/ColorShade.md +1 -1
  53. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  54. package/docs/api/interfaces/DataRecord.md +1 -1
  55. package/docs/api/interfaces/DataTableAction.md +1 -1
  56. package/docs/api/interfaces/DataTableColumn.md +1 -1
  57. package/docs/api/interfaces/DataTableProps.md +1 -1
  58. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  59. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  60. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  61. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  62. package/docs/api/interfaces/EventLogoProps.md +1 -1
  63. package/docs/api/interfaces/ExportColumn.md +1 -1
  64. package/docs/api/interfaces/ExportOptions.md +1 -1
  65. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  66. package/docs/api/interfaces/FileMetadata.md +1 -1
  67. package/docs/api/interfaces/FileReference.md +1 -1
  68. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  69. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  70. package/docs/api/interfaces/FileUploadProps.md +1 -1
  71. package/docs/api/interfaces/FooterProps.md +1 -1
  72. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  73. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  74. package/docs/api/interfaces/InputProps.md +1 -1
  75. package/docs/api/interfaces/LabelProps.md +1 -1
  76. package/docs/api/interfaces/LoginFormProps.md +1 -1
  77. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  78. package/docs/api/interfaces/NavigationContextType.md +1 -1
  79. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  80. package/docs/api/interfaces/NavigationItem.md +1 -1
  81. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  82. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  83. package/docs/api/interfaces/Organisation.md +1 -1
  84. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  85. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  86. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  87. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  88. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  89. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  90. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  91. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  92. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  93. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  94. package/docs/api/interfaces/PaletteData.md +1 -1
  95. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  96. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  97. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  98. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  99. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  100. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  101. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  102. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  103. package/docs/api/interfaces/RBACConfig.md +1 -1
  104. package/docs/api/interfaces/RBACLogger.md +1 -1
  105. package/docs/api/interfaces/ResourcePermissions.md +155 -0
  106. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  107. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  108. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  109. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  110. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  111. package/docs/api/interfaces/RouteConfig.md +1 -1
  112. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  113. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  114. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  115. package/docs/api/interfaces/StorageConfig.md +1 -1
  116. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  117. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  118. package/docs/api/interfaces/StorageListOptions.md +1 -1
  119. package/docs/api/interfaces/StorageListResult.md +1 -1
  120. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  121. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  122. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  123. package/docs/api/interfaces/StyleImport.md +1 -1
  124. package/docs/api/interfaces/SwitchProps.md +1 -1
  125. package/docs/api/interfaces/TabsContentProps.md +9 -0
  126. package/docs/api/interfaces/TabsListProps.md +9 -0
  127. package/docs/api/interfaces/TabsProps.md +9 -0
  128. package/docs/api/interfaces/TabsTriggerProps.md +9 -0
  129. package/docs/api/interfaces/TextareaProps.md +53 -0
  130. package/docs/api/interfaces/ToastActionElement.md +1 -1
  131. package/docs/api/interfaces/ToastProps.md +1 -1
  132. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  133. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  134. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  135. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  136. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  137. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  138. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  139. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  140. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  141. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  142. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  143. package/docs/api/interfaces/UseResourcePermissionsOptions.md +34 -0
  144. package/docs/api/interfaces/UserEventAccess.md +1 -1
  145. package/docs/api/interfaces/UserMenuProps.md +1 -1
  146. package/docs/api/interfaces/UserProfile.md +1 -1
  147. package/docs/api/modules.md +289 -2
  148. package/docs/rbac/README.md +2 -1
  149. package/docs/rbac/event-based-apps.md +872 -0
  150. package/package.json +3 -1
  151. package/src/components/Calendar/Calendar.test.tsx +338 -0
  152. package/src/components/Calendar/Calendar.tsx +192 -0
  153. package/src/components/Calendar/index.ts +10 -0
  154. package/src/components/Tabs/Tabs.test.tsx +439 -0
  155. package/src/components/Tabs/Tabs.tsx +202 -0
  156. package/src/components/Tabs/index.ts +10 -0
  157. package/src/components/Textarea/Textarea.test.tsx +269 -0
  158. package/src/components/Textarea/Textarea.tsx +133 -0
  159. package/src/components/Textarea/index.ts +10 -0
  160. package/src/components/index.ts +11 -0
  161. package/src/index.ts +11 -0
  162. package/src/rbac/hooks/index.ts +2 -0
  163. package/src/rbac/hooks/useResourcePermissions.test.ts +633 -0
  164. package/src/rbac/hooks/useResourcePermissions.ts +235 -0
  165. package/src/services/EventService.ts +29 -8
  166. package/src/services/__tests__/EventService.test.ts +48 -8
  167. package/dist/chunk-5JMOHWDI.js.map +0 -1
  168. package/dist/chunk-BOOI7GK2.js.map +0 -1
  169. package/dist/chunk-YCWDTTUK.js.map +0 -1
  170. package/src/rbac/docs/event-based-apps.md +0 -285
  171. /package/dist/{DataTable-JXFCA2BJ.js.map → DataTable-SKCX4SCB.js.map} +0 -0
  172. /package/dist/{UnifiedAuthProvider-XIQQ7LVU.js.map → UnifiedAuthProvider-BMJAP6Z7.js.map} +0 -0
  173. /package/dist/{chunk-22WKWKRX.js.map → chunk-2AKRP5QZ.js.map} +0 -0
  174. /package/dist/{chunk-4C7EXCAR.js.map → chunk-CRGFNQ2L.js.map} +0 -0
  175. /package/dist/{chunk-TLT2ZR3L.js.map → chunk-E6ZCVF4T.js.map} +0 -0
  176. /package/dist/{chunk-INQLMHPF.js.map → chunk-ERGKJX4D.js.map} +0 -0
  177. /package/dist/{chunk-6LAAY47Q.js.map → chunk-MSHEVJXS.js.map} +0 -0
  178. /package/dist/{chunk-MA6EPSGZ.js.map → chunk-PKW27QVS.js.map} +0 -0
  179. /package/dist/{chunk-T6JN6LH6.js.map → chunk-R53TUSFK.js.map} +0 -0
  180. /package/dist/{chunk-6DXZ6V5Q.js.map → chunk-SFVL7ZFI.js.map} +0 -0
@@ -0,0 +1,872 @@
1
+ ---
2
+ lastUpdated: 2025-01-27T00:00:00+00:00
3
+ version: 0.5.76
4
+ reviewedBy: content-audit
5
+ ---
6
+
7
+ # Event-Based Apps with RBAC
8
+
9
+ > **📚 RBAC System** | [← Back to Documentation](../../README.md) | [RBAC Overview](./README.md) | [Organisation-Based Quick Start](./quick-start.md)
10
+
11
+ > **⚠️ CRITICAL**: This guide is designed to be impossible to get wrong. Follow every step exactly as written, and your event-based RBAC system will work perfectly.
12
+
13
+ Build your first event-based RBAC-enabled application in under 10 minutes.
14
+
15
+ ## 🎯 What We'll Build
16
+
17
+ A simple event management app that demonstrates:
18
+ - Event-based permission checking
19
+ - Automatic organisation resolution from event context
20
+ - Page-level access control for event-based apps
21
+ - Proper provider setup for event context
22
+ - Event selection and context management
23
+
24
+ ## 🚨 Critical Rules (Follow These or It Won't Work)
25
+
26
+ 1. **ALWAYS call `setupRBAC()` first** - This is MANDATORY and must be called before any RBAC features
27
+ 2. **Never make direct database queries** to `rbac_apps`, `rbac_global_roles`, or other RBAC tables
28
+ 3. **ALWAYS use `PagePermissionGuard`** for page-level permissions - This is the ONLY way to properly protect pages
29
+ 4. **ALWAYS set up providers correctly** in the exact order shown below
30
+ 5. **Use the exact app name** from your environment variable (must match database exactly, case-sensitive)
31
+ 6. **Ensure database setup is complete** before starting the app - App must be registered with `requires_event = true`
32
+ 7. **User must have organisation role** - Users need roles assigned in `rbac_organisation_roles` table
33
+ 8. **Event must be selected** - `selectedEventId` must be set in UnifiedAuth context before permission checks
34
+ 9. **Event must belong to organisation** - The event's `organisation_id` must match the user's organisation
35
+
36
+ ## 🚀 Step-by-Step Implementation
37
+
38
+ ### 1. Project Setup
39
+
40
+ If you don't have a project yet:
41
+
42
+ ```bash
43
+ npx create-react-app event-manager --template typescript
44
+ cd event-manager
45
+ ```
46
+
47
+ ### 2. Install Dependencies
48
+
49
+ **CRITICAL**: Use the exact versions specified to avoid compatibility issues.
50
+
51
+ ```bash
52
+ npm install @jmruthers/pace-core@^0.4.1 @supabase/supabase-js@^2.38.0
53
+ npm install -D tailwindcss @types/react @types/react-dom
54
+ ```
55
+
56
+ ### 3. Configure Tailwind CSS v4
57
+
58
+ Install Tailwind CSS v4:
59
+ ```bash
60
+ npm install -D @tailwindcss/vite tailwindcss@^4.0.0
61
+ ```
62
+
63
+ Update `vite.config.ts`:
64
+
65
+ ```ts
66
+ // vite.config.ts
67
+ import { defineConfig } from 'vite'
68
+ import react from '@vitejs/plugin-react'
69
+ import tailwindcss from '@tailwindcss/vite'
70
+
71
+ export default defineConfig({
72
+ plugins: [
73
+ react(),
74
+ tailwindcss({
75
+ // CRITICAL: Include pace-core components for scanning
76
+ content: [
77
+ './src/**/*.{js,ts,jsx,tsx}',
78
+ './node_modules/@jmruthers/pace-core/**/*.{js,ts,jsx,tsx}'
79
+ ]
80
+ })
81
+ ],
82
+ })
83
+ ```
84
+
85
+ ### 4. Set Up Environment Variables
86
+
87
+ Create `.env.local`:
88
+
89
+ ```bash
90
+ # .env.local
91
+ VITE_SUPABASE_URL=https://your-project.supabase.co
92
+ VITE_SUPABASE_ANON_KEY=your-anon-key-here
93
+ VITE_APP_NAME=pace-trac
94
+ ```
95
+
96
+ **CRITICAL**:
97
+ - Use `VITE_` prefix for Vite projects
98
+ - Use `NEXT_PUBLIC_` prefix for Next.js projects
99
+ - The `VITE_APP_NAME` must match exactly what you have in the `rbac_apps` table
100
+
101
+ ### 5. Database Setup (CRITICAL)
102
+
103
+ **CRITICAL**: Your app must be registered in the database as an event-based app before you can use RBAC.
104
+
105
+ #### 5.1 Register Your App as Event-Based
106
+
107
+ Run this SQL in your Supabase SQL editor:
108
+
109
+ ```sql
110
+ -- Replace 'pace-trac' with your actual app name (must match VITE_APP_NAME)
111
+ -- CRITICAL: requires_event = true makes this an event-based app
112
+ INSERT INTO rbac_apps (name, display_name, requires_event, is_active)
113
+ VALUES ('pace-trac', 'PACE Trac', true, true)
114
+ ON CONFLICT (name) DO UPDATE SET
115
+ display_name = EXCLUDED.display_name,
116
+ requires_event = EXCLUDED.requires_event, -- CRITICAL: Must be true for event-based apps
117
+ is_active = EXCLUDED.is_active;
118
+
119
+ -- Verify the app is registered correctly
120
+ SELECT id, name, display_name, requires_event, is_active
121
+ FROM rbac_apps
122
+ WHERE name = 'pace-trac';
123
+ -- Should return: requires_event = true, is_active = true
124
+ ```
125
+
126
+ #### 5.2 Create App Pages
127
+
128
+ ```sql
129
+ -- Create pages for your event-based app (replace 'pace-trac' with your actual app name)
130
+ INSERT INTO rbac_app_pages (app_id, page_name, page_description)
131
+ SELECT
132
+ a.id,
133
+ unnest(ARRAY['dashboard', 'participants', 'settings', 'reports']),
134
+ unnest(ARRAY['Event Dashboard', 'Participant Management', 'Event Settings', 'Event Reports'])
135
+ FROM rbac_apps a
136
+ WHERE a.name = 'pace-trac';
137
+ ```
138
+
139
+ #### 5.3 Set Up Page Permissions
140
+
141
+ **CRITICAL**: Page permissions use the format `{operation}:page.{pageName}` (e.g., `read:page.dashboard`). For event-based apps, permissions are checked against the organisation resolved from the event.
142
+
143
+ ```sql
144
+ -- Set up basic permissions for each page (replace 'pace-trac' with your actual app name)
145
+ -- IMPORTANT: Replace the organisation_id UUID with your actual organisation ID
146
+ WITH app_pages AS (
147
+ SELECT ap.id as page_id, ap.page_name, a.id as app_id
148
+ FROM rbac_app_pages ap
149
+ JOIN rbac_apps a ON ap.app_id = a.id
150
+ WHERE a.name = 'pace-trac'
151
+ )
152
+ INSERT INTO rbac_page_permissions (app_page_id, operation, role_name, allowed, organisation_id)
153
+ SELECT
154
+ ap.page_id,
155
+ op.operation,
156
+ role.role_name,
157
+ CASE
158
+ WHEN role.role_name = 'org_admin' THEN true
159
+ WHEN role.role_name = 'leader' AND ap.page_name != 'reports' THEN true
160
+ WHEN role.role_name = 'member' AND ap.page_name IN ('dashboard', 'participants') THEN true
161
+ ELSE false
162
+ END,
163
+ '00000000-0000-0000-0000-000000000000'::uuid -- ⚠️ REPLACE THIS with your actual organisation ID
164
+ FROM app_pages ap
165
+ CROSS JOIN (SELECT unnest(ARRAY['read', 'create', 'update', 'delete']) as operation) op
166
+ CROSS JOIN (SELECT unnest(ARRAY['org_admin', 'leader', 'member']) as role_name) role;
167
+
168
+ -- Verify permissions were created correctly
169
+ SELECT
170
+ a.name as app_name,
171
+ ap.page_name,
172
+ pp.operation,
173
+ pp.role_name,
174
+ pp.allowed,
175
+ pp.organisation_id
176
+ FROM rbac_page_permissions pp
177
+ JOIN rbac_app_pages ap ON pp.app_page_id = ap.id
178
+ JOIN rbac_apps a ON ap.app_id = a.id
179
+ WHERE a.name = 'pace-trac'
180
+ ORDER BY ap.page_name, pp.operation, pp.role_name;
181
+ ```
182
+
183
+ #### 5.4 Create a Test Event
184
+
185
+ **CRITICAL**: For event-based apps, you need at least one event that belongs to your organisation.
186
+
187
+ ```sql
188
+ -- Create a test event (replace with your actual organisation_id)
189
+ INSERT INTO event (event_name, event_code, organisation_id, event_date, is_active)
190
+ VALUES (
191
+ 'Test Event 2025',
192
+ 'TEST2025',
193
+ '00000000-0000-0000-0000-000000000000'::uuid, -- ⚠️ REPLACE THIS with your actual organisation ID
194
+ CURRENT_DATE + INTERVAL '30 days',
195
+ true
196
+ )
197
+ RETURNING event_id, event_name, event_code, organisation_id;
198
+ -- Save the event_id - you'll need it for testing
199
+ ```
200
+
201
+ #### 5.5 Assign User Roles
202
+
203
+ ```sql
204
+ -- Grant a user the org_admin role (replace with actual user and organisation IDs)
205
+ INSERT INTO rbac_organisation_roles (user_id, organisation_id, role, status, granted_at)
206
+ VALUES (
207
+ 'your-user-id'::uuid,
208
+ 'your-organisation-id'::uuid,
209
+ 'org_admin',
210
+ 'active',
211
+ NOW()
212
+ )
213
+ ON CONFLICT (user_id, organisation_id) DO UPDATE SET
214
+ role = EXCLUDED.role,
215
+ status = EXCLUDED.status,
216
+ updated_at = NOW();
217
+ ```
218
+
219
+ ### 6. Create Supabase Client
220
+
221
+ Create `src/lib/supabase.ts`:
222
+
223
+ ```typescript
224
+ // src/lib/supabase.ts
225
+ import { createClient } from '@supabase/supabase-js'
226
+
227
+ const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
228
+ const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
229
+
230
+ if (!supabaseUrl || !supabaseAnonKey) {
231
+ throw new Error('Missing Supabase environment variables')
232
+ }
233
+
234
+ export const supabase = createClient(supabaseUrl, supabaseAnonKey)
235
+ ```
236
+
237
+ ### 7. Initialize RBAC (MANDATORY!)
238
+
239
+ **CRITICAL**: You MUST call `setupRBAC()` before using any RBAC features. This configures rate limiting (default: 1000 requests/minute) and enables security validation.
240
+
241
+ **Option 1: Setup in main entry point (Recommended)**
242
+
243
+ ```typescript
244
+ // src/main.tsx
245
+ import React from 'react'
246
+ import ReactDOM from 'react-dom/client'
247
+ import { setupRBAC } from '@jmruthers/pace-core/rbac'
248
+ import { supabase } from './lib/supabase'
249
+ import App from './App'
250
+
251
+ // ⚠️ CRITICAL: Call setupRBAC BEFORE rendering App
252
+ setupRBAC(supabase)
253
+
254
+ ReactDOM.createRoot(document.getElementById('root')!).render(
255
+ <React.StrictMode>
256
+ <App />
257
+ </React.StrictMode>
258
+ )
259
+ ```
260
+
261
+ **Option 2: Setup in separate file (Alternative)**
262
+
263
+ ```typescript
264
+ // src/lib/rbac-setup.ts
265
+ import { setupRBAC } from '@jmruthers/pace-core/rbac'
266
+ import { supabase } from './supabase'
267
+
268
+ // ⚠️ REQUIRED: Initialize RBAC before using any RBAC features
269
+ setupRBAC(supabase, {
270
+ // Optional: Configure rate limiting for high-traffic apps
271
+ security: {
272
+ maxPermissionChecksPerMinute: 2000 // Increase from default 1000 if needed
273
+ }
274
+ })
275
+
276
+ // src/main.tsx
277
+ import './lib/rbac-setup' // Import BEFORE App
278
+ import App from './App'
279
+ ```
280
+
281
+ ### 8. App Setup (CRITICAL - Event-Based Specific!)
282
+
283
+ Create `src/App.tsx` with this EXACT structure for event-based apps:
284
+
285
+ ```typescript
286
+ // src/App.tsx
287
+ import React from 'react'
288
+ import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
289
+ import {
290
+ UnifiedAuthProvider,
291
+ OrganisationProvider,
292
+ EventProvider
293
+ } from '@jmruthers/pace-core/providers'
294
+ import { supabase } from './lib/supabase'
295
+ import { Dashboard } from './pages/Dashboard'
296
+ import { Participants } from './pages/Participants'
297
+ import { Login } from './pages/Login'
298
+
299
+ // CRITICAL: App name for RBAC resolution
300
+ const APP_NAME = import.meta.env.VITE_APP_NAME
301
+
302
+ if (!APP_NAME) {
303
+ throw new Error('VITE_APP_NAME environment variable is required')
304
+ }
305
+
306
+ function App() {
307
+ return (
308
+ <UnifiedAuthProvider
309
+ supabaseClient={supabase}
310
+ appName={APP_NAME}
311
+ >
312
+ <OrganisationProvider>
313
+ {/* CRITICAL: EventProvider is REQUIRED for event-based apps */}
314
+ <EventProvider
315
+ autoSelectNextEvent={true} // Automatically select next upcoming event
316
+ onEventChange={(event) => {
317
+ console.log('Event changed:', event?.event_name)
318
+ }}
319
+ >
320
+ <Router>
321
+ <Routes>
322
+ <Route path="/login" element={<Login />} />
323
+ <Route path="/" element={<Dashboard />} />
324
+ <Route path="/participants" element={<Participants />} />
325
+ </Routes>
326
+ </Router>
327
+ </EventProvider>
328
+ </OrganisationProvider>
329
+ </UnifiedAuthProvider>
330
+ )
331
+ }
332
+
333
+ export default App
334
+ ```
335
+
336
+ **CRITICAL Differences for Event-Based Apps:**
337
+ - ✅ **EventProvider is REQUIRED** - Wraps your app content
338
+ - ✅ **OrganisationProvider still required** - Needed for organisation context
339
+ - ✅ **autoSelectNextEvent** - Automatically selects the next upcoming event
340
+ - ✅ **Event context is automatically available** - `PagePermissionGuard` will resolve organisation from event
341
+
342
+ ### 9. Create Login Page
343
+
344
+ Create `src/pages/Login.tsx`:
345
+
346
+ ```typescript
347
+ // src/pages/Login.tsx
348
+ import React, { useState } from 'react'
349
+ import { useNavigate } from 'react-router-dom'
350
+ import { supabase } from '../lib/supabase'
351
+
352
+ export function Login() {
353
+ const [email, setEmail] = useState('')
354
+ const [password, setPassword] = useState('')
355
+ const [loading, setLoading] = useState(false)
356
+ const navigate = useNavigate()
357
+
358
+ const handleLogin = async (e: React.FormEvent) => {
359
+ e.preventDefault()
360
+ setLoading(true)
361
+
362
+ try {
363
+ const { data, error } = await supabase.auth.signInWithPassword({
364
+ email,
365
+ password
366
+ })
367
+
368
+ if (error) {
369
+ alert('Login failed: ' + error.message)
370
+ return
371
+ }
372
+
373
+ if (data.user) {
374
+ navigate('/')
375
+ }
376
+ } catch (error) {
377
+ alert('Login failed: ' + (error as Error).message)
378
+ } finally {
379
+ setLoading(false)
380
+ }
381
+ }
382
+
383
+ return (
384
+ <div className="min-h-screen flex items-center justify-center bg-sec-50">
385
+ <div className="max-w-md w-full space-y-8">
386
+ <div>
387
+ <h2 className="mt-6 text-center text-3xl font-extrabold text-sec-900">
388
+ Sign in to your account
389
+ </h2>
390
+ </div>
391
+ <form className="mt-8 space-y-6" onSubmit={handleLogin}>
392
+ <div>
393
+ <label htmlFor="email" className="sr-only">
394
+ Email address
395
+ </label>
396
+ <input
397
+ id="email"
398
+ name="email"
399
+ type="email"
400
+ required
401
+ className="appearance-none rounded-md relative block w-full px-3 py-2 border border-sec-300 placeholder-sec-500 text-sec-900 focus:outline-none focus:ring-sec-500 focus:border-sec-500 focus:z-10 sm:text-sm"
402
+ placeholder="Email address"
403
+ value={email}
404
+ onChange={(e) => setEmail(e.target.value)}
405
+ />
406
+ </div>
407
+ <div>
408
+ <label htmlFor="password" className="sr-only">
409
+ Password
410
+ </label>
411
+ <input
412
+ id="password"
413
+ name="password"
414
+ type="password"
415
+ required
416
+ className="appearance-none rounded-md relative block w-full px-3 py-2 border border-sec-300 placeholder-sec-500 text-sec-900 focus:outline-none focus:ring-sec-500 focus:border-sec-500 focus:z-10 sm:text-sm"
417
+ placeholder="Password"
418
+ value={password}
419
+ onChange={(e) => setPassword(e.target.value)}
420
+ />
421
+ </div>
422
+ <div>
423
+ <button
424
+ type="submit"
425
+ disabled={loading}
426
+ className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-main-50 bg-sec-600 hover:bg-sec-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-sec-500 disabled:opacity-50"
427
+ >
428
+ {loading ? 'Signing in...' : 'Sign in'}
429
+ </button>
430
+ </div>
431
+ </form>
432
+ </div>
433
+ </div>
434
+ )
435
+ }
436
+ ```
437
+
438
+ ### 10. Create Dashboard Page (Event-Based)
439
+
440
+ Create `src/pages/Dashboard.tsx`:
441
+
442
+ ```typescript
443
+ // src/pages/Dashboard.tsx
444
+ import React from 'react'
445
+ import { useUnifiedAuth } from '@jmruthers/pace-core/providers'
446
+ import { PagePermissionGuard } from '@jmruthers/pace-core/rbac'
447
+ import { Link } from 'react-router-dom'
448
+ import { supabase } from '../lib/supabase'
449
+
450
+ export function Dashboard() {
451
+ const { user, selectedEventId, selectedOrganisationId } = useUnifiedAuth()
452
+
453
+ if (!user) {
454
+ return <div>Please log in</div>
455
+ }
456
+
457
+ return (
458
+ <div className="min-h-screen bg-sec-50">
459
+ <nav className="bg-white shadow">
460
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
461
+ <div className="flex justify-between h-16">
462
+ <div className="flex items-center">
463
+ <h1 className="text-xl font-semibold">Event Dashboard</h1>
464
+ </div>
465
+ <div className="flex items-center space-x-4">
466
+ <span className="text-sm text-sec-700">
467
+ {user.email} | Event: {selectedEventId || 'None selected'} | Org: {selectedOrganisationId}
468
+ </span>
469
+ <button
470
+ onClick={() => supabase.auth.signOut()}
471
+ className="text-sm text-sec-500 hover:text-sec-700"
472
+ >
473
+ Sign out
474
+ </button>
475
+ </div>
476
+ </div>
477
+ </div>
478
+ </nav>
479
+
480
+ <main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
481
+ <div className="px-4 py-6 sm:px-0">
482
+ <div className="border-4 border-dashed border-sec-200 rounded-lg h-96 p-8">
483
+ <h2 className="text-2xl font-bold mb-4">Welcome to your Event Dashboard</h2>
484
+
485
+ {/* CRITICAL: PagePermissionGuard automatically resolves organisation from event */}
486
+ {/* For event-based apps, you only need eventId - organisationId is resolved automatically */}
487
+ <PagePermissionGuard
488
+ pageName="dashboard"
489
+ operation="read"
490
+ fallback={
491
+ <div>
492
+ <p>You don't have permission to view the dashboard</p>
493
+ <p className="text-sm text-sec-600 mt-2">
494
+ {!selectedEventId && 'Please select an event first'}
495
+ {selectedEventId && 'Your role does not have access to this page'}
496
+ </p>
497
+ </div>
498
+ }
499
+ >
500
+ <div className="space-y-4">
501
+ <p>You have access to the dashboard!</p>
502
+ <p className="text-sm text-sec-600">
503
+ Current Event ID: {selectedEventId || 'No event selected'}
504
+ </p>
505
+ <p className="text-sm text-sec-600">
506
+ Organisation ID (resolved from event): {selectedOrganisationId || 'Resolving...'}
507
+ </p>
508
+
509
+ <div className="space-x-4">
510
+ <Link
511
+ to="/participants"
512
+ className="bg-main-500 text-main-50 px-4 py-2 rounded hover:bg-main-600"
513
+ >
514
+ View Participants
515
+ </Link>
516
+ </div>
517
+ </div>
518
+ </PagePermissionGuard>
519
+ </div>
520
+ </div>
521
+ </main>
522
+ </div>
523
+ )
524
+ }
525
+ ```
526
+
527
+ ### 11. Create Participants Page (Event-Based)
528
+
529
+ Create `src/pages/Participants.tsx`:
530
+
531
+ ```typescript
532
+ // src/pages/Participants.tsx
533
+ import React from 'react'
534
+ import { useUnifiedAuth } from '@jmruthers/pace-core/providers'
535
+ import { PagePermissionGuard } from '@jmruthers/pace-core/rbac'
536
+ import { Link } from 'react-router-dom'
537
+ import { supabase } from '../lib/supabase'
538
+
539
+ export function Participants() {
540
+ const { user, selectedEventId, selectedOrganisationId } = useUnifiedAuth()
541
+
542
+ if (!user) {
543
+ return <div>Please log in</div>
544
+ }
545
+
546
+ return (
547
+ <div className="min-h-screen bg-sec-50">
548
+ <nav className="bg-white shadow">
549
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
550
+ <div className="flex justify-between h-16">
551
+ <div className="flex items-center">
552
+ <Link to="/" className="text-main-600 hover:text-main-800 mr-4">
553
+ ← Back to Dashboard
554
+ </Link>
555
+ <h1 className="text-xl font-semibold">Participants</h1>
556
+ </div>
557
+ <div className="flex items-center space-x-4">
558
+ <span className="text-sm text-sec-700">
559
+ {user.email} | Event: {selectedEventId || 'None'} | Org: {selectedOrganisationId}
560
+ </span>
561
+ <button
562
+ onClick={() => supabase.auth.signOut()}
563
+ className="text-sm text-sec-500 hover:text-sec-700"
564
+ >
565
+ Sign out
566
+ </button>
567
+ </div>
568
+ </div>
569
+ </div>
570
+ </nav>
571
+
572
+ <main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
573
+ <div className="px-4 py-6 sm:px-0">
574
+ <div className="border-4 border-dashed border-sec-200 rounded-lg h-96 p-8">
575
+ <h2 className="text-2xl font-bold mb-4">Participant Management</h2>
576
+
577
+ {/* CRITICAL: PagePermissionGuard automatically resolves organisation from event */}
578
+ <PagePermissionGuard
579
+ pageName="participants"
580
+ operation="read"
581
+ fallback={
582
+ <div>
583
+ <p>You don't have permission to view participants</p>
584
+ <p className="text-sm text-sec-600 mt-2">
585
+ {!selectedEventId && 'Please select an event first'}
586
+ {selectedEventId && 'Your role does not have access to this page'}
587
+ </p>
588
+ </div>
589
+ }
590
+ >
591
+ <div className="space-y-4">
592
+ <p>You have access to the participants page!</p>
593
+ <p>This means your event-based RBAC system is working correctly.</p>
594
+
595
+ <div className="bg-main-100 border border-main-400 text-main-700 px-4 py-3 rounded">
596
+ <strong>Success!</strong> Your event-based RBAC setup is working correctly.
597
+ <p className="text-sm mt-2">
598
+ Event ID: {selectedEventId || 'Not set'}
599
+ <br />
600
+ Organisation ID (auto-resolved): {selectedOrganisationId || 'Resolving...'}
601
+ </p>
602
+ </div>
603
+ </div>
604
+ </PagePermissionGuard>
605
+ </div>
606
+ </div>
607
+ </main>
608
+ </div>
609
+ )
610
+ }
611
+ ```
612
+
613
+ ## 🧪 Test Your Setup
614
+
615
+ 1. **Start your development server**:
616
+ ```bash
617
+ npm run dev
618
+ ```
619
+
620
+ 2. **Navigate to your app** (usually `http://localhost:3000` or `http://localhost:5173`)
621
+
622
+ 3. **You should see the login page**
623
+
624
+ 4. **Log in with a user that has the `org_admin` role**
625
+
626
+ 5. **You should be redirected to the dashboard**
627
+ - If you see "No event selected", the EventProvider should auto-select the next event
628
+ - If no events exist, create one in the database (see step 5.4)
629
+
630
+ 6. **Click "View Participants" - you should see the participants page**
631
+
632
+ 7. **If you see "You don't have permission" messages, check the troubleshooting section below**
633
+
634
+ ## 🎉 Success Checklist
635
+
636
+ Your event-based RBAC setup is working correctly if:
637
+
638
+ **Setup Checklist:**
639
+ - [ ] `setupRBAC(supabase)` is called before rendering App
640
+ - [ ] App is registered in `rbac_apps` table with `requires_event = true` and `is_active = true`
641
+ - [ ] Pages exist in `rbac_app_pages` for your app
642
+ - [ ] Page permissions exist in `rbac_page_permissions` for your pages, roles, and organisation
643
+ - [ ] User has an active role in `rbac_organisation_roles` for the organisation
644
+ - [ ] At least one event exists in the `event` table for your organisation
645
+ - [ ] `VITE_APP_NAME` environment variable matches database `rbac_apps.name` exactly
646
+ - [ ] `EventProvider` wraps your app content
647
+ - [ ] `OrganisationProvider` is present (required even for event-based apps)
648
+
649
+ **Runtime Checklist:**
650
+ - [ ] You can log in successfully
651
+ - [ ] Browser console shows "RBAC system initialized successfully"
652
+ - [ ] An event is automatically selected (or you can select one manually)
653
+ - [ ] You see the dashboard without "Access Denied" messages
654
+ - [ ] `PagePermissionGuard` shows loading state, then renders content (not fallback)
655
+ - [ ] You can navigate to the participants page
656
+ - [ ] You see "Success! Your event-based RBAC setup is working correctly" on the participants page
657
+ - [ ] No 400/406 errors in the browser console
658
+ - [ ] No "App not found" errors in the console
659
+ - [ ] No "STRICT MODE VIOLATION" errors in the console
660
+ - [ ] No "Event context is required" errors
661
+ - [ ] Organisation ID is automatically resolved from event (visible in dashboard)
662
+
663
+ ## 🚨 Troubleshooting
664
+
665
+ ### Issue: "RBACNotInitializedError"
666
+
667
+ **Cause**: Forgot to call `setupRBAC()`.
668
+
669
+ **Solution**:
670
+ ```typescript
671
+ import { setupRBAC } from '@jmruthers/pace-core/rbac'
672
+ setupRBAC(supabase) // Must be called BEFORE rendering app
673
+ ```
674
+
675
+ ### Issue: "Event context is required" or "No event selected"
676
+
677
+ **Cause**: Event is not selected in UnifiedAuth context.
678
+
679
+ **Solutions**:
680
+
681
+ 1. **Check EventProvider is present**:
682
+ ```tsx
683
+ <EventProvider autoSelectNextEvent={true}>
684
+ <YourApp />
685
+ </EventProvider>
686
+ ```
687
+
688
+ 2. **Verify events exist for your organisation**:
689
+ ```sql
690
+ SELECT event_id, event_name, event_code, organisation_id, event_date
691
+ FROM event
692
+ WHERE organisation_id = 'your-organisation-id'::uuid
693
+ AND is_active = true
694
+ ORDER BY event_date ASC;
695
+ ```
696
+
697
+ 3. **Manually select an event** (for testing):
698
+ ```tsx
699
+ import { useUnifiedAuth } from '@jmruthers/pace-core/providers';
700
+
701
+ function TestComponent() {
702
+ const { setSelectedEventId } = useUnifiedAuth();
703
+
704
+ useEffect(() => {
705
+ // Set a test event ID
706
+ setSelectedEventId('your-event-id');
707
+ }, []);
708
+
709
+ return <div>...</div>;
710
+ }
711
+ ```
712
+
713
+ ### Issue: "Could not resolve organization from event context"
714
+
715
+ **Cause**: Event doesn't have a valid `organisation_id` or event doesn't exist.
716
+
717
+ **Solution**:
718
+ ```sql
719
+ -- Verify event has organisation_id
720
+ SELECT event_id, event_name, organisation_id
721
+ FROM event
722
+ WHERE event_id = 'your-event-id'::uuid;
723
+
724
+ -- If organisation_id is NULL, update it:
725
+ UPDATE event
726
+ SET organisation_id = 'your-organisation-id'::uuid
727
+ WHERE event_id = 'your-event-id'::uuid;
728
+ ```
729
+
730
+ ### Issue: "Access Denied" or "STRICT MODE VIOLATION" on all pages
731
+
732
+ **This is the most common issue. Check these in EXACT ORDER:**
733
+
734
+ 1. **Verify setupRBAC was called**:
735
+ - Check browser console for "RBAC system initialized successfully"
736
+ - If missing, add `setupRBAC(supabase)` in your main.tsx before rendering
737
+
738
+ 2. **Verify your app is registered as event-based**:
739
+ ```sql
740
+ SELECT id, name, display_name, requires_event, is_active
741
+ FROM rbac_apps
742
+ WHERE name = 'pace-trac';
743
+ ```
744
+ - Must return exactly 1 row
745
+ - `requires_event` must be `true` (not `false`)
746
+ - `is_active` must be `true`
747
+ - `name` must match your `VITE_APP_NAME` exactly (case-sensitive)
748
+
749
+ 3. **Check your environment variable matches database**:
750
+ ```typescript
751
+ console.log('App name from env:', import.meta.env.VITE_APP_NAME);
752
+ ```
753
+ - Must match database `rbac_apps.name` exactly
754
+
755
+ 4. **Verify pages exist for your app**:
756
+ ```sql
757
+ SELECT ap.id, ap.page_name, ap.page_description
758
+ FROM rbac_app_pages ap
759
+ JOIN rbac_apps a ON ap.app_id = a.id
760
+ WHERE a.name = 'pace-trac'
761
+ ORDER BY ap.page_name;
762
+ ```
763
+ - Must have pages for `dashboard`, `participants`, etc.
764
+
765
+ 5. **Verify user has organisation role**:
766
+ ```sql
767
+ SELECT ror.*, u.email
768
+ FROM rbac_organisation_roles ror
769
+ JOIN auth.users u ON ror.user_id = u.id
770
+ WHERE ror.user_id = 'your-user-id'::uuid
771
+ AND ror.status = 'active';
772
+ ```
773
+ - User must have at least one active role
774
+ - Must be for the organisation that owns the event
775
+
776
+ 6. **Verify event belongs to user's organisation**:
777
+ ```sql
778
+ SELECT e.event_id, e.event_name, e.organisation_id, ror.organisation_id as user_org_id
779
+ FROM event e
780
+ JOIN rbac_organisation_roles ror ON e.organisation_id = ror.organisation_id
781
+ WHERE e.event_id = 'your-event-id'::uuid
782
+ AND ror.user_id = 'your-user-id'::uuid
783
+ AND ror.status = 'active';
784
+ ```
785
+ - Event's `organisation_id` must match user's organisation role
786
+
787
+ 7. **Check page permissions exist for user's role**:
788
+ ```sql
789
+ SELECT
790
+ ap.page_name,
791
+ pp.operation,
792
+ pp.role_name,
793
+ pp.allowed,
794
+ pp.organisation_id
795
+ FROM rbac_page_permissions pp
796
+ JOIN rbac_app_pages ap ON pp.app_page_id = ap.id
797
+ JOIN rbac_apps a ON ap.app_id = a.id
798
+ WHERE a.name = 'pace-trac'
799
+ AND pp.role_name = 'org_admin' -- Replace with your user's role
800
+ AND pp.organisation_id = 'your-org-id'::uuid -- Replace with your org ID
801
+ AND pp.allowed = true;
802
+ ```
803
+ - Must have permissions for the pages you're trying to access
804
+ - Must have `allowed = true` for the operation (read, create, etc.)
805
+ - `organisation_id` must match the organisation that owns the event
806
+
807
+ 8. **Check browser console for specific errors**:
808
+ - Look for `[PagePermissionGuard]` error messages
809
+ - Look for `[useCan]` permission check logs
810
+ - Look for "Event context is required" errors
811
+ - Look for rate limit errors (if you see these, you may need to increase the limit)
812
+
813
+ ### Issue: 400/406 Database Errors
814
+
815
+ **This means your app is making incorrect database queries. Check:**
816
+
817
+ 1. **You're using the providers correctly** (Step 8)
818
+ 2. **You're not making direct database queries to RBAC tables**
819
+ 3. **You're using the PagePermissionGuard component** (not manual permission checks)
820
+ 4. **EventProvider is wrapping your app content**
821
+
822
+ ### Issue: "App not found or inactive"
823
+
824
+ **Check:**
825
+
826
+ 1. **Your app name in the database matches your environment variable exactly**
827
+ 2. **Your app is marked as active** (`is_active = true`)
828
+ 3. **Your app is marked as event-based** (`requires_event = true`)
829
+ 4. **You're using the correct app name in the UnifiedAuthProvider**
830
+
831
+ ### Issue: Components not rendering
832
+
833
+ **Check:**
834
+
835
+ 1. **You're using PagePermissionGuard (not manual permission checks)**
836
+ 2. **You're providing the correct pageName and operation**
837
+ 3. **The page exists in rbac_app_pages table**
838
+ 4. **An event is selected** (`selectedEventId` is not null)
839
+ 5. **EventProvider is wrapping your components**
840
+
841
+ ## 🔑 Key Differences: Event-Based vs Organisation-Based Apps
842
+
843
+ | Aspect | Organisation-Based | Event-Based |
844
+ |--------|-------------------|-------------|
845
+ | **Database Config** | `requires_event = false` | `requires_event = true` |
846
+ | **Required Provider** | `OrganisationProvider` | `OrganisationProvider` + `EventProvider` |
847
+ | **Context Required** | `organisationId` | `eventId` (org auto-resolved) |
848
+ | **PagePermissionGuard** | Needs `organisationId` | Needs `eventId` (org auto-resolved) |
849
+ | **Scope Resolution** | Direct from context | Organisation resolved from event |
850
+ | **Use Case** | User management, org settings | Event registration, event management |
851
+
852
+ ## 🚀 Next Steps
853
+
854
+ - **[RBAC Overview](./README.md)** - Complete RBAC system documentation
855
+ - **[API Reference](./api-reference.md)** - Complete API documentation
856
+ - **[Examples](./examples.md)** - More complex usage patterns
857
+ - **[Troubleshooting](./troubleshooting.md)** - Advanced troubleshooting
858
+ - **[Migration Guide](../migration/rbac-migration.md)** - If migrating from legacy RBAC
859
+
860
+ ## 🆘 Still Having Issues?
861
+
862
+ If you're still having problems after following this guide exactly:
863
+
864
+ 1. **Check the browser console** for specific error messages
865
+ 2. **Verify your database setup** matches the SQL commands exactly
866
+ 3. **Make sure your environment variables** are set correctly
867
+ 4. **Check that your user has the correct roles** in the database
868
+ 5. **Verify an event exists** and belongs to your organisation
869
+ 6. **Ensure EventProvider is wrapping your app** content
870
+
871
+ This guide is designed to be foolproof - if you follow it exactly, your event-based RBAC system will work correctly.
872
+