@jmruthers/pace-core 0.5.185 → 0.5.186
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/{PublicPageProvider-BABf6JCh.d.ts → PublicPageProvider-DIzEzwKl.d.ts} +4 -2
- package/dist/{chunk-STTZQK2I.js → chunk-DAGICKHT.js} +7 -5
- package/dist/chunk-DAGICKHT.js.map +1 -0
- package/dist/{chunk-AISXLWGZ.js → chunk-GRIQLQ52.js} +2 -2
- package/dist/{chunk-HC67NW5K.js → chunk-HDCUMOOI.js} +125 -47
- package/dist/chunk-HDCUMOOI.js.map +1 -0
- package/dist/{chunk-OKI34GZD.js → chunk-OALXJH4Y.js} +2 -2
- package/dist/{chunk-MX3EIJGQ.js → chunk-TC7D3CR3.js} +86 -7
- package/dist/chunk-TC7D3CR3.js.map +1 -0
- package/dist/{chunk-IXSNYUCT.js → chunk-UQWSHFVX.js} +1 -1
- package/dist/chunk-UQWSHFVX.js.map +1 -0
- package/dist/components.d.ts +2 -2
- package/dist/components.js +3 -3
- package/dist/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
- package/dist/{file-reference-BjR39ktt.d.ts → file-reference-PRTSLxKx.d.ts} +3 -0
- package/dist/hooks.d.ts +49 -5
- package/dist/hooks.js +6 -4
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +9 -8
- package/dist/index.js.map +1 -1
- package/dist/rbac/index.d.ts +1 -1
- package/dist/rbac/index.js +2 -2
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-CvnC3d-e.d.ts → usePublicRouteParams-D71QLlg4.d.ts} +2 -2
- package/dist/utils.d.ts +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/Logger.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/RBACAuditManager.md +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +2 -2
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +5 -5
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.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/ComplianceResult.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/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.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 +33 -9
- package/docs/api/interfaces/FileUploadProps.md +36 -14
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.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/LoggerConfig.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/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
- 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/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +2 -2
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.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/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.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/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.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 +2 -2
- 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/UsePublicFileDisplayOptions.md +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.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 +17 -17
- package/docs/api-reference/components.md +26 -12
- package/docs/implementation-guides/file-reference-system.md +24 -2
- package/docs/implementation-guides/file-upload-storage.md +9 -1
- package/package.json +1 -1
- package/scripts/check-pace-core-compliance.js +512 -0
- package/src/components/FileUpload/FileUpload.test.tsx +2 -0
- package/src/components/FileUpload/FileUpload.tsx +7 -1
- package/src/components/Header/Header.tsx +2 -5
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +134 -1
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useFileReference.test.ts +1 -0
- package/src/hooks/usePreventTabReload.ts +106 -0
- package/src/hooks/useSecureDataAccess.ts +2 -2
- package/src/rbac/__tests__/rbac-role-isolation.test.ts +456 -0
- package/src/styles/core.css +5 -5
- package/src/types/database.generated.ts +63 -9
- package/src/types/file-reference.ts +3 -0
- package/src/utils/file-reference/__tests__/file-reference.test.ts +58 -4
- package/src/utils/file-reference/index.ts +12 -2
- package/src/utils/security/secureDataAccess.ts +1 -1
- package/src/utils/storage/helpers.ts +68 -0
- package/dist/chunk-HC67NW5K.js.map +0 -1
- package/dist/chunk-IXSNYUCT.js.map +0 -1
- package/dist/chunk-MX3EIJGQ.js.map +0 -1
- package/dist/chunk-STTZQK2I.js.map +0 -1
- /package/dist/{chunk-AISXLWGZ.js.map → chunk-GRIQLQ52.js.map} +0 -0
- /package/dist/{chunk-OKI34GZD.js.map → chunk-OALXJH4Y.js.map} +0 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Static Analysis Script for pace-core Compliance
|
|
5
|
+
* @package @jmruthers/pace-core
|
|
6
|
+
* @module Scripts/check-pace-core-compliance
|
|
7
|
+
*
|
|
8
|
+
* Scans a consuming app's codebase to check compliance with pace-core usage.
|
|
9
|
+
* Generates a report of violations and suggestions.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
// ANSI color codes for terminal output
|
|
16
|
+
const colors = {
|
|
17
|
+
reset: '\x1b[0m',
|
|
18
|
+
red: '\x1b[31m',
|
|
19
|
+
green: '\x1b[32m',
|
|
20
|
+
yellow: '\x1b[33m',
|
|
21
|
+
blue: '\x1b[34m',
|
|
22
|
+
cyan: '\x1b[36m',
|
|
23
|
+
bold: '\x1b[1m'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Load manifest
|
|
27
|
+
function loadManifest() {
|
|
28
|
+
const manifestPath = path.join(__dirname, '../core-usage-manifest.json');
|
|
29
|
+
if (!fs.existsSync(manifestPath)) {
|
|
30
|
+
console.error(`${colors.red}Error: core-usage-manifest.json not found at ${manifestPath}${colors.reset}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Find project root (look for package.json, going up from current dir or script location)
|
|
37
|
+
function findProjectRoot(startDir = process.cwd()) {
|
|
38
|
+
let current = path.resolve(startDir);
|
|
39
|
+
while (current !== path.dirname(current)) {
|
|
40
|
+
if (fs.existsSync(path.join(current, 'package.json'))) {
|
|
41
|
+
return current;
|
|
42
|
+
}
|
|
43
|
+
current = path.dirname(current);
|
|
44
|
+
}
|
|
45
|
+
return startDir;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Scan file for violations
|
|
49
|
+
function scanFile(filePath, manifest) {
|
|
50
|
+
const violations = {
|
|
51
|
+
restrictedImports: [],
|
|
52
|
+
duplicateComponents: [],
|
|
53
|
+
duplicateHooks: [],
|
|
54
|
+
duplicateUtils: [],
|
|
55
|
+
suggestions: [],
|
|
56
|
+
customAuthCode: [],
|
|
57
|
+
duplicateConfig: [],
|
|
58
|
+
unprotectedPages: [],
|
|
59
|
+
directSupabaseAuth: []
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
63
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
64
|
+
|
|
65
|
+
// Check for restricted imports
|
|
66
|
+
manifest.restrictedImports.forEach(({ module, reason }) => {
|
|
67
|
+
const importPattern = new RegExp(`from\\s+['"]${module.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"]`, 'g');
|
|
68
|
+
if (importPattern.test(content)) {
|
|
69
|
+
violations.restrictedImports.push({
|
|
70
|
+
module,
|
|
71
|
+
reason,
|
|
72
|
+
file: relativePath,
|
|
73
|
+
line: getLineNumber(content, content.match(importPattern)[0])
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Also check for @radix-ui/* pattern
|
|
78
|
+
if (module.startsWith('@radix-ui/')) {
|
|
79
|
+
const radixPattern = /from\s+['"]@radix-ui\/[^'"]+['"]/g;
|
|
80
|
+
const matches = content.match(radixPattern);
|
|
81
|
+
if (matches) {
|
|
82
|
+
matches.forEach(match => {
|
|
83
|
+
const matchedModule = match.match(/['"]([^'"]+)['"]/)[1];
|
|
84
|
+
if (!manifest.restrictedImports.find(ri => ri.module === matchedModule)) {
|
|
85
|
+
violations.restrictedImports.push({
|
|
86
|
+
module: matchedModule,
|
|
87
|
+
reason: 'Use pace-core component instead of direct Radix UI import',
|
|
88
|
+
file: relativePath,
|
|
89
|
+
line: getLineNumber(content, match)
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Check for duplicate component names
|
|
98
|
+
const filename = path.basename(filePath);
|
|
99
|
+
const componentName = filename.replace(/\.(tsx?|jsx?)$/, '').replace(/\.(test|spec)$/, '');
|
|
100
|
+
|
|
101
|
+
if (manifest.components.includes(componentName)) {
|
|
102
|
+
// Check if this file exports a component
|
|
103
|
+
if (content.match(/export\s+(default\s+)?(function|const|class)\s+(\w+)?/)) {
|
|
104
|
+
violations.duplicateComponents.push({
|
|
105
|
+
component: componentName,
|
|
106
|
+
file: relativePath
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check for duplicate hook names
|
|
112
|
+
if (filename.startsWith('use') && filename.endsWith('.ts') || filename.endsWith('.tsx')) {
|
|
113
|
+
const hookName = filename.replace(/\.(tsx?|jsx?)$/, '').replace(/\.(test|spec)$/, '');
|
|
114
|
+
if (manifest.hooks.includes(hookName)) {
|
|
115
|
+
if (content.match(/export\s+(default\s+)?(function|const)\s+(\w+)?/)) {
|
|
116
|
+
violations.duplicateHooks.push({
|
|
117
|
+
hook: hookName,
|
|
118
|
+
file: relativePath
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check for duplicate util names
|
|
125
|
+
const utilName = filename.replace(/\.(ts|js)$/, '').replace(/\.(test|spec)$/, '');
|
|
126
|
+
if (manifest.utils.includes(utilName)) {
|
|
127
|
+
if (content.match(/export\s+(default\s+)?(function|const)\s+(\w+)?/)) {
|
|
128
|
+
violations.duplicateUtils.push({
|
|
129
|
+
util: utilName,
|
|
130
|
+
file: relativePath
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Check for native HTML elements that should use pace-core components
|
|
136
|
+
const nativeElementPatterns = {
|
|
137
|
+
'<button': { suggestion: 'Use Button from @jmruthers/pace-core' },
|
|
138
|
+
'<input': { suggestion: 'Use Input from @jmruthers/pace-core' },
|
|
139
|
+
'<textarea': { suggestion: 'Use Textarea from @jmruthers/pace-core' },
|
|
140
|
+
'<label': { suggestion: 'Use Label from @jmruthers/pace-core' }
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
Object.entries(nativeElementPatterns).forEach(([pattern, { suggestion }]) => {
|
|
144
|
+
if (content.includes(pattern) && !content.includes('from \'@jmruthers/pace-core\'')) {
|
|
145
|
+
violations.suggestions.push({
|
|
146
|
+
type: 'native-element',
|
|
147
|
+
suggestion,
|
|
148
|
+
file: relativePath,
|
|
149
|
+
pattern
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// ============================================
|
|
155
|
+
// RBAC/Auth Compliance Checks
|
|
156
|
+
// ============================================
|
|
157
|
+
|
|
158
|
+
// Check for custom auth/rbac/permission code that doesn't import from pace-core
|
|
159
|
+
const authRbacPatterns = [
|
|
160
|
+
// Custom auth hooks
|
|
161
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+useAuth\s*[=\(]/g, name: 'useAuth', type: 'hook' },
|
|
162
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+useLogin\s*[=\(]/g, name: 'useLogin', type: 'hook' },
|
|
163
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+useLogout\s*[=\(]/g, name: 'useLogout', type: 'hook' },
|
|
164
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+useSession\s*[=\(]/g, name: 'useSession', type: 'hook' },
|
|
165
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+useUser\s*[=\(]/g, name: 'useUser', type: 'hook' },
|
|
166
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+useAuthentication\s*[=\(]/g, name: 'useAuthentication', type: 'hook' },
|
|
167
|
+
// Custom RBAC hooks
|
|
168
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+usePermissions\s*[=\(]/g, name: 'usePermissions', type: 'hook' },
|
|
169
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+useCan\s*[=\(]/g, name: 'useCan', type: 'hook' },
|
|
170
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+useAccessLevel\s*[=\(]/g, name: 'useAccessLevel', type: 'hook' },
|
|
171
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+useRole\s*[=\(]/g, name: 'useRole', type: 'hook' },
|
|
172
|
+
// Custom RBAC components
|
|
173
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+PermissionGuard\s*[=\(]/g, name: 'PermissionGuard', type: 'component' },
|
|
174
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+AuthGuard\s*[=\(]/g, name: 'AuthGuard', type: 'component' },
|
|
175
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+RoleGuard\s*[=\(]/g, name: 'RoleGuard', type: 'component' },
|
|
176
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+AccessGuard\s*[=\(]/g, name: 'AccessGuard', type: 'component' },
|
|
177
|
+
// Custom permission utilities
|
|
178
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+checkPermission\s*[=\(]/g, name: 'checkPermission', type: 'util' },
|
|
179
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+hasPermission\s*[=\(]/g, name: 'hasPermission', type: 'util' },
|
|
180
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+hasAccess\s*[=\(]/g, name: 'hasAccess', type: 'util' },
|
|
181
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+canAccess\s*[=\(]/g, name: 'canAccess', type: 'util' },
|
|
182
|
+
{ pattern: /export\s+(default\s+)?(function|const)\s+isPermitted\s*[=\(]/g, name: 'isPermitted', type: 'util' }
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
// Check if file imports from pace-core for auth/rbac
|
|
186
|
+
const hasPaceCoreImport = /from\s+['"]@jmruthers\/pace-core/.test(content) ||
|
|
187
|
+
/from\s+['"]@jmruthers\/pace-core\/rbac/.test(content) ||
|
|
188
|
+
/from\s+['"]@jmruthers\/pace-core\/providers/.test(content);
|
|
189
|
+
|
|
190
|
+
authRbacPatterns.forEach(({ pattern, name, type }) => {
|
|
191
|
+
if (pattern.test(content) && !hasPaceCoreImport) {
|
|
192
|
+
violations.customAuthCode.push({
|
|
193
|
+
name,
|
|
194
|
+
type,
|
|
195
|
+
file: relativePath,
|
|
196
|
+
line: getLineNumber(content, content.match(pattern)[0]),
|
|
197
|
+
reason: `Custom ${type} '${name}' detected. Use pace-core's ${name} instead.`
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Check for duplicate Supabase client configurations
|
|
203
|
+
const supabaseCreateClientPattern = /createClient\s*\(/g;
|
|
204
|
+
const supabaseCreateClientMatches = content.match(supabaseCreateClientPattern);
|
|
205
|
+
if (supabaseCreateClientMatches && supabaseCreateClientMatches.length > 1) {
|
|
206
|
+
violations.duplicateConfig.push({
|
|
207
|
+
type: 'supabase-client',
|
|
208
|
+
file: relativePath,
|
|
209
|
+
count: supabaseCreateClientMatches.length,
|
|
210
|
+
reason: `Multiple Supabase client instantiations found (${supabaseCreateClientMatches.length}). Consolidate to a single client configuration.`
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check for Supabase URL/key configuration in multiple places
|
|
215
|
+
const supabaseUrlPattern = /(SUPABASE_URL|VITE_SUPABASE_URL|NEXT_PUBLIC_SUPABASE_URL|REACT_APP_SUPABASE_URL)/g;
|
|
216
|
+
const supabaseKeyPattern = /(SUPABASE_ANON_KEY|VITE_SUPABASE_ANON_KEY|NEXT_PUBLIC_SUPABASE_ANON_KEY|REACT_APP_SUPABASE_ANON_KEY)/g;
|
|
217
|
+
const urlMatches = content.match(supabaseUrlPattern);
|
|
218
|
+
const keyMatches = content.match(supabaseKeyPattern);
|
|
219
|
+
|
|
220
|
+
if ((urlMatches && urlMatches.length > 2) || (keyMatches && keyMatches.length > 2)) {
|
|
221
|
+
violations.duplicateConfig.push({
|
|
222
|
+
type: 'supabase-env',
|
|
223
|
+
file: relativePath,
|
|
224
|
+
reason: 'Supabase environment variables referenced multiple times. Consider centralizing configuration.'
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Check for unprotected pages/routes
|
|
229
|
+
// Look for route definitions without PagePermissionGuard
|
|
230
|
+
const routePatterns = [
|
|
231
|
+
/<Route\s+path=["'][^"']+["']/g,
|
|
232
|
+
/<Route\s+element\s*=/g,
|
|
233
|
+
/createBrowserRouter\s*\(/g,
|
|
234
|
+
/createRoutesFromElements/g
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
const isRouteFile = routePatterns.some(pattern => pattern.test(content));
|
|
238
|
+
const hasPagePermissionGuard = /PagePermissionGuard/.test(content) ||
|
|
239
|
+
/from\s+['"]@jmruthers\/pace-core\/rbac['"]/.test(content);
|
|
240
|
+
|
|
241
|
+
if (isRouteFile && !hasPagePermissionGuard && !relativePath.includes('test') && !relativePath.includes('spec')) {
|
|
242
|
+
violations.unprotectedPages.push({
|
|
243
|
+
file: relativePath,
|
|
244
|
+
reason: 'Route file found without PagePermissionGuard. All routes should be protected with PagePermissionGuard from pace-core.'
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Check for direct Supabase auth usage (should use UnifiedAuthProvider)
|
|
249
|
+
const directSupabaseAuthPatterns = [
|
|
250
|
+
/\.auth\.signIn/g,
|
|
251
|
+
/\.auth\.signUp/g,
|
|
252
|
+
/\.auth\.signOut/g,
|
|
253
|
+
/\.auth\.getSession/g,
|
|
254
|
+
/\.auth\.getUser/g,
|
|
255
|
+
/supabase\.auth\./g
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
const hasUnifiedAuthImport = /UnifiedAuthProvider/.test(content) ||
|
|
259
|
+
/useUnifiedAuth/.test(content) ||
|
|
260
|
+
/from\s+['"]@jmruthers\/pace-core\/providers/.test(content);
|
|
261
|
+
|
|
262
|
+
directSupabaseAuthPatterns.forEach(pattern => {
|
|
263
|
+
if (pattern.test(content) && !hasUnifiedAuthImport && !hasPaceCoreImport) {
|
|
264
|
+
violations.directSupabaseAuth.push({
|
|
265
|
+
file: relativePath,
|
|
266
|
+
line: getLineNumber(content, content.match(pattern)[0]),
|
|
267
|
+
reason: 'Direct Supabase auth usage detected. Use UnifiedAuthProvider and useUnifiedAuth from pace-core instead.'
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return violations;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Get line number for a match
|
|
276
|
+
function getLineNumber(content, match) {
|
|
277
|
+
const lines = content.substring(0, content.indexOf(match)).split('\n');
|
|
278
|
+
return lines.length;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Generate report
|
|
282
|
+
function generateReport(allViolations, manifest) {
|
|
283
|
+
const totalRestricted = allViolations.restrictedImports.length;
|
|
284
|
+
const totalDuplicates =
|
|
285
|
+
allViolations.duplicateComponents.length +
|
|
286
|
+
allViolations.duplicateHooks.length +
|
|
287
|
+
allViolations.duplicateUtils.length;
|
|
288
|
+
const totalSuggestions = allViolations.suggestions.length;
|
|
289
|
+
const totalRbacAuth =
|
|
290
|
+
allViolations.customAuthCode.length +
|
|
291
|
+
allViolations.duplicateConfig.length +
|
|
292
|
+
allViolations.unprotectedPages.length +
|
|
293
|
+
allViolations.directSupabaseAuth.length;
|
|
294
|
+
const totalIssues = totalRestricted + totalDuplicates + totalSuggestions + totalRbacAuth;
|
|
295
|
+
|
|
296
|
+
console.log(`\n${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
|
|
297
|
+
console.log(`${colors.bold}${colors.cyan} pace-core Compliance Report${colors.reset}`);
|
|
298
|
+
console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}\n`);
|
|
299
|
+
|
|
300
|
+
// Restricted Imports
|
|
301
|
+
if (totalRestricted > 0) {
|
|
302
|
+
console.log(`${colors.red}${colors.bold}❌ Restricted Imports Found: ${totalRestricted}${colors.reset}\n`);
|
|
303
|
+
allViolations.restrictedImports.forEach(({ module, reason, file, line }) => {
|
|
304
|
+
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
|
|
305
|
+
console.log(` Import: ${colors.cyan}${module}${colors.reset}`);
|
|
306
|
+
console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
|
|
307
|
+
});
|
|
308
|
+
} else {
|
|
309
|
+
console.log(`${colors.green}✅ No restricted imports found${colors.reset}\n`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Duplicate Components
|
|
313
|
+
if (allViolations.duplicateComponents.length > 0) {
|
|
314
|
+
console.log(`${colors.red}${colors.bold}❌ Duplicate Components Found: ${allViolations.duplicateComponents.length}${colors.reset}\n`);
|
|
315
|
+
allViolations.duplicateComponents.forEach(({ component, file }) => {
|
|
316
|
+
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
317
|
+
console.log(` Component '${colors.cyan}${component}${colors.reset}' conflicts with pace-core component`);
|
|
318
|
+
console.log(` ${colors.yellow}Suggestion:${colors.reset} Use '${component}' from '@jmruthers/pace-core' instead\n`);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Duplicate Hooks
|
|
323
|
+
if (allViolations.duplicateHooks.length > 0) {
|
|
324
|
+
console.log(`${colors.red}${colors.bold}❌ Duplicate Hooks Found: ${allViolations.duplicateHooks.length}${colors.reset}\n`);
|
|
325
|
+
allViolations.duplicateHooks.forEach(({ hook, file }) => {
|
|
326
|
+
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
327
|
+
console.log(` Hook '${colors.cyan}${hook}${colors.reset}' conflicts with pace-core hook`);
|
|
328
|
+
console.log(` ${colors.yellow}Suggestion:${colors.reset} Use '${hook}' from '@jmruthers/pace-core' instead\n`);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Duplicate Utils
|
|
333
|
+
if (allViolations.duplicateUtils.length > 0) {
|
|
334
|
+
console.log(`${colors.red}${colors.bold}❌ Duplicate Utils Found: ${allViolations.duplicateUtils.length}${colors.reset}\n`);
|
|
335
|
+
allViolations.duplicateUtils.forEach(({ util, file }) => {
|
|
336
|
+
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
337
|
+
console.log(` Util '${colors.cyan}${util}${colors.reset}' conflicts with pace-core util`);
|
|
338
|
+
console.log(` ${colors.yellow}Suggestion:${colors.reset} Use '${util}' from '@jmruthers/pace-core' instead\n`);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Suggestions
|
|
343
|
+
if (totalSuggestions > 0) {
|
|
344
|
+
console.log(`${colors.yellow}${colors.bold}💡 Suggestions: ${totalSuggestions}${colors.reset}\n`);
|
|
345
|
+
const grouped = {};
|
|
346
|
+
allViolations.suggestions.forEach(s => {
|
|
347
|
+
if (!grouped[s.file]) grouped[s.file] = [];
|
|
348
|
+
grouped[s.file].push(s);
|
|
349
|
+
});
|
|
350
|
+
Object.entries(grouped).forEach(([file, suggestions]) => {
|
|
351
|
+
console.log(` ${colors.yellow}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
352
|
+
suggestions.forEach(s => {
|
|
353
|
+
console.log(` ${s.suggestion}\n`);
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// RBAC/Auth Compliance Section
|
|
359
|
+
if (totalRbacAuth > 0) {
|
|
360
|
+
console.log(`\n${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
|
|
361
|
+
console.log(`${colors.bold}${colors.cyan} RBAC/Auth Compliance${colors.reset}`);
|
|
362
|
+
console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}\n`);
|
|
363
|
+
|
|
364
|
+
// Custom Auth/RBAC Code
|
|
365
|
+
if (allViolations.customAuthCode.length > 0) {
|
|
366
|
+
console.log(`${colors.red}${colors.bold}❌ Custom Auth/RBAC Code Found: ${allViolations.customAuthCode.length}${colors.reset}\n`);
|
|
367
|
+
allViolations.customAuthCode.forEach(({ name, type, file, line, reason }) => {
|
|
368
|
+
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
|
|
369
|
+
console.log(` ${type}: ${colors.cyan}${name}${colors.reset}`);
|
|
370
|
+
console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Duplicate Configurations
|
|
375
|
+
if (allViolations.duplicateConfig.length > 0) {
|
|
376
|
+
console.log(`${colors.red}${colors.bold}❌ Duplicate Configurations Found: ${allViolations.duplicateConfig.length}${colors.reset}\n`);
|
|
377
|
+
allViolations.duplicateConfig.forEach(({ type, file, count, reason }) => {
|
|
378
|
+
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
379
|
+
console.log(` Type: ${colors.cyan}${type}${colors.reset}${count ? ` (${count} instances)` : ''}`);
|
|
380
|
+
console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Unprotected Pages
|
|
385
|
+
if (allViolations.unprotectedPages.length > 0) {
|
|
386
|
+
console.log(`${colors.red}${colors.bold}❌ Unprotected Pages Found: ${allViolations.unprotectedPages.length}${colors.reset}\n`);
|
|
387
|
+
allViolations.unprotectedPages.forEach(({ file, reason }) => {
|
|
388
|
+
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
389
|
+
console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Direct Supabase Auth Usage
|
|
394
|
+
if (allViolations.directSupabaseAuth.length > 0) {
|
|
395
|
+
console.log(`${colors.red}${colors.bold}❌ Direct Supabase Auth Usage Found: ${allViolations.directSupabaseAuth.length}${colors.reset}\n`);
|
|
396
|
+
allViolations.directSupabaseAuth.forEach(({ file, line, reason }) => {
|
|
397
|
+
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
|
|
398
|
+
console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
} else {
|
|
402
|
+
console.log(`\n${colors.green}✅ RBAC/Auth compliance: All checks passed${colors.reset}\n`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Summary
|
|
406
|
+
console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
|
|
407
|
+
console.log(`${colors.bold}Summary:${colors.reset}`);
|
|
408
|
+
console.log(` Total Issues: ${totalIssues > 0 ? colors.red : colors.green}${totalIssues}${colors.reset}`);
|
|
409
|
+
console.log(` - Restricted Imports: ${totalRestricted > 0 ? colors.red : colors.green}${totalRestricted}${colors.reset}`);
|
|
410
|
+
console.log(` - Duplicate Components/Hooks/Utils: ${totalDuplicates > 0 ? colors.red : colors.green}${totalDuplicates}${colors.reset}`);
|
|
411
|
+
console.log(` - Suggestions: ${colors.yellow}${totalSuggestions}${colors.reset}`);
|
|
412
|
+
console.log(` - RBAC/Auth Issues: ${totalRbacAuth > 0 ? colors.red : colors.green}${totalRbacAuth}${colors.reset}`);
|
|
413
|
+
|
|
414
|
+
if (totalIssues === 0) {
|
|
415
|
+
console.log(`\n${colors.green}${colors.bold}✅ Excellent! Your codebase is fully compliant with pace-core standards.${colors.reset}\n`);
|
|
416
|
+
return 0;
|
|
417
|
+
} else {
|
|
418
|
+
console.log(`\n${colors.yellow}${colors.bold}⚠️ Please review the issues above and migrate to pace-core components/hooks/utils.${colors.reset}\n`);
|
|
419
|
+
return 1;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Recursively find source files
|
|
424
|
+
function findSourceFiles(dir, fileList = []) {
|
|
425
|
+
const ignoreDirs = ['node_modules', 'dist', 'build', '.next', 'coverage', '__tests__', '__mocks__'];
|
|
426
|
+
const ignoreFiles = /\.(test|spec)\.(ts|tsx|js|jsx)$/;
|
|
427
|
+
const sourceExtensions = /\.(ts|tsx|js|jsx)$/;
|
|
428
|
+
|
|
429
|
+
try {
|
|
430
|
+
const items = fs.readdirSync(dir);
|
|
431
|
+
|
|
432
|
+
items.forEach(item => {
|
|
433
|
+
const fullPath = path.join(dir, item);
|
|
434
|
+
const stat = fs.statSync(fullPath);
|
|
435
|
+
|
|
436
|
+
if (stat.isDirectory()) {
|
|
437
|
+
if (!ignoreDirs.includes(item) && !item.startsWith('.')) {
|
|
438
|
+
findSourceFiles(fullPath, fileList);
|
|
439
|
+
}
|
|
440
|
+
} else if (stat.isFile()) {
|
|
441
|
+
if (sourceExtensions.test(item) && !ignoreFiles.test(item)) {
|
|
442
|
+
fileList.push(fullPath);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
} catch (error) {
|
|
447
|
+
// Skip directories we can't read
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return fileList;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Main function
|
|
454
|
+
function main() {
|
|
455
|
+
const manifest = loadManifest();
|
|
456
|
+
const projectRoot = findProjectRoot();
|
|
457
|
+
|
|
458
|
+
console.log(`${colors.cyan}Scanning project at: ${projectRoot}${colors.reset}`);
|
|
459
|
+
|
|
460
|
+
// Find all TypeScript/JavaScript files (excluding node_modules, dist, etc.)
|
|
461
|
+
const files = findSourceFiles(projectRoot);
|
|
462
|
+
|
|
463
|
+
console.log(`Found ${files.length} files to scan...\n`);
|
|
464
|
+
|
|
465
|
+
// Scan all files
|
|
466
|
+
const allViolations = {
|
|
467
|
+
restrictedImports: [],
|
|
468
|
+
duplicateComponents: [],
|
|
469
|
+
duplicateHooks: [],
|
|
470
|
+
duplicateUtils: [],
|
|
471
|
+
suggestions: [],
|
|
472
|
+
customAuthCode: [],
|
|
473
|
+
duplicateConfig: [],
|
|
474
|
+
unprotectedPages: [],
|
|
475
|
+
directSupabaseAuth: []
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
files.forEach(file => {
|
|
479
|
+
try {
|
|
480
|
+
const violations = scanFile(file, manifest);
|
|
481
|
+
allViolations.restrictedImports.push(...violations.restrictedImports);
|
|
482
|
+
allViolations.duplicateComponents.push(...violations.duplicateComponents);
|
|
483
|
+
allViolations.duplicateHooks.push(...violations.duplicateHooks);
|
|
484
|
+
allViolations.duplicateUtils.push(...violations.duplicateUtils);
|
|
485
|
+
allViolations.suggestions.push(...violations.suggestions);
|
|
486
|
+
allViolations.customAuthCode.push(...violations.customAuthCode);
|
|
487
|
+
allViolations.duplicateConfig.push(...violations.duplicateConfig);
|
|
488
|
+
allViolations.unprotectedPages.push(...violations.unprotectedPages);
|
|
489
|
+
allViolations.directSupabaseAuth.push(...violations.directSupabaseAuth);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
console.error(`${colors.red}Error scanning ${file}: ${error.message}${colors.reset}`);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Generate and display report
|
|
496
|
+
const exitCode = generateReport(allViolations, manifest);
|
|
497
|
+
process.exit(exitCode);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Run if called directly
|
|
501
|
+
if (require.main === module) {
|
|
502
|
+
try {
|
|
503
|
+
main();
|
|
504
|
+
} catch (error) {
|
|
505
|
+
console.error(`${colors.red}Error: ${error.message}${colors.reset}`);
|
|
506
|
+
console.error(error.stack);
|
|
507
|
+
process.exit(1);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
module.exports = { main, scanFile, generateReport };
|
|
512
|
+
|
|
@@ -22,7 +22,9 @@ export interface FileUploadProps {
|
|
|
22
22
|
organisation_id: string;
|
|
23
23
|
app_id?: string; // Optional - will be resolved from app name if not provided
|
|
24
24
|
category: FileCategory;
|
|
25
|
+
folder: string; // Folder name in storage bucket (e.g., 'profile_photos', 'documents')
|
|
25
26
|
pageContext: string; // The page context where the file upload occurs (e.g., 'configuration', 'forms', 'applications')
|
|
27
|
+
event_id?: string; // Optional event ID for event-scoped permission checks (required for event-based apps)
|
|
26
28
|
accept?: string;
|
|
27
29
|
maxSize?: number;
|
|
28
30
|
multiple?: boolean;
|
|
@@ -51,7 +53,9 @@ export function FileUpload({
|
|
|
51
53
|
organisation_id,
|
|
52
54
|
app_id,
|
|
53
55
|
category,
|
|
56
|
+
folder,
|
|
54
57
|
pageContext,
|
|
58
|
+
event_id,
|
|
55
59
|
accept = '*/*',
|
|
56
60
|
maxSize = 10 * 1024 * 1024, // 10MB default
|
|
57
61
|
multiple = false,
|
|
@@ -286,7 +290,9 @@ export function FileUpload({
|
|
|
286
290
|
organisation_id,
|
|
287
291
|
app_id: resolvedAppId ? assertAppId(resolvedAppId) : assertAppId(''),
|
|
288
292
|
category,
|
|
293
|
+
folder,
|
|
289
294
|
pageContext,
|
|
295
|
+
event_id,
|
|
290
296
|
is_public: isPublic
|
|
291
297
|
}, file);
|
|
292
298
|
|
|
@@ -384,7 +390,7 @@ export function FileUpload({
|
|
|
384
390
|
onUploadError?.(errorMessage, file);
|
|
385
391
|
}
|
|
386
392
|
}
|
|
387
|
-
}, [uploadFile, table_name, record_id, organisation_id, resolvedAppId, category, isPublic, maxSize, onUploadSuccess, onUploadError, onProgress, validateFile, generatePreview, showPreview, appIdError]);
|
|
393
|
+
}, [uploadFile, table_name, record_id, organisation_id, resolvedAppId, category, folder, isPublic, maxSize, onUploadSuccess, onUploadError, onProgress, validateFile, generatePreview, showPreview, appIdError]);
|
|
388
394
|
|
|
389
395
|
const handleDragOver = useCallback((e: React.DragEvent) => {
|
|
390
396
|
e.preventDefault();
|
|
@@ -260,7 +260,7 @@ export function Header({
|
|
|
260
260
|
"w-full border-b border-main-200 h-16 shadow-sm bg-main-100 ",
|
|
261
261
|
className
|
|
262
262
|
)} role="banner">
|
|
263
|
-
<nav className="px-4 w-[min(var(--app-width),100%)] mx-auto
|
|
263
|
+
<nav className="px-4 w-[min(var(--app-width),100%)] mx-auto flex items-center gap-4 h-full">
|
|
264
264
|
{/* Logo */}
|
|
265
265
|
{logo ? (
|
|
266
266
|
logoHref ? (
|
|
@@ -318,7 +318,7 @@ export function Header({
|
|
|
318
318
|
|
|
319
319
|
|
|
320
320
|
{/* Right side: Organisation Selector, Event Selector, Actions, and User Menu */}
|
|
321
|
-
<div className="flex items-center gap-4
|
|
321
|
+
<div className="flex items-center gap-4 ml-auto">
|
|
322
322
|
{/* Organisation Selector */}
|
|
323
323
|
{showOrgSelector ? (
|
|
324
324
|
<OrganisationSelector
|
|
@@ -337,10 +337,7 @@ export function Header({
|
|
|
337
337
|
data-testid="event-selector"
|
|
338
338
|
/>
|
|
339
339
|
) : null}
|
|
340
|
-
</div>
|
|
341
340
|
|
|
342
|
-
{/* Custom Actions and User Menu */}
|
|
343
|
-
<div className="flex items-center gap-4">
|
|
344
341
|
{/* Custom Actions */}
|
|
345
342
|
{actions}
|
|
346
343
|
|