@jmruthers/pace-core 0.5.89 → 0.5.91
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/{AuthService-DcTI5Ov4.d.ts → AuthService-DX15wM6y.d.ts} +8 -0
- package/dist/{DataTable-PWBMKMOG.js → DataTable-VIP44OB6.js} +7 -6
- package/dist/{PublicLoadingSpinner-BQXD1fbO.d.ts → PublicLoadingSpinner-Dx5c2g3S.d.ts} +9 -3
- package/dist/{UnifiedAuthProvider-5D3HEQND.js → UnifiedAuthProvider-6JRTOFPS.js} +4 -3
- package/dist/{chunk-BNXBJOGL.js → chunk-4DYK5KCK.js} +4 -4
- package/dist/{chunk-KWICIQVK.js → chunk-7NIERLC6.js} +8 -8
- package/dist/{chunk-KWICIQVK.js.map → chunk-7NIERLC6.js.map} +1 -1
- package/dist/{chunk-KTPG5VCH.js → chunk-7XBW2P7B.js} +2 -2
- package/dist/{chunk-DP5X5ORK.js → chunk-AIV3VYBQ.js} +82 -25
- package/dist/chunk-AIV3VYBQ.js.map +1 -0
- package/dist/{chunk-YY4YYM3E.js → chunk-G2SCPUKC.js} +2 -2
- package/dist/{chunk-CXKMRKRF.js → chunk-G2YT64FA.js} +3 -3
- package/dist/{chunk-AQGF5OG7.js → chunk-GD3ENUKD.js} +3 -3
- package/dist/{chunk-7VJDS5QD.js → chunk-JDPFQV3V.js} +4 -4
- package/dist/{chunk-3RZBKQ5Y.js → chunk-JQWSAYZC.js} +2 -2
- package/dist/{chunk-A6HBIY5P.js → chunk-SMJZMKYN.js} +11 -2
- package/dist/{chunk-A6HBIY5P.js.map → chunk-SMJZMKYN.js.map} +1 -1
- package/dist/{chunk-YWAFPVJA.js → chunk-VJJNZKHO.js} +40 -18
- package/dist/chunk-VJJNZKHO.js.map +1 -0
- package/dist/{chunk-L3RV2ALE.js → chunk-VKOCWWVY.js} +6 -1
- package/dist/{chunk-L3RV2ALE.js.map → chunk-VKOCWWVY.js.map} +1 -1
- package/dist/{chunk-6UHXQH7P.js → chunk-XZHZYSAK.js} +5 -5
- package/dist/components.d.ts +1 -1
- package/dist/components.js +9 -8
- package/dist/components.js.map +1 -1
- package/dist/hooks.js +8 -8
- package/dist/index.d.ts +3 -3
- package/dist/index.js +13 -13
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +3 -2
- package/dist/rbac/index.js +8 -7
- package/dist/styles/index.js +2 -2
- package/dist/theming/runtime.js +3 -1
- package/dist/utils.js +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/DataRecord.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/EventLogoProps.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 +34 -21
- 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/RBACLogger.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/UseEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UseEventLogoReturn.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/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.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 +9 -7
- package/package.json +1 -1
- package/src/components/Header/Header.tsx +52 -15
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +4 -0
- package/src/components/PaceAppLayout/README.md +30 -0
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +10 -0
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +20 -14
- package/src/hooks/public/usePublicEvent.ts +0 -2
- package/src/services/AuthService.ts +29 -29
- package/src/services/EventService.ts +65 -0
- package/src/services/__tests__/AuthService.restoreSession.test.ts +35 -0
- package/src/services/__tests__/AuthService.test.ts +6 -7
- package/src/services/__tests__/EventService.eventColours.test.ts +72 -0
- package/dist/chunk-DP5X5ORK.js.map +0 -1
- package/dist/chunk-YWAFPVJA.js.map +0 -1
- /package/dist/{DataTable-PWBMKMOG.js.map → DataTable-VIP44OB6.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-5D3HEQND.js.map → UnifiedAuthProvider-6JRTOFPS.js.map} +0 -0
- /package/dist/{chunk-BNXBJOGL.js.map → chunk-4DYK5KCK.js.map} +0 -0
- /package/dist/{chunk-KTPG5VCH.js.map → chunk-7XBW2P7B.js.map} +0 -0
- /package/dist/{chunk-YY4YYM3E.js.map → chunk-G2SCPUKC.js.map} +0 -0
- /package/dist/{chunk-CXKMRKRF.js.map → chunk-G2YT64FA.js.map} +0 -0
- /package/dist/{chunk-AQGF5OG7.js.map → chunk-GD3ENUKD.js.map} +0 -0
- /package/dist/{chunk-7VJDS5QD.js.map → chunk-JDPFQV3V.js.map} +0 -0
- /package/dist/{chunk-3RZBKQ5Y.js.map → chunk-JQWSAYZC.js.map} +0 -0
- /package/dist/{chunk-6UHXQH7P.js.map → chunk-XZHZYSAK.js.map} +0 -0
|
@@ -292,15 +292,18 @@ export const PaceLoginPage: React.FC<PaceLoginPageProps> = ({
|
|
|
292
292
|
try {
|
|
293
293
|
const { error } = await signIn(data.email, data.password);
|
|
294
294
|
|
|
295
|
-
if (
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
295
|
+
if (error) {
|
|
296
|
+
// Throw error so LoginForm can catch and display it
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Navigation will be handled by the useEffect that checks app access
|
|
301
|
+
// Don't navigate here if requireAppAccess is true
|
|
302
|
+
if (!requireAppAccess) {
|
|
303
|
+
try {
|
|
304
|
+
navigate(onSuccessRedirectPath, { replace: true });
|
|
305
|
+
} catch (navError) {
|
|
306
|
+
console.error('Navigation error after sign-in:', navError);
|
|
304
307
|
}
|
|
305
308
|
}
|
|
306
309
|
} finally {
|
|
@@ -325,11 +328,14 @@ export const PaceLoginPage: React.FC<PaceLoginPageProps> = ({
|
|
|
325
328
|
console.error('Login error:', error);
|
|
326
329
|
}}
|
|
327
330
|
/>
|
|
328
|
-
{
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
331
|
+
{(() => {
|
|
332
|
+
const benign = !!(authError && (
|
|
333
|
+
authError.name === 'AuthSessionMissingError' || /Auth session missing/i.test(authError.message)
|
|
334
|
+
));
|
|
335
|
+
return authError && !benign ? (
|
|
336
|
+
<em className="mt-4 text-destructive text-center">{authError.message}</em>
|
|
337
|
+
) : null;
|
|
338
|
+
})()}
|
|
333
339
|
{accessError && (
|
|
334
340
|
<em className="mt-4 text-destructive text-center">
|
|
335
341
|
{accessError}
|
|
@@ -224,7 +224,6 @@ export function usePublicEvent(
|
|
|
224
224
|
event_catering_email,
|
|
225
225
|
event_news,
|
|
226
226
|
event_billing,
|
|
227
|
-
event_footer,
|
|
228
227
|
event_email
|
|
229
228
|
`)
|
|
230
229
|
.eq('event_code', eventCode)
|
|
@@ -301,7 +300,6 @@ export function usePublicEvent(
|
|
|
301
300
|
event_catering_email,
|
|
302
301
|
event_news,
|
|
303
302
|
event_billing,
|
|
304
|
-
event_footer,
|
|
305
303
|
event_email
|
|
306
304
|
`)
|
|
307
305
|
.eq('event_code', eventCode)
|
|
@@ -453,42 +453,42 @@ export class AuthService extends BaseService implements IAuthService {
|
|
|
453
453
|
// Record error but continue to attempt getUser to satisfy edge cases
|
|
454
454
|
console.debug('[AuthService] getSession returned error, attempting to fetch user anyway');
|
|
455
455
|
this.authError = sessionError;
|
|
456
|
+
|
|
457
|
+
// Attempt getUser as fallback when getSession fails
|
|
458
|
+
try {
|
|
459
|
+
const getUserFn = (this.supabaseClient.auth as any)?.getUser as (() => Promise<{ data?: { user?: User | null }, error?: AuthError | null }>) | undefined;
|
|
460
|
+
if (typeof getUserFn === 'function') {
|
|
461
|
+
const userResult = await getUserFn();
|
|
462
|
+
const currentUser = userResult?.data?.user ?? null;
|
|
463
|
+
const userError = userResult?.error ?? null;
|
|
464
|
+
|
|
465
|
+
if (currentUser) {
|
|
466
|
+
this.user = currentUser;
|
|
467
|
+
// If we got a user but no session, we still don't have a valid session
|
|
468
|
+
this.session = null;
|
|
469
|
+
}
|
|
470
|
+
if (userError && !this.authError) {
|
|
471
|
+
this.authError = userError;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
} catch (getUserError) {
|
|
475
|
+
// If getUser also fails, we've already recorded the sessionError
|
|
476
|
+
console.debug('[AuthService] getUser also failed:', getUserError);
|
|
477
|
+
}
|
|
456
478
|
}
|
|
457
479
|
|
|
458
480
|
if (currentSession) {
|
|
459
481
|
this.session = currentSession;
|
|
460
482
|
this.user = currentSession.user;
|
|
461
483
|
this.authError = null;
|
|
462
|
-
} else {
|
|
463
|
-
|
|
484
|
+
} else if (!sessionError) {
|
|
485
|
+
// Only skip getUser if we didn't already attempt it due to a sessionError
|
|
486
|
+
// Treat missing session as a normal cold-start state on public pages (e.g., login)
|
|
487
|
+
// Do not call getUser() which can raise AuthSessionMissingError and surface a noisy banner
|
|
488
|
+
console.debug('[AuthService] No active session found; treating as normal unauthenticated state');
|
|
464
489
|
this.session = null;
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
let userError: AuthError | null = null;
|
|
468
|
-
const getUserFn = (this.supabaseClient.auth as any)?.getUser as (() => Promise<{ data?: { user?: User | null }, error?: AuthError | null }>) | undefined;
|
|
469
|
-
if (typeof getUserFn === 'function') {
|
|
470
|
-
const userResult = await getUserFn();
|
|
471
|
-
currentUser = userResult?.data?.user ?? null;
|
|
472
|
-
userError = userResult?.error ?? null;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
if (userError) {
|
|
476
|
-
console.debug('[AuthService] getUser returned error during restoration');
|
|
477
|
-
this.authError = userError;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (currentUser) {
|
|
481
|
-
this.user = currentUser;
|
|
482
|
-
console.debug('[AuthService] Found user without active session during restoration');
|
|
483
|
-
} else {
|
|
484
|
-
this.user = null;
|
|
485
|
-
this.session = null;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Only clear authError if we successfully got user or had no errors
|
|
489
|
-
if (!userError && !sessionError) {
|
|
490
|
-
this.authError = null;
|
|
491
|
-
}
|
|
490
|
+
this.user = null;
|
|
491
|
+
this.authError = null;
|
|
492
492
|
}
|
|
493
493
|
|
|
494
494
|
// Finish successfully even if earlier calls reported an error, to avoid noisy warnings in benign cases
|
|
@@ -14,6 +14,7 @@ import { IEventService } from './interfaces/IEventService';
|
|
|
14
14
|
import { Event } from '../types/unified';
|
|
15
15
|
import { Organisation } from '../types/organisation';
|
|
16
16
|
import { DebugLogger } from '../utils/debugLogger';
|
|
17
|
+
import { applyPalette, clearPalette } from '../theming/runtime';
|
|
17
18
|
|
|
18
19
|
export class EventService extends BaseService implements IEventService {
|
|
19
20
|
private events: Event[] = [];
|
|
@@ -135,6 +136,8 @@ export class EventService extends BaseService implements IEventService {
|
|
|
135
136
|
this.persistEventSelection(event.event_id);
|
|
136
137
|
// Reset the user cleared flag when selecting an event
|
|
137
138
|
this.userClearedEventRef = false;
|
|
139
|
+
// Apply theme for the newly selected event
|
|
140
|
+
this.updateThemeForSelectedEvent();
|
|
138
141
|
} else {
|
|
139
142
|
this.selectedEvent = null;
|
|
140
143
|
this.setSelectedEventId?.(null);
|
|
@@ -145,6 +148,8 @@ export class EventService extends BaseService implements IEventService {
|
|
|
145
148
|
this.hasAutoSelectedRef = false;
|
|
146
149
|
// Mark that user explicitly cleared the event to prevent auto-selection
|
|
147
150
|
this.userClearedEventRef = true;
|
|
151
|
+
// Clear theme when event is cleared
|
|
152
|
+
this.updateThemeForSelectedEvent();
|
|
148
153
|
}
|
|
149
154
|
this.notify();
|
|
150
155
|
}
|
|
@@ -175,6 +180,8 @@ export class EventService extends BaseService implements IEventService {
|
|
|
175
180
|
if (persistedEvent) {
|
|
176
181
|
this.selectedEvent = persistedEvent;
|
|
177
182
|
this.setSelectedEventId?.(persistedEventId);
|
|
183
|
+
// Apply theme for persisted event
|
|
184
|
+
this.updateThemeForSelectedEvent();
|
|
178
185
|
return true;
|
|
179
186
|
} else {
|
|
180
187
|
// Clear invalid persisted event
|
|
@@ -218,6 +225,8 @@ export class EventService extends BaseService implements IEventService {
|
|
|
218
225
|
this.selectedEvent = nextEvent;
|
|
219
226
|
this.setSelectedEventId?.(nextEvent.event_id);
|
|
220
227
|
this.persistEventSelection(nextEvent.event_id);
|
|
228
|
+
// Apply theme for auto-selected event
|
|
229
|
+
this.updateThemeForSelectedEvent();
|
|
221
230
|
}
|
|
222
231
|
}
|
|
223
232
|
|
|
@@ -338,6 +347,62 @@ export class EventService extends BaseService implements IEventService {
|
|
|
338
347
|
}
|
|
339
348
|
}
|
|
340
349
|
|
|
350
|
+
/**
|
|
351
|
+
* Parse and normalize event_colours to PaletteData (supports ev-* keys and string JSON)
|
|
352
|
+
*/
|
|
353
|
+
private parseAndNormalizeEventColours(input: unknown): { main: any; sec: any; acc: any } | null {
|
|
354
|
+
try {
|
|
355
|
+
if (!input) return null;
|
|
356
|
+
let obj: any = input;
|
|
357
|
+
if (typeof input === 'string') {
|
|
358
|
+
try {
|
|
359
|
+
obj = JSON.parse(input);
|
|
360
|
+
} catch {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
} else if (typeof input !== 'object') {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const pick = (o: any, pref: string, plain: string) => (o?.[pref] ?? o?.[plain]) || null;
|
|
368
|
+
const main = pick(obj, 'ev-main', 'main');
|
|
369
|
+
const sec = pick(obj, 'ev-sec', 'sec');
|
|
370
|
+
const acc = pick(obj, 'ev-acc', 'acc');
|
|
371
|
+
if (!main && !sec && !acc) return null;
|
|
372
|
+
|
|
373
|
+
// Fill helper: ensure all TW shades exist using raw or 500 as fallback
|
|
374
|
+
const shades = ['50','100','200','300','400','500','600','700','800','900','950'];
|
|
375
|
+
const fill = (p: any) => {
|
|
376
|
+
if (!p) return {} as any;
|
|
377
|
+
const out: any = {};
|
|
378
|
+
for (const s of shades) out[s] = p[s] || p?.raw || p?.['500'];
|
|
379
|
+
if (p?.raw) out.raw = p.raw;
|
|
380
|
+
return out;
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
return { main: fill(main), sec: fill(sec), acc: fill(acc) };
|
|
384
|
+
} catch (error) {
|
|
385
|
+
console.warn('[EventService] Failed to parse/normalize event colours:', error);
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Apply or clear runtime theme variables based on the current selected event's colours
|
|
392
|
+
*/
|
|
393
|
+
private updateThemeForSelectedEvent(): void {
|
|
394
|
+
try {
|
|
395
|
+
const normalized = this.parseAndNormalizeEventColours(this.selectedEvent?.event_colours);
|
|
396
|
+
if (normalized) {
|
|
397
|
+
applyPalette(normalized);
|
|
398
|
+
} else {
|
|
399
|
+
clearPalette();
|
|
400
|
+
}
|
|
401
|
+
} catch (error) {
|
|
402
|
+
console.warn('[EventService] Failed to update theme from event colours:', error);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
341
406
|
getNextEventByDate(events?: Event[]): Event | null {
|
|
342
407
|
const eventsToUse = events || this.events;
|
|
343
408
|
if (!eventsToUse || eventsToUse.length === 0) {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { AuthService } from '../../services/AuthService';
|
|
3
|
+
|
|
4
|
+
// Minimal Supabase auth mock sufficient for restoreSession/initialize paths
|
|
5
|
+
function createSupabaseMock() {
|
|
6
|
+
const auth = {
|
|
7
|
+
getSession: vi.fn().mockResolvedValue({ data: { session: null }, error: null }),
|
|
8
|
+
getUser: vi.fn(), // should not be called when session is null
|
|
9
|
+
onAuthStateChange: vi.fn().mockImplementation((cb: any) => {
|
|
10
|
+
// Provide a subscription-like object
|
|
11
|
+
return { unsubscribe: () => {} } as any;
|
|
12
|
+
}),
|
|
13
|
+
} as any;
|
|
14
|
+
|
|
15
|
+
return { auth } as any;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('AuthService.restoreSession (no session startup)', () => {
|
|
19
|
+
it('does not call getUser() and does not set authError when session is null', async () => {
|
|
20
|
+
const supabase = createSupabaseMock();
|
|
21
|
+
const service = new AuthService(supabase);
|
|
22
|
+
|
|
23
|
+
// initialize() triggers setup listener and restoreSession()
|
|
24
|
+
await service.initialize();
|
|
25
|
+
|
|
26
|
+
// Assertions
|
|
27
|
+
expect(supabase.auth.getSession).toHaveBeenCalledTimes(1);
|
|
28
|
+
expect(supabase.auth.getUser).not.toHaveBeenCalled();
|
|
29
|
+
expect(service.getError()).toBeNull();
|
|
30
|
+
expect(service.getUser()).toBeNull();
|
|
31
|
+
expect(service.getSession()).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
|
|
@@ -518,23 +518,22 @@ describe('AuthService', () => {
|
|
|
518
518
|
expect(mockSupabase.auth.getUser).toHaveBeenCalled();
|
|
519
519
|
});
|
|
520
520
|
|
|
521
|
-
it('should
|
|
522
|
-
const userError = new AuthError('User error');
|
|
521
|
+
it('should not call getUser when getSession succeeds with no session (prevents AuthSessionMissingError)', async () => {
|
|
523
522
|
mockSupabase.auth.getSession.mockResolvedValue({
|
|
524
523
|
data: { session: null },
|
|
525
524
|
error: null
|
|
526
525
|
});
|
|
527
|
-
mockSupabase.auth.getUser.mockResolvedValue({
|
|
528
|
-
data: { user: null },
|
|
529
|
-
error: userError
|
|
530
|
-
});
|
|
531
526
|
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
532
527
|
data: { subscription: { unsubscribe: vi.fn() } }
|
|
533
528
|
});
|
|
534
529
|
|
|
535
530
|
await authService.initialize();
|
|
536
531
|
|
|
537
|
-
|
|
532
|
+
// When getSession succeeds with no session, getUser is NOT called to prevent
|
|
533
|
+
// AuthSessionMissingError from being raised on public pages (e.g., login page)
|
|
534
|
+
// This is intentional behavior to avoid noisy error banners for benign unauthenticated states
|
|
535
|
+
expect(mockSupabase.auth.getSession).toHaveBeenCalled();
|
|
536
|
+
expect(mockSupabase.auth.getUser).not.toHaveBeenCalled();
|
|
538
537
|
});
|
|
539
538
|
|
|
540
539
|
it('should restore session from storage during initialization', async () => {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { EventService } from '../../services/EventService';
|
|
3
|
+
|
|
4
|
+
function createSupabaseMock() {
|
|
5
|
+
const auth = {
|
|
6
|
+
onAuthStateChange: vi.fn().mockReturnValue({ unsubscribe: () => {} }),
|
|
7
|
+
} as any;
|
|
8
|
+
return { auth } as any;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const baseEvent = {
|
|
12
|
+
event_id: 'e1',
|
|
13
|
+
event_name: 'Test',
|
|
14
|
+
organisation_id: 'org1',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe('EventService event_colours normalization', () => {
|
|
18
|
+
it('applies palettes when event_colours uses ev-* keys (object)', async () => {
|
|
19
|
+
const supabase = createSupabaseMock();
|
|
20
|
+
const svc = new EventService(supabase, null, null, 'APP', { id: 'org1' } as any, () => {});
|
|
21
|
+
// @ts-ignore access private for test by casting
|
|
22
|
+
(svc as any).selectedEvent = {
|
|
23
|
+
...baseEvent,
|
|
24
|
+
event_colours: {
|
|
25
|
+
'ev-main': { raw: { L: 0.75, C: 0.1, H: 200 }, '500': { L: 0.75, C: 0.1, H: 200 } },
|
|
26
|
+
'ev-sec': { raw: { L: 0.7, C: 0.12, H: 260 }, '500': { L: 0.7, C: 0.12, H: 260 } },
|
|
27
|
+
'ev-acc': { raw: { L: 0.65, C: 0.14, H: 20 }, '500': { L: 0.65, C: 0.14, H: 20 } },
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Spy on document style updates by calling the method and ensuring no throw
|
|
32
|
+
expect(() => (svc as any).updateThemeForSelectedEvent()).not.toThrow();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('parses string JSON with ev-* keys', () => {
|
|
36
|
+
const supabase = createSupabaseMock();
|
|
37
|
+
const svc = new EventService(supabase, null, null, 'APP', { id: 'org1' } as any, () => {});
|
|
38
|
+
const payload = JSON.stringify({
|
|
39
|
+
'ev-main': { '500': { L: 0.75, C: 0.1, H: 200 }, raw: { L: 0.75, C: 0.1, H: 200 } },
|
|
40
|
+
'ev-sec': { '500': { L: 0.7, C: 0.12, H: 260 }, raw: { L: 0.7, C: 0.12, H: 260 } },
|
|
41
|
+
'ev-acc': { '500': { L: 0.65, C: 0.14, H: 20 }, raw: { L: 0.65, C: 0.14, H: 20 } },
|
|
42
|
+
});
|
|
43
|
+
// @ts-ignore private
|
|
44
|
+
(svc as any).selectedEvent = { ...baseEvent, event_colours: payload };
|
|
45
|
+
expect(() => (svc as any).updateThemeForSelectedEvent()).not.toThrow();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('accepts plain main/sec/acc', () => {
|
|
49
|
+
const supabase = createSupabaseMock();
|
|
50
|
+
const svc = new EventService(supabase, null, null, 'APP', { id: 'org1' } as any, () => {});
|
|
51
|
+
// @ts-ignore private
|
|
52
|
+
(svc as any).selectedEvent = {
|
|
53
|
+
...baseEvent,
|
|
54
|
+
event_colours: {
|
|
55
|
+
main: { '500': { L: 0.75, C: 0.1, H: 200 } },
|
|
56
|
+
sec: { '500': { L: 0.7, C: 0.12, H: 260 } },
|
|
57
|
+
acc: { '500': { L: 0.65, C: 0.14, H: 20 } },
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
expect(() => (svc as any).updateThemeForSelectedEvent()).not.toThrow();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('clears when invalid structure', () => {
|
|
64
|
+
const supabase = createSupabaseMock();
|
|
65
|
+
const svc = new EventService(supabase, null, null, 'APP', { id: 'org1' } as any, () => {});
|
|
66
|
+
// @ts-ignore private
|
|
67
|
+
(svc as any).selectedEvent = { ...baseEvent, event_colours: { something: {} } };
|
|
68
|
+
expect(() => (svc as any).updateThemeForSelectedEvent()).not.toThrow();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
|