@jmruthers/pace-core 0.5.1 → 0.5.4
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-GX3XERFJ.js → DataTable-ZQDRE46Q.js} +7 -6
- package/dist/{PublicLoadingSpinner-DztrzuJr.d.ts → PublicLoadingSpinner-Bq_-BeK-.d.ts} +1 -1
- package/dist/RBACProvider-BO4ilsQB.d.ts +63 -0
- package/dist/{UnifiedAuthProvider-w66zSCUf.d.ts → UnifiedAuthProvider-DGQsy-vY.d.ts} +2 -59
- package/dist/{api-ETQ6YJ3C.js → api-H5A3H4IR.js} +2 -2
- package/dist/{chunk-T3XIA4AJ.js → chunk-5H3C2SWM.js} +14 -16
- package/dist/chunk-5H3C2SWM.js.map +1 -0
- package/dist/chunk-5SIXIV7R.js +1925 -0
- package/dist/chunk-5SIXIV7R.js.map +1 -0
- package/dist/chunk-GNTALZV3.js +17 -0
- package/dist/chunk-GNTALZV3.js.map +1 -0
- package/dist/{chunk-C5G2A4PO.js → chunk-GWSBHC4J.js} +6 -6
- package/dist/{chunk-XJK2J4N6.js → chunk-HD7PYDUV.js} +4 -6
- package/dist/{chunk-XJK2J4N6.js.map → chunk-HD7PYDUV.js.map} +1 -1
- package/dist/{chunk-TGDCLPP2.js → chunk-HXX35Q2M.js} +6 -21
- package/dist/chunk-HXX35Q2M.js.map +1 -0
- package/dist/{chunk-5EL3KHOQ.js → chunk-K6B7BLSE.js} +2 -2
- package/dist/{chunk-GSNM5D6H.js → chunk-M4RW7PIP.js} +4 -4
- package/dist/{chunk-U6JDHVC2.js → chunk-PVMYVQSM.js} +6 -8
- package/dist/{chunk-U6JDHVC2.js.map → chunk-PVMYVQSM.js.map} +1 -1
- package/dist/{chunk-6CR3MRZN.js → chunk-QKHFMQ5R.js} +372 -11
- package/dist/{chunk-6CR3MRZN.js.map → chunk-QKHFMQ5R.js.map} +1 -1
- package/dist/chunk-QVYBYGT2.js +428 -0
- package/dist/chunk-QVYBYGT2.js.map +1 -0
- package/dist/{chunk-OEGRKULD.js → chunk-WJARTBCT.js} +56 -1
- package/dist/chunk-WJARTBCT.js.map +1 -0
- package/dist/components.d.ts +4 -3
- package/dist/components.js +16 -162
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +7 -9
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.js +152 -17
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -2
- package/dist/providers.js +6 -12
- package/dist/rbac/index.d.ts +167 -98
- package/dist/rbac/index.js +48 -1881
- package/dist/rbac/index.js.map +1 -1
- package/dist/styles/core.css +0 -55
- package/dist/types.d.ts +2 -2
- package/dist/{unified-CM7T0aTK.d.ts → unified-CMPjE_fv.d.ts} +1 -1
- package/dist/{usePublicRouteParams-B6i0KtXW.d.ts → usePublicRouteParams-B2OcAsur.d.ts} +1 -1
- package/dist/utils.js +12 -14
- package/dist/utils.js.map +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +73 -0
- package/docs/api/classes/MissingUserContextError.md +66 -0
- package/docs/api/classes/OrganisationContextRequiredError.md +66 -0
- package/docs/api/classes/PermissionDeniedError.md +73 -0
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +270 -0
- package/docs/api/classes/RBACCache.md +284 -0
- package/docs/api/classes/RBACEngine.md +141 -0
- package/docs/api/classes/RBACError.md +76 -0
- package/docs/api/classes/RBACNotInitializedError.md +66 -0
- package/docs/api/classes/SecureSupabaseClient.md +135 -0
- 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 +96 -0
- 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 +235 -0
- 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 +107 -0
- package/docs/api/interfaces/NavigationContextType.md +164 -0
- package/docs/api/interfaces/NavigationGuardProps.md +139 -0
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +117 -0
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +2 -2
- 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 +85 -0
- package/docs/api/interfaces/PagePermissionContextType.md +140 -0
- package/docs/api/interfaces/PagePermissionGuardProps.md +153 -0
- package/docs/api/interfaces/PagePermissionProviderProps.md +119 -0
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +153 -0
- 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 +99 -0
- package/docs/api/interfaces/RBACContextType.md +474 -0
- package/docs/api/interfaces/RBACLogger.md +112 -0
- package/docs/api/interfaces/RBACProviderProps.md +107 -0
- package/docs/api/interfaces/RoleBasedRouterContextType.md +151 -0
- package/docs/api/interfaces/RoleBasedRouterProps.md +156 -0
- package/docs/api/interfaces/RouteAccessRecord.md +107 -0
- package/docs/api/interfaces/RouteConfig.md +121 -0
- package/docs/api/interfaces/SecureDataContextType.md +168 -0
- package/docs/api/interfaces/SecureDataProviderProps.md +132 -0
- 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 +85 -85
- 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 +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +2244 -3
- package/docs/migration-guide.md +43 -18
- package/docs/styles/README.md +187 -98
- package/docs/usage.md +32 -7
- package/package.json +2 -2
- package/src/components/Footer/Footer.test.tsx +482 -0
- package/src/components/Form/Form.test.tsx +1158 -0
- package/src/components/Header/Header.test.tsx +582 -0
- package/src/components/Header/Header.tsx +1 -1
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +489 -0
- package/src/components/Input/Input.test.tsx +466 -0
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +450 -0
- package/src/components/LoginForm/LoginForm.test.tsx +816 -0
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +883 -0
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +748 -0
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +891 -0
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +475 -0
- package/src/components/PasswordReset/PasswordChangeForm.test.tsx +621 -0
- package/src/components/PasswordReset/PasswordResetForm.test.tsx +605 -0
- package/src/components/Select/Select.test.tsx +948 -0
- package/src/components/SuperAdminGuard.tsx +1 -1
- package/src/components/Toast/Toast.test.tsx +586 -0
- package/src/components/Tooltip/Tooltip.test.tsx +852 -0
- package/src/components/UserMenu/UserMenu.test.tsx +702 -0
- package/src/components/UserMenu/UserMenu.tsx +2 -2
- package/src/hooks/useDebounce.test.ts +375 -0
- package/src/hooks/useOrganisationPermissions.test.ts +528 -0
- package/src/hooks/useOrganisationSecurity.test.ts +734 -0
- package/src/hooks/usePermissionCache.test.ts +542 -0
- package/src/hooks/usePermissionCache.ts +1 -1
- package/src/index.ts +2 -3
- package/src/providers/UnifiedAuthProvider.tsx +2 -2
- package/src/providers/index.ts +3 -1
- package/src/rbac/__tests__/integration.test.tsx +218 -0
- package/src/rbac/api.test.ts +952 -0
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +843 -0
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +1007 -0
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +806 -0
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +741 -0
- package/src/rbac/hooks/index.ts +21 -0
- package/src/rbac/hooks/useCan.test.ts +461 -0
- package/src/rbac/hooks/usePermissions.test.ts +364 -0
- package/src/rbac/hooks/usePermissions.ts +567 -0
- package/src/rbac/hooks/useRBAC.simple.test.ts +90 -0
- package/src/rbac/hooks/useRBAC.test.ts +551 -0
- package/src/{hooks → rbac/hooks}/useRBAC.ts +7 -7
- package/src/rbac/index.ts +5 -10
- package/src/{providers → rbac/providers}/RBACProvider.tsx +6 -6
- package/src/rbac/providers/__tests__/RBACProvider.test.tsx +687 -0
- package/src/rbac/providers/index.ts +11 -0
- package/src/styles/core.css +0 -55
- package/src/utils/formatDate.test.ts +241 -0
- package/dist/chunk-AUE24LVR.js +0 -268
- package/dist/chunk-AUE24LVR.js.map +0 -1
- package/dist/chunk-COBPIXXQ.js +0 -379
- package/dist/chunk-COBPIXXQ.js.map +0 -1
- package/dist/chunk-OEGRKULD.js.map +0 -1
- package/dist/chunk-OYRY44Q2.js +0 -62
- package/dist/chunk-OYRY44Q2.js.map +0 -1
- package/dist/chunk-T3XIA4AJ.js.map +0 -1
- package/dist/chunk-TGDCLPP2.js.map +0 -1
- package/src/components/RBAC/PagePermissionGuard.tsx +0 -287
- package/src/components/RBAC/RBACGuard.tsx +0 -143
- package/src/components/RBAC/RBACProvider.tsx +0 -186
- package/src/components/RBAC/RoleBasedContent.tsx +0 -129
- package/src/components/RBAC/index.ts +0 -23
- package/src/rbac/hooks.ts +0 -570
- /package/dist/{DataTable-GX3XERFJ.js.map → DataTable-ZQDRE46Q.js.map} +0 -0
- /package/dist/{api-ETQ6YJ3C.js.map → api-H5A3H4IR.js.map} +0 -0
- /package/dist/{chunk-C5G2A4PO.js.map → chunk-GWSBHC4J.js.map} +0 -0
- /package/dist/{chunk-5EL3KHOQ.js.map → chunk-K6B7BLSE.js.map} +0 -0
- /package/dist/{chunk-GSNM5D6H.js.map → chunk-M4RW7PIP.js.map} +0 -0
package/src/styles/core.css
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
|
|
3
|
-
/* @source directives for Tailwind v4 content scanning */
|
|
4
|
-
@source "../../src/**/*.{js,ts,jsx,tsx}";
|
|
5
|
-
@source "../../src/components/**/*.{js,ts,jsx,tsx}";
|
|
6
|
-
@source "../../src/providers/**/*.{js,ts,jsx,tsx}";
|
|
7
|
-
@source "../../src/hooks/**/*.{js,ts,jsx,tsx}";
|
|
8
|
-
@source "../../src/utils/**/*.{js,ts,jsx,tsx}";
|
|
9
|
-
@source "../../src/types/**/*.{js,ts,jsx,tsx}";
|
|
10
|
-
@source "../../src/validation/**/*.{js,ts,jsx,tsx}";
|
|
11
|
-
@source "../../src/rbac/**/*.{js,ts,jsx,tsx}";
|
|
12
|
-
|
|
13
3
|
@theme {
|
|
14
4
|
/* reset all color variables and fonts */
|
|
15
5
|
--color-*: initial;
|
|
@@ -17,55 +7,10 @@
|
|
|
17
7
|
--font-serif: "Open Sans", sans-serif;
|
|
18
8
|
--font-mono: "Reddit Mono", monospace;
|
|
19
9
|
}
|
|
20
|
-
|
|
21
10
|
@theme static {
|
|
22
|
-
|
|
23
11
|
--app-width: 90rem;
|
|
24
|
-
|
|
25
|
-
/* MAIN palette - silver */
|
|
26
|
-
--color-main-raw: oklch(0.7 0.057 252.02);
|
|
27
|
-
--color-main-50: oklch(0.98 0.001 252.02);
|
|
28
|
-
--color-main-100: oklch(0.96 0.005 252.02);
|
|
29
|
-
--color-main-200: oklch(0.927 0.012 252.02);
|
|
30
|
-
--color-main-300: oklch(0.881 0.021 252.02);
|
|
31
|
-
--color-main-400: oklch(0.822 0.032 252.02);
|
|
32
|
-
--color-main-500: oklch(0.75 0.047 252.02);
|
|
33
|
-
--color-main-600: oklch(0.7 0.057 252.02);
|
|
34
|
-
--color-main-700: oklch(0.567 0.044 252.02);
|
|
35
|
-
--color-main-800: oklch(0.456 0.034 252.02);
|
|
36
|
-
--color-main-900: oklch(0.332 0.024 252.02);
|
|
37
|
-
--color-main-950: oklch(0.195 0.014 252.02);
|
|
38
|
-
|
|
39
|
-
/* SEC palette - violet */
|
|
40
|
-
--color-sec-raw: oklch(0.58 0.23 280.75);
|
|
41
|
-
--color-sec-50: oklch(0.98 0.003 280.75);
|
|
42
|
-
--color-sec-100: oklch(0.96 0.014 280.75);
|
|
43
|
-
--color-sec-200: oklch(0.927 0.033 280.75);
|
|
44
|
-
--color-sec-300: oklch(0.881 0.059 280.75);
|
|
45
|
-
--color-sec-400: oklch(0.822 0.093 280.75);
|
|
46
|
-
--color-sec-500: oklch(0.75 0.133 280.75);
|
|
47
|
-
--color-sec-600: oklch(0.665 0.182 280.75);
|
|
48
|
-
--color-sec-700: oklch(0.58 0.23 280.75);
|
|
49
|
-
--color-sec-800: oklch(0.456 0.158 280.75);
|
|
50
|
-
--color-sec-900: oklch(0.332 0.099 280.75);
|
|
51
|
-
--color-sec-950: oklch(0.195 0.047 280.75);
|
|
52
|
-
|
|
53
|
-
/* ACC palette - blood orange */
|
|
54
|
-
--color-acc-raw: oklch(0.64 0.21 37.76);
|
|
55
|
-
--color-acc-50: oklch(0.98 0.003 37.76);
|
|
56
|
-
--color-acc-100: oklch(0.96 0.015 37.76);
|
|
57
|
-
--color-acc-200: oklch(0.927 0.035 37.76);
|
|
58
|
-
--color-acc-300: oklch(0.881 0.063 37.76);
|
|
59
|
-
--color-acc-400: oklch(0.822 0.099 37.76);
|
|
60
|
-
--color-acc-500: oklch(0.75 0.143 37.76);
|
|
61
|
-
--color-acc-600: oklch(0.64 0.21 37.76);
|
|
62
|
-
--color-acc-700: oklch(0.567 0.177 37.76);
|
|
63
|
-
--color-acc-800: oklch(0.456 0.13 37.76);
|
|
64
|
-
--color-acc-900: oklch(0.332 0.085 37.76);
|
|
65
|
-
--color-acc-950: oklch(0.195 0.044 37.76);
|
|
66
12
|
}
|
|
67
13
|
|
|
68
|
-
|
|
69
14
|
@theme inline {
|
|
70
15
|
/* Semantic token mapping */
|
|
71
16
|
--color-border: var(--color-main-500);
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file formatDate Utility Tests
|
|
3
|
+
* @description Comprehensive tests for the formatDate utility function
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
7
|
+
import { formatDate } from './formatting';
|
|
8
|
+
|
|
9
|
+
describe('formatDate Utility', () => {
|
|
10
|
+
let originalLocale: string;
|
|
11
|
+
let originalTimezone: string;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Store original values
|
|
15
|
+
originalLocale = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
16
|
+
originalTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
vi.restoreAllMocks();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('Date Object Input', () => {
|
|
24
|
+
it('formats a Date object correctly', () => {
|
|
25
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
26
|
+
const result = formatDate(date);
|
|
27
|
+
|
|
28
|
+
expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('formats different Date objects', () => {
|
|
32
|
+
const dates = [
|
|
33
|
+
new Date('2023-12-25T00:00:00.000Z'),
|
|
34
|
+
new Date('2024-06-15T12:00:00.000Z'),
|
|
35
|
+
new Date('2024-12-31T23:59:59.999Z')
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
dates.forEach(date => {
|
|
39
|
+
const result = formatDate(date);
|
|
40
|
+
expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('String Input', () => {
|
|
46
|
+
it('formats ISO date strings correctly', () => {
|
|
47
|
+
const result = formatDate('2024-01-15T10:30:00.000Z');
|
|
48
|
+
expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('formats date-only strings correctly', () => {
|
|
52
|
+
const result = formatDate('2024-01-15');
|
|
53
|
+
expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('formats various date string formats', () => {
|
|
57
|
+
const dateStrings = [
|
|
58
|
+
'2024-01-15',
|
|
59
|
+
'2024-01-15T10:30:00Z',
|
|
60
|
+
'2024-01-15T10:30:00.000Z',
|
|
61
|
+
'2024-01-15T10:30:00+00:00'
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
dateStrings.forEach(dateString => {
|
|
65
|
+
const result = formatDate(dateString);
|
|
66
|
+
expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('Number Input', () => {
|
|
72
|
+
it('formats timestamp numbers correctly', () => {
|
|
73
|
+
const timestamp = new Date('2024-01-15T10:30:00.000Z').getTime();
|
|
74
|
+
const result = formatDate(timestamp);
|
|
75
|
+
expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('formats Unix timestamps correctly', () => {
|
|
79
|
+
const unixTimestamp = Math.floor(new Date('2024-01-15T10:30:00.000Z').getTime() / 1000);
|
|
80
|
+
const result = formatDate(unixTimestamp * 1000); // Convert back to milliseconds
|
|
81
|
+
expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('Edge Cases', () => {
|
|
86
|
+
it('handles invalid date strings gracefully', () => {
|
|
87
|
+
const invalidDates = [
|
|
88
|
+
'invalid-date',
|
|
89
|
+
'not-a-date',
|
|
90
|
+
'2024-13-45', // Invalid month and day
|
|
91
|
+
'2024-02-30' // Invalid day for February
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
invalidDates.forEach(invalidDate => {
|
|
95
|
+
const result = formatDate(invalidDate);
|
|
96
|
+
// Should return a string even for invalid dates (may be "Invalid Date")
|
|
97
|
+
expect(typeof result).toBe('string');
|
|
98
|
+
expect(result.length).toBeGreaterThan(0);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('handles empty string input', () => {
|
|
103
|
+
const result = formatDate('');
|
|
104
|
+
expect(typeof result).toBe('string');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('handles null and undefined input', () => {
|
|
108
|
+
// @ts-expect-error - Testing runtime behavior with invalid types
|
|
109
|
+
expect(() => formatDate(null)).toThrow();
|
|
110
|
+
// @ts-expect-error - Testing runtime behavior with invalid types
|
|
111
|
+
expect(() => formatDate(undefined)).toThrow();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('handles very large numbers', () => {
|
|
115
|
+
const largeNumber = Number.MAX_SAFE_INTEGER;
|
|
116
|
+
const result = formatDate(largeNumber);
|
|
117
|
+
expect(typeof result).toBe('string');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('handles negative numbers', () => {
|
|
121
|
+
const negativeNumber = -1000000;
|
|
122
|
+
const result = formatDate(negativeNumber);
|
|
123
|
+
expect(typeof result).toBe('string');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('handles zero', () => {
|
|
127
|
+
const result = formatDate(0);
|
|
128
|
+
expect(typeof result).toBe('string');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('Format Consistency', () => {
|
|
133
|
+
it('returns consistent format for same date', () => {
|
|
134
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
135
|
+
const result1 = formatDate(date);
|
|
136
|
+
const result2 = formatDate(date);
|
|
137
|
+
|
|
138
|
+
expect(result1).toBe(result2);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('returns consistent format across different input types for same date', () => {
|
|
142
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
143
|
+
const dateString = '2024-01-15T10:30:00.000Z';
|
|
144
|
+
const timestamp = date.getTime();
|
|
145
|
+
|
|
146
|
+
const result1 = formatDate(date);
|
|
147
|
+
const result2 = formatDate(dateString);
|
|
148
|
+
const result3 = formatDate(timestamp);
|
|
149
|
+
|
|
150
|
+
expect(result1).toBe(result2);
|
|
151
|
+
expect(result2).toBe(result3);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('Locale Handling', () => {
|
|
156
|
+
it('uses default locale when no locale is specified', () => {
|
|
157
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
158
|
+
const result = formatDate(date);
|
|
159
|
+
|
|
160
|
+
// Should use the default locale format
|
|
161
|
+
expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('handles different locales correctly', () => {
|
|
165
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
166
|
+
|
|
167
|
+
const result = formatDate(date);
|
|
168
|
+
expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('Time Zone Handling', () => {
|
|
173
|
+
it('handles different time zones correctly', () => {
|
|
174
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
175
|
+
const result = formatDate(date);
|
|
176
|
+
|
|
177
|
+
// Should format consistently regardless of timezone
|
|
178
|
+
expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('Performance', () => {
|
|
183
|
+
it('handles large number of calls efficiently', () => {
|
|
184
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
185
|
+
const startTime = performance.now();
|
|
186
|
+
|
|
187
|
+
// Call formatDate many times
|
|
188
|
+
for (let i = 0; i < 1000; i++) {
|
|
189
|
+
formatDate(date);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const endTime = performance.now();
|
|
193
|
+
const duration = endTime - startTime;
|
|
194
|
+
|
|
195
|
+
// Should complete in reasonable time (less than 100ms for 1000 calls)
|
|
196
|
+
expect(duration).toBeLessThan(100);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe('Type Safety', () => {
|
|
201
|
+
it('accepts valid Date types', () => {
|
|
202
|
+
const date = new Date();
|
|
203
|
+
expect(() => formatDate(date)).not.toThrow();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('accepts valid string types', () => {
|
|
207
|
+
const dateString = '2024-01-15';
|
|
208
|
+
expect(() => formatDate(dateString)).not.toThrow();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('accepts valid number types', () => {
|
|
212
|
+
const timestamp = Date.now();
|
|
213
|
+
expect(() => formatDate(timestamp)).not.toThrow();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('Return Value', () => {
|
|
218
|
+
it('returns a string', () => {
|
|
219
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
220
|
+
const result = formatDate(date);
|
|
221
|
+
|
|
222
|
+
expect(typeof result).toBe('string');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('returns non-empty string for valid dates', () => {
|
|
226
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
227
|
+
const result = formatDate(date);
|
|
228
|
+
|
|
229
|
+
expect(result).toBeTruthy();
|
|
230
|
+
expect(result.length).toBeGreaterThan(0);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('returns expected format pattern', () => {
|
|
234
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
235
|
+
const result = formatDate(date);
|
|
236
|
+
|
|
237
|
+
// Should match pattern: "MMM DD, YYYY"
|
|
238
|
+
expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
});
|
package/dist/chunk-AUE24LVR.js
DELETED
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getAccessLevel,
|
|
3
|
-
getPermissionMap,
|
|
4
|
-
isPermitted,
|
|
5
|
-
isPermittedCached
|
|
6
|
-
} from "./chunk-C5G2A4PO.js";
|
|
7
|
-
|
|
8
|
-
// src/rbac/hooks.ts
|
|
9
|
-
import { useState, useEffect, useCallback, useMemo } from "react";
|
|
10
|
-
function usePermissions(userId, scope) {
|
|
11
|
-
const [permissions, setPermissions] = useState({});
|
|
12
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
13
|
-
const [error, setError] = useState(null);
|
|
14
|
-
const fetchPermissions = useCallback(async () => {
|
|
15
|
-
if (!userId) {
|
|
16
|
-
setPermissions({});
|
|
17
|
-
setIsLoading(false);
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
try {
|
|
21
|
-
setIsLoading(true);
|
|
22
|
-
setError(null);
|
|
23
|
-
const result = await getPermissionMap({ userId, scope });
|
|
24
|
-
setPermissions(result);
|
|
25
|
-
} catch (err) {
|
|
26
|
-
setError(err instanceof Error ? err : new Error("Failed to fetch permissions"));
|
|
27
|
-
} finally {
|
|
28
|
-
setIsLoading(false);
|
|
29
|
-
}
|
|
30
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId]);
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
fetchPermissions();
|
|
33
|
-
}, [fetchPermissions]);
|
|
34
|
-
return {
|
|
35
|
-
permissions,
|
|
36
|
-
isLoading,
|
|
37
|
-
error,
|
|
38
|
-
refetch: fetchPermissions
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
function useCan(userId, scope, permission, pageId, useCache = true) {
|
|
42
|
-
const [can, setCan] = useState(false);
|
|
43
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
44
|
-
const [error, setError] = useState(null);
|
|
45
|
-
const check = useCallback(async () => {
|
|
46
|
-
console.log("[useCan] check() called with:", { userId, scope, permission, pageId });
|
|
47
|
-
console.log("[useCan] Hook parameters:", { userId, scope, permission, pageId, useCache });
|
|
48
|
-
if (!userId) {
|
|
49
|
-
console.log("[useCan] No userId, denying access");
|
|
50
|
-
setCan(false);
|
|
51
|
-
setIsLoading(false);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
try {
|
|
55
|
-
const { isSuperAdmin } = await import("./api-ETQ6YJ3C.js");
|
|
56
|
-
const isSuper = await isSuperAdmin(userId);
|
|
57
|
-
if (isSuper) {
|
|
58
|
-
console.log("[useCan] User is super admin, granting access");
|
|
59
|
-
setCan(true);
|
|
60
|
-
setIsLoading(false);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
} catch (error2) {
|
|
64
|
-
console.error("[useCan] Error checking super admin status:", error2);
|
|
65
|
-
}
|
|
66
|
-
if (!scope || !scope.organisationId || !scope.appId) {
|
|
67
|
-
console.log("[useCan] Incomplete scope, waiting for resolution:", scope);
|
|
68
|
-
setCan(false);
|
|
69
|
-
setIsLoading(true);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
console.log("[useCan] Scope is complete, checking permission...");
|
|
73
|
-
console.log("[useCan] Detailed scope info:", {
|
|
74
|
-
organisationId: scope.organisationId,
|
|
75
|
-
eventId: scope.eventId,
|
|
76
|
-
appId: scope.appId,
|
|
77
|
-
permission,
|
|
78
|
-
pageId
|
|
79
|
-
});
|
|
80
|
-
try {
|
|
81
|
-
setIsLoading(true);
|
|
82
|
-
setError(null);
|
|
83
|
-
console.log("[useCan] About to call isPermitted/isPermittedCached...");
|
|
84
|
-
const result = useCache ? await isPermittedCached({ userId, scope, permission, pageId }) : await isPermitted({ userId, scope, permission, pageId });
|
|
85
|
-
console.log("[useCan] Permission check result:", result);
|
|
86
|
-
console.log("[useCan] Permission check details:", {
|
|
87
|
-
userId,
|
|
88
|
-
scope,
|
|
89
|
-
permission,
|
|
90
|
-
pageId,
|
|
91
|
-
result,
|
|
92
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
93
|
-
});
|
|
94
|
-
setCan(result);
|
|
95
|
-
} catch (err) {
|
|
96
|
-
console.error("[useCan] Permission check error:", err);
|
|
97
|
-
console.error("[useCan] Error details:", {
|
|
98
|
-
userId,
|
|
99
|
-
scope,
|
|
100
|
-
permission,
|
|
101
|
-
pageId,
|
|
102
|
-
error: err instanceof Error ? err.message : "Unknown error",
|
|
103
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
104
|
-
});
|
|
105
|
-
setError(err instanceof Error ? err : new Error("Failed to check permission"));
|
|
106
|
-
setCan(false);
|
|
107
|
-
} finally {
|
|
108
|
-
setIsLoading(false);
|
|
109
|
-
}
|
|
110
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId, permission, pageId, useCache]);
|
|
111
|
-
useEffect(() => {
|
|
112
|
-
check();
|
|
113
|
-
}, [check]);
|
|
114
|
-
return {
|
|
115
|
-
can,
|
|
116
|
-
isLoading,
|
|
117
|
-
error,
|
|
118
|
-
check
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
function useAccessLevel(userId, scope) {
|
|
122
|
-
const [accessLevel, setAccessLevel] = useState(null);
|
|
123
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
124
|
-
const [error, setError] = useState(null);
|
|
125
|
-
const fetchAccessLevel = useCallback(async () => {
|
|
126
|
-
if (!userId) {
|
|
127
|
-
setAccessLevel(null);
|
|
128
|
-
setIsLoading(false);
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
try {
|
|
132
|
-
setIsLoading(true);
|
|
133
|
-
setError(null);
|
|
134
|
-
const result = await getAccessLevel({ userId, scope });
|
|
135
|
-
setAccessLevel(result);
|
|
136
|
-
} catch (err) {
|
|
137
|
-
setError(err instanceof Error ? err : new Error("Failed to fetch access level"));
|
|
138
|
-
setAccessLevel(null);
|
|
139
|
-
} finally {
|
|
140
|
-
setIsLoading(false);
|
|
141
|
-
}
|
|
142
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId]);
|
|
143
|
-
useEffect(() => {
|
|
144
|
-
fetchAccessLevel();
|
|
145
|
-
}, [fetchAccessLevel]);
|
|
146
|
-
return {
|
|
147
|
-
accessLevel,
|
|
148
|
-
isLoading,
|
|
149
|
-
error,
|
|
150
|
-
refetch: fetchAccessLevel
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
function useMultiplePermissions(userId, scope, permissions, pageId, useCache = true) {
|
|
154
|
-
const [permissionResults, setPermissionResults] = useState({});
|
|
155
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
156
|
-
const [error, setError] = useState(null);
|
|
157
|
-
const fetchPermissions = useCallback(async () => {
|
|
158
|
-
if (!userId || permissions.length === 0) {
|
|
159
|
-
setPermissionResults({});
|
|
160
|
-
setIsLoading(false);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
try {
|
|
164
|
-
setIsLoading(true);
|
|
165
|
-
setError(null);
|
|
166
|
-
const results = {};
|
|
167
|
-
const promises = permissions.map(async (permission) => {
|
|
168
|
-
const result = useCache ? await isPermittedCached({ userId, scope, permission, pageId }) : await isPermitted({ userId, scope, permission, pageId });
|
|
169
|
-
return { permission, result };
|
|
170
|
-
});
|
|
171
|
-
const resolved = await Promise.all(promises);
|
|
172
|
-
resolved.forEach(({ permission, result }) => {
|
|
173
|
-
results[permission] = result;
|
|
174
|
-
});
|
|
175
|
-
setPermissionResults(results);
|
|
176
|
-
} catch (err) {
|
|
177
|
-
setError(err instanceof Error ? err : new Error("Failed to check permissions"));
|
|
178
|
-
setPermissionResults({});
|
|
179
|
-
} finally {
|
|
180
|
-
setIsLoading(false);
|
|
181
|
-
}
|
|
182
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, pageId, useCache]);
|
|
183
|
-
useEffect(() => {
|
|
184
|
-
fetchPermissions();
|
|
185
|
-
}, [fetchPermissions]);
|
|
186
|
-
return {
|
|
187
|
-
permissions: permissionResults,
|
|
188
|
-
isLoading,
|
|
189
|
-
error,
|
|
190
|
-
refetch: fetchPermissions
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
function useHasAnyPermission(userId, scope, permissions, pageId) {
|
|
194
|
-
const { permissions: permissionResults, isLoading, error, refetch } = useMultiplePermissions(
|
|
195
|
-
userId,
|
|
196
|
-
scope,
|
|
197
|
-
permissions,
|
|
198
|
-
pageId
|
|
199
|
-
);
|
|
200
|
-
const hasAny = useMemo(() => {
|
|
201
|
-
return Object.values(permissionResults).some(Boolean);
|
|
202
|
-
}, [permissionResults]);
|
|
203
|
-
return {
|
|
204
|
-
hasAny,
|
|
205
|
-
isLoading,
|
|
206
|
-
error,
|
|
207
|
-
refetch
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
function useHasAllPermissions(userId, scope, permissions, pageId) {
|
|
211
|
-
const { permissions: permissionResults, isLoading, error, refetch } = useMultiplePermissions(
|
|
212
|
-
userId,
|
|
213
|
-
scope,
|
|
214
|
-
permissions,
|
|
215
|
-
pageId
|
|
216
|
-
);
|
|
217
|
-
const hasAll = useMemo(() => {
|
|
218
|
-
return Object.values(permissionResults).every(Boolean);
|
|
219
|
-
}, [permissionResults]);
|
|
220
|
-
return {
|
|
221
|
-
hasAll,
|
|
222
|
-
isLoading,
|
|
223
|
-
error,
|
|
224
|
-
refetch
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
function useCachedPermissions(userId, scope) {
|
|
228
|
-
const [permissions, setPermissions] = useState({});
|
|
229
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
230
|
-
const [error, setError] = useState(null);
|
|
231
|
-
const fetchCachedPermissions = useCallback(async () => {
|
|
232
|
-
if (!userId) {
|
|
233
|
-
setPermissions({});
|
|
234
|
-
setIsLoading(false);
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
try {
|
|
238
|
-
setIsLoading(true);
|
|
239
|
-
setError(null);
|
|
240
|
-
const result = await getPermissionMap({ userId, scope });
|
|
241
|
-
setPermissions(result);
|
|
242
|
-
} catch (err) {
|
|
243
|
-
setError(err instanceof Error ? err : new Error("Failed to fetch cached permissions"));
|
|
244
|
-
} finally {
|
|
245
|
-
setIsLoading(false);
|
|
246
|
-
}
|
|
247
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId]);
|
|
248
|
-
useEffect(() => {
|
|
249
|
-
fetchCachedPermissions();
|
|
250
|
-
}, [fetchCachedPermissions]);
|
|
251
|
-
return {
|
|
252
|
-
permissions,
|
|
253
|
-
isLoading,
|
|
254
|
-
error,
|
|
255
|
-
refetch: fetchCachedPermissions
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
export {
|
|
260
|
-
usePermissions,
|
|
261
|
-
useCan,
|
|
262
|
-
useAccessLevel,
|
|
263
|
-
useMultiplePermissions,
|
|
264
|
-
useHasAnyPermission,
|
|
265
|
-
useHasAllPermissions,
|
|
266
|
-
useCachedPermissions
|
|
267
|
-
};
|
|
268
|
-
//# sourceMappingURL=chunk-AUE24LVR.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/rbac/hooks.ts"],"sourcesContent":["/**\n * RBAC React Hooks\n * @package @jmruthers/pace-core\n * @module RBAC/Hooks\n * @since 1.0.0\n * \n * This module provides React hooks for RBAC functionality.\n */\n\nimport { useState, useEffect, useCallback, useMemo } from 'react';\nimport { \n UUID, \n Scope, \n Permission, \n AccessLevel, \n PermissionMap,\n UsePermissionsReturn,\n UseCanReturn\n} from './types';\nimport { \n getAccessLevel, \n getPermissionMap, \n isPermitted,\n isPermittedCached \n} from './api';\n\n/**\n * Hook to get user's permissions in a scope\n * \n * @param userId - User ID\n * @param scope - Permission scope\n * @returns Permission data and loading state\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { permissions, isLoading, error } = usePermissions(\n * 'user-123',\n * { organisationId: 'org-456' }\n * );\n * \n * if (isLoading) return <div>Loading...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return (\n * <div>\n * {permissions['page-1']?.includes('read') && <ReadButton />}\n * {permissions['page-1']?.includes('manage') && <ManageButton />}\n * </div>\n * );\n * }\n * ```\n */\nexport function usePermissions(userId: UUID, scope: Scope): UsePermissionsReturn {\n const [permissions, setPermissions] = useState<PermissionMap>({});\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchPermissions = useCallback(async () => {\n if (!userId) {\n setPermissions({});\n setIsLoading(false);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n const result = await getPermissionMap({ userId, scope });\n setPermissions(result);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to fetch permissions'));\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope.organisationId, scope.eventId, scope.appId]);\n\n useEffect(() => {\n fetchPermissions();\n }, [fetchPermissions]);\n\n return {\n permissions,\n isLoading,\n error,\n refetch: fetchPermissions,\n };\n}\n\n/**\n * Hook to check if user has a specific permission\n * \n * @param userId - User ID\n * @param scope - Permission scope\n * @param permission - Permission to check\n * @param pageId - Optional page ID\n * @param useCache - Whether to use cached results (default: true)\n * @returns Permission check result and loading state\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { can, isLoading } = useCan(\n * 'user-123',\n * { organisationId: 'org-456' },\n * 'manage:events',\n * 'page-789'\n * );\n * \n * if (isLoading) return <div>Checking permission...</div>;\n * \n * return (\n * <div>\n * {can ? <AdminPanel /> : <AccessDenied />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useCan(\n userId: UUID,\n scope: Scope,\n permission: Permission,\n pageId?: UUID,\n useCache: boolean = true\n): UseCanReturn {\n const [can, setCan] = useState(false);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const check = useCallback(async () => {\n console.log('[useCan] check() called with:', { userId, scope, permission, pageId });\n console.log('[useCan] Hook parameters:', { userId, scope, permission, pageId, useCache });\n \n if (!userId) {\n console.log('[useCan] No userId, denying access');\n setCan(false);\n setIsLoading(false);\n return;\n }\n\n // Check for super admin status first - super admins bypass all scope requirements\n try {\n const { isSuperAdmin } = await import('./api');\n const isSuper = await isSuperAdmin(userId);\n if (isSuper) {\n console.log('[useCan] User is super admin, granting access');\n setCan(true);\n setIsLoading(false);\n return;\n }\n } catch (error) {\n console.error('[useCan] Error checking super admin status:', error);\n // Continue with normal permission check if super admin check fails\n }\n\n // Check if scope is incomplete (missing required fields)\n if (!scope || !scope.organisationId || !scope.appId) {\n console.log('[useCan] Incomplete scope, waiting for resolution:', scope);\n setCan(false);\n setIsLoading(true); // Keep loading until scope is complete\n return;\n }\n\n console.log('[useCan] Scope is complete, checking permission...');\n console.log('[useCan] Detailed scope info:', {\n organisationId: scope.organisationId,\n eventId: scope.eventId,\n appId: scope.appId,\n permission,\n pageId\n });\n \n try {\n setIsLoading(true);\n setError(null);\n \n console.log('[useCan] About to call isPermitted/isPermittedCached...');\n const result = useCache \n ? await isPermittedCached({ userId, scope, permission, pageId })\n : await isPermitted({ userId, scope, permission, pageId });\n \n console.log('[useCan] Permission check result:', result);\n console.log('[useCan] Permission check details:', {\n userId,\n scope,\n permission,\n pageId,\n result,\n timestamp: new Date().toISOString()\n });\n setCan(result);\n } catch (err) {\n console.error('[useCan] Permission check error:', err);\n console.error('[useCan] Error details:', {\n userId,\n scope,\n permission,\n pageId,\n error: err instanceof Error ? err.message : 'Unknown error',\n timestamp: new Date().toISOString()\n });\n setError(err instanceof Error ? err : new Error('Failed to check permission'));\n setCan(false);\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope.organisationId, scope.eventId, scope.appId, permission, pageId, useCache]);\n\n useEffect(() => {\n check();\n }, [check]);\n\n return {\n can,\n isLoading,\n error,\n check,\n };\n}\n\n/**\n * Hook to get user's access level in a scope\n * \n * @param userId - User ID\n * @param scope - Permission scope\n * @returns Access level and loading state\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { accessLevel, isLoading } = useAccessLevel(\n * 'user-123',\n * { organisationId: 'org-456' }\n * );\n * \n * if (isLoading) return <div>Loading...</div>;\n * \n * return (\n * <div>\n * {accessLevel === 'super' && <SuperAdminPanel />}\n * {accessLevel === 'admin' && <AdminPanel />}\n * {accessLevel === 'planner' && <PlannerPanel />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useAccessLevel(userId: UUID, scope: Scope): {\n accessLevel: AccessLevel | null;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n} {\n const [accessLevel, setAccessLevel] = useState<AccessLevel | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchAccessLevel = useCallback(async () => {\n if (!userId) {\n setAccessLevel(null);\n setIsLoading(false);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n const result = await getAccessLevel({ userId, scope });\n setAccessLevel(result);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to fetch access level'));\n setAccessLevel(null);\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope.organisationId, scope.eventId, scope.appId]);\n\n useEffect(() => {\n fetchAccessLevel();\n }, [fetchAccessLevel]);\n\n return {\n accessLevel,\n isLoading,\n error,\n refetch: fetchAccessLevel,\n };\n}\n\n/**\n * Hook to check multiple permissions at once\n * \n * @param userId - User ID\n * @param scope - Permission scope\n * @param permissions - Array of permissions to check\n * @param pageId - Optional page ID\n * @param useCache - Whether to use cached results (default: true)\n * @returns Object with permission results and loading state\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { permissions, isLoading } = useMultiplePermissions(\n * 'user-123',\n * { organisationId: 'org-456' },\n * ['read:events', 'manage:events', 'delete:events']\n * );\n * \n * return (\n * <div>\n * {permissions['read:events'] && <ReadButton />}\n * {permissions['manage:events'] && <ManageButton />}\n * {permissions['delete:events'] && <DeleteButton />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useMultiplePermissions(\n userId: UUID,\n scope: Scope,\n permissions: Permission[],\n pageId?: UUID,\n useCache: boolean = true\n): {\n permissions: Record<Permission, boolean>;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n} {\n const [permissionResults, setPermissionResults] = useState<Record<Permission, boolean>>({} as Record<Permission, boolean>);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchPermissions = useCallback(async () => {\n if (!userId || permissions.length === 0) {\n setPermissionResults({} as Record<Permission, boolean>);\n setIsLoading(false);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n const results: Record<Permission, boolean> = {} as Record<Permission, boolean>;\n \n // Check all permissions in parallel\n const promises = permissions.map(async (permission) => {\n const result = useCache \n ? await isPermittedCached({ userId, scope, permission, pageId })\n : await isPermitted({ userId, scope, permission, pageId });\n \n return { permission, result };\n });\n \n const resolved = await Promise.all(promises);\n \n resolved.forEach(({ permission, result }) => {\n results[permission] = result;\n });\n \n setPermissionResults(results);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to check permissions'));\n setPermissionResults({} as Record<Permission, boolean>);\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, pageId, useCache]);\n\n useEffect(() => {\n fetchPermissions();\n }, [fetchPermissions]);\n\n return {\n permissions: permissionResults,\n isLoading,\n error,\n refetch: fetchPermissions,\n };\n}\n\n/**\n * Hook to check if user has any of the specified permissions\n * \n * @param userId - User ID\n * @param scope - Permission scope\n * @param permissions - Array of permissions to check\n * @param pageId - Optional page ID\n * @returns True if user has any permission and loading state\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { hasAny, isLoading } = useHasAnyPermission(\n * 'user-123',\n * { organisationId: 'org-456' },\n * ['read:events', 'manage:events']\n * );\n * \n * return (\n * <div>\n * {hasAny ? <EventContent /> : <AccessDenied />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useHasAnyPermission(\n userId: UUID,\n scope: Scope,\n permissions: Permission[],\n pageId?: UUID\n): {\n hasAny: boolean;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n} {\n const { permissions: permissionResults, isLoading, error, refetch } = useMultiplePermissions(\n userId,\n scope,\n permissions,\n pageId\n );\n\n const hasAny = useMemo(() => {\n return Object.values(permissionResults).some(Boolean);\n }, [permissionResults]);\n\n return {\n hasAny,\n isLoading,\n error,\n refetch,\n };\n}\n\n/**\n * Hook to check if user has all of the specified permissions\n * \n * @param userId - User ID\n * @param scope - Permission scope\n * @param permissions - Array of permissions to check\n * @param pageId - Optional page ID\n * @returns True if user has all permissions and loading state\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { hasAll, isLoading } = useHasAllPermissions(\n * 'user-123',\n * { organisationId: 'org-456' },\n * ['read:events', 'manage:events']\n * );\n * \n * return (\n * <div>\n * {hasAll ? <FullAccessPanel /> : <LimitedAccessPanel />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useHasAllPermissions(\n userId: UUID,\n scope: Scope,\n permissions: Permission[],\n pageId?: UUID\n): {\n hasAll: boolean;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n} {\n const { permissions: permissionResults, isLoading, error, refetch } = useMultiplePermissions(\n userId,\n scope,\n permissions,\n pageId\n );\n\n const hasAll = useMemo(() => {\n return Object.values(permissionResults).every(Boolean);\n }, [permissionResults]);\n\n return {\n hasAll,\n isLoading,\n error,\n refetch,\n };\n}\n\n/**\n * Hook to read cached permissions (contract requirement)\n * \n * This hook only reads from the core cache and does not perform\n * any bespoke caching as per the contract requirements.\n * \n * @param userId - User ID\n * @param scope - Permission scope\n * @returns Cached permission data and loading state\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { permissions, isLoading, error } = useCachedPermissions(\n * 'user-123',\n * { organisationId: 'org-456' }\n * );\n * \n * if (isLoading) return <div>Loading cached permissions...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return (\n * <div>\n * {permissions['page-1']?.includes('read') && <ReadButton />}\n * {permissions['page-1']?.includes('manage') && <ManageButton />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useCachedPermissions(userId: UUID, scope: Scope): {\n permissions: PermissionMap;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n} {\n const [permissions, setPermissions] = useState<PermissionMap>({});\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchCachedPermissions = useCallback(async () => {\n if (!userId) {\n setPermissions({});\n setIsLoading(false);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n // Use cached version of getPermissionMap\n const result = await getPermissionMap({ userId, scope });\n setPermissions(result);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to fetch cached permissions'));\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope.organisationId, scope.eventId, scope.appId]);\n\n useEffect(() => {\n fetchCachedPermissions();\n }, [fetchCachedPermissions]);\n\n return {\n permissions,\n isLoading,\n error,\n refetch: fetchCachedPermissions,\n };\n}\n"],"mappings":"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","names":["error"]}
|