@orsetra/shared-ui 1.0.24 → 1.0.26
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.
|
@@ -11,6 +11,7 @@ import { useIsMobile } from "../../hooks/use-mobile"
|
|
|
11
11
|
import { Menu } from "lucide-react"
|
|
12
12
|
|
|
13
13
|
interface LayoutContainerProps {
|
|
14
|
+
main_base_url: string
|
|
14
15
|
children: React.ReactNode
|
|
15
16
|
sidebarMenus: SidebarMenus
|
|
16
17
|
user?: { profile?: { email?: string; preferred_username?: string } } | null
|
|
@@ -19,7 +20,7 @@ interface LayoutContainerProps {
|
|
|
19
20
|
userMenuConfig?: UserMenuConfig
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
function LayoutContent({ children, sidebarMenus, user, onSignOut, mode = 'expanded', userMenuConfig }: LayoutContainerProps) {
|
|
23
|
+
function LayoutContent({ children, sidebarMenus, user, onSignOut, mode = 'expanded', userMenuConfig, main_base_url }: LayoutContainerProps) {
|
|
23
24
|
const pathname = usePathname()
|
|
24
25
|
const { setOpen } = useSidebar()
|
|
25
26
|
const isMobile = useIsMobile()
|
|
@@ -69,6 +70,7 @@ function LayoutContent({ children, sidebarMenus, user, onSignOut, mode = 'expand
|
|
|
69
70
|
|
|
70
71
|
{/* MainSidebar - always available, opens as overlay when isHidden */}
|
|
71
72
|
<MainSidebar
|
|
73
|
+
main_base_url={main_base_url}
|
|
72
74
|
isOpen={isMainSidebarOpen}
|
|
73
75
|
onToggle={handleMainSidebarToggle}
|
|
74
76
|
onMenuSelect={handleMenuSelect}
|
|
@@ -13,6 +13,7 @@ import Link from "next/link"
|
|
|
13
13
|
export type SidebarMode = 'expanded' | 'minimized' | 'hidden'
|
|
14
14
|
|
|
15
15
|
interface MainSidebarProps {
|
|
16
|
+
main_base_url: string
|
|
16
17
|
isOpen: boolean
|
|
17
18
|
onToggle: () => void
|
|
18
19
|
onMenuSelect: (menu: string) => void
|
|
@@ -23,6 +24,7 @@ interface MainSidebarProps {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export function MainSidebar({
|
|
27
|
+
main_base_url,
|
|
26
28
|
isOpen,
|
|
27
29
|
onToggle,
|
|
28
30
|
onMenuSelect,
|
|
@@ -49,7 +51,7 @@ export function MainSidebar({
|
|
|
49
51
|
router.push(subMenus[0].href)
|
|
50
52
|
} else {
|
|
51
53
|
// Fallback: naviguer vers /{menuId} (ex: /assets-manager)
|
|
52
|
-
router.push(
|
|
54
|
+
router.push(`${main_base_url}/${menuId}`)
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
if (!isMinimized) {
|
package/lib/http-client.ts
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
export type AuthHeadersProvider = () => Promise<Record<string, string>> | Record<string, string>;
|
|
4
4
|
|
|
5
|
+
export class ApiError extends Error {
|
|
6
|
+
status: number;
|
|
7
|
+
code?: number;
|
|
8
|
+
details?: unknown;
|
|
9
|
+
raw?: unknown;
|
|
10
|
+
|
|
11
|
+
constructor(message: string, params: { status: number; code?: number; details?: unknown; raw?: unknown }) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = 'ApiError';
|
|
14
|
+
this.status = params.status;
|
|
15
|
+
this.code = params.code;
|
|
16
|
+
this.details = params.details;
|
|
17
|
+
this.raw = params.raw;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
5
21
|
class HttpClient {
|
|
6
22
|
private baseUrl: string;
|
|
7
23
|
private authHeadersProvider?: AuthHeadersProvider;
|
|
@@ -58,8 +74,9 @@ class HttpClient {
|
|
|
58
74
|
if (!response.ok) {
|
|
59
75
|
// Try to extract error message from API response
|
|
60
76
|
let errorMessage = `Request failed: ${response.status}`;
|
|
77
|
+
let errorBody: any = undefined;
|
|
61
78
|
try {
|
|
62
|
-
|
|
79
|
+
errorBody = await response.json();
|
|
63
80
|
// Zitadel API returns { code, message, details } on error
|
|
64
81
|
if (errorBody?.message) {
|
|
65
82
|
errorMessage = errorBody.message;
|
|
@@ -69,7 +86,32 @@ class HttpClient {
|
|
|
69
86
|
} catch {
|
|
70
87
|
// If we can't parse JSON, use the default message
|
|
71
88
|
}
|
|
72
|
-
|
|
89
|
+
|
|
90
|
+
const apiError = new ApiError(errorMessage, {
|
|
91
|
+
status: response.status,
|
|
92
|
+
code: typeof errorBody?.code === 'number' ? errorBody.code : undefined,
|
|
93
|
+
details: errorBody?.details,
|
|
94
|
+
raw: errorBody,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Zitadel step-up requirement
|
|
98
|
+
const isMfaRequired =
|
|
99
|
+
apiError.code === 7 ||
|
|
100
|
+
(typeof errorMessage === 'string' && errorMessage.toLowerCase().includes('mfa required'));
|
|
101
|
+
|
|
102
|
+
if (isMfaRequired && typeof window !== 'undefined') {
|
|
103
|
+
try {
|
|
104
|
+
window.dispatchEvent(
|
|
105
|
+
new CustomEvent('zitadel:mfa-required', {
|
|
106
|
+
detail: { url, status: apiError.status, code: apiError.code, error: apiError.raw },
|
|
107
|
+
})
|
|
108
|
+
);
|
|
109
|
+
} catch {
|
|
110
|
+
// ignore event dispatch issues
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
throw apiError;
|
|
73
115
|
}
|
|
74
116
|
|
|
75
117
|
if (response.status === 204) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orsetra/shared-ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.26",
|
|
4
4
|
"description": "Shared UI components for Orsetra platform",
|
|
5
5
|
"main": "./index.ts",
|
|
6
6
|
"types": "./index.ts",
|
|
@@ -92,4 +92,4 @@
|
|
|
92
92
|
"next": "^16.0.7",
|
|
93
93
|
"typescript": "^5"
|
|
94
94
|
}
|
|
95
|
-
}
|
|
95
|
+
}
|