@jmruthers/pace-core 0.5.72 → 0.5.74
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-RCFGXSPQ.js → DataTable-2QR5TER5.js} +7 -5
- package/dist/UnifiedAuthProvider-K4NRGXL4.js +14 -0
- package/dist/UnifiedAuthProvider-K4NRGXL4.js.map +1 -0
- package/dist/chunk-3SP4P7NS.js +82 -0
- package/dist/chunk-3SP4P7NS.js.map +1 -0
- package/dist/chunk-B5LK25HV.js +953 -0
- package/dist/chunk-B5LK25HV.js.map +1 -0
- package/dist/{chunk-BUM2ZPYC.js → chunk-BKVGJVUR.js} +4 -4
- package/dist/{chunk-WMYLD5WP.js → chunk-C5Q5LRU5.js} +57 -36
- package/dist/chunk-C5Q5LRU5.js.map +1 -0
- package/dist/{chunk-6RBH67W7.js → chunk-CDDYJCYU.js} +2 -77
- package/dist/chunk-CDDYJCYU.js.map +1 -0
- package/dist/{chunk-GDIBOLKV.js → chunk-DG5Z55HH.js} +6 -4
- package/dist/{chunk-GDIBOLKV.js.map → chunk-DG5Z55HH.js.map} +1 -1
- package/dist/{chunk-2PDTIGS4.js → chunk-H2TNUICK.js} +98 -49
- package/dist/chunk-H2TNUICK.js.map +1 -0
- package/dist/{chunk-4CET7YQI.js → chunk-IHMMNKNA.js} +11 -947
- package/dist/chunk-IHMMNKNA.js.map +1 -0
- package/dist/{chunk-C353TCFY.js → chunk-LVQ26TCN.js} +2 -2
- package/dist/{chunk-MTI7X73I.js → chunk-ORSMVXO2.js} +7 -5
- package/dist/{chunk-MTI7X73I.js.map → chunk-ORSMVXO2.js.map} +1 -1
- package/dist/{chunk-67FGPOHX.js → chunk-UJMCGBLS.js} +6 -4
- package/dist/{chunk-67FGPOHX.js.map → chunk-UJMCGBLS.js.map} +1 -1
- package/dist/{chunk-XC4ZCSO4.js → chunk-V6BHACCH.js} +6 -4
- package/dist/{chunk-XC4ZCSO4.js.map → chunk-V6BHACCH.js.map} +1 -1
- package/dist/components.js +9 -7
- package/dist/components.js.map +1 -1
- package/dist/hooks.js +7 -5
- package/dist/hooks.js.map +1 -1
- package/dist/index.js +19 -16
- package/dist/index.js.map +1 -1
- package/dist/providers.js +10 -7
- package/dist/rbac/index.js +7 -5
- package/dist/utils.js +8 -6
- package/dist/utils.js.map +1 -1
- package/docs/api/classes/ColumnFactory.md +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/enums/FileCategory.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/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.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/SwitchProps.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/implementation-guides/data-tables.md +82 -1
- package/package.json +1 -1
- package/src/components/DataTable/components/DataTableCore.tsx +37 -3
- package/src/components/Dialog/Dialog.tsx +2 -2
- package/src/components/NavigationMenu/NavigationMenu.tsx +32 -2
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +81 -46
- package/src/components/Toast/Toast.test.tsx +1 -1
- package/src/components/Toast/Toast.tsx +1 -1
- package/dist/chunk-2PDTIGS4.js.map +0 -1
- package/dist/chunk-4CET7YQI.js.map +0 -1
- package/dist/chunk-6RBH67W7.js.map +0 -1
- package/dist/chunk-WMYLD5WP.js.map +0 -1
- /package/dist/{DataTable-RCFGXSPQ.js.map → DataTable-2QR5TER5.js.map} +0 -0
- /package/dist/{chunk-BUM2ZPYC.js.map → chunk-BKVGJVUR.js.map} +0 -0
- /package/dist/{chunk-C353TCFY.js.map → chunk-LVQ26TCN.js.map} +0 -0
|
@@ -0,0 +1,953 @@
|
|
|
1
|
+
import {
|
|
2
|
+
init_UnifiedAuthProvider,
|
|
3
|
+
useUnifiedAuth
|
|
4
|
+
} from "./chunk-IHMMNKNA.js";
|
|
5
|
+
import {
|
|
6
|
+
init_organisationContext,
|
|
7
|
+
setOrganisationContext
|
|
8
|
+
} from "./chunk-3SP4P7NS.js";
|
|
9
|
+
import {
|
|
10
|
+
DebugLogger,
|
|
11
|
+
init_debugLogger
|
|
12
|
+
} from "./chunk-CDDYJCYU.js";
|
|
13
|
+
import {
|
|
14
|
+
applyPalette,
|
|
15
|
+
clearPalette,
|
|
16
|
+
init_runtime
|
|
17
|
+
} from "./chunk-SMJZMKYN.js";
|
|
18
|
+
import {
|
|
19
|
+
__esm,
|
|
20
|
+
__export
|
|
21
|
+
} from "./chunk-PLDDJCW6.js";
|
|
22
|
+
|
|
23
|
+
// src/providers/OrganisationProvider.tsx
|
|
24
|
+
var OrganisationProvider_exports = {};
|
|
25
|
+
__export(OrganisationProvider_exports, {
|
|
26
|
+
OrganisationProvider: () => OrganisationProvider,
|
|
27
|
+
useOrganisations: () => useOrganisations
|
|
28
|
+
});
|
|
29
|
+
import { createContext, useContext, useState, useEffect, useCallback, useMemo, useRef } from "react";
|
|
30
|
+
import { useNavigate } from "react-router-dom";
|
|
31
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
32
|
+
function OrganisationProvider({ children }) {
|
|
33
|
+
const [selectedOrganisation, setSelectedOrganisation] = useState(null);
|
|
34
|
+
const [organisations, setOrganisations] = useState([]);
|
|
35
|
+
const [userMemberships, setUserMemberships] = useState([]);
|
|
36
|
+
const [roleMapState, setRoleMapState] = useState(/* @__PURE__ */ new Map());
|
|
37
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
38
|
+
const [error, setError] = useState(null);
|
|
39
|
+
const [isContextReady, setIsContextReady] = useState(false);
|
|
40
|
+
const [retryCount, setRetryCount] = useState(0);
|
|
41
|
+
const isLoadingRef = useRef(false);
|
|
42
|
+
const lastLoadTimeRef = useRef(0);
|
|
43
|
+
const hasFailedRef = useRef(false);
|
|
44
|
+
const abortControllerRef = useRef(null);
|
|
45
|
+
const { user, session, supabase, signOut } = useUnifiedAuth();
|
|
46
|
+
let navigate = null;
|
|
47
|
+
try {
|
|
48
|
+
navigate = useNavigate();
|
|
49
|
+
} catch (error2) {
|
|
50
|
+
navigate = null;
|
|
51
|
+
}
|
|
52
|
+
const clearAllCachedData = useCallback(() => {
|
|
53
|
+
localStorage.removeItem(STORAGE_KEYS.SELECTED_ORGANISATION);
|
|
54
|
+
localStorage.removeItem(STORAGE_KEYS.ORGANISATION_CONTEXT);
|
|
55
|
+
setSelectedOrganisation(null);
|
|
56
|
+
setOrganisations([]);
|
|
57
|
+
setUserMemberships([]);
|
|
58
|
+
setRoleMapState(/* @__PURE__ */ new Map());
|
|
59
|
+
setError(null);
|
|
60
|
+
setRetryCount(0);
|
|
61
|
+
setIsContextReady(false);
|
|
62
|
+
}, []);
|
|
63
|
+
const setDatabaseOrganisationContext = useCallback(async (organisation) => {
|
|
64
|
+
if (!supabase || !session) {
|
|
65
|
+
console.warn("[OrganisationProvider] No Supabase client or session available for setting organisation context");
|
|
66
|
+
setIsContextReady(false);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
await setOrganisationContext(supabase, organisation.id);
|
|
71
|
+
DebugLogger.log("OrganisationProvider", "Database organisation context set to:", organisation.display_name);
|
|
72
|
+
setIsContextReady(true);
|
|
73
|
+
} catch (error2) {
|
|
74
|
+
console.error("[OrganisationProvider] Failed to set database organisation context:", error2);
|
|
75
|
+
setIsContextReady(false);
|
|
76
|
+
}
|
|
77
|
+
}, [supabase, session]);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (selectedOrganisation && supabase && session) {
|
|
80
|
+
setIsContextReady(false);
|
|
81
|
+
(async () => {
|
|
82
|
+
await setDatabaseOrganisationContext(selectedOrganisation);
|
|
83
|
+
})();
|
|
84
|
+
} else {
|
|
85
|
+
setIsContextReady(false);
|
|
86
|
+
}
|
|
87
|
+
}, [selectedOrganisation, setDatabaseOrganisationContext, supabase, session]);
|
|
88
|
+
const loadUserOrganisations = useCallback(async () => {
|
|
89
|
+
const callId = Math.random().toString(36).substr(2, 9);
|
|
90
|
+
console.log(`[OrganisationProvider] Starting loadUserOrganisations call ${callId}`);
|
|
91
|
+
if (!user || !session || !supabase) {
|
|
92
|
+
DebugLogger.log("OrganisationProvider", "Clearing organisation state - no user, session, or supabase client");
|
|
93
|
+
setSelectedOrganisation(null);
|
|
94
|
+
setOrganisations([]);
|
|
95
|
+
setUserMemberships([]);
|
|
96
|
+
setIsLoading(false);
|
|
97
|
+
setError(null);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (isLoadingRef.current) {
|
|
101
|
+
console.log("OrganisationProvider", "Already loading, skipping duplicate load");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const now = Date.now();
|
|
105
|
+
if (now - lastLoadTimeRef.current < 2e3) {
|
|
106
|
+
console.log("OrganisationProvider", "Too soon since last load, skipping");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (abortControllerRef.current) {
|
|
110
|
+
abortControllerRef.current.abort();
|
|
111
|
+
}
|
|
112
|
+
abortControllerRef.current = new AbortController();
|
|
113
|
+
const abortSignal = abortControllerRef.current.signal;
|
|
114
|
+
lastLoadTimeRef.current = now;
|
|
115
|
+
isLoadingRef.current = true;
|
|
116
|
+
setIsLoading(true);
|
|
117
|
+
setError(null);
|
|
118
|
+
try {
|
|
119
|
+
DebugLogger.log("OrganisationProvider", "Loading organisations for user:", user.id);
|
|
120
|
+
console.log("[OrganisationProvider] Supabase client ready:", {
|
|
121
|
+
isConnected: !!supabase,
|
|
122
|
+
hasAuth: !!supabase.auth,
|
|
123
|
+
hasRpc: !!supabase.rpc
|
|
124
|
+
});
|
|
125
|
+
let memberships, membershipError;
|
|
126
|
+
try {
|
|
127
|
+
console.log("[OrganisationProvider] Making RPC call to data_user_organisation_roles_get...");
|
|
128
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
129
|
+
const timeoutId = setTimeout(() => reject(new Error("RPC call timeout after 10 seconds")), 1e4);
|
|
130
|
+
abortSignal.addEventListener("abort", () => {
|
|
131
|
+
clearTimeout(timeoutId);
|
|
132
|
+
reject(new Error("Request aborted"));
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
const rpcPromise = supabase.rpc("data_user_organisation_roles_get", {
|
|
136
|
+
p_user_id: user.id,
|
|
137
|
+
p_organisation_id: null
|
|
138
|
+
});
|
|
139
|
+
if (abortSignal.aborted) {
|
|
140
|
+
throw new Error("Request aborted");
|
|
141
|
+
}
|
|
142
|
+
const result = await Promise.race([rpcPromise, timeoutPromise]);
|
|
143
|
+
console.log("[OrganisationProvider] RPC call completed:", {
|
|
144
|
+
hasData: !!result.data,
|
|
145
|
+
hasError: !!result.error,
|
|
146
|
+
dataLength: result.data?.length || 0,
|
|
147
|
+
errorMessage: result.error?.message || "No error"
|
|
148
|
+
});
|
|
149
|
+
memberships = result.data?.filter(
|
|
150
|
+
(role) => ["org_admin", "leader", "member"].includes(role.role)
|
|
151
|
+
) || [];
|
|
152
|
+
membershipError = result.error;
|
|
153
|
+
} catch (queryError) {
|
|
154
|
+
membershipError = queryError;
|
|
155
|
+
}
|
|
156
|
+
if (membershipError) {
|
|
157
|
+
console.error("[OrganisationProvider] Error loading memberships:", membershipError);
|
|
158
|
+
if (membershipError.message?.includes("timeout")) {
|
|
159
|
+
console.log("[OrganisationProvider] RPC timed out, trying direct database query as fallback...");
|
|
160
|
+
try {
|
|
161
|
+
if (abortSignal.aborted) {
|
|
162
|
+
throw new Error("Request aborted");
|
|
163
|
+
}
|
|
164
|
+
const { data: fallbackData, error: fallbackError } = await supabase.from("rbac_organisation_roles").select(`
|
|
165
|
+
id,
|
|
166
|
+
user_id,
|
|
167
|
+
organisation_id,
|
|
168
|
+
role,
|
|
169
|
+
status,
|
|
170
|
+
granted_at,
|
|
171
|
+
granted_by,
|
|
172
|
+
revoked_at,
|
|
173
|
+
revoked_by,
|
|
174
|
+
notes,
|
|
175
|
+
created_at,
|
|
176
|
+
updated_at,
|
|
177
|
+
organisations!inner(
|
|
178
|
+
id,
|
|
179
|
+
name,
|
|
180
|
+
display_name,
|
|
181
|
+
subscription_tier,
|
|
182
|
+
settings,
|
|
183
|
+
is_active,
|
|
184
|
+
parent_id,
|
|
185
|
+
created_at,
|
|
186
|
+
updated_at
|
|
187
|
+
)
|
|
188
|
+
`).eq("user_id", user.id).eq("status", "active").is("revoked_at", null).in("role", ["org_admin", "leader", "member"]);
|
|
189
|
+
if (fallbackError) {
|
|
190
|
+
console.error("[OrganisationProvider] Fallback query also failed:", fallbackError);
|
|
191
|
+
throw membershipError;
|
|
192
|
+
}
|
|
193
|
+
console.log("[OrganisationProvider] Fallback query successful, got", fallbackData?.length || 0, "memberships");
|
|
194
|
+
memberships = fallbackData || [];
|
|
195
|
+
membershipError = null;
|
|
196
|
+
} catch (fallbackErr) {
|
|
197
|
+
console.error("[OrganisationProvider] Fallback query failed:", fallbackErr);
|
|
198
|
+
throw membershipError;
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
throw membershipError;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
DebugLogger.log("OrganisationProvider", "Raw memberships data:", memberships);
|
|
205
|
+
if (!memberships || memberships.length === 0) {
|
|
206
|
+
throw new Error("User has no active organisation memberships");
|
|
207
|
+
}
|
|
208
|
+
console.log("[OrganisationProvider] All memberships data:", memberships);
|
|
209
|
+
memberships.forEach((membership, index) => {
|
|
210
|
+
console.log(`[OrganisationProvider] Membership ${index}:`, {
|
|
211
|
+
organisation_id: membership.organisation_id,
|
|
212
|
+
type: typeof membership.organisation_id,
|
|
213
|
+
length: membership.organisation_id ? membership.organisation_id.length : "null/undefined",
|
|
214
|
+
trimmed: membership.organisation_id ? membership.organisation_id.trim() : "null/undefined"
|
|
215
|
+
});
|
|
216
|
+
if (!membership.organisation_id || membership.organisation_id.trim() === "") {
|
|
217
|
+
console.warn(`[OrganisationProvider] Membership ${index} has invalid organisation_id:`, membership);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
const organisationIds = memberships.map((m) => m.organisation_id).filter((id) => {
|
|
221
|
+
if (!id || typeof id !== "string") {
|
|
222
|
+
console.warn("[OrganisationProvider] Invalid organisation ID (not string):", id);
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
const trimmedId = id.trim();
|
|
226
|
+
if (trimmedId === "") {
|
|
227
|
+
console.warn("[OrganisationProvider] Empty organisation ID found");
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
const isValidUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(trimmedId);
|
|
231
|
+
if (!isValidUuid) {
|
|
232
|
+
console.warn("[OrganisationProvider] Invalid UUID format:", trimmedId);
|
|
233
|
+
}
|
|
234
|
+
return isValidUuid;
|
|
235
|
+
});
|
|
236
|
+
if (organisationIds.length === 0) {
|
|
237
|
+
console.warn("[OrganisationProvider] No valid organisation IDs found in memberships:", memberships);
|
|
238
|
+
throw new Error("No valid organisation IDs found in memberships");
|
|
239
|
+
}
|
|
240
|
+
DebugLogger.log("OrganisationProvider", "Valid organisation IDs:", organisationIds);
|
|
241
|
+
console.log("[OrganisationProvider] Raw organisation IDs before cleaning:", organisationIds);
|
|
242
|
+
console.log("[OrganisationProvider] Raw organisation IDs types:", organisationIds.map((id) => typeof id));
|
|
243
|
+
console.log("[OrganisationProvider] Raw organisation IDs lengths:", organisationIds.map((id) => id ? id.length : "null/undefined"));
|
|
244
|
+
const cleanOrganisationIds = organisationIds.filter((id) => {
|
|
245
|
+
const isValid = id && typeof id === "string" && id.trim() !== "";
|
|
246
|
+
if (!isValid) {
|
|
247
|
+
console.warn("[OrganisationProvider] Filtering out invalid ID:", { id, type: typeof id, length: id ? id.length : "null/undefined" });
|
|
248
|
+
}
|
|
249
|
+
return isValid;
|
|
250
|
+
}).map((id) => id.trim());
|
|
251
|
+
console.log("[OrganisationProvider] Clean organisation IDs after filtering:", cleanOrganisationIds);
|
|
252
|
+
if (cleanOrganisationIds.length === 0) {
|
|
253
|
+
console.warn("[OrganisationProvider] No clean organisation IDs after filtering:", organisationIds);
|
|
254
|
+
throw new Error("No valid organisation IDs found after cleaning");
|
|
255
|
+
}
|
|
256
|
+
DebugLogger.log("OrganisationProvider", "Clean organisation IDs for query:", cleanOrganisationIds);
|
|
257
|
+
const finalOrganisationIds = cleanOrganisationIds.filter((id) => {
|
|
258
|
+
const isEmpty = !id || id.trim() === "";
|
|
259
|
+
if (isEmpty) {
|
|
260
|
+
console.error("[OrganisationProvider] CRITICAL: Empty string found in final array:", { id, type: typeof id });
|
|
261
|
+
}
|
|
262
|
+
return !isEmpty;
|
|
263
|
+
});
|
|
264
|
+
if (finalOrganisationIds.length !== cleanOrganisationIds.length) {
|
|
265
|
+
console.error("[OrganisationProvider] CRITICAL: Empty strings were filtered out in final validation!");
|
|
266
|
+
console.error("Original:", cleanOrganisationIds);
|
|
267
|
+
console.error("Final:", finalOrganisationIds);
|
|
268
|
+
}
|
|
269
|
+
console.log("[OrganisationProvider] FINAL QUERY ARRAY:", {
|
|
270
|
+
array: finalOrganisationIds,
|
|
271
|
+
length: finalOrganisationIds.length,
|
|
272
|
+
types: finalOrganisationIds.map((id) => typeof id),
|
|
273
|
+
hasEmpty: finalOrganisationIds.some((id) => !id || id.trim() === ""),
|
|
274
|
+
stringified: JSON.stringify(finalOrganisationIds)
|
|
275
|
+
});
|
|
276
|
+
if (finalOrganisationIds.some((id) => !id || id.trim() === "")) {
|
|
277
|
+
console.error("[OrganisationProvider] ABORTING QUERY - Empty string detected in final array!");
|
|
278
|
+
throw new Error("Empty string detected in organisation IDs array - aborting query");
|
|
279
|
+
}
|
|
280
|
+
const safeOrganisationIds = [...finalOrganisationIds].filter((id) => {
|
|
281
|
+
const isValid = id && typeof id === "string" && id.trim() !== "";
|
|
282
|
+
if (!isValid) {
|
|
283
|
+
console.error("[OrganisationProvider] SAFETY FILTER: Removing invalid ID:", { id, type: typeof id });
|
|
284
|
+
}
|
|
285
|
+
return isValid;
|
|
286
|
+
});
|
|
287
|
+
console.log("[OrganisationProvider] SAFE ARRAY FOR QUERY:", {
|
|
288
|
+
original: finalOrganisationIds,
|
|
289
|
+
safe: safeOrganisationIds,
|
|
290
|
+
lengths: { original: finalOrganisationIds.length, safe: safeOrganisationIds.length }
|
|
291
|
+
});
|
|
292
|
+
console.log("[OrganisationProvider] Using direct table query with manual filtering");
|
|
293
|
+
if (abortSignal.aborted) {
|
|
294
|
+
throw new Error("Request aborted");
|
|
295
|
+
}
|
|
296
|
+
const { data: allOrganisations, error: orgError } = await supabase.from("organisations").select("id, name, display_name, subscription_tier, settings, is_active, parent_id, created_at, updated_at");
|
|
297
|
+
if (orgError) {
|
|
298
|
+
console.error("[OrganisationProvider] Error loading organisations:", orgError);
|
|
299
|
+
throw orgError;
|
|
300
|
+
}
|
|
301
|
+
const organisations2 = allOrganisations?.filter(
|
|
302
|
+
(org) => safeOrganisationIds.includes(org.id)
|
|
303
|
+
) || [];
|
|
304
|
+
const roleMap = /* @__PURE__ */ new Map();
|
|
305
|
+
memberships?.forEach((membership) => {
|
|
306
|
+
roleMap.set(membership.organisation_id, membership.role);
|
|
307
|
+
});
|
|
308
|
+
const orgs = organisations2;
|
|
309
|
+
const activeOrgs = orgs.filter((org) => org.is_active);
|
|
310
|
+
if (activeOrgs.length === 0) {
|
|
311
|
+
throw new Error("User has no access to active organisations");
|
|
312
|
+
}
|
|
313
|
+
DebugLogger.log("OrganisationProvider", "Active organisations:", activeOrgs);
|
|
314
|
+
setOrganisations(activeOrgs);
|
|
315
|
+
setUserMemberships(memberships);
|
|
316
|
+
setRoleMapState(roleMap);
|
|
317
|
+
let initialOrg = null;
|
|
318
|
+
try {
|
|
319
|
+
const persistedOrgString = localStorage.getItem(STORAGE_KEYS.SELECTED_ORGANISATION);
|
|
320
|
+
if (persistedOrgString) {
|
|
321
|
+
const persistedOrg = JSON.parse(persistedOrgString);
|
|
322
|
+
if (persistedOrg.id && typeof persistedOrg.id === "string" && persistedOrg.id.trim() !== "") {
|
|
323
|
+
const validPersistedOrg = activeOrgs.find((org) => org.id === persistedOrg.id);
|
|
324
|
+
if (validPersistedOrg) {
|
|
325
|
+
initialOrg = validPersistedOrg;
|
|
326
|
+
DebugLogger.log("OrganisationProvider", "Restored persisted organisation:", initialOrg.display_name);
|
|
327
|
+
} else {
|
|
328
|
+
console.warn("[OrganisationProvider] Persisted organisation not found in active orgs, clearing cache");
|
|
329
|
+
localStorage.removeItem(STORAGE_KEYS.SELECTED_ORGANISATION);
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
console.warn("[OrganisationProvider] Invalid persisted organisation ID, clearing cache");
|
|
333
|
+
localStorage.removeItem(STORAGE_KEYS.SELECTED_ORGANISATION);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
} catch (storageError) {
|
|
337
|
+
console.warn("[OrganisationProvider] Failed to restore persisted organisation:", storageError);
|
|
338
|
+
localStorage.removeItem(STORAGE_KEYS.SELECTED_ORGANISATION);
|
|
339
|
+
}
|
|
340
|
+
if (!initialOrg) {
|
|
341
|
+
const adminMembership = memberships.find((m) => m.role === "org_admin");
|
|
342
|
+
if (adminMembership) {
|
|
343
|
+
const foundOrg = organisations2.find((org) => org.id === adminMembership.organisation_id);
|
|
344
|
+
if (foundOrg) {
|
|
345
|
+
initialOrg = foundOrg;
|
|
346
|
+
DebugLogger.log("OrganisationProvider", "Selected org_admin organisation:", initialOrg.display_name);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (!initialOrg) {
|
|
351
|
+
initialOrg = activeOrgs[0];
|
|
352
|
+
DebugLogger.log("OrganisationProvider", "Selected first organisation:", initialOrg.display_name);
|
|
353
|
+
}
|
|
354
|
+
if (!initialOrg) {
|
|
355
|
+
throw new Error("No valid organisation found for user");
|
|
356
|
+
}
|
|
357
|
+
setSelectedOrganisation(initialOrg);
|
|
358
|
+
localStorage.setItem(STORAGE_KEYS.SELECTED_ORGANISATION, JSON.stringify(initialOrg));
|
|
359
|
+
DebugLogger.log("OrganisationProvider", "Organisation context established:", {
|
|
360
|
+
selectedOrganisation: initialOrg.display_name,
|
|
361
|
+
totalOrganisations: activeOrgs.length,
|
|
362
|
+
userRole: roleMap.get(initialOrg.id)
|
|
363
|
+
});
|
|
364
|
+
setRetryCount(0);
|
|
365
|
+
hasFailedRef.current = false;
|
|
366
|
+
} catch (err) {
|
|
367
|
+
console.error("[OrganisationProvider] Failed to load organisations:", err);
|
|
368
|
+
setError(err);
|
|
369
|
+
setRetryCount((prev) => prev + 1);
|
|
370
|
+
hasFailedRef.current = true;
|
|
371
|
+
clearAllCachedData();
|
|
372
|
+
} finally {
|
|
373
|
+
isLoadingRef.current = false;
|
|
374
|
+
setIsLoading(false);
|
|
375
|
+
abortControllerRef.current = null;
|
|
376
|
+
}
|
|
377
|
+
}, [user, session, supabase, clearAllCachedData]);
|
|
378
|
+
useEffect(() => {
|
|
379
|
+
if (user && session && supabase && !isLoading && !isLoadingRef.current) {
|
|
380
|
+
if (retryCount >= 3 || hasFailedRef.current) {
|
|
381
|
+
console.error("[OrganisationProvider] Max retry count reached or failed flag set, stopping organisation loading");
|
|
382
|
+
setError(new Error("Failed to load organisations after multiple attempts"));
|
|
383
|
+
setIsLoading(false);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (retryCount > 0 && Date.now() - lastLoadTimeRef.current < 5e3) {
|
|
387
|
+
console.log("[OrganisationProvider] Circuit breaker active - too soon since last attempt");
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
console.log("[OrganisationProvider] Authentication stable, loading organizations... (retry:", retryCount, ")");
|
|
391
|
+
loadUserOrganisations();
|
|
392
|
+
} else if (!user && !session) {
|
|
393
|
+
console.log("[OrganisationProvider] No authentication, clearing organization state");
|
|
394
|
+
setSelectedOrganisation(null);
|
|
395
|
+
setOrganisations([]);
|
|
396
|
+
setUserMemberships([]);
|
|
397
|
+
setRoleMapState(/* @__PURE__ */ new Map());
|
|
398
|
+
setIsLoading(false);
|
|
399
|
+
setError(null);
|
|
400
|
+
setRetryCount(0);
|
|
401
|
+
isLoadingRef.current = false;
|
|
402
|
+
hasFailedRef.current = false;
|
|
403
|
+
localStorage.removeItem(STORAGE_KEYS.SELECTED_ORGANISATION);
|
|
404
|
+
localStorage.removeItem(STORAGE_KEYS.ORGANISATION_CONTEXT);
|
|
405
|
+
}
|
|
406
|
+
}, [user, session, supabase, loadUserOrganisations]);
|
|
407
|
+
useEffect(() => {
|
|
408
|
+
return () => {
|
|
409
|
+
isLoadingRef.current = false;
|
|
410
|
+
hasFailedRef.current = false;
|
|
411
|
+
lastLoadTimeRef.current = 0;
|
|
412
|
+
if (abortControllerRef.current) {
|
|
413
|
+
abortControllerRef.current.abort();
|
|
414
|
+
abortControllerRef.current = null;
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
}, []);
|
|
418
|
+
const handleLogoutAndRedirect = useCallback(async () => {
|
|
419
|
+
try {
|
|
420
|
+
await signOut();
|
|
421
|
+
if (navigate) {
|
|
422
|
+
navigate("/login", { replace: true });
|
|
423
|
+
} else {
|
|
424
|
+
window.location.href = "/login";
|
|
425
|
+
}
|
|
426
|
+
} catch (error2) {
|
|
427
|
+
console.error("[OrganisationProvider] Error during logout:", error2);
|
|
428
|
+
if (navigate) {
|
|
429
|
+
navigate("/login", { replace: true });
|
|
430
|
+
} else {
|
|
431
|
+
window.location.href = "/login";
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}, [signOut, navigate]);
|
|
435
|
+
const ensureOrganisationContext = useCallback(() => {
|
|
436
|
+
if (!selectedOrganisation) {
|
|
437
|
+
throw new Error("Organisation context is required but not available");
|
|
438
|
+
}
|
|
439
|
+
return selectedOrganisation;
|
|
440
|
+
}, [selectedOrganisation]);
|
|
441
|
+
const getUserRole = useCallback((orgId) => {
|
|
442
|
+
const targetOrgId = orgId || selectedOrganisation?.id;
|
|
443
|
+
if (!targetOrgId) return "no_access";
|
|
444
|
+
return roleMapState.get(targetOrgId) || "no_access";
|
|
445
|
+
}, [roleMapState, selectedOrganisation]);
|
|
446
|
+
const validateOrganisationAccess = useCallback((orgId) => {
|
|
447
|
+
return userMemberships.some(
|
|
448
|
+
(m) => m.organisation_id === orgId && m.status === "active" && m.revoked_at === null
|
|
449
|
+
);
|
|
450
|
+
}, [userMemberships]);
|
|
451
|
+
const switchOrganisation = useCallback(async (orgId) => {
|
|
452
|
+
DebugLogger.log("OrganisationProvider", "Switching to organisation:", orgId);
|
|
453
|
+
if (!validateOrganisationAccess(orgId)) {
|
|
454
|
+
throw new Error(`User does not have access to organisation ${orgId}`);
|
|
455
|
+
}
|
|
456
|
+
const targetOrg = organisations.find((org) => org.id === orgId);
|
|
457
|
+
if (!targetOrg) {
|
|
458
|
+
throw new Error(`Organisation ${orgId} not found in user's organisations`);
|
|
459
|
+
}
|
|
460
|
+
setSelectedOrganisation(targetOrg);
|
|
461
|
+
localStorage.setItem(STORAGE_KEYS.SELECTED_ORGANISATION, JSON.stringify(targetOrg));
|
|
462
|
+
await setDatabaseOrganisationContext(targetOrg);
|
|
463
|
+
DebugLogger.log("OrganisationProvider", "Switched to organisation:", targetOrg.display_name);
|
|
464
|
+
}, [organisations, validateOrganisationAccess, setDatabaseOrganisationContext]);
|
|
465
|
+
const refreshOrganisations = useCallback(async () => {
|
|
466
|
+
if (!user || !session || !supabase) return;
|
|
467
|
+
setIsLoading(true);
|
|
468
|
+
}, [user, session, supabase]);
|
|
469
|
+
const getPrimaryOrganisation = useCallback(() => {
|
|
470
|
+
const rolePriority = ["org_admin", "leader", "member"];
|
|
471
|
+
for (const role of rolePriority) {
|
|
472
|
+
const membership = userMemberships.find((m) => m.role === role);
|
|
473
|
+
if (membership) {
|
|
474
|
+
return organisations.find((org) => org.id === membership.organisation_id) || null;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return null;
|
|
478
|
+
}, [userMemberships, organisations]);
|
|
479
|
+
const isOrganisationSecure = useCallback(() => {
|
|
480
|
+
return !!(selectedOrganisation && user);
|
|
481
|
+
}, [selectedOrganisation, user]);
|
|
482
|
+
const buildOrganisationHierarchy = useCallback((orgs) => {
|
|
483
|
+
const orgMap = /* @__PURE__ */ new Map();
|
|
484
|
+
orgs.forEach((org) => orgMap.set(org.id, org));
|
|
485
|
+
const roots = [];
|
|
486
|
+
orgs.forEach((org) => {
|
|
487
|
+
if (!org.parent_id) {
|
|
488
|
+
roots.push({
|
|
489
|
+
organisation: org,
|
|
490
|
+
children: [],
|
|
491
|
+
depth: 0
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
return roots;
|
|
496
|
+
}, []);
|
|
497
|
+
const hasValidOrganisationContext = useMemo(() => {
|
|
498
|
+
return !!(selectedOrganisation && !isLoading && !error && isContextReady);
|
|
499
|
+
}, [selectedOrganisation, isLoading, error, isContextReady]);
|
|
500
|
+
const contextValue = useMemo(() => {
|
|
501
|
+
if (!selectedOrganisation) {
|
|
502
|
+
const placeholderOrg = {
|
|
503
|
+
id: "",
|
|
504
|
+
name: "",
|
|
505
|
+
display_name: "",
|
|
506
|
+
subscription_tier: "standard",
|
|
507
|
+
settings: {},
|
|
508
|
+
is_active: false,
|
|
509
|
+
created_at: "",
|
|
510
|
+
updated_at: ""
|
|
511
|
+
};
|
|
512
|
+
return {
|
|
513
|
+
selectedOrganisation: placeholderOrg,
|
|
514
|
+
organisations: [],
|
|
515
|
+
userMemberships: [],
|
|
516
|
+
isLoading,
|
|
517
|
+
error,
|
|
518
|
+
hasValidOrganisationContext: false,
|
|
519
|
+
setSelectedOrganisation: () => {
|
|
520
|
+
},
|
|
521
|
+
switchOrganisation: async () => {
|
|
522
|
+
},
|
|
523
|
+
getUserRole: () => "no_access",
|
|
524
|
+
validateOrganisationAccess: () => false,
|
|
525
|
+
refreshOrganisations: async () => {
|
|
526
|
+
},
|
|
527
|
+
ensureOrganisationContext: () => {
|
|
528
|
+
throw new Error("No organisation context");
|
|
529
|
+
},
|
|
530
|
+
isOrganisationSecure: () => false,
|
|
531
|
+
getPrimaryOrganisation: () => null
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
return {
|
|
535
|
+
selectedOrganisation,
|
|
536
|
+
organisations,
|
|
537
|
+
userMemberships,
|
|
538
|
+
isLoading,
|
|
539
|
+
error,
|
|
540
|
+
hasValidOrganisationContext,
|
|
541
|
+
setSelectedOrganisation,
|
|
542
|
+
switchOrganisation,
|
|
543
|
+
getUserRole,
|
|
544
|
+
validateOrganisationAccess,
|
|
545
|
+
refreshOrganisations,
|
|
546
|
+
ensureOrganisationContext,
|
|
547
|
+
isOrganisationSecure,
|
|
548
|
+
getPrimaryOrganisation
|
|
549
|
+
};
|
|
550
|
+
}, [
|
|
551
|
+
selectedOrganisation,
|
|
552
|
+
organisations,
|
|
553
|
+
userMemberships,
|
|
554
|
+
isLoading,
|
|
555
|
+
error,
|
|
556
|
+
hasValidOrganisationContext,
|
|
557
|
+
switchOrganisation,
|
|
558
|
+
getUserRole,
|
|
559
|
+
validateOrganisationAccess,
|
|
560
|
+
refreshOrganisations,
|
|
561
|
+
ensureOrganisationContext,
|
|
562
|
+
isOrganisationSecure,
|
|
563
|
+
getPrimaryOrganisation
|
|
564
|
+
]);
|
|
565
|
+
if (isLoading || selectedOrganisation && !isContextReady) {
|
|
566
|
+
return /* @__PURE__ */ jsx("div", { className: "organisation-loading", role: "status", "aria-label": "Loading organisation context", children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
|
|
567
|
+
/* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4" }),
|
|
568
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: isLoading ? "Loading organisation context..." : "Setting up organisation context..." })
|
|
569
|
+
] }) }) });
|
|
570
|
+
}
|
|
571
|
+
if (error || user && !selectedOrganisation) {
|
|
572
|
+
return /* @__PURE__ */ jsx("div", { className: "organisation-error", role: "alert", children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsxs("div", { className: "text-center max-w-md mx-auto p-6", children: [
|
|
573
|
+
/* @__PURE__ */ jsx("div", { className: "text-destructive mb-4", children: /* @__PURE__ */ jsx("svg", { className: "h-12 w-12 mx-auto", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.464 0L4.35 16.5c-.77.833.192 2.5 1.732 2.5z" }) }) }),
|
|
574
|
+
/* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold text-foreground mb-2", children: "Organisation Access Required" }),
|
|
575
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground mb-4", children: error?.message || "No valid organisation context available. Please contact your administrator to be added to an organisation." }),
|
|
576
|
+
/* @__PURE__ */ jsx(
|
|
577
|
+
"button",
|
|
578
|
+
{
|
|
579
|
+
onClick: handleLogoutAndRedirect,
|
|
580
|
+
className: "px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90",
|
|
581
|
+
children: "Sign Out"
|
|
582
|
+
}
|
|
583
|
+
)
|
|
584
|
+
] }) }) });
|
|
585
|
+
}
|
|
586
|
+
return /* @__PURE__ */ jsx(OrganisationContext.Provider, { value: contextValue, children });
|
|
587
|
+
}
|
|
588
|
+
var OrganisationContext, STORAGE_KEYS, useOrganisations;
|
|
589
|
+
var init_OrganisationProvider = __esm({
|
|
590
|
+
"src/providers/OrganisationProvider.tsx"() {
|
|
591
|
+
"use strict";
|
|
592
|
+
init_UnifiedAuthProvider();
|
|
593
|
+
init_organisationContext();
|
|
594
|
+
init_debugLogger();
|
|
595
|
+
OrganisationContext = createContext(void 0);
|
|
596
|
+
STORAGE_KEYS = {
|
|
597
|
+
SELECTED_ORGANISATION: "pace-core-selected-organisation",
|
|
598
|
+
ORGANISATION_CONTEXT: "pace-core-organisation-context"
|
|
599
|
+
};
|
|
600
|
+
useOrganisations = () => {
|
|
601
|
+
const context = useContext(OrganisationContext);
|
|
602
|
+
if (!context) {
|
|
603
|
+
throw new Error("useOrganisations must be used within an OrganisationProvider");
|
|
604
|
+
}
|
|
605
|
+
return context;
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
// src/providers/EventProvider.tsx
|
|
611
|
+
var EventProvider_exports = {};
|
|
612
|
+
__export(EventProvider_exports, {
|
|
613
|
+
EventProvider: () => EventProvider,
|
|
614
|
+
useEvents: () => useEvents
|
|
615
|
+
});
|
|
616
|
+
import {
|
|
617
|
+
createContext as createContext2,
|
|
618
|
+
useContext as useContext2,
|
|
619
|
+
useState as useState2,
|
|
620
|
+
useEffect as useEffect2,
|
|
621
|
+
useLayoutEffect,
|
|
622
|
+
useCallback as useCallback2,
|
|
623
|
+
useRef as useRef2,
|
|
624
|
+
useMemo as useMemo2
|
|
625
|
+
} from "react";
|
|
626
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
627
|
+
function getNextEventByDate(events) {
|
|
628
|
+
if (!events || events.length === 0) {
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
const now = /* @__PURE__ */ new Date();
|
|
632
|
+
const futureEvents = events.filter((event) => {
|
|
633
|
+
if (!event.event_date) return false;
|
|
634
|
+
const eventDate = new Date(event.event_date);
|
|
635
|
+
return eventDate >= now;
|
|
636
|
+
});
|
|
637
|
+
if (futureEvents.length === 0) {
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
const sortedFutureEvents = futureEvents.sort((a, b) => {
|
|
641
|
+
const dateA = new Date(a.event_date);
|
|
642
|
+
const dateB = new Date(b.event_date);
|
|
643
|
+
return dateA.getTime() - dateB.getTime();
|
|
644
|
+
});
|
|
645
|
+
return sortedFutureEvents[0];
|
|
646
|
+
}
|
|
647
|
+
function EventProvider({ children }) {
|
|
648
|
+
const [events, setEvents] = useState2([]);
|
|
649
|
+
const [selectedEvent, setSelectedEventState] = useState2(null);
|
|
650
|
+
const [isLoading, setIsLoading] = useState2(true);
|
|
651
|
+
const [error, setError] = useState2(null);
|
|
652
|
+
const { user, session, supabase, appName, setSelectedEventId } = useUnifiedAuth();
|
|
653
|
+
const isInitializedRef = useRef2(false);
|
|
654
|
+
const isFetchingRef = useRef2(false);
|
|
655
|
+
const hasAutoSelectedRef = useRef2(false);
|
|
656
|
+
const userClearedEventRef = useRef2(false);
|
|
657
|
+
const setSelectedEventIdRef = useRef2(setSelectedEventId);
|
|
658
|
+
let selectedOrganisation = null;
|
|
659
|
+
let ensureOrganisationContext = null;
|
|
660
|
+
try {
|
|
661
|
+
const orgContext = useOrganisations();
|
|
662
|
+
selectedOrganisation = orgContext.selectedOrganisation;
|
|
663
|
+
ensureOrganisationContext = orgContext.ensureOrganisationContext;
|
|
664
|
+
} catch (error2) {
|
|
665
|
+
console.warn("[EventProvider] Organisation context not available:", error2);
|
|
666
|
+
}
|
|
667
|
+
const loadPersistedEvent = useCallback2(async (events2) => {
|
|
668
|
+
try {
|
|
669
|
+
let persistedEventId = sessionStorage.getItem("pace-core-selected-event");
|
|
670
|
+
if (!persistedEventId) {
|
|
671
|
+
persistedEventId = localStorage.getItem("pace-core-selected-event");
|
|
672
|
+
if (persistedEventId) {
|
|
673
|
+
sessionStorage.setItem("pace-core-selected-event", persistedEventId);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
if (persistedEventId && events2.length > 0) {
|
|
677
|
+
const persistedEvent = events2.find((event) => event.event_id === persistedEventId);
|
|
678
|
+
if (persistedEvent) {
|
|
679
|
+
DebugLogger.log("EventProvider", "Restoring persisted event:", persistedEvent.event_name);
|
|
680
|
+
setSelectedEventState(persistedEvent);
|
|
681
|
+
setSelectedEventId(persistedEventId);
|
|
682
|
+
return true;
|
|
683
|
+
} else {
|
|
684
|
+
DebugLogger.log("EventProvider", "Persisted event not found in current events, clearing storage");
|
|
685
|
+
sessionStorage.removeItem("pace-core-selected-event");
|
|
686
|
+
localStorage.removeItem("pace-core-selected-event");
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
} catch (error2) {
|
|
690
|
+
console.warn("[EventProvider] Failed to load persisted event:", error2);
|
|
691
|
+
}
|
|
692
|
+
return false;
|
|
693
|
+
}, [setSelectedEventId]);
|
|
694
|
+
const persistEventSelection = useCallback2((eventId) => {
|
|
695
|
+
try {
|
|
696
|
+
sessionStorage.setItem("pace-core-selected-event", eventId);
|
|
697
|
+
localStorage.setItem("pace-core-selected-event", eventId);
|
|
698
|
+
} catch (error2) {
|
|
699
|
+
console.warn("[EventProvider] Failed to persist event selection:", error2);
|
|
700
|
+
}
|
|
701
|
+
}, []);
|
|
702
|
+
const autoSelectNextEvent = useCallback2((events2) => {
|
|
703
|
+
const nextEvent = getNextEventByDate(events2);
|
|
704
|
+
if (nextEvent) {
|
|
705
|
+
DebugLogger.log("EventProvider", "Auto-selecting next event:", nextEvent.event_name);
|
|
706
|
+
setSelectedEventState(nextEvent);
|
|
707
|
+
setSelectedEventId(nextEvent.event_id);
|
|
708
|
+
persistEventSelection(nextEvent.event_id);
|
|
709
|
+
}
|
|
710
|
+
}, [setSelectedEventId, persistEventSelection]);
|
|
711
|
+
const fetchEvents = useCallback2(async () => {
|
|
712
|
+
if (!user || !session || !supabase || !appName || !selectedOrganisation) {
|
|
713
|
+
DebugLogger.log("EventProvider", "Missing required dependencies, skipping fetch");
|
|
714
|
+
setIsLoading(false);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
if (isFetchingRef.current) {
|
|
718
|
+
DebugLogger.log("EventProvider", "Already fetching events, skipping");
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
DebugLogger.log("EventProvider", "User and organisation found, fetching events for:", {
|
|
722
|
+
userId: user.id,
|
|
723
|
+
appName,
|
|
724
|
+
organisationId: selectedOrganisation.id,
|
|
725
|
+
organisationName: selectedOrganisation.display_name
|
|
726
|
+
});
|
|
727
|
+
isFetchingRef.current = true;
|
|
728
|
+
let isMounted = true;
|
|
729
|
+
try {
|
|
730
|
+
if (ensureOrganisationContext) {
|
|
731
|
+
await ensureOrganisationContext();
|
|
732
|
+
await setOrganisationContext(supabase, selectedOrganisation.id);
|
|
733
|
+
}
|
|
734
|
+
DebugLogger.log("EventProvider", "Calling data_user_events_get RPC with:", {
|
|
735
|
+
user_id: user.id,
|
|
736
|
+
organisation_id: selectedOrganisation.id,
|
|
737
|
+
app_name: appName
|
|
738
|
+
});
|
|
739
|
+
const { data, error: rpcError } = await supabase.rpc("data_user_events_get", {
|
|
740
|
+
p_user_id: user.id,
|
|
741
|
+
p_organisation_id: selectedOrganisation.id,
|
|
742
|
+
p_app_name: appName
|
|
743
|
+
});
|
|
744
|
+
DebugLogger.log("EventProvider", "RPC response:", {
|
|
745
|
+
data,
|
|
746
|
+
error: rpcError,
|
|
747
|
+
dataLength: data?.length || 0,
|
|
748
|
+
organisationId: selectedOrganisation.id
|
|
749
|
+
});
|
|
750
|
+
if (rpcError) {
|
|
751
|
+
throw new Error(rpcError.message || "Failed to fetch events");
|
|
752
|
+
}
|
|
753
|
+
if (isMounted) {
|
|
754
|
+
const eventsData = data || [];
|
|
755
|
+
console.log("[EventProvider] Loaded events:", eventsData.map((event) => ({
|
|
756
|
+
eventId: event.event_id,
|
|
757
|
+
eventName: event.event_name,
|
|
758
|
+
organisationId: event.organisation_id,
|
|
759
|
+
selectedOrganisationId: selectedOrganisation?.id
|
|
760
|
+
})));
|
|
761
|
+
const transformedEvents = eventsData.map((event) => ({
|
|
762
|
+
id: event.event_id,
|
|
763
|
+
// Use event_id as the primary id
|
|
764
|
+
event_id: event.event_id,
|
|
765
|
+
event_name: event.event_name,
|
|
766
|
+
event_date: event.event_date,
|
|
767
|
+
event_venue: event.event_venue,
|
|
768
|
+
event_participants: event.event_participants,
|
|
769
|
+
event_colours: event.event_colours,
|
|
770
|
+
event_logo: "",
|
|
771
|
+
// No logo field in event table
|
|
772
|
+
organisation_id: event.organisation_id,
|
|
773
|
+
is_visible: event.is_visible,
|
|
774
|
+
// Legacy compatibility
|
|
775
|
+
name: event.event_name,
|
|
776
|
+
start_date: event.event_date
|
|
777
|
+
}));
|
|
778
|
+
setEvents(transformedEvents);
|
|
779
|
+
setError(null);
|
|
780
|
+
hasAutoSelectedRef.current = false;
|
|
781
|
+
const persistedEventLoaded = await loadPersistedEvent(transformedEvents);
|
|
782
|
+
if (!persistedEventLoaded) {
|
|
783
|
+
const nextEvent = getNextEventByDate(transformedEvents);
|
|
784
|
+
if (nextEvent) {
|
|
785
|
+
DebugLogger.log("EventProvider", "Auto-selecting next event after no persisted event found:", nextEvent.event_name);
|
|
786
|
+
hasAutoSelectedRef.current = true;
|
|
787
|
+
setSelectedEventState(nextEvent);
|
|
788
|
+
setSelectedEventIdRef.current(nextEvent.event_id);
|
|
789
|
+
persistEventSelection(nextEvent.event_id);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
} catch (err) {
|
|
794
|
+
console.error("[EventProvider] Error fetching events:", err);
|
|
795
|
+
const _error = err instanceof Error ? err : new Error("Unknown error occurred");
|
|
796
|
+
if (isMounted) {
|
|
797
|
+
setError(_error);
|
|
798
|
+
setEvents([]);
|
|
799
|
+
}
|
|
800
|
+
} finally {
|
|
801
|
+
if (isMounted) {
|
|
802
|
+
setIsLoading(false);
|
|
803
|
+
}
|
|
804
|
+
isFetchingRef.current = false;
|
|
805
|
+
}
|
|
806
|
+
return () => {
|
|
807
|
+
isMounted = false;
|
|
808
|
+
};
|
|
809
|
+
}, [user, session, supabase, appName, selectedOrganisation, ensureOrganisationContext, loadPersistedEvent, autoSelectNextEvent]);
|
|
810
|
+
useEffect2(() => {
|
|
811
|
+
if (!isInitializedRef.current) {
|
|
812
|
+
isInitializedRef.current = true;
|
|
813
|
+
fetchEvents();
|
|
814
|
+
}
|
|
815
|
+
}, [fetchEvents]);
|
|
816
|
+
useEffect2(() => {
|
|
817
|
+
setSelectedEventIdRef.current = setSelectedEventId;
|
|
818
|
+
}, [setSelectedEventId]);
|
|
819
|
+
useLayoutEffect(() => {
|
|
820
|
+
if (events.length > 0 && !selectedEvent && !hasAutoSelectedRef.current && !userClearedEventRef.current) {
|
|
821
|
+
const nextEvent = getNextEventByDate(events);
|
|
822
|
+
if (nextEvent) {
|
|
823
|
+
DebugLogger.log("EventProvider", "Auto-selecting next event:", nextEvent.event_name);
|
|
824
|
+
hasAutoSelectedRef.current = true;
|
|
825
|
+
setSelectedEventState(nextEvent);
|
|
826
|
+
setSelectedEventIdRef.current(nextEvent.event_id);
|
|
827
|
+
persistEventSelection(nextEvent.event_id);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}, [events, selectedEvent]);
|
|
831
|
+
useEffect2(() => {
|
|
832
|
+
console.log("[EventProvider] Event theming useEffect triggered, selectedEvent:", selectedEvent?.event_name, selectedEvent?.event_colours);
|
|
833
|
+
if (!selectedEvent) {
|
|
834
|
+
console.log("[EventProvider] No selected event, clearing palette");
|
|
835
|
+
clearPalette();
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
const eventColours = selectedEvent.event_colours;
|
|
839
|
+
console.log("[EventProvider] Event colours:", eventColours);
|
|
840
|
+
console.log("[EventProvider] Event colours keys:", Object.keys(eventColours || {}));
|
|
841
|
+
if (!eventColours || typeof eventColours !== "object") {
|
|
842
|
+
console.log("[EventProvider] No valid event_colours, clearing palette");
|
|
843
|
+
clearPalette();
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
const evMainPalette = eventColours["ev-main"] || eventColours.main || {};
|
|
847
|
+
const evSecPalette = eventColours["ev-sec"] || eventColours.sec || {};
|
|
848
|
+
const evAccPalette = eventColours["ev-acc"] || eventColours.acc || {};
|
|
849
|
+
console.log("[EventProvider] Mapped palettes:", {
|
|
850
|
+
"ev-main": Object.keys(evMainPalette),
|
|
851
|
+
"ev-sec": Object.keys(evSecPalette),
|
|
852
|
+
"ev-acc": Object.keys(evAccPalette)
|
|
853
|
+
});
|
|
854
|
+
const fullPalette = {
|
|
855
|
+
main: evMainPalette,
|
|
856
|
+
sec: evSecPalette,
|
|
857
|
+
acc: evAccPalette
|
|
858
|
+
};
|
|
859
|
+
if (!fullPalette.main && !fullPalette.sec && !fullPalette.acc) {
|
|
860
|
+
console.log("[EventProvider] No valid palettes found in event_colours, clearing palette");
|
|
861
|
+
clearPalette();
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
console.log("[EventProvider] Applying full palette:", fullPalette);
|
|
865
|
+
try {
|
|
866
|
+
applyPalette(fullPalette);
|
|
867
|
+
console.log("[EventProvider] Palette applied successfully");
|
|
868
|
+
} catch (error2) {
|
|
869
|
+
console.error("[EventProvider] Failed to apply event palette:", error2);
|
|
870
|
+
}
|
|
871
|
+
}, [selectedEvent]);
|
|
872
|
+
const setSelectedEvent = useCallback2((event) => {
|
|
873
|
+
if (event) {
|
|
874
|
+
try {
|
|
875
|
+
console.log("[EventProvider] Event selection validation:", {
|
|
876
|
+
eventId: event.event_id,
|
|
877
|
+
eventName: event.event_name,
|
|
878
|
+
eventOrganisationId: event.organisation_id,
|
|
879
|
+
selectedOrganisationId: selectedOrganisation?.id,
|
|
880
|
+
selectedOrganisationName: selectedOrganisation?.display_name,
|
|
881
|
+
match: event.organisation_id === selectedOrganisation?.id
|
|
882
|
+
});
|
|
883
|
+
if (selectedOrganisation && event.organisation_id !== selectedOrganisation.id) {
|
|
884
|
+
console.error("[EventProvider] Event organisation_id does not match selected organisation", {
|
|
885
|
+
eventOrganisationId: event.organisation_id,
|
|
886
|
+
selectedOrganisationId: selectedOrganisation.id,
|
|
887
|
+
eventName: event.event_name
|
|
888
|
+
});
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
} catch (error2) {
|
|
892
|
+
console.error("[EventProvider] Error during event validation:", error2);
|
|
893
|
+
}
|
|
894
|
+
setSelectedEventState(event);
|
|
895
|
+
setSelectedEventId(event.event_id);
|
|
896
|
+
persistEventSelection(event.event_id);
|
|
897
|
+
userClearedEventRef.current = false;
|
|
898
|
+
} else {
|
|
899
|
+
setSelectedEventState(null);
|
|
900
|
+
setSelectedEventId(null);
|
|
901
|
+
sessionStorage.removeItem("pace-core-selected-event");
|
|
902
|
+
localStorage.removeItem("pace-core-selected-event");
|
|
903
|
+
hasAutoSelectedRef.current = false;
|
|
904
|
+
userClearedEventRef.current = true;
|
|
905
|
+
}
|
|
906
|
+
}, [selectedOrganisation, setSelectedEventId, persistEventSelection]);
|
|
907
|
+
const refreshEvents = useCallback2(async () => {
|
|
908
|
+
isInitializedRef.current = false;
|
|
909
|
+
isFetchingRef.current = false;
|
|
910
|
+
userClearedEventRef.current = false;
|
|
911
|
+
await fetchEvents();
|
|
912
|
+
}, [fetchEvents]);
|
|
913
|
+
const contextValue = useMemo2(() => ({
|
|
914
|
+
events,
|
|
915
|
+
selectedEvent,
|
|
916
|
+
isLoading,
|
|
917
|
+
error,
|
|
918
|
+
setSelectedEvent,
|
|
919
|
+
refreshEvents
|
|
920
|
+
}), [events, selectedEvent, isLoading, error, setSelectedEvent, refreshEvents]);
|
|
921
|
+
return /* @__PURE__ */ jsx2(EventContext.Provider, { value: contextValue, children });
|
|
922
|
+
}
|
|
923
|
+
var EventContext, useEvents;
|
|
924
|
+
var init_EventProvider = __esm({
|
|
925
|
+
"src/providers/EventProvider.tsx"() {
|
|
926
|
+
"use strict";
|
|
927
|
+
init_UnifiedAuthProvider();
|
|
928
|
+
init_OrganisationProvider();
|
|
929
|
+
init_organisationContext();
|
|
930
|
+
init_debugLogger();
|
|
931
|
+
init_runtime();
|
|
932
|
+
EventContext = createContext2(void 0);
|
|
933
|
+
useEvents = () => {
|
|
934
|
+
const context = useContext2(EventContext);
|
|
935
|
+
if (context === void 0) {
|
|
936
|
+
throw new Error("useEvents must be used within an EventProvider");
|
|
937
|
+
}
|
|
938
|
+
return context;
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
export {
|
|
944
|
+
OrganisationProvider,
|
|
945
|
+
useOrganisations,
|
|
946
|
+
OrganisationProvider_exports,
|
|
947
|
+
init_OrganisationProvider,
|
|
948
|
+
useEvents,
|
|
949
|
+
EventProvider,
|
|
950
|
+
EventProvider_exports,
|
|
951
|
+
init_EventProvider
|
|
952
|
+
};
|
|
953
|
+
//# sourceMappingURL=chunk-B5LK25HV.js.map
|