@payez/next-mvp 4.1.8 → 4.1.9
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/README.md +792 -792
- package/dist/auth/better-auth.js +15 -3
- package/dist/components/account/MobileNavDrawer.js +18 -18
- package/dist/components/account/UserAvatarMenu.js +1 -1
- package/dist/components/admin/AlertSettingsTab.js +40 -40
- package/dist/components/admin/LoggingSettingsTab.js +28 -28
- package/dist/lib/idp-fetch.js +9 -25
- package/dist/pages/login/page.js +28 -28
- package/dist/pages/settings/EnhancedSettingsPage.js +17 -17
- package/package.json +1 -1
- package/scripts/dev-broker.ps1 +35 -35
- package/scripts/dev-local.ps1 +45 -45
- package/src/api/auth-handler.ts +548 -548
- package/src/api-handlers/account/change-password.ts +142 -142
- package/src/api-handlers/account/update-phone.ts +79 -79
- package/src/api-handlers/account/verify-email.ts +125 -125
- package/src/api-handlers/account/verify-sms.ts +125 -125
- package/src/api-handlers/admin/analytics.ts +443 -443
- package/src/api-handlers/admin/audit.ts +223 -223
- package/src/api-handlers/admin/index.ts +63 -63
- package/src/api-handlers/admin/redis-sessions.ts +251 -251
- package/src/api-handlers/admin/sessions.ts +318 -318
- package/src/api-handlers/admin/site-logs.ts +365 -365
- package/src/api-handlers/admin/users.ts +242 -242
- package/src/api-handlers/admin/vibe-data.ts +324 -324
- package/src/api-handlers/auth/refresh.ts +682 -682
- package/src/api-handlers/auth/signout.ts +199 -199
- package/src/api-handlers/auth/status.ts +20 -20
- package/src/api-handlers/auth/update-session.ts +110 -110
- package/src/api-handlers/auth/verify-code.ts +112 -112
- package/src/api-handlers/session/refresh-viability.ts +36 -36
- package/src/api-handlers/test/force-expire.ts +60 -60
- package/src/auth/auth-decision.ts +229 -229
- package/src/auth/better-auth.ts +15 -3
- package/src/auth/route-config.ts +219 -219
- package/src/auth/types/auth-types.ts +555 -555
- package/src/auth/types/index.ts +7 -7
- package/src/auth/utils/idp-client.ts +445 -445
- package/src/auth/utils/index.ts +6 -6
- package/src/auth/utils/token-utils.ts +243 -243
- package/src/client/AuthContext.tsx +143 -143
- package/src/client/fetch-with-auth.ts +47 -47
- package/src/components/SessionSync.tsx +138 -138
- package/src/components/SignalRHealthCheck.tsx +131 -131
- package/src/components/account/MobileNavDrawer.tsx +305 -305
- package/src/components/account/UserAvatarMenu.tsx +266 -266
- package/src/components/account/index.ts +13 -13
- package/src/components/admin/AlertSettingsTab.tsx +728 -728
- package/src/components/admin/AnalyticsTab.tsx +703 -703
- package/src/components/admin/DataBrowserTab.tsx +505 -505
- package/src/components/admin/LoggingSettingsTab.tsx +665 -665
- package/src/components/admin/SessionsTab.tsx +414 -414
- package/src/components/admin/StatsTab.tsx +379 -379
- package/src/components/admin/VibeAdminLayout.tsx +187 -187
- package/src/components/admin/index.ts +59 -59
- package/src/components/auth/FederatedAuthSection.tsx +95 -95
- package/src/components/auth/ModeAwareLoginPage.tsx +135 -135
- package/src/components/auth/ModeAwareSignupPage.tsx +267 -267
- package/src/components/auth/TraditionalAuthSection.tsx +99 -99
- package/src/components/recovery/CompleteStep.tsx +36 -36
- package/src/components/recovery/InitiateRecoveryStep.tsx +68 -68
- package/src/components/recovery/SelectMethodStep.tsx +73 -73
- package/src/components/recovery/SetPasswordStep.tsx +97 -97
- package/src/components/recovery/VerifyCodeStep.tsx +90 -90
- package/src/components/reserved/ReservedRecoveryWarning.tsx +160 -160
- package/src/components/reserved/ReservedStatusBox.tsx +118 -118
- package/src/components/ui/BetaBadge.tsx +58 -58
- package/src/components/ui/Footer.tsx +93 -93
- package/src/config/env.ts +57 -57
- package/src/config/logger.ts +62 -62
- package/src/config/vibe-log-transport.ts +264 -264
- package/src/hooks/useAuth.ts +117 -117
- package/src/hooks/useAuthSettings.ts +97 -97
- package/src/hooks/useAvailableProviders.ts +120 -120
- package/src/hooks/usePasswordValidation.ts +127 -127
- package/src/hooks/usePublicAuthSettings.ts +149 -149
- package/src/hooks/useSessionExpiration.ts +101 -101
- package/src/hooks/useViabilitySession.ts +336 -336
- package/src/index.ts +60 -60
- package/src/lib/api-handler.ts +643 -643
- package/src/lib/app-slug.ts +178 -178
- package/src/lib/geolocation.ts +265 -265
- package/src/lib/idp-fetch.ts +10 -26
- package/src/lib/jwt-decode.ts +82 -82
- package/src/lib/roles.ts +177 -177
- package/src/lib/site-logger.ts +245 -245
- package/src/lib/standardized-client-api.ts +901 -901
- package/src/lib/test-aware-get-token.ts +41 -41
- package/src/lib/user-agent-parser.ts +252 -252
- package/src/logging/api/admin-analytics.ts +51 -51
- package/src/logging/api/audit-log.ts +53 -53
- package/src/logging/components/AdminAnalyticsLayout.tsx +49 -49
- package/src/logging/components/AuditLogViewer.tsx +125 -125
- package/src/logging/components/ErrorMetricsCard.tsx +98 -98
- package/src/logging/components/HealthMetricsCard.tsx +70 -70
- package/src/logging/hooks/useAdminAnalytics.ts +22 -22
- package/src/logging/hooks/useAuditLog.ts +24 -24
- package/src/logging/hooks/useErrorMetrics.ts +40 -40
- package/src/logging/hooks/useHealthMetrics.ts +44 -44
- package/src/logging/index.ts +18 -18
- package/src/logging/types/analytics.ts +81 -81
- package/src/logging/types/audit.ts +31 -31
- package/src/logging/types/index.ts +3 -3
- package/src/middleware/create-middleware.ts +663 -663
- package/src/middleware/rbac-check.ts +279 -279
- package/src/middleware/twofa-presets.ts +224 -224
- package/src/pages/admin-login/page.tsx +221 -221
- package/src/pages/admin-page-permissions/PagePermissionsAdminPage.tsx +527 -527
- package/src/pages/admin-page-permissions/index.ts +7 -7
- package/src/pages/admin-roles/RolesAdminPage.tsx +533 -533
- package/src/pages/admin-roles/modals.tsx +469 -469
- package/src/pages/client-admin/ClientSiteAdminPage.tsx +382 -382
- package/src/pages/client-admin/index.ts +33 -33
- package/src/pages/coming-soon/page.tsx +83 -83
- package/src/pages/login/page.tsx +456 -456
- package/src/pages/profile/EnhancedProfilePage.tsx +485 -485
- package/src/pages/profile/index.ts +9 -9
- package/src/pages/profile/page.tsx +166 -166
- package/src/pages/recovery/page.tsx +234 -234
- package/src/pages/roles/MyRolesPage.tsx +211 -211
- package/src/pages/roles/components.tsx +294 -294
- package/src/pages/roles/index.ts +17 -17
- package/src/pages/security/EnhancedSecurityPage.tsx +565 -565
- package/src/pages/security/index.ts +9 -9
- package/src/pages/security/page.tsx +507 -507
- package/src/pages/settings/EnhancedSettingsPage.tsx +642 -642
- package/src/pages/settings/index.ts +9 -9
- package/src/pages/settings/page.tsx +47 -47
- package/src/pages/showcase/ShowcasePage.tsx +532 -532
- package/src/pages/showcase/index.ts +13 -13
- package/src/pages/test-env/EmergencyLogoutPage.tsx +180 -180
- package/src/pages/test-env/JwtInspectPage.tsx +456 -456
- package/src/pages/test-env/RefreshTokenPage.tsx +157 -157
- package/src/pages/test-env/TestEnvPage.tsx +118 -118
- package/src/pages/test-env/index.ts +25 -25
- package/src/pages/verify-code/page.tsx +652 -652
- package/src/routes/account/update-phone.ts +13 -13
- package/src/routes/auth/logout.ts +108 -108
- package/src/routes/auth/nextauth.ts +45 -45
- package/src/routes/auth/settings.ts +75 -75
- package/src/routes/auth/viability.ts +224 -224
- package/src/server/auth-guard.ts +170 -170
- package/src/server/slim-middleware.ts +117 -117
- package/src/server/with-auth.ts +93 -93
- package/src/stores/authStore.ts +1899 -1899
- package/src/templates/instrumentation.ts +41 -41
- package/src/utils/logout.ts +30 -30
- package/src/vibe/client.ts +590 -590
- package/src/vibe/enterprise-auth.ts +208 -208
- package/src/vibe/errors.ts +185 -185
- package/src/vibe/generic.ts +431 -431
- package/src/vibe/hooks/index.ts +367 -367
- package/src/vibe/index.ts +132 -132
- package/src/vibe/sessions.ts +551 -551
- package/src/vibe/types.ts +577 -577
|
@@ -1,211 +1,211 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* My Roles Page for @payez/next-mvp
|
|
3
|
-
*
|
|
4
|
-
* User view of their assigned roles (/account/roles).
|
|
5
|
-
* Shows roles from both IDP and app sources with expandable permissions.
|
|
6
|
-
* Read-only - users cannot self-assign roles.
|
|
7
|
-
*
|
|
8
|
-
* @see docs/specs/ROLES_MANAGEMENT_SPEC.md
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
'use client';
|
|
12
|
-
|
|
13
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
14
|
-
import { useLayout, useColors } from '../../theme/useTheme';
|
|
15
|
-
import { Role, RoleCard, RoleSourceHeader } from './components';
|
|
16
|
-
|
|
17
|
-
// API response types
|
|
18
|
-
interface MyRolesResponse {
|
|
19
|
-
summary: {
|
|
20
|
-
total_roles: number;
|
|
21
|
-
sources: string[];
|
|
22
|
-
};
|
|
23
|
-
idp_roles: Role[];
|
|
24
|
-
app_roles: Role[];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface MyRolesPageProps {
|
|
28
|
-
// Optional initial data (for SSR)
|
|
29
|
-
initialData?: MyRolesResponse;
|
|
30
|
-
// API endpoint
|
|
31
|
-
rolesEndpoint?: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export default function MyRolesPage({
|
|
35
|
-
initialData,
|
|
36
|
-
rolesEndpoint = '/api/account/my-roles',
|
|
37
|
-
}: MyRolesPageProps) {
|
|
38
|
-
const layout = useLayout();
|
|
39
|
-
const colors = useColors();
|
|
40
|
-
|
|
41
|
-
// Determine dark mode
|
|
42
|
-
const isDark = colors?.background?.includes('slate-9') ||
|
|
43
|
-
colors?.background?.includes('gray-9') ||
|
|
44
|
-
colors?.card?.includes('slate-8');
|
|
45
|
-
|
|
46
|
-
// State
|
|
47
|
-
const [data, setData] = useState<MyRolesResponse | null>(initialData || null);
|
|
48
|
-
const [loading, setLoading] = useState(!initialData);
|
|
49
|
-
const [error, setError] = useState<string | null>(null);
|
|
50
|
-
|
|
51
|
-
// Theme colors
|
|
52
|
-
const bgColor = isDark ? 'bg-slate-900' : 'bg-gray-50';
|
|
53
|
-
const cardBg = isDark ? 'bg-slate-800' : 'bg-white';
|
|
54
|
-
const borderColor = isDark ? 'border-slate-700' : 'border-gray-200';
|
|
55
|
-
const textPrimary = isDark ? 'text-white' : 'text-gray-900';
|
|
56
|
-
const textMuted = isDark ? 'text-slate-400' : 'text-gray-500';
|
|
57
|
-
|
|
58
|
-
// Fetch roles
|
|
59
|
-
const fetchRoles = useCallback(async () => {
|
|
60
|
-
try {
|
|
61
|
-
setLoading(true);
|
|
62
|
-
setError(null);
|
|
63
|
-
|
|
64
|
-
const response = await fetch(rolesEndpoint, { credentials: 'include' });
|
|
65
|
-
if (!response.ok) {
|
|
66
|
-
throw new Error('Failed to load roles');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const result = await response.json();
|
|
70
|
-
setData(result);
|
|
71
|
-
} catch (err) {
|
|
72
|
-
setError(err instanceof Error ? err.message : 'Failed to load roles');
|
|
73
|
-
} finally {
|
|
74
|
-
setLoading(false);
|
|
75
|
-
}
|
|
76
|
-
}, [rolesEndpoint]);
|
|
77
|
-
|
|
78
|
-
useEffect(() => {
|
|
79
|
-
if (!initialData) {
|
|
80
|
-
fetchRoles();
|
|
81
|
-
}
|
|
82
|
-
}, [initialData, fetchRoles]);
|
|
83
|
-
|
|
84
|
-
// Loading state
|
|
85
|
-
if (loading) {
|
|
86
|
-
return (
|
|
87
|
-
<div className={`min-h-screen ${bgColor} flex items-center justify-center`}>
|
|
88
|
-
<div className="flex flex-col items-center space-y-4">
|
|
89
|
-
<svg className="animate-spin h-8 w-8 text-blue-500" viewBox="0 0 24 24" fill="none">
|
|
90
|
-
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
91
|
-
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
|
|
92
|
-
</svg>
|
|
93
|
-
<p className={textMuted}>Loading roles...</p>
|
|
94
|
-
</div>
|
|
95
|
-
</div>
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Error state
|
|
100
|
-
if (error) {
|
|
101
|
-
return (
|
|
102
|
-
<div className={`min-h-screen ${bgColor} flex items-center justify-center`}>
|
|
103
|
-
<div className={`${cardBg} border ${borderColor} rounded-lg p-8 text-center max-w-md`}>
|
|
104
|
-
<svg className="w-12 h-12 text-red-500 mx-auto mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
105
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
106
|
-
</svg>
|
|
107
|
-
<h2 className={`text-lg font-semibold ${textPrimary} mb-2`}>Failed to Load Roles</h2>
|
|
108
|
-
<p className={`${textMuted} mb-4`}>{error}</p>
|
|
109
|
-
<button
|
|
110
|
-
onClick={fetchRoles}
|
|
111
|
-
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
|
|
112
|
-
>
|
|
113
|
-
Try Again
|
|
114
|
-
</button>
|
|
115
|
-
</div>
|
|
116
|
-
</div>
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const idpRoles = data?.idp_roles || [];
|
|
121
|
-
const appRoles = data?.app_roles || [];
|
|
122
|
-
const totalRoles = idpRoles.length + appRoles.length;
|
|
123
|
-
|
|
124
|
-
return (
|
|
125
|
-
<div className={`min-h-screen ${bgColor}`}>
|
|
126
|
-
<div className={`max-w-2xl mx-auto ${layout?.padding || 'p-6'}`}>
|
|
127
|
-
{/* Page Header */}
|
|
128
|
-
<div className="flex items-center justify-between mb-6">
|
|
129
|
-
<div>
|
|
130
|
-
<h1 className={`text-3xl font-bold ${textPrimary}`}>My Roles</h1>
|
|
131
|
-
{totalRoles > 0 && (
|
|
132
|
-
<p className={`mt-1 ${textMuted}`}>
|
|
133
|
-
You have {totalRoles} role{totalRoles !== 1 ? 's' : ''} across {data?.summary?.sources?.length || 0} source{(data?.summary?.sources?.length || 0) !== 1 ? 's' : ''}.
|
|
134
|
-
</p>
|
|
135
|
-
)}
|
|
136
|
-
</div>
|
|
137
|
-
<a
|
|
138
|
-
href="/account/profile"
|
|
139
|
-
className={`text-sm hover:underline ${textMuted}`}
|
|
140
|
-
>
|
|
141
|
-
Back to Profile
|
|
142
|
-
</a>
|
|
143
|
-
</div>
|
|
144
|
-
|
|
145
|
-
{/* Empty state */}
|
|
146
|
-
{totalRoles === 0 && (
|
|
147
|
-
<div className={`${cardBg} border ${borderColor} rounded-lg p-8 text-center`}>
|
|
148
|
-
<svg className={`w-12 h-12 mx-auto mb-4 ${textMuted}`} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
149
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
|
150
|
-
</svg>
|
|
151
|
-
<h2 className={`text-lg font-semibold ${textPrimary} mb-2`}>No Roles Assigned</h2>
|
|
152
|
-
<p className={textMuted}>You don't have any roles assigned yet. Contact your administrator for access.</p>
|
|
153
|
-
</div>
|
|
154
|
-
)}
|
|
155
|
-
|
|
156
|
-
{/* IDP Roles */}
|
|
157
|
-
{idpRoles.length > 0 && (
|
|
158
|
-
<div className="mb-6">
|
|
159
|
-
<RoleSourceHeader source="idp" count={idpRoles.length} isDark={isDark} />
|
|
160
|
-
<div className="mt-3 space-y-3">
|
|
161
|
-
{idpRoles.map((role) => (
|
|
162
|
-
<RoleCard
|
|
163
|
-
key={role.role_name}
|
|
164
|
-
role={role}
|
|
165
|
-
source="idp"
|
|
166
|
-
isDark={isDark}
|
|
167
|
-
/>
|
|
168
|
-
))}
|
|
169
|
-
</div>
|
|
170
|
-
</div>
|
|
171
|
-
)}
|
|
172
|
-
|
|
173
|
-
{/* App Roles */}
|
|
174
|
-
{appRoles.length > 0 && (
|
|
175
|
-
<div className="mb-6">
|
|
176
|
-
<RoleSourceHeader source="app" count={appRoles.length} isDark={isDark} />
|
|
177
|
-
<div className="mt-3 space-y-3">
|
|
178
|
-
{appRoles.map((role) => (
|
|
179
|
-
<RoleCard
|
|
180
|
-
key={role.role_name}
|
|
181
|
-
role={role}
|
|
182
|
-
source="app"
|
|
183
|
-
isDark={isDark}
|
|
184
|
-
/>
|
|
185
|
-
))}
|
|
186
|
-
</div>
|
|
187
|
-
</div>
|
|
188
|
-
)}
|
|
189
|
-
|
|
190
|
-
{/* Footer */}
|
|
191
|
-
{totalRoles > 0 && (
|
|
192
|
-
<div className={`border-t ${borderColor} pt-4 mt-6`}>
|
|
193
|
-
<p className={`text-sm ${textMuted}`}>
|
|
194
|
-
Need additional access? Contact your administrator.
|
|
195
|
-
</p>
|
|
196
|
-
</div>
|
|
197
|
-
)}
|
|
198
|
-
|
|
199
|
-
{/* Navigation */}
|
|
200
|
-
<div className="mt-6 flex justify-between">
|
|
201
|
-
<a href="/account/security" className={`text-sm hover:underline ${textMuted}`}>
|
|
202
|
-
← Security Settings
|
|
203
|
-
</a>
|
|
204
|
-
<a href="/account/settings" className={`text-sm hover:underline ${textMuted}`}>
|
|
205
|
-
Settings →
|
|
206
|
-
</a>
|
|
207
|
-
</div>
|
|
208
|
-
</div>
|
|
209
|
-
</div>
|
|
210
|
-
);
|
|
211
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* My Roles Page for @payez/next-mvp
|
|
3
|
+
*
|
|
4
|
+
* User view of their assigned roles (/account/roles).
|
|
5
|
+
* Shows roles from both IDP and app sources with expandable permissions.
|
|
6
|
+
* Read-only - users cannot self-assign roles.
|
|
7
|
+
*
|
|
8
|
+
* @see docs/specs/ROLES_MANAGEMENT_SPEC.md
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use client';
|
|
12
|
+
|
|
13
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
14
|
+
import { useLayout, useColors } from '../../theme/useTheme';
|
|
15
|
+
import { Role, RoleCard, RoleSourceHeader } from './components';
|
|
16
|
+
|
|
17
|
+
// API response types
|
|
18
|
+
interface MyRolesResponse {
|
|
19
|
+
summary: {
|
|
20
|
+
total_roles: number;
|
|
21
|
+
sources: string[];
|
|
22
|
+
};
|
|
23
|
+
idp_roles: Role[];
|
|
24
|
+
app_roles: Role[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface MyRolesPageProps {
|
|
28
|
+
// Optional initial data (for SSR)
|
|
29
|
+
initialData?: MyRolesResponse;
|
|
30
|
+
// API endpoint
|
|
31
|
+
rolesEndpoint?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default function MyRolesPage({
|
|
35
|
+
initialData,
|
|
36
|
+
rolesEndpoint = '/api/account/my-roles',
|
|
37
|
+
}: MyRolesPageProps) {
|
|
38
|
+
const layout = useLayout();
|
|
39
|
+
const colors = useColors();
|
|
40
|
+
|
|
41
|
+
// Determine dark mode
|
|
42
|
+
const isDark = colors?.background?.includes('slate-9') ||
|
|
43
|
+
colors?.background?.includes('gray-9') ||
|
|
44
|
+
colors?.card?.includes('slate-8');
|
|
45
|
+
|
|
46
|
+
// State
|
|
47
|
+
const [data, setData] = useState<MyRolesResponse | null>(initialData || null);
|
|
48
|
+
const [loading, setLoading] = useState(!initialData);
|
|
49
|
+
const [error, setError] = useState<string | null>(null);
|
|
50
|
+
|
|
51
|
+
// Theme colors
|
|
52
|
+
const bgColor = isDark ? 'bg-slate-900' : 'bg-gray-50';
|
|
53
|
+
const cardBg = isDark ? 'bg-slate-800' : 'bg-white';
|
|
54
|
+
const borderColor = isDark ? 'border-slate-700' : 'border-gray-200';
|
|
55
|
+
const textPrimary = isDark ? 'text-white' : 'text-gray-900';
|
|
56
|
+
const textMuted = isDark ? 'text-slate-400' : 'text-gray-500';
|
|
57
|
+
|
|
58
|
+
// Fetch roles
|
|
59
|
+
const fetchRoles = useCallback(async () => {
|
|
60
|
+
try {
|
|
61
|
+
setLoading(true);
|
|
62
|
+
setError(null);
|
|
63
|
+
|
|
64
|
+
const response = await fetch(rolesEndpoint, { credentials: 'include' });
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
throw new Error('Failed to load roles');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const result = await response.json();
|
|
70
|
+
setData(result);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
setError(err instanceof Error ? err.message : 'Failed to load roles');
|
|
73
|
+
} finally {
|
|
74
|
+
setLoading(false);
|
|
75
|
+
}
|
|
76
|
+
}, [rolesEndpoint]);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!initialData) {
|
|
80
|
+
fetchRoles();
|
|
81
|
+
}
|
|
82
|
+
}, [initialData, fetchRoles]);
|
|
83
|
+
|
|
84
|
+
// Loading state
|
|
85
|
+
if (loading) {
|
|
86
|
+
return (
|
|
87
|
+
<div className={`min-h-screen ${bgColor} flex items-center justify-center`}>
|
|
88
|
+
<div className="flex flex-col items-center space-y-4">
|
|
89
|
+
<svg className="animate-spin h-8 w-8 text-blue-500" viewBox="0 0 24 24" fill="none">
|
|
90
|
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
91
|
+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
|
|
92
|
+
</svg>
|
|
93
|
+
<p className={textMuted}>Loading roles...</p>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Error state
|
|
100
|
+
if (error) {
|
|
101
|
+
return (
|
|
102
|
+
<div className={`min-h-screen ${bgColor} flex items-center justify-center`}>
|
|
103
|
+
<div className={`${cardBg} border ${borderColor} rounded-lg p-8 text-center max-w-md`}>
|
|
104
|
+
<svg className="w-12 h-12 text-red-500 mx-auto mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
105
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
106
|
+
</svg>
|
|
107
|
+
<h2 className={`text-lg font-semibold ${textPrimary} mb-2`}>Failed to Load Roles</h2>
|
|
108
|
+
<p className={`${textMuted} mb-4`}>{error}</p>
|
|
109
|
+
<button
|
|
110
|
+
onClick={fetchRoles}
|
|
111
|
+
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
|
|
112
|
+
>
|
|
113
|
+
Try Again
|
|
114
|
+
</button>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const idpRoles = data?.idp_roles || [];
|
|
121
|
+
const appRoles = data?.app_roles || [];
|
|
122
|
+
const totalRoles = idpRoles.length + appRoles.length;
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div className={`min-h-screen ${bgColor}`}>
|
|
126
|
+
<div className={`max-w-2xl mx-auto ${layout?.padding || 'p-6'}`}>
|
|
127
|
+
{/* Page Header */}
|
|
128
|
+
<div className="flex items-center justify-between mb-6">
|
|
129
|
+
<div>
|
|
130
|
+
<h1 className={`text-3xl font-bold ${textPrimary}`}>My Roles</h1>
|
|
131
|
+
{totalRoles > 0 && (
|
|
132
|
+
<p className={`mt-1 ${textMuted}`}>
|
|
133
|
+
You have {totalRoles} role{totalRoles !== 1 ? 's' : ''} across {data?.summary?.sources?.length || 0} source{(data?.summary?.sources?.length || 0) !== 1 ? 's' : ''}.
|
|
134
|
+
</p>
|
|
135
|
+
)}
|
|
136
|
+
</div>
|
|
137
|
+
<a
|
|
138
|
+
href="/account/profile"
|
|
139
|
+
className={`text-sm hover:underline ${textMuted}`}
|
|
140
|
+
>
|
|
141
|
+
Back to Profile
|
|
142
|
+
</a>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
{/* Empty state */}
|
|
146
|
+
{totalRoles === 0 && (
|
|
147
|
+
<div className={`${cardBg} border ${borderColor} rounded-lg p-8 text-center`}>
|
|
148
|
+
<svg className={`w-12 h-12 mx-auto mb-4 ${textMuted}`} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
149
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
|
150
|
+
</svg>
|
|
151
|
+
<h2 className={`text-lg font-semibold ${textPrimary} mb-2`}>No Roles Assigned</h2>
|
|
152
|
+
<p className={textMuted}>You don't have any roles assigned yet. Contact your administrator for access.</p>
|
|
153
|
+
</div>
|
|
154
|
+
)}
|
|
155
|
+
|
|
156
|
+
{/* IDP Roles */}
|
|
157
|
+
{idpRoles.length > 0 && (
|
|
158
|
+
<div className="mb-6">
|
|
159
|
+
<RoleSourceHeader source="idp" count={idpRoles.length} isDark={isDark} />
|
|
160
|
+
<div className="mt-3 space-y-3">
|
|
161
|
+
{idpRoles.map((role) => (
|
|
162
|
+
<RoleCard
|
|
163
|
+
key={role.role_name}
|
|
164
|
+
role={role}
|
|
165
|
+
source="idp"
|
|
166
|
+
isDark={isDark}
|
|
167
|
+
/>
|
|
168
|
+
))}
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
|
|
173
|
+
{/* App Roles */}
|
|
174
|
+
{appRoles.length > 0 && (
|
|
175
|
+
<div className="mb-6">
|
|
176
|
+
<RoleSourceHeader source="app" count={appRoles.length} isDark={isDark} />
|
|
177
|
+
<div className="mt-3 space-y-3">
|
|
178
|
+
{appRoles.map((role) => (
|
|
179
|
+
<RoleCard
|
|
180
|
+
key={role.role_name}
|
|
181
|
+
role={role}
|
|
182
|
+
source="app"
|
|
183
|
+
isDark={isDark}
|
|
184
|
+
/>
|
|
185
|
+
))}
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
)}
|
|
189
|
+
|
|
190
|
+
{/* Footer */}
|
|
191
|
+
{totalRoles > 0 && (
|
|
192
|
+
<div className={`border-t ${borderColor} pt-4 mt-6`}>
|
|
193
|
+
<p className={`text-sm ${textMuted}`}>
|
|
194
|
+
Need additional access? Contact your administrator.
|
|
195
|
+
</p>
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
|
|
199
|
+
{/* Navigation */}
|
|
200
|
+
<div className="mt-6 flex justify-between">
|
|
201
|
+
<a href="/account/security" className={`text-sm hover:underline ${textMuted}`}>
|
|
202
|
+
← Security Settings
|
|
203
|
+
</a>
|
|
204
|
+
<a href="/account/settings" className={`text-sm hover:underline ${textMuted}`}>
|
|
205
|
+
Settings →
|
|
206
|
+
</a>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
}
|