@robosystems/core 0.3.0
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/LICENSE +21 -0
- package/README.md +252 -0
- package/actions/entity-actions.d.ts +8 -0
- package/actions/entity-actions.js +16 -0
- package/actions/graph-actions.d.ts +3 -0
- package/actions/graph-actions.js +13 -0
- package/auth-components/AppSwitcher.d.ts +6 -0
- package/auth-components/AppSwitcher.js +28 -0
- package/auth-components/AuthGuard.d.ts +7 -0
- package/auth-components/AuthGuard.js +31 -0
- package/auth-components/AuthProvider.d.ts +9 -0
- package/auth-components/AuthProvider.js +565 -0
- package/auth-components/SessionWarningDialog.d.ts +1 -0
- package/auth-components/SessionWarningDialog.js +51 -0
- package/auth-components/SignInForm.d.ts +20 -0
- package/auth-components/SignInForm.js +139 -0
- package/auth-components/SignUpForm.d.ts +15 -0
- package/auth-components/SignUpForm.js +167 -0
- package/auth-components/TurnstileWidget.d.ts +99 -0
- package/auth-components/TurnstileWidget.js +224 -0
- package/auth-components/index.d.ts +6 -0
- package/auth-components/index.js +6 -0
- package/auth-core/cleanup.d.ts +44 -0
- package/auth-core/cleanup.js +157 -0
- package/auth-core/client.d.ts +121 -0
- package/auth-core/client.js +767 -0
- package/auth-core/config.d.ts +19 -0
- package/auth-core/config.js +56 -0
- package/auth-core/hooks.d.ts +19 -0
- package/auth-core/hooks.js +63 -0
- package/auth-core/index.d.ts +6 -0
- package/auth-core/index.js +6 -0
- package/auth-core/sso.d.ts +42 -0
- package/auth-core/sso.js +281 -0
- package/auth-core/token-storage.d.ts +58 -0
- package/auth-core/token-storage.js +177 -0
- package/auth-core/types.d.ts +145 -0
- package/auth-core/types.js +1 -0
- package/components/EntitySelector.d.ts +44 -0
- package/components/EntitySelector.js +64 -0
- package/components/EntitySelectorCore.d.ts +45 -0
- package/components/EntitySelectorCore.js +67 -0
- package/components/GraphSelectorCore.d.ts +21 -0
- package/components/GraphSelectorCore.js +39 -0
- package/components/PageLayout.d.ts +8 -0
- package/components/PageLayout.js +5 -0
- package/components/RepositoryGuard.d.ts +68 -0
- package/components/RepositoryGuard.js +91 -0
- package/components/console/ConsoleContent.d.ts +4 -0
- package/components/console/ConsoleContent.js +612 -0
- package/components/console/ConsoleMarkdown.d.ts +11 -0
- package/components/console/ConsoleMarkdown.js +49 -0
- package/components/console/ProgressiveText.d.ts +6 -0
- package/components/console/ProgressiveText.js +21 -0
- package/components/console/graphAwareConfig.d.ts +70 -0
- package/components/console/graphAwareConfig.js +411 -0
- package/components/console/index.d.ts +6 -0
- package/components/console/index.js +4 -0
- package/components/console/types.d.ts +84 -0
- package/components/console/types.js +1 -0
- package/components/graph-filters.d.ts +75 -0
- package/components/graph-filters.js +111 -0
- package/components/index.d.ts +10 -0
- package/components/index.js +9 -0
- package/components/repositories/ActiveSubscriptions.d.ts +26 -0
- package/components/repositories/ActiveSubscriptions.js +103 -0
- package/components/repositories/BrowseRepositories.d.ts +11 -0
- package/components/repositories/BrowseRepositories.js +184 -0
- package/components/repositories/index.d.ts +2 -0
- package/components/repositories/index.js +2 -0
- package/components/search/SearchContent.d.ts +4 -0
- package/components/search/SearchContent.js +168 -0
- package/components/search/index.d.ts +2 -0
- package/components/search/index.js +1 -0
- package/components/search/types.d.ts +15 -0
- package/components/search/types.js +1 -0
- package/contexts/entity-context.d.ts +28 -0
- package/contexts/entity-context.js +89 -0
- package/contexts/graph-context.d.ts +27 -0
- package/contexts/graph-context.js +128 -0
- package/contexts/index.d.ts +5 -0
- package/contexts/index.js +5 -0
- package/contexts/org-context.d.ts +25 -0
- package/contexts/org-context.js +64 -0
- package/contexts/service-offerings-context.d.ts +45 -0
- package/contexts/service-offerings-context.js +98 -0
- package/contexts/sidebar-context.d.ts +18 -0
- package/contexts/sidebar-context.js +44 -0
- package/hooks/index.d.ts +6 -0
- package/hooks/index.js +6 -0
- package/hooks/use-api-error.d.ts +3 -0
- package/hooks/use-api-error.js +24 -0
- package/hooks/use-media-query.d.ts +1 -0
- package/hooks/use-media-query.js +21 -0
- package/hooks/use-streaming-query.d.ts +22 -0
- package/hooks/use-streaming-query.js +142 -0
- package/hooks/use-toast.d.ts +37 -0
- package/hooks/use-toast.js +117 -0
- package/hooks/use-user-limits.d.ts +12 -0
- package/hooks/use-user-limits.js +73 -0
- package/hooks/use-user.d.ts +11 -0
- package/hooks/use-user.js +39 -0
- package/index.d.ts +29 -0
- package/index.js +77 -0
- package/lib/entity-cookie.d.ts +10 -0
- package/lib/entity-cookie.js +38 -0
- package/lib/graph-cookie.d.ts +11 -0
- package/lib/graph-cookie.js +62 -0
- package/lib/graph-tiers.d.ts +41 -0
- package/lib/graph-tiers.js +107 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +3 -0
- package/lib/sidebar-cookie.d.ts +11 -0
- package/lib/sidebar-cookie.js +42 -0
- package/library/colors.d.ts +4 -0
- package/library/colors.js +41 -0
- package/library/components/ClassificationPicker.d.ts +8 -0
- package/library/components/ClassificationPicker.js +55 -0
- package/library/components/ElementBrowser.d.ts +11 -0
- package/library/components/ElementBrowser.js +106 -0
- package/library/components/ElementDetail.d.ts +7 -0
- package/library/components/ElementDetail.js +97 -0
- package/library/components/LibraryHierarchy.d.ts +13 -0
- package/library/components/LibraryHierarchy.js +380 -0
- package/library/components/TaxonomySidebar.d.ts +6 -0
- package/library/components/TaxonomySidebar.js +45 -0
- package/library/index.d.ts +8 -0
- package/library/index.js +7 -0
- package/package.json +37 -0
- package/research/CoverageCard.d.ts +12 -0
- package/research/CoverageCard.js +15 -0
- package/research/CoverageGrid.d.ts +6 -0
- package/research/CoverageGrid.js +9 -0
- package/research/CoverageHistory.d.ts +5 -0
- package/research/CoverageHistory.js +7 -0
- package/research/ResearchArticle.d.ts +15 -0
- package/research/ResearchArticle.js +26 -0
- package/research/catalog.d.ts +14 -0
- package/research/catalog.js +42 -0
- package/research/index.d.ts +6 -0
- package/research/index.js +9 -0
- package/research/types.d.ts +38 -0
- package/research/types.js +4 -0
- package/task-monitoring/hooks.d.ts +26 -0
- package/task-monitoring/hooks.js +126 -0
- package/task-monitoring/index.d.ts +5 -0
- package/task-monitoring/index.js +7 -0
- package/task-monitoring/operationErrors.d.ts +82 -0
- package/task-monitoring/operationErrors.js +251 -0
- package/task-monitoring/operationHooks.d.ts +117 -0
- package/task-monitoring/operationHooks.js +536 -0
- package/task-monitoring/operationMonitor.d.ts +78 -0
- package/task-monitoring/operationMonitor.js +200 -0
- package/task-monitoring/operationTypes.d.ts +236 -0
- package/task-monitoring/operationTypes.js +60 -0
- package/task-monitoring/taskMonitor.d.ts +14 -0
- package/task-monitoring/taskMonitor.js +132 -0
- package/task-monitoring/types.d.ts +31 -0
- package/task-monitoring/types.js +1 -0
- package/theme/flowbite-theme.d.ts +758 -0
- package/theme/flowbite-theme.js +765 -0
- package/theme/index.d.ts +1 -0
- package/theme/index.js +1 -0
- package/types/entity.d.ts +19 -0
- package/types/index.d.ts +2 -0
- package/types/index.js +1 -0
- package/types/user.d.ts +6 -0
- package/ui-components/ConfirmModal.d.ts +41 -0
- package/ui-components/ConfirmModal.js +19 -0
- package/ui-components/EmptyState.d.ts +22 -0
- package/ui-components/EmptyState.js +10 -0
- package/ui-components/LoadingState.d.ts +27 -0
- package/ui-components/LoadingState.js +12 -0
- package/ui-components/Logo.d.ts +28 -0
- package/ui-components/Logo.js +95 -0
- package/ui-components/Spinner.d.ts +37 -0
- package/ui-components/Spinner.js +48 -0
- package/ui-components/StatCard.d.ts +21 -0
- package/ui-components/StatCard.js +10 -0
- package/ui-components/api-keys/ApiKeyDisplay.d.ts +13 -0
- package/ui-components/api-keys/ApiKeyDisplay.js +7 -0
- package/ui-components/api-keys/ApiKeyTable.d.ts +9 -0
- package/ui-components/api-keys/ApiKeyTable.js +17 -0
- package/ui-components/api-keys/ApiKeysCard.d.ts +6 -0
- package/ui-components/api-keys/ApiKeysCard.js +97 -0
- package/ui-components/api-keys/CreateApiKeyModal.d.ts +11 -0
- package/ui-components/api-keys/CreateApiKeyModal.js +47 -0
- package/ui-components/api-keys/SecureApiKeyField.d.ts +13 -0
- package/ui-components/api-keys/SecureApiKeyField.js +64 -0
- package/ui-components/api-keys/index.d.ts +5 -0
- package/ui-components/api-keys/index.js +5 -0
- package/ui-components/chat/ChatHeader.d.ts +12 -0
- package/ui-components/chat/ChatHeader.js +5 -0
- package/ui-components/chat/ChatInputArea.d.ts +16 -0
- package/ui-components/chat/ChatInputArea.js +9 -0
- package/ui-components/chat/ChatMessage.d.ts +7 -0
- package/ui-components/chat/ChatMessage.js +24 -0
- package/ui-components/chat/DeepResearchToggle.d.ts +8 -0
- package/ui-components/chat/DeepResearchToggle.js +13 -0
- package/ui-components/chat/index.d.ts +9 -0
- package/ui-components/chat/index.js +8 -0
- package/ui-components/chat/types.d.ts +23 -0
- package/ui-components/chat/types.js +51 -0
- package/ui-components/forms/PasswordRequirements.d.ts +10 -0
- package/ui-components/forms/PasswordRequirements.js +15 -0
- package/ui-components/forms/SettingsCard.d.ts +11 -0
- package/ui-components/forms/SettingsCard.js +5 -0
- package/ui-components/forms/SettingsFormField.d.ts +18 -0
- package/ui-components/forms/SettingsFormField.js +17 -0
- package/ui-components/forms/StatusAlert.d.ts +8 -0
- package/ui-components/forms/StatusAlert.js +7 -0
- package/ui-components/forms/index.d.ts +4 -0
- package/ui-components/forms/index.js +4 -0
- package/ui-components/index.d.ts +13 -0
- package/ui-components/index.js +18 -0
- package/ui-components/layout/CoreNavbar.d.ts +14 -0
- package/ui-components/layout/CoreNavbar.js +53 -0
- package/ui-components/layout/CoreSidebar.d.ts +28 -0
- package/ui-components/layout/CoreSidebar.js +62 -0
- package/ui-components/layout/LandingFooter.d.ts +28 -0
- package/ui-components/layout/LandingFooter.js +34 -0
- package/ui-components/layout/PageContainer.d.ts +8 -0
- package/ui-components/layout/PageContainer.js +17 -0
- package/ui-components/layout/PageHeader.d.ts +23 -0
- package/ui-components/layout/PageHeader.js +12 -0
- package/ui-components/layout/SettingsContainer.d.ts +10 -0
- package/ui-components/layout/SettingsContainer.js +9 -0
- package/ui-components/layout/ThemeToggle.d.ts +4 -0
- package/ui-components/layout/ThemeToggle.js +6 -0
- package/ui-components/layout/index.d.ts +7 -0
- package/ui-components/layout/index.js +7 -0
- package/ui-components/settings/GeneralInformationCard.d.ts +12 -0
- package/ui-components/settings/GeneralInformationCard.js +72 -0
- package/ui-components/settings/PasswordInformationCard.d.ts +10 -0
- package/ui-components/settings/PasswordInformationCard.js +167 -0
- package/ui-components/settings/SettingsPageHeader.d.ts +7 -0
- package/ui-components/settings/SettingsPageHeader.js +6 -0
- package/ui-components/settings/index.d.ts +3 -0
- package/ui-components/settings/index.js +3 -0
- package/ui-components/support/SupportModal.d.ts +15 -0
- package/ui-components/support/SupportModal.js +74 -0
- package/ui-components/support/index.d.ts +2 -0
- package/ui-components/support/index.js +1 -0
- package/ui-components/types/index.d.ts +39 -0
- package/ui-components/types/index.js +1 -0
- package/utils/index.d.ts +31 -0
- package/utils/index.js +38 -0
- package/utils/turnstile-config.d.ts +11 -0
- package/utils/turnstile-config.js +19 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT Token Storage Management
|
|
3
|
+
*
|
|
4
|
+
* Provides secure storage for JWT tokens using localStorage
|
|
5
|
+
* for cross-domain authentication support and persistent sessions.
|
|
6
|
+
*/
|
|
7
|
+
const TOKEN_KEY = 'robosystems_jwt_token';
|
|
8
|
+
const TOKEN_EXPIRY_KEY = 'robosystems_jwt_expiry';
|
|
9
|
+
const TOKEN_THRESHOLD_KEY = 'robosystems_jwt_threshold';
|
|
10
|
+
/**
|
|
11
|
+
* Store JWT token in localStorage
|
|
12
|
+
* @param token - The JWT token from login response
|
|
13
|
+
* @param expiresIn - Optional expiry time in seconds (default 30 minutes)
|
|
14
|
+
* @param refreshThreshold - Optional refresh threshold in seconds (default 5 minutes)
|
|
15
|
+
*/
|
|
16
|
+
export function storeToken(token, expiresIn = 1800, refreshThreshold = 300) {
|
|
17
|
+
// 30 minutes default to match backend token lifetime
|
|
18
|
+
if (typeof window === 'undefined')
|
|
19
|
+
return;
|
|
20
|
+
try {
|
|
21
|
+
const expiresAt = Date.now() + expiresIn * 1000;
|
|
22
|
+
localStorage.setItem(TOKEN_KEY, token);
|
|
23
|
+
localStorage.setItem(TOKEN_EXPIRY_KEY, expiresAt.toString());
|
|
24
|
+
localStorage.setItem(TOKEN_THRESHOLD_KEY, (refreshThreshold * 1000).toString());
|
|
25
|
+
// Log token storage for security monitoring
|
|
26
|
+
console.debug('[TokenStorage] Token stored with expiry:', new Date(expiresAt).toISOString(), 'Refresh threshold:', refreshThreshold, 'seconds');
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.error('[TokenStorage] Failed to store token:', error);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Retrieve JWT token from localStorage
|
|
34
|
+
* @returns The stored token or null if not found/expired
|
|
35
|
+
*/
|
|
36
|
+
export function getToken() {
|
|
37
|
+
if (typeof window === 'undefined')
|
|
38
|
+
return null;
|
|
39
|
+
try {
|
|
40
|
+
const token = localStorage.getItem(TOKEN_KEY);
|
|
41
|
+
const expiryStr = localStorage.getItem(TOKEN_EXPIRY_KEY);
|
|
42
|
+
if (!token)
|
|
43
|
+
return null;
|
|
44
|
+
// Check if token is expired
|
|
45
|
+
if (expiryStr) {
|
|
46
|
+
const expiry = parseInt(expiryStr, 10);
|
|
47
|
+
if (Date.now() > expiry) {
|
|
48
|
+
clearToken();
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return token;
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error('[TokenStorage] Failed to retrieve token:', error);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Clear JWT token from storage
|
|
61
|
+
*/
|
|
62
|
+
export function clearToken() {
|
|
63
|
+
if (typeof window === 'undefined')
|
|
64
|
+
return;
|
|
65
|
+
try {
|
|
66
|
+
const hadToken = !!localStorage.getItem(TOKEN_KEY);
|
|
67
|
+
localStorage.removeItem(TOKEN_KEY);
|
|
68
|
+
localStorage.removeItem(TOKEN_EXPIRY_KEY);
|
|
69
|
+
localStorage.removeItem(TOKEN_THRESHOLD_KEY);
|
|
70
|
+
if (hadToken) {
|
|
71
|
+
logSecurityEvent('token_cleared', {
|
|
72
|
+
timestamp: new Date().toISOString(),
|
|
73
|
+
url: window.location.href,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error('[TokenStorage] Failed to clear token:', error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get current token status
|
|
83
|
+
* @returns 'valid' if token is fresh, 'warning' if approaching expiry, 'expired' if expired
|
|
84
|
+
*/
|
|
85
|
+
export function getTokenStatus() {
|
|
86
|
+
if (typeof window === 'undefined')
|
|
87
|
+
return 'expired';
|
|
88
|
+
try {
|
|
89
|
+
const expiryStr = localStorage.getItem(TOKEN_EXPIRY_KEY);
|
|
90
|
+
const thresholdStr = localStorage.getItem(TOKEN_THRESHOLD_KEY);
|
|
91
|
+
if (!expiryStr)
|
|
92
|
+
return 'expired';
|
|
93
|
+
const expiry = parseInt(expiryStr, 10);
|
|
94
|
+
const threshold = thresholdStr ? parseInt(thresholdStr, 10) : 300000; // Default 5 min
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
if (now > expiry)
|
|
97
|
+
return 'expired';
|
|
98
|
+
if (now > expiry - threshold)
|
|
99
|
+
return 'warning';
|
|
100
|
+
return 'valid';
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.error('[TokenStorage] Failed to get token status:', error);
|
|
104
|
+
return 'expired';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get time until token expiry in milliseconds
|
|
109
|
+
* @returns Time until expiry in ms, or 0 if expired
|
|
110
|
+
*/
|
|
111
|
+
export function getTimeUntilExpiry() {
|
|
112
|
+
if (typeof window === 'undefined')
|
|
113
|
+
return 0;
|
|
114
|
+
try {
|
|
115
|
+
const expiryStr = localStorage.getItem(TOKEN_EXPIRY_KEY);
|
|
116
|
+
if (!expiryStr)
|
|
117
|
+
return 0;
|
|
118
|
+
const expiry = parseInt(expiryStr, 10);
|
|
119
|
+
return Math.max(0, expiry - Date.now());
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
console.error('[TokenStorage] Failed to get time until expiry:', error);
|
|
123
|
+
return 0;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Check if a valid token exists
|
|
128
|
+
* @returns True if a non-expired token exists
|
|
129
|
+
*/
|
|
130
|
+
export function hasValidToken() {
|
|
131
|
+
return getToken() !== null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get the Authorization header value for API requests
|
|
135
|
+
* @returns Bearer token header or null
|
|
136
|
+
*/
|
|
137
|
+
export function getAuthHeader() {
|
|
138
|
+
const token = getToken();
|
|
139
|
+
return token ? `Bearer ${token}` : null;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Extract token from login response and store it
|
|
143
|
+
* @param response - The auth response from login/refresh
|
|
144
|
+
*/
|
|
145
|
+
export function handleAuthResponse(response) {
|
|
146
|
+
if (response === null || response === void 0 ? void 0 : response.token) {
|
|
147
|
+
storeToken(response.token, response.expires_in || 1800, response.refresh_threshold || 300);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get valid token (backend handles refresh via grace period)
|
|
152
|
+
* @returns Valid token or null if expired
|
|
153
|
+
*/
|
|
154
|
+
export async function getValidToken() {
|
|
155
|
+
const token = getToken();
|
|
156
|
+
if (!token)
|
|
157
|
+
return null;
|
|
158
|
+
// Just check if token is expired - let backend handle refresh
|
|
159
|
+
const expiryStr = localStorage.getItem(TOKEN_EXPIRY_KEY);
|
|
160
|
+
if (expiryStr) {
|
|
161
|
+
const expiry = parseInt(expiryStr, 10);
|
|
162
|
+
if (Date.now() > expiry) {
|
|
163
|
+
// Token is expired, clear it and let AuthProvider handle re-auth
|
|
164
|
+
clearToken();
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return token;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Security event logging (for monitoring)
|
|
172
|
+
*/
|
|
173
|
+
function logSecurityEvent(event, details = {}) {
|
|
174
|
+
if (process.env.NODE_ENV === 'development') {
|
|
175
|
+
console.log(`[Security] ${event}:`, details);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
export interface AuthUser {
|
|
2
|
+
id: string;
|
|
3
|
+
email: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
emailVerified?: boolean;
|
|
6
|
+
createdAt: string;
|
|
7
|
+
updatedAt: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AuthResponse {
|
|
10
|
+
user: AuthUser;
|
|
11
|
+
success: boolean;
|
|
12
|
+
message?: string;
|
|
13
|
+
token?: string;
|
|
14
|
+
expires_in?: number;
|
|
15
|
+
refresh_threshold?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface LoginRequest {
|
|
18
|
+
email: string;
|
|
19
|
+
password: string;
|
|
20
|
+
}
|
|
21
|
+
export interface RegisterRequest {
|
|
22
|
+
email: string;
|
|
23
|
+
password: string;
|
|
24
|
+
name?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface APIKey {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
key: string;
|
|
30
|
+
permissions: string[];
|
|
31
|
+
graphId?: string;
|
|
32
|
+
createdAt: string;
|
|
33
|
+
expiresAt?: string;
|
|
34
|
+
lastUsedAt?: string;
|
|
35
|
+
isActive: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface CreateAPIKeyRequest {
|
|
38
|
+
name: string;
|
|
39
|
+
permissions: string[];
|
|
40
|
+
graphId?: string;
|
|
41
|
+
expiresAt?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface SSOTokenResponse {
|
|
44
|
+
token: string;
|
|
45
|
+
expires_at: string;
|
|
46
|
+
apps: string[];
|
|
47
|
+
}
|
|
48
|
+
export type AppName = 'robosystems' | 'roboledger' | 'roboinvestor';
|
|
49
|
+
export interface AppConfig {
|
|
50
|
+
name: string;
|
|
51
|
+
displayName: string;
|
|
52
|
+
url: string;
|
|
53
|
+
description: string;
|
|
54
|
+
initials: string;
|
|
55
|
+
/**
|
|
56
|
+
* Literal Tailwind palette class for the app's brand (e.g. `bg-violet-600`).
|
|
57
|
+
* Uses literal palette colors — NOT `primary`/`secondary` tokens — so it
|
|
58
|
+
* renders the correct brand for any app even from inside another app.
|
|
59
|
+
*/
|
|
60
|
+
colorClass: string;
|
|
61
|
+
/** The app's fixed brand hex — cross-app safe; drives the colored AnimatedLogo. */
|
|
62
|
+
brandColor: string;
|
|
63
|
+
}
|
|
64
|
+
export interface AuthContextType {
|
|
65
|
+
user: AuthUser | null;
|
|
66
|
+
isLoading: boolean;
|
|
67
|
+
isAuthenticated: boolean;
|
|
68
|
+
sessionWarning: {
|
|
69
|
+
show: boolean;
|
|
70
|
+
timeLeft: number;
|
|
71
|
+
};
|
|
72
|
+
login: (email: string, password: string) => Promise<AuthUser>;
|
|
73
|
+
register: (email: string, password: string, name?: string) => Promise<AuthUser>;
|
|
74
|
+
logout: () => Promise<void>;
|
|
75
|
+
refreshUser: () => Promise<AuthUser | null>;
|
|
76
|
+
refreshSession: (force?: boolean) => Promise<void>;
|
|
77
|
+
forgotPassword: (email: string) => Promise<{
|
|
78
|
+
success: boolean;
|
|
79
|
+
message?: string;
|
|
80
|
+
}>;
|
|
81
|
+
resetPassword: (token: string, newPassword: string) => Promise<{
|
|
82
|
+
success: boolean;
|
|
83
|
+
message?: string;
|
|
84
|
+
}>;
|
|
85
|
+
validateResetToken: (token: string) => Promise<{
|
|
86
|
+
valid: boolean;
|
|
87
|
+
email?: string;
|
|
88
|
+
message?: string;
|
|
89
|
+
}>;
|
|
90
|
+
verifyEmail: (token: string) => Promise<{
|
|
91
|
+
success: boolean;
|
|
92
|
+
message?: string;
|
|
93
|
+
user?: AuthUser;
|
|
94
|
+
}>;
|
|
95
|
+
resendVerificationEmail: (email: string) => Promise<{
|
|
96
|
+
success: boolean;
|
|
97
|
+
message?: string;
|
|
98
|
+
}>;
|
|
99
|
+
}
|
|
100
|
+
export interface UseUserHook {
|
|
101
|
+
/**
|
|
102
|
+
* Refresh the current user's data from the server
|
|
103
|
+
*/
|
|
104
|
+
refreshUser: () => Promise<AuthUser | null>;
|
|
105
|
+
/**
|
|
106
|
+
* Update the current user's profile information
|
|
107
|
+
*/
|
|
108
|
+
updateProfile: (data: {
|
|
109
|
+
name?: string;
|
|
110
|
+
email?: string;
|
|
111
|
+
}) => Promise<AuthUser>;
|
|
112
|
+
}
|
|
113
|
+
export interface SDKAuthResponse {
|
|
114
|
+
user: AuthUser;
|
|
115
|
+
message?: string;
|
|
116
|
+
token?: string;
|
|
117
|
+
expires_in?: number | null;
|
|
118
|
+
refresh_threshold?: number | null;
|
|
119
|
+
}
|
|
120
|
+
export interface SDKCurrentUserResponse {
|
|
121
|
+
user: AuthUser;
|
|
122
|
+
}
|
|
123
|
+
export interface SDKApiKeyResponse {
|
|
124
|
+
api_key: {
|
|
125
|
+
id: string;
|
|
126
|
+
name: string;
|
|
127
|
+
created_at: string;
|
|
128
|
+
is_active: boolean;
|
|
129
|
+
last_used_at: string | null;
|
|
130
|
+
};
|
|
131
|
+
key: string;
|
|
132
|
+
}
|
|
133
|
+
export interface SDKApiKeysListResponse {
|
|
134
|
+
api_keys: Array<{
|
|
135
|
+
id: string;
|
|
136
|
+
name: string;
|
|
137
|
+
prefix: string;
|
|
138
|
+
created_at: string;
|
|
139
|
+
is_active: boolean;
|
|
140
|
+
last_used_at: string | null;
|
|
141
|
+
}>;
|
|
142
|
+
}
|
|
143
|
+
export interface SDKSsoExchangeResponse {
|
|
144
|
+
session_id: string;
|
|
145
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface SelectableEntity {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
subtitle?: string;
|
|
6
|
+
icon?: React.ComponentType<{
|
|
7
|
+
className?: string;
|
|
8
|
+
}>;
|
|
9
|
+
metadata?: Record<string, any>;
|
|
10
|
+
}
|
|
11
|
+
export interface EntityGroup {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
entities: SelectableEntity[];
|
|
15
|
+
}
|
|
16
|
+
export interface EntitySelectorProps<T extends SelectableEntity> {
|
|
17
|
+
entities: T[];
|
|
18
|
+
currentEntity?: T | null;
|
|
19
|
+
onEntityChange: (entity: T) => Promise<void>;
|
|
20
|
+
title: string;
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
emptyStateMessage?: string;
|
|
23
|
+
emptyStateAction?: {
|
|
24
|
+
label: string;
|
|
25
|
+
href: string;
|
|
26
|
+
};
|
|
27
|
+
filter?: (entity: T) => boolean;
|
|
28
|
+
groupBy?: (entities: T[]) => EntityGroup[];
|
|
29
|
+
isLoading?: boolean;
|
|
30
|
+
isChanging?: boolean;
|
|
31
|
+
className?: string;
|
|
32
|
+
triggerClassName?: string;
|
|
33
|
+
defaultIcon?: React.ComponentType<{
|
|
34
|
+
className?: string;
|
|
35
|
+
}>;
|
|
36
|
+
additionalActions?: Array<{
|
|
37
|
+
label: string;
|
|
38
|
+
href: string;
|
|
39
|
+
icon?: React.ComponentType<{
|
|
40
|
+
className?: string;
|
|
41
|
+
}>;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
export declare function EntitySelector<T extends SelectableEntity>({ entities, currentEntity, onEntityChange, title, placeholder, emptyStateMessage, emptyStateAction, filter, groupBy, isLoading, isChanging, className, triggerClassName, defaultIcon: DefaultIcon, additionalActions, }: EntitySelectorProps<T>): React.JSX.Element;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { Dropdown, DropdownItem, Spinner } from 'flowbite-react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { useCallback, useState } from 'react';
|
|
6
|
+
import { HiChevronDown } from 'react-icons/hi';
|
|
7
|
+
import { useApiError, useToast } from '../hooks';
|
|
8
|
+
export function EntitySelector({ entities, currentEntity, onEntityChange, title, placeholder = 'Select item', emptyStateMessage = 'No items available', emptyStateAction, filter, groupBy, isLoading = false, isChanging = false, className = '', triggerClassName = '', defaultIcon: DefaultIcon, additionalActions = [], }) {
|
|
9
|
+
const router = useRouter();
|
|
10
|
+
const { handleApiError } = useApiError();
|
|
11
|
+
const { showSuccess } = useToast();
|
|
12
|
+
const [switching, setSwitching] = useState(false);
|
|
13
|
+
// Filter entities if filter function is provided
|
|
14
|
+
const filteredEntities = filter ? entities.filter(filter) : entities;
|
|
15
|
+
// Group entities if groupBy function is provided
|
|
16
|
+
const entityGroups = groupBy ? groupBy(filteredEntities) : null;
|
|
17
|
+
const handleEntityChange = useCallback(async (entity) => {
|
|
18
|
+
if (entity.id === (currentEntity === null || currentEntity === void 0 ? void 0 : currentEntity.id) || switching)
|
|
19
|
+
return;
|
|
20
|
+
try {
|
|
21
|
+
setSwitching(true);
|
|
22
|
+
await onEntityChange(entity);
|
|
23
|
+
showSuccess(`Switched to ${entity.name}`);
|
|
24
|
+
router.refresh();
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
handleApiError(error, `Failed to switch to ${entity.name}`);
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
setSwitching(false);
|
|
31
|
+
}
|
|
32
|
+
}, [
|
|
33
|
+
currentEntity,
|
|
34
|
+
switching,
|
|
35
|
+
onEntityChange,
|
|
36
|
+
showSuccess,
|
|
37
|
+
router,
|
|
38
|
+
handleApiError,
|
|
39
|
+
]);
|
|
40
|
+
const isLoadingState = isLoading || isChanging || switching;
|
|
41
|
+
if (isLoadingState && !currentEntity && filteredEntities.length === 0) {
|
|
42
|
+
return (_jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-gray-100 px-3 py-2 dark:bg-zinc-800", children: [_jsx(Spinner, { size: "sm" }), _jsxs("span", { className: "text-sm text-gray-500", children: ["Loading ", title.toLowerCase(), "..."] })] }));
|
|
43
|
+
}
|
|
44
|
+
// Empty state
|
|
45
|
+
if (!currentEntity && filteredEntities.length === 0) {
|
|
46
|
+
const EmptyIcon = emptyStateAction ? 'a' : 'div';
|
|
47
|
+
const emptyProps = emptyStateAction ? { href: emptyStateAction.href } : {};
|
|
48
|
+
return (_jsxs(EmptyIcon, Object.assign({}, emptyProps, { className: `flex items-center gap-2 rounded-lg bg-gray-100 px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-200 dark:bg-zinc-800 dark:text-gray-300 dark:hover:bg-gray-700 ${emptyStateAction ? 'cursor-pointer' : ''}`, children: [DefaultIcon && _jsx(DefaultIcon, { className: "h-4 w-4" }), _jsx("span", { children: (emptyStateAction === null || emptyStateAction === void 0 ? void 0 : emptyStateAction.label) || emptyStateMessage })] })));
|
|
49
|
+
}
|
|
50
|
+
const displayEntity = currentEntity || filteredEntities[0];
|
|
51
|
+
const EntityIcon = (displayEntity === null || displayEntity === void 0 ? void 0 : displayEntity.icon) || DefaultIcon;
|
|
52
|
+
return (_jsx("div", { className: className, children: _jsx(Dropdown, { label: "", renderTrigger: () => (_jsxs("button", { className: `flex items-center gap-2 rounded-lg bg-gray-100 px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-200 dark:bg-zinc-800 dark:text-white dark:hover:bg-zinc-700 ${triggerClassName}`, disabled: isLoadingState, children: [EntityIcon && _jsx(EntityIcon, { className: "h-4 w-4" }), _jsx("span", { className: "max-w-[150px] truncate", children: (displayEntity === null || displayEntity === void 0 ? void 0 : displayEntity.name) || placeholder }), isLoadingState ? (_jsx(Spinner, { size: "sm" })) : (_jsx(HiChevronDown, { className: `h-4 w-4 transition-transform` }))] })), children: filteredEntities.length === 0 ? (_jsx(_Fragment, { children: emptyStateAction && (_jsx(DropdownItem, { href: emptyStateAction.href, children: _jsxs("div", { className: "flex items-center gap-2", children: [DefaultIcon && _jsx(DefaultIcon, { className: "h-4 w-4" }), _jsx("span", { children: emptyStateAction.label })] }) })) })) : (_jsxs(_Fragment, { children: [entityGroups
|
|
53
|
+
? // Grouped entities
|
|
54
|
+
entityGroups.map((group, groupIndex) => (_jsxs("div", { children: [groupIndex > 0 && (_jsx("div", { className: "border-t border-gray-200 dark:border-gray-600" })), _jsx("div", { className: "px-4 py-2 text-xs font-medium text-gray-500 uppercase dark:text-gray-400", children: group.name }), group.entities.map((entity) => (_jsx(EntityDropdownItem, { entity: entity, currentEntity: currentEntity, onEntityChange: handleEntityChange, isDisabled: isLoadingState }, entity.id)))] }, group.id)))
|
|
55
|
+
: // Ungrouped entities
|
|
56
|
+
filteredEntities.map((entity) => (_jsx(EntityDropdownItem, { entity: entity, currentEntity: currentEntity, onEntityChange: handleEntityChange, isDisabled: isLoadingState }, entity.id))), additionalActions.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "border-t border-gray-200 dark:border-gray-600" }), additionalActions.map((action, index) => (_jsx(DropdownItem, { href: action.href, children: _jsxs("div", { className: "flex items-center gap-2", children: [action.icon && _jsx(action.icon, { className: "h-4 w-4" }), _jsx("span", { children: action.label })] }) }, index)))] }))] })) }) }));
|
|
57
|
+
}
|
|
58
|
+
function EntityDropdownItem({ entity, currentEntity, onEntityChange, isDisabled, }) {
|
|
59
|
+
var _a;
|
|
60
|
+
const EntityIcon = entity.icon;
|
|
61
|
+
const isSelected = (currentEntity === null || currentEntity === void 0 ? void 0 : currentEntity.id) === entity.id;
|
|
62
|
+
const isRepository = ((_a = entity.metadata) === null || _a === void 0 ? void 0 : _a.isRepository) || false;
|
|
63
|
+
return (_jsx(DropdownItem, { onClick: () => onEntityChange(entity), disabled: isDisabled, className: `text-left ${isSelected ? 'bg-gray-200 dark:bg-gray-700/50' : ''}`, children: _jsxs("div", { className: "flex w-full items-center gap-3", children: [_jsx("div", { className: `flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ${isRepository ? 'bg-secondary-600/20' : 'bg-primary-600/20'}`, children: EntityIcon && (_jsx(EntityIcon, { className: `h-4 w-4 ${isRepository ? 'text-secondary-400' : 'text-primary-400'}` })) }), _jsxs("div", { className: "min-w-0 flex-1 text-left", children: [_jsx("div", { className: "text-sm font-medium text-gray-900 dark:text-white", children: entity.name }), _jsx("div", { className: "truncate text-xs text-gray-500 dark:text-gray-400", children: entity.id.slice(0, 10) })] })] }) }));
|
|
64
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type SelectableEntity } from './EntitySelector';
|
|
3
|
+
export interface EntityLike {
|
|
4
|
+
identifier: string;
|
|
5
|
+
name: string;
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
}
|
|
8
|
+
export interface GraphWithEntities {
|
|
9
|
+
graphId: string;
|
|
10
|
+
graphName: string;
|
|
11
|
+
entities: EntityLike[];
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
export interface EntityRecord extends SelectableEntity {
|
|
15
|
+
entity: EntityLike;
|
|
16
|
+
graphId: string;
|
|
17
|
+
graphName: string;
|
|
18
|
+
}
|
|
19
|
+
export interface EntitySelectorProps {
|
|
20
|
+
graphsWithEntities: GraphWithEntities[];
|
|
21
|
+
currentEntity?: EntityLike | null;
|
|
22
|
+
currentGraphId?: string | null;
|
|
23
|
+
onEntityChange: (entity: EntityLike, graphId: string, graphName: string) => Promise<void>;
|
|
24
|
+
filter?: (entity: EntityLike, graphId: string) => boolean;
|
|
25
|
+
graphFilter?: (graph: GraphWithEntities) => boolean;
|
|
26
|
+
title?: string;
|
|
27
|
+
placeholder?: string;
|
|
28
|
+
emptyStateMessage?: string;
|
|
29
|
+
emptyStateAction?: {
|
|
30
|
+
label: string;
|
|
31
|
+
href: string;
|
|
32
|
+
};
|
|
33
|
+
groupByGraph?: boolean;
|
|
34
|
+
isLoading?: boolean;
|
|
35
|
+
isChanging?: boolean;
|
|
36
|
+
className?: string;
|
|
37
|
+
additionalActions?: Array<{
|
|
38
|
+
label: string;
|
|
39
|
+
href: string;
|
|
40
|
+
icon?: React.ComponentType<{
|
|
41
|
+
className?: string;
|
|
42
|
+
}>;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
export declare function EntitySelectorCore({ graphsWithEntities, currentEntity, currentGraphId, onEntityChange, filter, graphFilter, title, placeholder, emptyStateMessage, emptyStateAction, groupByGraph, isLoading, isChanging, className, additionalActions, }: EntitySelectorProps): React.JSX.Element;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { HiOfficeBuilding } from 'react-icons/hi';
|
|
5
|
+
import { EntitySelector, } from './EntitySelector';
|
|
6
|
+
// Transform entity to entity
|
|
7
|
+
function entityToEntity(entity, graphId, graphName) {
|
|
8
|
+
return {
|
|
9
|
+
id: `${graphId}-${entity.identifier}`,
|
|
10
|
+
name: entity.name,
|
|
11
|
+
subtitle: graphName,
|
|
12
|
+
icon: HiOfficeBuilding,
|
|
13
|
+
metadata: { entity, graphId, graphName },
|
|
14
|
+
entity,
|
|
15
|
+
graphId,
|
|
16
|
+
graphName,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function EntitySelectorCore({ graphsWithEntities, currentEntity, currentGraphId, onEntityChange, filter, graphFilter, title = 'Entity', placeholder = 'Select Entity', emptyStateMessage = 'No entities available', emptyStateAction, groupByGraph = false, isLoading = false, isChanging = false, className = '', additionalActions = [], }) {
|
|
20
|
+
// Transform entities to entities
|
|
21
|
+
const entities = React.useMemo(() => {
|
|
22
|
+
const filteredGraphs = graphFilter
|
|
23
|
+
? graphsWithEntities.filter(graphFilter)
|
|
24
|
+
: graphsWithEntities;
|
|
25
|
+
const allEntities = [];
|
|
26
|
+
for (const graph of filteredGraphs) {
|
|
27
|
+
const filteredEntities = filter
|
|
28
|
+
? graph.entities.filter((entity) => filter(entity, graph.graphId))
|
|
29
|
+
: graph.entities;
|
|
30
|
+
for (const entity of filteredEntities) {
|
|
31
|
+
allEntities.push(entityToEntity(entity, graph.graphId, graph.graphName));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return allEntities;
|
|
35
|
+
}, [graphsWithEntities, filter, graphFilter]);
|
|
36
|
+
// Get current entity record
|
|
37
|
+
const currentEntityRecord = React.useMemo(() => {
|
|
38
|
+
if (!currentEntity || !currentGraphId)
|
|
39
|
+
return null;
|
|
40
|
+
return (entities.find((e) => e.entity.identifier === currentEntity.identifier &&
|
|
41
|
+
e.graphId === currentGraphId) || null);
|
|
42
|
+
}, [currentEntity, currentGraphId, entities]);
|
|
43
|
+
// Handle entity change
|
|
44
|
+
const handleEntityChange = React.useCallback(async (entity) => {
|
|
45
|
+
await onEntityChange(entity.entity, entity.graphId, entity.graphName);
|
|
46
|
+
}, [onEntityChange]);
|
|
47
|
+
// Group entities by graph if requested
|
|
48
|
+
const groupBy = React.useMemo(() => {
|
|
49
|
+
if (!groupByGraph)
|
|
50
|
+
return undefined;
|
|
51
|
+
return (entities) => {
|
|
52
|
+
const groups = new Map();
|
|
53
|
+
for (const entity of entities) {
|
|
54
|
+
if (!groups.has(entity.graphId)) {
|
|
55
|
+
groups.set(entity.graphId, {
|
|
56
|
+
id: entity.graphId,
|
|
57
|
+
name: entity.graphName,
|
|
58
|
+
entities: [],
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
groups.get(entity.graphId).entities.push(entity);
|
|
62
|
+
}
|
|
63
|
+
return Array.from(groups.values());
|
|
64
|
+
};
|
|
65
|
+
}, [groupByGraph]);
|
|
66
|
+
return (_jsx(EntitySelector, { entities: entities, currentEntity: currentEntityRecord, onEntityChange: handleEntityChange, title: title, placeholder: placeholder, emptyStateMessage: emptyStateMessage, emptyStateAction: emptyStateAction, groupBy: groupBy, isLoading: isLoading, isChanging: isChanging, className: className, defaultIcon: HiOfficeBuilding, additionalActions: additionalActions }));
|
|
67
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { GraphInfo } from '@robosystems/client';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
export interface GraphSelectorProps {
|
|
4
|
+
filter?: (graph: GraphInfo) => boolean;
|
|
5
|
+
title?: string;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
emptyStateMessage?: string;
|
|
8
|
+
emptyStateAction?: {
|
|
9
|
+
label: string;
|
|
10
|
+
href: string;
|
|
11
|
+
};
|
|
12
|
+
className?: string;
|
|
13
|
+
additionalActions?: Array<{
|
|
14
|
+
label: string;
|
|
15
|
+
href: string;
|
|
16
|
+
icon?: React.ComponentType<{
|
|
17
|
+
className?: string;
|
|
18
|
+
}>;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
export declare function GraphSelectorCore({ filter, title, placeholder, emptyStateMessage, emptyStateAction, className, additionalActions, }: GraphSelectorProps): React.JSX.Element;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { HiDatabase } from 'react-icons/hi';
|
|
5
|
+
import { useGraphContext } from '../contexts/graph-context';
|
|
6
|
+
import { EntitySelector } from './EntitySelector';
|
|
7
|
+
function graphToEntity(graph) {
|
|
8
|
+
return {
|
|
9
|
+
id: graph.graphId,
|
|
10
|
+
name: graph.graphName,
|
|
11
|
+
subtitle: graph.isRepository
|
|
12
|
+
? `Shared Repository • ${graph.role}`
|
|
13
|
+
: graph.role,
|
|
14
|
+
icon: HiDatabase,
|
|
15
|
+
metadata: Object.assign(Object.assign({}, graph), { isRepository: graph.isRepository }),
|
|
16
|
+
graphInfo: graph,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function GraphSelectorCore({ filter, title = 'Graph', placeholder = 'Select Graph', emptyStateMessage = 'No graphs available', emptyStateAction, className = '', additionalActions = [], }) {
|
|
20
|
+
const { state, setCurrentGraph } = useGraphContext();
|
|
21
|
+
// Transform graphs to entities
|
|
22
|
+
const entities = React.useMemo(() => {
|
|
23
|
+
const filteredGraphs = filter ? state.graphs.filter(filter) : state.graphs;
|
|
24
|
+
return filteredGraphs.map(graphToEntity);
|
|
25
|
+
}, [state.graphs, filter]);
|
|
26
|
+
// Get current entity
|
|
27
|
+
const currentEntity = React.useMemo(() => {
|
|
28
|
+
if (!state.currentGraphId)
|
|
29
|
+
return null;
|
|
30
|
+
const currentGraph = state.graphs.find((g) => g.graphId === state.currentGraphId);
|
|
31
|
+
return currentGraph ? graphToEntity(currentGraph) : null;
|
|
32
|
+
}, [state.currentGraphId, state.graphs]);
|
|
33
|
+
// Handle entity change
|
|
34
|
+
const handleEntityChange = React.useCallback(async (entity) => {
|
|
35
|
+
// Update graph context (handles backend update and persistence)
|
|
36
|
+
await setCurrentGraph(entity.id);
|
|
37
|
+
}, [setCurrentGraph]);
|
|
38
|
+
return (_jsx(EntitySelector, { entities: entities, currentEntity: currentEntity, onEntityChange: handleEntityChange, title: title, placeholder: placeholder, emptyStateMessage: emptyStateMessage, emptyStateAction: emptyStateAction, isLoading: state.isLoading, className: className, defaultIcon: HiDatabase, additionalActions: additionalActions }));
|
|
39
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
interface PageLayoutProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
variant?: 'constrained' | 'full-width';
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function PageLayout({ children, variant, className, }: PageLayoutProps): import("react").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
export function PageLayout({ children, variant = 'constrained', className, }) {
|
|
4
|
+
return (_jsx("div", { className: twMerge('space-y-6 p-6', variant === 'constrained' && 'mx-auto max-w-7xl', className), children: children }));
|
|
5
|
+
}
|