@jmruthers/pace-core 0.5.57 → 0.5.59
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.
- package/dist/{DataTable-GABLO6H5.js → DataTable-BKUUSZC5.js} +5 -5
- package/dist/{chunk-77TYN5B4.js → chunk-5MLDIGHB.js} +3 -3
- package/dist/{chunk-YI25YXER.js → chunk-CGSYCF2W.js} +30 -14
- package/dist/chunk-CGSYCF2W.js.map +1 -0
- package/dist/{chunk-CACNKQ3B.js → chunk-E4FPK232.js} +3 -3
- package/dist/{chunk-CACNKQ3B.js.map → chunk-E4FPK232.js.map} +1 -1
- package/dist/{chunk-DH7VDOQQ.js → chunk-HSK6AJKC.js} +3 -3
- package/dist/{chunk-D3X2CGAG.js → chunk-ITPVFKDH.js} +2 -2
- package/dist/{chunk-S3MPG3BA.js → chunk-OJK7SV4M.js} +5 -5
- package/dist/{chunk-S3MPG3BA.js.map → chunk-OJK7SV4M.js.map} +1 -1
- package/dist/{chunk-E3ITLZBM.js → chunk-PHWA52IO.js} +3 -3
- package/dist/{chunk-VY3DOGPU.js → chunk-QWS45MLA.js} +4 -4
- package/dist/{chunk-JO3Y2BOW.js → chunk-STLY4XWV.js} +5 -5
- package/dist/{chunk-TCX7STCC.js → chunk-W7PPXKTZ.js} +2 -2
- package/dist/components.js +7 -7
- package/dist/hooks.js +4 -4
- package/dist/index.js +10 -10
- package/dist/providers.js +3 -3
- package/dist/rbac/index.js +5 -5
- package/dist/types.d.ts +7 -1
- package/dist/utils.js +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventContextType.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/EventProviderProps.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContextType.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACProviderProps.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +2 -2
- package/docs/getting-started/quick-start.md +21 -3
- package/docs/rbac/getting-started.md +43 -0
- package/docs/troubleshooting/common-issues.md +116 -1516
- package/package.json +1 -1
- package/src/__tests__/TESTING_GUIDELINES.md +1 -1
- package/src/hooks/__tests__/useRBAC.unit.test.ts +8 -8
- package/src/providers/EventProvider.tsx +32 -17
- package/src/providers/OrganisationProvider.tsx +2 -2
- package/src/rbac/hooks/useRBAC.ts +2 -2
- package/src/types/supabase.ts +17 -0
- package/dist/chunk-YI25YXER.js.map +0 -1
- /package/dist/{DataTable-GABLO6H5.js.map → DataTable-BKUUSZC5.js.map} +0 -0
- /package/dist/{chunk-77TYN5B4.js.map → chunk-5MLDIGHB.js.map} +0 -0
- /package/dist/{chunk-DH7VDOQQ.js.map → chunk-HSK6AJKC.js.map} +0 -0
- /package/dist/{chunk-D3X2CGAG.js.map → chunk-ITPVFKDH.js.map} +0 -0
- /package/dist/{chunk-E3ITLZBM.js.map → chunk-PHWA52IO.js.map} +0 -0
- /package/dist/{chunk-VY3DOGPU.js.map → chunk-QWS45MLA.js.map} +0 -0
- /package/dist/{chunk-JO3Y2BOW.js.map → chunk-STLY4XWV.js.map} +0 -0
- /package/dist/{chunk-TCX7STCC.js.map → chunk-W7PPXKTZ.js.map} +0 -0
package/package.json
CHANGED
|
@@ -528,7 +528,7 @@ mockSupabase.rpc.mockImplementation((functionName: string, params) => {
|
|
|
528
528
|
// useRBAC makes TWO RPC calls - must mock both
|
|
529
529
|
const setupRBACMock = (permissions: any[] = []) => {
|
|
530
530
|
mockSupabase.rpc.mockImplementation((functionName, params) => {
|
|
531
|
-
if (functionName === '
|
|
531
|
+
if (functionName === 'util_app_resolve') {
|
|
532
532
|
return Promise.resolve({
|
|
533
533
|
data: [{ app_id: 'test-app-id', has_access: true }],
|
|
534
534
|
error: null
|
|
@@ -24,10 +24,10 @@ vi.mock('../../providers/EventProvider', () => ({
|
|
|
24
24
|
const mockSupabase = createMockSupabaseClient();
|
|
25
25
|
|
|
26
26
|
// Helper to setup RPC mocks for useRBAC hook
|
|
27
|
-
// useRBAC makes TWO RPC calls:
|
|
27
|
+
// useRBAC makes TWO RPC calls: util_app_resolve and rbac_permissions_get
|
|
28
28
|
const setupRBACMock = (permissions: any[] = []) => {
|
|
29
29
|
mockSupabase.rpc.mockImplementation((functionName: string, params?: any) => {
|
|
30
|
-
if (functionName === '
|
|
30
|
+
if (functionName === 'util_app_resolve') {
|
|
31
31
|
return Promise.resolve({
|
|
32
32
|
data: [{
|
|
33
33
|
app_id: 'test-app-id',
|
|
@@ -279,7 +279,7 @@ describe('useRBAC', () => {
|
|
|
279
279
|
|
|
280
280
|
// Fully mock RPC responses for this test
|
|
281
281
|
mockSupabase.rpc.mockImplementation((functionName: string, params?: any) => {
|
|
282
|
-
if (functionName === '
|
|
282
|
+
if (functionName === 'util_app_resolve') {
|
|
283
283
|
return Promise.resolve({
|
|
284
284
|
data: [{ app_id: 'test-app-id', app_name: 'test-app', has_access: true, is_active: true }],
|
|
285
285
|
error: null
|
|
@@ -324,7 +324,7 @@ describe('useRBAC', () => {
|
|
|
324
324
|
|
|
325
325
|
// Fully mock RPC responses for this test
|
|
326
326
|
mockSupabase.rpc.mockImplementation((functionName: string, params?: any) => {
|
|
327
|
-
if (functionName === '
|
|
327
|
+
if (functionName === 'util_app_resolve') {
|
|
328
328
|
return Promise.resolve({
|
|
329
329
|
data: [{ app_id: 'test-app-id', app_name: 'test-app', has_access: true, is_active: true }],
|
|
330
330
|
error: null
|
|
@@ -363,7 +363,7 @@ describe('useRBAC', () => {
|
|
|
363
363
|
|
|
364
364
|
// Fully mock RPC responses (reject on access check)
|
|
365
365
|
mockSupabase.rpc.mockImplementation((functionName: string, params?: any) => {
|
|
366
|
-
if (functionName === '
|
|
366
|
+
if (functionName === 'util_app_resolve') {
|
|
367
367
|
return Promise.resolve({
|
|
368
368
|
data: [{ app_id: 'test-app-id', app_name: 'test-app', has_access: true, is_active: true }],
|
|
369
369
|
error: null
|
|
@@ -402,7 +402,7 @@ describe('useRBAC', () => {
|
|
|
402
402
|
|
|
403
403
|
// Fully mock RPC responses
|
|
404
404
|
mockSupabase.rpc.mockImplementation((functionName: string, params?: any) => {
|
|
405
|
-
if (functionName === '
|
|
405
|
+
if (functionName === 'util_app_resolve') {
|
|
406
406
|
return Promise.resolve({
|
|
407
407
|
data: [{ app_id: 'test-app-id', app_name: 'test-app', has_access: true, is_active: true }],
|
|
408
408
|
error: null
|
|
@@ -670,7 +670,7 @@ describe('useRBAC', () => {
|
|
|
670
670
|
|
|
671
671
|
// Mock the app resolve to succeed, but permissions to fail
|
|
672
672
|
mockSupabase.rpc.mockImplementation((functionName: string) => {
|
|
673
|
-
if (functionName === '
|
|
673
|
+
if (functionName === 'util_app_resolve') {
|
|
674
674
|
return Promise.resolve({
|
|
675
675
|
data: [{
|
|
676
676
|
app_id: 'test-app-id',
|
|
@@ -705,7 +705,7 @@ describe('useRBAC', () => {
|
|
|
705
705
|
|
|
706
706
|
// Mock the app resolve to succeed, but permissions to fail
|
|
707
707
|
mockSupabase.rpc.mockImplementation((functionName: string) => {
|
|
708
|
-
if (functionName === '
|
|
708
|
+
if (functionName === 'util_app_resolve') {
|
|
709
709
|
return Promise.resolve({
|
|
710
710
|
data: [{
|
|
711
711
|
app_id: 'test-app-id',
|
|
@@ -180,21 +180,18 @@ export function EventProvider({ children }: EventProviderProps) {
|
|
|
180
180
|
await setOrganisationContext(supabase, selectedOrganisation.id);
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
});
|
|
183
|
+
// Call the RPC function following the established pattern
|
|
184
|
+
DebugLogger.log('EventProvider', 'Calling data_user_events_get RPC with:', {
|
|
185
|
+
user_id: user.id,
|
|
186
|
+
organisation_id: selectedOrganisation.id,
|
|
187
|
+
app_name: appName
|
|
188
|
+
});
|
|
190
189
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const { data, error: rpcError } = response || {};
|
|
190
|
+
const { data, error: rpcError } = await supabase.rpc('data_user_events_get', {
|
|
191
|
+
p_user_id: user.id,
|
|
192
|
+
p_organisation_id: selectedOrganisation.id,
|
|
193
|
+
p_app_name: appName
|
|
194
|
+
});
|
|
198
195
|
|
|
199
196
|
DebugLogger.log('EventProvider', 'RPC response:', {
|
|
200
197
|
data,
|
|
@@ -215,7 +212,25 @@ export function EventProvider({ children }: EventProviderProps) {
|
|
|
215
212
|
organisationId: event.organisation_id,
|
|
216
213
|
selectedOrganisationId: selectedOrganisation?.id
|
|
217
214
|
})));
|
|
218
|
-
|
|
215
|
+
|
|
216
|
+
// Transform the data to match our Event interface
|
|
217
|
+
const transformedEvents: Event[] = eventsData.map((event: any) => ({
|
|
218
|
+
id: event.event_id, // Use event_id as the primary id
|
|
219
|
+
event_id: event.event_id,
|
|
220
|
+
event_name: event.event_name,
|
|
221
|
+
event_date: event.event_date,
|
|
222
|
+
event_venue: event.event_venue,
|
|
223
|
+
event_participants: event.event_participants,
|
|
224
|
+
event_colours: event.event_colours,
|
|
225
|
+
event_logo: '', // No logo field in event table
|
|
226
|
+
organisation_id: event.organisation_id,
|
|
227
|
+
is_visible: event.is_visible,
|
|
228
|
+
// Legacy compatibility
|
|
229
|
+
name: event.event_name,
|
|
230
|
+
start_date: event.event_date
|
|
231
|
+
}));
|
|
232
|
+
|
|
233
|
+
setEvents(transformedEvents);
|
|
219
234
|
setError(null);
|
|
220
235
|
|
|
221
236
|
// Note: Event colors are now managed by useEventTheme hook based on route and selected event
|
|
@@ -225,11 +240,11 @@ export function EventProvider({ children }: EventProviderProps) {
|
|
|
225
240
|
hasAutoSelectedRef.current = false;
|
|
226
241
|
|
|
227
242
|
// Try to restore persisted event first
|
|
228
|
-
const persistedEventLoaded = await loadPersistedEvent(
|
|
243
|
+
const persistedEventLoaded = await loadPersistedEvent(transformedEvents);
|
|
229
244
|
|
|
230
245
|
// If no persisted event was loaded, auto-select the next event
|
|
231
246
|
if (!persistedEventLoaded) {
|
|
232
|
-
const nextEvent = getNextEventByDate(
|
|
247
|
+
const nextEvent = getNextEventByDate(transformedEvents);
|
|
233
248
|
if (nextEvent) {
|
|
234
249
|
DebugLogger.log('EventProvider', 'Auto-selecting next event after no persisted event found:', nextEvent.event_name);
|
|
235
250
|
hasAutoSelectedRef.current = true;
|
|
@@ -220,14 +220,14 @@ export function OrganisationProvider({ children }: OrganisationProviderProps) {
|
|
|
220
220
|
// Only get actual members (org_admin, leader, member) - exclude supporters
|
|
221
221
|
let memberships, membershipError;
|
|
222
222
|
try {
|
|
223
|
-
console.log("[OrganisationProvider] Making RPC call to
|
|
223
|
+
console.log("[OrganisationProvider] Making RPC call to data_user_organisation_roles_get...");
|
|
224
224
|
|
|
225
225
|
// Add timeout to prevent hanging RPC calls
|
|
226
226
|
const timeoutPromise = new Promise((_, reject) => {
|
|
227
227
|
setTimeout(() => reject(new Error('RPC call timeout after 10 seconds')), 10000);
|
|
228
228
|
});
|
|
229
229
|
|
|
230
|
-
const rpcPromise = supabase.rpc('
|
|
230
|
+
const rpcPromise = supabase.rpc('data_user_organisation_roles_get', {
|
|
231
231
|
p_user_id: user.id,
|
|
232
232
|
p_organisation_id: null
|
|
233
233
|
});
|
|
@@ -117,7 +117,7 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
117
117
|
|
|
118
118
|
try {
|
|
119
119
|
// First resolve app name to app_id using secure RPC function
|
|
120
|
-
const { data: appData, error: appError } = await supabase.rpc('
|
|
120
|
+
const { data: appData, error: appError } = await supabase.rpc('util_app_resolve', {
|
|
121
121
|
p_app_name: appName,
|
|
122
122
|
p_user_id: user.id
|
|
123
123
|
});
|
|
@@ -180,7 +180,7 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
180
180
|
|
|
181
181
|
try {
|
|
182
182
|
// First resolve app name to app_id using secure RPC function
|
|
183
|
-
const { data: appData, error: appError } = await supabase.rpc('
|
|
183
|
+
const { data: appData, error: appError } = await supabase.rpc('util_app_resolve', {
|
|
184
184
|
p_app_name: appName,
|
|
185
185
|
p_user_id: user.id
|
|
186
186
|
});
|
package/src/types/supabase.ts
CHANGED
|
@@ -115,8 +115,15 @@ export type TableName =
|
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
117
|
* Supabase RPC function names
|
|
118
|
+
*
|
|
119
|
+
* Categories:
|
|
120
|
+
* - Core RBAC (10): Permission checking, role management, session & audit
|
|
121
|
+
* - Data Access (5): Secure data access functions with RBAC checks
|
|
122
|
+
* - Utility (3): Helper functions supporting RBAC operations
|
|
123
|
+
* - Legacy (2): Deprecated functions to be removed
|
|
118
124
|
*/
|
|
119
125
|
export type RPCFunction =
|
|
126
|
+
// Core RBAC Functions
|
|
120
127
|
| 'rbac_permission_check'
|
|
121
128
|
| 'rbac_permissions_get'
|
|
122
129
|
| 'rbac_access_validate'
|
|
@@ -127,6 +134,16 @@ export type RPCFunction =
|
|
|
127
134
|
| 'rbac_role_validate'
|
|
128
135
|
| 'rbac_session_track'
|
|
129
136
|
| 'rbac_audit_log'
|
|
137
|
+
// Data Access Functions
|
|
138
|
+
| 'data_user_events_get'
|
|
139
|
+
| 'data_user_organisation_roles_get'
|
|
140
|
+
| 'data_user_organisations_get'
|
|
141
|
+
| 'data_cake_meals_get'
|
|
142
|
+
// Utility Functions
|
|
143
|
+
| 'util_app_resolve'
|
|
144
|
+
| 'debug_auth_context'
|
|
145
|
+
| 'handle_new_user'
|
|
146
|
+
// Other functions
|
|
130
147
|
| 'get_pace_user_events';
|
|
131
148
|
|
|
132
149
|
/**
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/providers/EventProvider.tsx"],"sourcesContent":["import React, {\n createContext,\n useContext,\n useState,\n useEffect,\n useLayoutEffect,\n useCallback,\n useRef,\n useMemo,\n} from 'react';\nimport { useUnifiedAuth } from './UnifiedAuthProvider';\nimport { useOrganisations } from './OrganisationProvider';\nimport { setOrganisationContext } from '../utils/organisationContext';\nimport { DebugLogger } from '../utils/debugLogger';\n\nimport { Event } from '../types/unified';\n\nexport interface EventContextType {\n events: Event[];\n selectedEvent: Event | null;\n isLoading: boolean;\n error: Error | null;\n setSelectedEvent: (event: Event | null) => void;\n refreshEvents: () => Promise<void>;\n}\n\nconst EventContext = createContext<EventContextType | undefined>(undefined);\n\nexport const useEvents = () => {\n const context = useContext(EventContext);\n if (context === undefined) {\n throw new Error('useEvents must be used within an EventProvider');\n }\n return context;\n};\n\nexport interface EventProviderProps {\n children: React.ReactNode;\n}\n\n// Helper function to get the next event by date\nfunction getNextEventByDate(events: Event[]): Event | null {\n if (!events || events.length === 0) {\n return null;\n }\n\n const now = new Date();\n const futureEvents = events.filter(event => {\n if (!event.event_date) return false;\n const eventDate = new Date(event.event_date);\n return eventDate >= now;\n });\n\n if (futureEvents.length === 0) {\n return null;\n }\n\n // Sort by date (ascending) to get the next event\n const sortedFutureEvents = futureEvents.sort((a, b) => {\n const dateA = new Date(a.event_date!);\n const dateB = new Date(b.event_date!);\n return dateA.getTime() - dateB.getTime();\n });\n\n return sortedFutureEvents[0];\n}\n\nexport function EventProvider({ children }: EventProviderProps) {\n const [events, setEvents] = useState<Event[]>([]);\n const [selectedEvent, setSelectedEventState] = useState<Event | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const { user, session, supabase, appName, setSelectedEventId } = useUnifiedAuth();\n \n // Refs to prevent infinite loops\n const isInitializedRef = useRef(false);\n const isFetchingRef = useRef(false);\n const hasAutoSelectedRef = useRef(false);\n const userClearedEventRef = useRef(false);\n const setSelectedEventIdRef = useRef(setSelectedEventId);\n \n // Try to get organisation context, but don't fail if not available\n let selectedOrganisation = null;\n let ensureOrganisationContext = null;\n \n try {\n const orgContext = useOrganisations();\n selectedOrganisation = orgContext.selectedOrganisation;\n ensureOrganisationContext = orgContext.ensureOrganisationContext;\n } catch (error) {\n console.warn('[EventProvider] Organisation context not available:', error);\n }\n\n // Load persisted event selection with tab-scoped storage\n const loadPersistedEvent = useCallback(async (events: Event[]) => {\n try {\n // Try sessionStorage first (tab-specific)\n let persistedEventId = sessionStorage.getItem('pace-core-selected-event');\n \n // Fallback to localStorage if no sessionStorage value (for new tabs)\n if (!persistedEventId) {\n persistedEventId = localStorage.getItem('pace-core-selected-event');\n // If we found a value in localStorage, also store it in sessionStorage for this tab\n if (persistedEventId) {\n sessionStorage.setItem('pace-core-selected-event', persistedEventId);\n }\n }\n \n if (persistedEventId && events.length > 0) {\n const persistedEvent = events.find(event => event.event_id === persistedEventId);\n if (persistedEvent) {\n DebugLogger.log('EventProvider', 'Restoring persisted event:', persistedEvent.event_name);\n setSelectedEventState(persistedEvent);\n setSelectedEventId(persistedEventId);\n return true;\n } else {\n DebugLogger.log('EventProvider', 'Persisted event not found in current events, clearing storage');\n // Clear invalid persisted event\n sessionStorage.removeItem('pace-core-selected-event');\n localStorage.removeItem('pace-core-selected-event');\n }\n }\n } catch (error) {\n console.warn('[EventProvider] Failed to load persisted event:', error);\n }\n return false;\n }, [setSelectedEventId]);\n\n // Persist event selection with tab-scoped storage\n const persistEventSelection = useCallback((eventId: string) => {\n try {\n // Store in sessionStorage for tab-specific persistence\n sessionStorage.setItem('pace-core-selected-event', eventId);\n // Also store in localStorage as fallback for new tabs\n localStorage.setItem('pace-core-selected-event', eventId);\n } catch (error) {\n console.warn('[EventProvider] Failed to persist event selection:', error);\n }\n }, []);\n\n // Auto-select next event\n const autoSelectNextEvent = useCallback((events: Event[]) => {\n const nextEvent = getNextEventByDate(events);\n if (nextEvent) {\n DebugLogger.log('EventProvider', 'Auto-selecting next event:', nextEvent.event_name);\n setSelectedEventState(nextEvent);\n setSelectedEventId(nextEvent.event_id);\n persistEventSelection(nextEvent.event_id);\n }\n }, [setSelectedEventId, persistEventSelection]);\n\n // Main fetch function\n const fetchEvents = useCallback(async () => {\n if (!user || !session || !supabase || !appName || !selectedOrganisation) {\n DebugLogger.log('EventProvider', 'Missing required dependencies, skipping fetch');\n setIsLoading(false);\n return;\n }\n\n // Prevent multiple simultaneous fetches\n if (isFetchingRef.current) {\n DebugLogger.log('EventProvider', 'Already fetching events, skipping');\n return;\n }\n\n DebugLogger.log('EventProvider', 'User and organisation found, fetching events for:', {\n userId: user.id,\n appName: appName,\n organisationId: selectedOrganisation.id,\n organisationName: selectedOrganisation.display_name\n });\n\n isFetchingRef.current = true;\n let isMounted = true;\n\n try {\n // Ensure organisation context is set\n if (ensureOrganisationContext) {\n await ensureOrganisationContext();\n await setOrganisationContext(supabase, selectedOrganisation.id);\n }\n\n // Resolve app name to app ID\n // Call the RPC function\n DebugLogger.log('EventProvider', 'Calling get_pace_user_events RPC with:', {\n user_uuid: user.id,\n app_name: appName,\n p_organisation_id: selectedOrganisation.id\n });\n\n const response = await supabase.rpc('get_pace_user_events', {\n user_uuid: user.id,\n app_name: appName,\n p_organisation_id: selectedOrganisation.id,\n });\n \n const { data, error: rpcError } = response || {};\n\n DebugLogger.log('EventProvider', 'RPC response:', {\n data,\n error: rpcError,\n dataLength: data?.length || 0,\n organisationId: selectedOrganisation.id\n });\n\n if (rpcError) {\n throw new Error(rpcError.message || 'Failed to fetch events');\n }\n\n if (isMounted) {\n const eventsData = data || [];\n console.log('[EventProvider] Loaded events:', eventsData.map((event: any) => ({\n eventId: event.event_id,\n eventName: event.event_name,\n organisationId: event.organisation_id,\n selectedOrganisationId: selectedOrganisation?.id\n })));\n setEvents(eventsData);\n setError(null);\n\n // Note: Event colors are now managed by useEventTheme hook based on route and selected event\n // This prevents unwanted color cycling on non-event pages\n\n // Reset auto-selection ref for new events\n hasAutoSelectedRef.current = false;\n\n // Try to restore persisted event first\n const persistedEventLoaded = await loadPersistedEvent(eventsData);\n \n // If no persisted event was loaded, auto-select the next event\n if (!persistedEventLoaded) {\n const nextEvent = getNextEventByDate(eventsData);\n if (nextEvent) {\n DebugLogger.log('EventProvider', 'Auto-selecting next event after no persisted event found:', nextEvent.event_name);\n hasAutoSelectedRef.current = true;\n setSelectedEventState(nextEvent);\n setSelectedEventIdRef.current(nextEvent.event_id);\n persistEventSelection(nextEvent.event_id);\n }\n }\n }\n } catch (err) {\n console.error('[EventProvider] Error fetching events:', err);\n const _error = err instanceof Error ? err : new Error('Unknown error occurred');\n \n if (isMounted) {\n setError(_error);\n setEvents([]);\n }\n } finally {\n if (isMounted) {\n setIsLoading(false);\n }\n isFetchingRef.current = false;\n }\n\n return () => {\n isMounted = false;\n };\n }, [user, session, supabase, appName, selectedOrganisation, ensureOrganisationContext, loadPersistedEvent, autoSelectNextEvent]);\n\n // Initialize events only once\n useEffect(() => {\n if (!isInitializedRef.current) {\n isInitializedRef.current = true;\n fetchEvents();\n }\n }, [fetchEvents]);\n\n // Update ref when setSelectedEventId changes\n useEffect(() => {\n setSelectedEventIdRef.current = setSelectedEventId;\n }, [setSelectedEventId]);\n\n // Auto-select next event when events are loaded and no event is selected\n useLayoutEffect(() => {\n if (events.length > 0 && !selectedEvent && !hasAutoSelectedRef.current && !userClearedEventRef.current) {\n const nextEvent = getNextEventByDate(events);\n if (nextEvent) {\n DebugLogger.log('EventProvider', 'Auto-selecting next event:', nextEvent.event_name);\n hasAutoSelectedRef.current = true;\n setSelectedEventState(nextEvent);\n setSelectedEventIdRef.current(nextEvent.event_id);\n persistEventSelection(nextEvent.event_id);\n }\n }\n }, [events, selectedEvent]);\n\n const setSelectedEvent = useCallback((event: Event | null) => {\n if (event) {\n // SECURITY: Validate event belongs to current organisation\n try {\n console.log('[EventProvider] Event selection validation:', {\n eventId: event.event_id,\n eventName: event.event_name,\n eventOrganisationId: event.organisation_id,\n selectedOrganisationId: selectedOrganisation?.id,\n selectedOrganisationName: selectedOrganisation?.display_name,\n match: event.organisation_id === selectedOrganisation?.id\n });\n \n if (selectedOrganisation && event.organisation_id !== selectedOrganisation.id) {\n console.error('[EventProvider] Event organisation_id does not match selected organisation', {\n eventOrganisationId: event.organisation_id,\n selectedOrganisationId: selectedOrganisation.id,\n eventName: event.event_name\n });\n return;\n }\n } catch (error) {\n console.error('[EventProvider] Error during event validation:', error);\n }\n\n setSelectedEventState(event);\n setSelectedEventId(event.event_id);\n persistEventSelection(event.event_id);\n // Reset the user cleared flag when selecting an event\n userClearedEventRef.current = false;\n } else {\n setSelectedEventState(null);\n setSelectedEventId(null);\n // Clear both sessionStorage and localStorage\n sessionStorage.removeItem('pace-core-selected-event');\n localStorage.removeItem('pace-core-selected-event');\n // Reset the auto-selection flag when clearing the event\n hasAutoSelectedRef.current = false;\n // Mark that user explicitly cleared the event to prevent auto-selection\n userClearedEventRef.current = true;\n }\n }, [selectedOrganisation, setSelectedEventId, persistEventSelection]);\n\n const refreshEvents = useCallback(async () => {\n isInitializedRef.current = false;\n isFetchingRef.current = false;\n // Reset the user cleared flag when refreshing events\n userClearedEventRef.current = false;\n await fetchEvents();\n }, [fetchEvents]);\n\n // Memoize the context value to prevent unnecessary re-renders\n const contextValue: EventContextType = useMemo(() => ({\n events,\n selectedEvent,\n isLoading,\n error,\n setSelectedEvent,\n refreshEvents,\n }), [events, selectedEvent, isLoading, error, setSelectedEvent, refreshEvents]);\n\n return (\n <EventContext.Provider value={contextValue}>\n {children}\n </EventContext.Provider>\n );\n}"],"mappings":"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","names":["error","events"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|