@logickernel/bridge 0.9.8 → 0.10.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/README.md +20 -7
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/next/components.cjs +80 -95
- package/dist/next/components.cjs.map +1 -1
- package/dist/next/components.d.cts +1 -1
- package/dist/next/components.d.ts +1 -1
- package/dist/next/components.js +81 -96
- package/dist/next/components.js.map +1 -1
- package/dist/next/index.cjs +5 -5
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.js +5 -5
- package/dist/next/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,7 +46,20 @@ Use webpack instead of Turbopack (the default) in your `package.json` scripts:
|
|
|
46
46
|
}
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
### 3.
|
|
49
|
+
### 3. Configure Tailwind CSS
|
|
50
|
+
|
|
51
|
+
Add the `@source` directive to your `src/app/globals.css` to ensure Tailwind scans classes from this package:
|
|
52
|
+
|
|
53
|
+
```css
|
|
54
|
+
@import "tailwindcss";
|
|
55
|
+
/* ... other imports ... */
|
|
56
|
+
|
|
57
|
+
@source "../../node_modules/@logickernel/bridge/dist/**/*.{js,cjs}";
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Note**: If using a local `file:../bridge` dependency, use `@source "../../../bridge/dist/**/*.{js,cjs}";` instead. Restart your dev server after adding this directive.
|
|
61
|
+
|
|
62
|
+
### 4. Use the Layout Component
|
|
50
63
|
|
|
51
64
|
Create a wrapper component to establish the client/server boundary:
|
|
52
65
|
|
|
@@ -97,7 +110,7 @@ export default async function Layout({
|
|
|
97
110
|
const session = await auth()
|
|
98
111
|
|
|
99
112
|
if (!session?.user) {
|
|
100
|
-
redirect("/auth/signin?callbackUrl=/app")
|
|
113
|
+
redirect("/core/auth/signin?callbackUrl=/core/app")
|
|
101
114
|
}
|
|
102
115
|
|
|
103
116
|
return (
|
|
@@ -110,11 +123,11 @@ export default async function Layout({
|
|
|
110
123
|
|
|
111
124
|
The layout automatically fetches user information from the navigation API endpoint.
|
|
112
125
|
|
|
113
|
-
###
|
|
126
|
+
### 5. Navigation API Endpoint
|
|
114
127
|
|
|
115
|
-
The layout automatically loads navigation items from `/api/navigation/[organization_id]`.
|
|
128
|
+
The layout automatically loads navigation items from `/core/api/navigation/[organization_id]`.
|
|
116
129
|
|
|
117
|
-
**Required Endpoint:** `GET /api/navigation/[organization_id]`
|
|
130
|
+
**Required Endpoint:** `GET /core/api/navigation/[organization_id]`
|
|
118
131
|
|
|
119
132
|
**Response Payload:**
|
|
120
133
|
|
|
@@ -173,7 +186,7 @@ export default async function DashboardPage() {
|
|
|
173
186
|
const session = await getSession()
|
|
174
187
|
|
|
175
188
|
if (!session) {
|
|
176
|
-
redirect("/auth/signin")
|
|
189
|
+
redirect("/core/auth/signin")
|
|
177
190
|
}
|
|
178
191
|
|
|
179
192
|
return <div>Welcome, {session.user.email}!</div>
|
|
@@ -318,7 +331,7 @@ import { AppLayout, type User } from "@logickernel/bridge/next/components"
|
|
|
318
331
|
interface AppLayoutProps {
|
|
319
332
|
user?: User // Optional: Auto-fetched if not provided
|
|
320
333
|
organizationId?: string // Optional: Current organization ID
|
|
321
|
-
apiBaseUrl?: string // Optional: Defaults to "/api"
|
|
334
|
+
apiBaseUrl?: string // Optional: Defaults to "/core/api"
|
|
322
335
|
children: React.ReactNode
|
|
323
336
|
}
|
|
324
337
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -87,7 +87,7 @@ async function fetchUserRoles(options) {
|
|
|
87
87
|
const cookieName = getCookieName(isSecure);
|
|
88
88
|
try {
|
|
89
89
|
const response = await fetchFn(
|
|
90
|
-
`${kernelUrl}/api/user/organizations/${organizationId}/roles`,
|
|
90
|
+
`${kernelUrl}/core/api/user/organizations/${organizationId}/roles`,
|
|
91
91
|
{
|
|
92
92
|
headers: {
|
|
93
93
|
Cookie: `${cookieName}=${sessionToken}`
|
|
@@ -157,7 +157,7 @@ function createConfig(options) {
|
|
|
157
157
|
}
|
|
158
158
|
function buildSignInUrl(callbackUrl) {
|
|
159
159
|
const authUrl = getAuthUrl();
|
|
160
|
-
const url = new URL("/auth/signin", authUrl);
|
|
160
|
+
const url = new URL("/core/auth/signin", authUrl);
|
|
161
161
|
if (callbackUrl) {
|
|
162
162
|
url.searchParams.set("callbackUrl", callbackUrl);
|
|
163
163
|
}
|
|
@@ -165,7 +165,7 @@ function buildSignInUrl(callbackUrl) {
|
|
|
165
165
|
}
|
|
166
166
|
function buildSignOutUrl(callbackUrl) {
|
|
167
167
|
const authUrl = getAuthUrl();
|
|
168
|
-
const url = new URL("/auth/signout", authUrl);
|
|
168
|
+
const url = new URL("/core/auth/signout", authUrl);
|
|
169
169
|
if (callbackUrl) {
|
|
170
170
|
url.searchParams.set("callbackUrl", callbackUrl);
|
|
171
171
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/jwt.ts","../src/roles.ts","../src/routes.ts"],"names":["decode"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Kernel Bridge JWT\n * Framework-agnostic JWT decoding for AuthJS/NextAuth tokens\n */\n\nimport { decode } from \"@auth/core/jwt\"\nimport type { Session, SessionCookie, DecodeResult, DecodedToken } from \"./types\"\n\n/**\n * Get the base cookie name from AUTH_COOKIE env var or default to authjs.session-token\n * Strips __Secure- prefix if present, as it will be added automatically for secure cookies\n */\nfunction getBaseCookieName(): string {\n const cookieName = process.env.AUTH_COOKIE || \"authjs.session-token\"\n // Remove __Secure- prefix if present, as we'll add it back for secure cookies\n return cookieName.startsWith(\"__Secure-\") ? cookieName.slice(10) : cookieName\n}\n\n/**\n * Cookie names used by AuthJS/NextAuth\n * Defaults to authjs.session-token, can be overridden via AUTH_COOKIE env var\n */\nexport const COOKIE_NAMES = {\n get secure(): string {\n const baseName = getBaseCookieName()\n // Secure cookies must start with __Secure- prefix\n return `__Secure-${baseName}`\n },\n get dev(): string {\n return getBaseCookieName()\n },\n} as const\n\n/**\n * Get the AUTH_URL from environment variables\n * This is used internally for API calls to the kernel\n */\nexport function getAuthUrl(): string {\n return process.env.AUTH_URL || process.env.BASE_URL || \"http://localhost:3000\"\n}\n\n/**\n * Get the appropriate salt for JWT decoding based on cookie type\n */\nexport function getSalt(isSecure: boolean): string {\n return isSecure ? COOKIE_NAMES.secure : COOKIE_NAMES.dev\n}\n\n/**\n * Get the cookie name based on security mode\n */\nexport function getCookieName(isSecure: boolean): string {\n return isSecure ? COOKIE_NAMES.secure : COOKIE_NAMES.dev\n}\n\n/**\n * Check if a token is expired\n */\nexport function isTokenExpired(decoded: DecodedToken): boolean {\n if (!decoded.exp) return false\n return decoded.exp * 1000 < Date.now()\n}\n\n/**\n * Map decoded JWT payload to Session structure\n */\nexport function mapToSession(decoded: DecodedToken): Session {\n return {\n user: {\n id: decoded.sub as string,\n email: decoded.email as string,\n name: (decoded.name as string) || null,\n image: (decoded.picture as string) || null,\n },\n expires: decoded.exp\n ? new Date(decoded.exp * 1000).toISOString()\n : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // Default 30 days\n }\n}\n\n/**\n * Decode and validate a session token\n *\n * This is the core function - framework-agnostic JWT decoding.\n * The caller is responsible for extracting the token from their framework's\n * cookie/header mechanism.\n *\n * @param token - The JWT session token\n * @param secret - The AUTH_SECRET used to sign the token\n * @param isSecure - Whether the token came from a secure cookie\n * @returns DecodeResult with session data or error\n *\n * @example\n * ```typescript\n * const result = await decodeSessionToken(token, process.env.AUTH_SECRET, isSecure)\n * if (result.success) {\n * console.log(result.session.user.email)\n * } else {\n * console.error(result.error)\n * }\n * ```\n */\nexport async function decodeSessionToken(\n token: string | undefined,\n secret: string | undefined,\n isSecure: boolean = false\n): Promise<DecodeResult> {\n if (!token) {\n return { success: false, error: \"missing_token\" }\n }\n\n if (!secret) {\n return { success: false, error: \"missing_secret\" }\n }\n\n try {\n const salt = getSalt(isSecure)\n const decoded = await decode({ token, secret, salt })\n\n if (!decoded) {\n return { success: false, error: \"decode_error\" }\n }\n\n if (isTokenExpired(decoded as DecodedToken)) {\n return { success: false, error: \"expired\" }\n }\n\n const session = mapToSession(decoded as DecodedToken)\n return {\n success: true,\n session,\n decoded: decoded as DecodedToken,\n }\n } catch {\n return { success: false, error: \"decode_error\" }\n }\n}\n\n/**\n * Extract session cookie from a cookies object (generic interface)\n *\n * This works with any object that has a `get(name)` method returning\n * `{ value: string } | undefined`, like Next.js cookies() or similar.\n *\n * @param cookies - Object with get(name) method\n * @returns SessionCookie or undefined if no session cookie found\n */\nexport function extractSessionCookie(cookies: {\n get(name: string): { value: string } | undefined\n}): SessionCookie | undefined {\n // Try secure cookie first (production with HTTPS)\n const secureCookie = cookies.get(COOKIE_NAMES.secure)\n if (secureCookie?.value) {\n return { value: secureCookie.value, isSecure: true }\n }\n\n // Fall back to non-secure cookie (development)\n const devCookie = cookies.get(COOKIE_NAMES.dev)\n if (devCookie?.value) {\n return { value: devCookie.value, isSecure: false }\n }\n\n return undefined\n}\n","/**\n * Kernel Bridge Roles\n * Role checking and permission utilities\n */\n\nimport { getCookieName, getAuthUrl } from \"./jwt\"\n\n/**\n * Options for fetching user roles from the kernel\n */\nexport interface FetchRolesOptions {\n /**\n * Organization ID to fetch roles in\n */\n organizationId: string\n\n /**\n * Session token for authentication (user ID is determined from the token)\n */\n sessionToken: string\n\n /**\n * Whether using secure cookie\n * Defaults to NODE_ENV === \"production\" if not provided\n */\n isSecure?: boolean\n\n /**\n * Custom fetch function (optional, for testing or SSR)\n */\n fetchFn?: typeof fetch\n}\n\n/**\n * Result of role fetching operation\n */\nexport type FetchRolesResult =\n | { success: true; roles: string[] }\n | { success: false; error: string }\n\n/**\n * Fetch user roles from the kernel API\n *\n * This is a framework-agnostic function that makes a fetch request\n * to the kernel's roles endpoint. Uses AUTH_URL environment variable internally.\n * The user ID is determined from the session token.\n *\n * @param options - Configuration for fetching roles\n * @returns Promise with roles array or error\n *\n * @example\n * ```typescript\n * const result = await fetchUserRoles({\n * organizationId: \"org-123\",\n * sessionToken: token,\n * // isSecure defaults to NODE_ENV === \"production\"\n * })\n * if (result.success) {\n * console.log(result.roles)\n * }\n * ```\n */\nexport async function fetchUserRoles(\n options: FetchRolesOptions\n): Promise<FetchRolesResult> {\n const { organizationId, sessionToken, fetchFn = fetch } = options\n // Default isSecure to production mode\n const isSecure = options.isSecure ?? (process.env.NODE_ENV === \"production\")\n const kernelUrl = getAuthUrl()\n const cookieName = getCookieName(isSecure)\n\n try {\n const response = await fetchFn(\n `${kernelUrl}/api/user/organizations/${organizationId}/roles`,\n {\n headers: {\n Cookie: `${cookieName}=${sessionToken}`,\n },\n cache: \"no-store\",\n }\n )\n\n if (!response.ok) {\n return {\n success: false,\n error: `Failed to fetch roles: ${response.status}`,\n }\n }\n\n const data = await response.json()\n const roles = data.roles?.map((r: { roleName: string }) => r.roleName) || []\n\n return { success: true, roles }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n }\n }\n}\n\n/**\n * Check if user has at least one of the required roles\n */\nexport function hasAnyRole(userRoles: string[], requiredRoles: string[]): boolean {\n if (requiredRoles.length === 0) return true // No roles required = any authenticated user\n return requiredRoles.some((role) => userRoles.includes(role))\n}\n\n/**\n * Check if user has all of the required roles\n */\nexport function hasAllRoles(userRoles: string[], requiredRoles: string[]): boolean {\n if (requiredRoles.length === 0) return true\n return requiredRoles.every((role) => userRoles.includes(role))\n}\n\n/**\n * Check if user has a specific role\n */\nexport function hasRole(userRoles: string[], role: string): boolean {\n return userRoles.includes(role)\n}\n\n","/**\n * Kernel Bridge Routes\n * Route matching and protection utilities\n */\n\nimport { getAuthUrl } from \"./jwt\"\nimport type { RouteConfig, RouteMatch, MicroFrontendConfig } from \"./types\"\n\n/**\n * Check if a pathname matches any route in the list\n */\nexport function matchesAnyRoute(pathname: string, routes: string[]): boolean {\n return routes.some((route) => pathname.startsWith(route))\n}\n\n/**\n * Find the first matching protected route for a pathname\n */\nexport function findMatchingProtectedRoute(\n pathname: string,\n protectedRoutes: RouteConfig\n): { route: string; requiredRoles: string[] } | undefined {\n for (const [route, roles] of Object.entries(protectedRoutes)) {\n if (pathname.startsWith(route)) {\n return { route, requiredRoles: roles }\n }\n }\n return undefined\n}\n\n/**\n * Match a pathname against route configuration\n *\n * @param pathname - The URL pathname to match\n * @param config - Micro-frontend configuration\n * @returns RouteMatch indicating the type of route\n *\n * @example\n * ```typescript\n * const match = matchRoute(\"/dashboard\", {\n * publicRoutes: [\"/\", \"/api/health\"],\n * protectedRoutes: { \"/dashboard\": [], \"/admin\": [\"organization.owner\"] },\n * })\n * // Returns: { type: \"protected\", requiredRoles: [] }\n * ```\n */\nexport function matchRoute(\n pathname: string,\n config: Pick<MicroFrontendConfig, \"publicRoutes\" | \"protectedRoutes\">\n): RouteMatch {\n // Check public routes first (if specified)\n if (config.publicRoutes && matchesAnyRoute(pathname, config.publicRoutes)) {\n return { type: \"public\" }\n }\n\n // Check protected routes\n const match = findMatchingProtectedRoute(pathname, config.protectedRoutes)\n if (match) {\n return { type: \"protected\", requiredRoles: match.requiredRoles }\n }\n\n // Not explicitly configured = unprotected\n return { type: \"unprotected\" }\n}\n\n/**\n * Create a default configuration for a micro-frontend\n */\nexport function createConfig(\n options: Partial<MicroFrontendConfig> & Pick<MicroFrontendConfig, \"basePath\">\n): MicroFrontendConfig {\n return {\n basePath: options.basePath,\n protectedRoutes: options.protectedRoutes || {\n \"/dashboard\": [],\n \"/admin\": [\"organization.owner\"],\n },\n publicRoutes: options.publicRoutes || [],\n }\n}\n\n/**\n * Build a sign-in redirect URL\n * Uses AUTH_URL environment variable internally.\n * \n * @param callbackUrl - Optional callback URL to return to after sign-in\n */\nexport function buildSignInUrl(callbackUrl?: string): URL {\n const authUrl = getAuthUrl()\n const url = new URL(\"/auth/signin\", authUrl)\n if (callbackUrl) {\n url.searchParams.set(\"callbackUrl\", callbackUrl)\n }\n return url\n}\n\n/**\n * Build a sign-out redirect URL\n * Uses AUTH_URL environment variable internally.\n * \n * @param callbackUrl - Optional callback URL to return to after sign-out\n */\nexport function buildSignOutUrl(callbackUrl?: string): URL {\n const authUrl = getAuthUrl()\n const url = new URL(\"/auth/signout\", authUrl)\n if (callbackUrl) {\n url.searchParams.set(\"callbackUrl\", callbackUrl)\n }\n return url\n}\n\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/jwt.ts","../src/roles.ts","../src/routes.ts"],"names":["decode"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Kernel Bridge JWT\n * Framework-agnostic JWT decoding for AuthJS/NextAuth tokens\n */\n\nimport { decode } from \"@auth/core/jwt\"\nimport type { Session, SessionCookie, DecodeResult, DecodedToken } from \"./types\"\n\n/**\n * Get the base cookie name from AUTH_COOKIE env var or default to authjs.session-token\n * Strips __Secure- prefix if present, as it will be added automatically for secure cookies\n */\nfunction getBaseCookieName(): string {\n const cookieName = process.env.AUTH_COOKIE || \"authjs.session-token\"\n // Remove __Secure- prefix if present, as we'll add it back for secure cookies\n return cookieName.startsWith(\"__Secure-\") ? cookieName.slice(10) : cookieName\n}\n\n/**\n * Cookie names used by AuthJS/NextAuth\n * Defaults to authjs.session-token, can be overridden via AUTH_COOKIE env var\n */\nexport const COOKIE_NAMES = {\n get secure(): string {\n const baseName = getBaseCookieName()\n // Secure cookies must start with __Secure- prefix\n return `__Secure-${baseName}`\n },\n get dev(): string {\n return getBaseCookieName()\n },\n} as const\n\n/**\n * Get the AUTH_URL from environment variables\n * This is used internally for API calls to the kernel\n */\nexport function getAuthUrl(): string {\n return process.env.AUTH_URL || process.env.BASE_URL || \"http://localhost:3000\"\n}\n\n/**\n * Get the appropriate salt for JWT decoding based on cookie type\n */\nexport function getSalt(isSecure: boolean): string {\n return isSecure ? COOKIE_NAMES.secure : COOKIE_NAMES.dev\n}\n\n/**\n * Get the cookie name based on security mode\n */\nexport function getCookieName(isSecure: boolean): string {\n return isSecure ? COOKIE_NAMES.secure : COOKIE_NAMES.dev\n}\n\n/**\n * Check if a token is expired\n */\nexport function isTokenExpired(decoded: DecodedToken): boolean {\n if (!decoded.exp) return false\n return decoded.exp * 1000 < Date.now()\n}\n\n/**\n * Map decoded JWT payload to Session structure\n */\nexport function mapToSession(decoded: DecodedToken): Session {\n return {\n user: {\n id: decoded.sub as string,\n email: decoded.email as string,\n name: (decoded.name as string) || null,\n image: (decoded.picture as string) || null,\n },\n expires: decoded.exp\n ? new Date(decoded.exp * 1000).toISOString()\n : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // Default 30 days\n }\n}\n\n/**\n * Decode and validate a session token\n *\n * This is the core function - framework-agnostic JWT decoding.\n * The caller is responsible for extracting the token from their framework's\n * cookie/header mechanism.\n *\n * @param token - The JWT session token\n * @param secret - The AUTH_SECRET used to sign the token\n * @param isSecure - Whether the token came from a secure cookie\n * @returns DecodeResult with session data or error\n *\n * @example\n * ```typescript\n * const result = await decodeSessionToken(token, process.env.AUTH_SECRET, isSecure)\n * if (result.success) {\n * console.log(result.session.user.email)\n * } else {\n * console.error(result.error)\n * }\n * ```\n */\nexport async function decodeSessionToken(\n token: string | undefined,\n secret: string | undefined,\n isSecure: boolean = false\n): Promise<DecodeResult> {\n if (!token) {\n return { success: false, error: \"missing_token\" }\n }\n\n if (!secret) {\n return { success: false, error: \"missing_secret\" }\n }\n\n try {\n const salt = getSalt(isSecure)\n const decoded = await decode({ token, secret, salt })\n\n if (!decoded) {\n return { success: false, error: \"decode_error\" }\n }\n\n if (isTokenExpired(decoded as DecodedToken)) {\n return { success: false, error: \"expired\" }\n }\n\n const session = mapToSession(decoded as DecodedToken)\n return {\n success: true,\n session,\n decoded: decoded as DecodedToken,\n }\n } catch {\n return { success: false, error: \"decode_error\" }\n }\n}\n\n/**\n * Extract session cookie from a cookies object (generic interface)\n *\n * This works with any object that has a `get(name)` method returning\n * `{ value: string } | undefined`, like Next.js cookies() or similar.\n *\n * @param cookies - Object with get(name) method\n * @returns SessionCookie or undefined if no session cookie found\n */\nexport function extractSessionCookie(cookies: {\n get(name: string): { value: string } | undefined\n}): SessionCookie | undefined {\n // Try secure cookie first (production with HTTPS)\n const secureCookie = cookies.get(COOKIE_NAMES.secure)\n if (secureCookie?.value) {\n return { value: secureCookie.value, isSecure: true }\n }\n\n // Fall back to non-secure cookie (development)\n const devCookie = cookies.get(COOKIE_NAMES.dev)\n if (devCookie?.value) {\n return { value: devCookie.value, isSecure: false }\n }\n\n return undefined\n}\n","/**\n * Kernel Bridge Roles\n * Role checking and permission utilities\n */\n\nimport { getCookieName, getAuthUrl } from \"./jwt\"\n\n/**\n * Options for fetching user roles from the kernel\n */\nexport interface FetchRolesOptions {\n /**\n * Organization ID to fetch roles in\n */\n organizationId: string\n\n /**\n * Session token for authentication (user ID is determined from the token)\n */\n sessionToken: string\n\n /**\n * Whether using secure cookie\n * Defaults to NODE_ENV === \"production\" if not provided\n */\n isSecure?: boolean\n\n /**\n * Custom fetch function (optional, for testing or SSR)\n */\n fetchFn?: typeof fetch\n}\n\n/**\n * Result of role fetching operation\n */\nexport type FetchRolesResult =\n | { success: true; roles: string[] }\n | { success: false; error: string }\n\n/**\n * Fetch user roles from the kernel API\n *\n * This is a framework-agnostic function that makes a fetch request\n * to the kernel's roles endpoint. Uses AUTH_URL environment variable internally.\n * The user ID is determined from the session token.\n *\n * @param options - Configuration for fetching roles\n * @returns Promise with roles array or error\n *\n * @example\n * ```typescript\n * const result = await fetchUserRoles({\n * organizationId: \"org-123\",\n * sessionToken: token,\n * // isSecure defaults to NODE_ENV === \"production\"\n * })\n * if (result.success) {\n * console.log(result.roles)\n * }\n * ```\n */\nexport async function fetchUserRoles(\n options: FetchRolesOptions\n): Promise<FetchRolesResult> {\n const { organizationId, sessionToken, fetchFn = fetch } = options\n // Default isSecure to production mode\n const isSecure = options.isSecure ?? (process.env.NODE_ENV === \"production\")\n const kernelUrl = getAuthUrl()\n const cookieName = getCookieName(isSecure)\n\n try {\n const response = await fetchFn(\n `${kernelUrl}/core/api/user/organizations/${organizationId}/roles`,\n {\n headers: {\n Cookie: `${cookieName}=${sessionToken}`,\n },\n cache: \"no-store\",\n }\n )\n\n if (!response.ok) {\n return {\n success: false,\n error: `Failed to fetch roles: ${response.status}`,\n }\n }\n\n const data = await response.json()\n const roles = data.roles?.map((r: { roleName: string }) => r.roleName) || []\n\n return { success: true, roles }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n }\n }\n}\n\n/**\n * Check if user has at least one of the required roles\n */\nexport function hasAnyRole(userRoles: string[], requiredRoles: string[]): boolean {\n if (requiredRoles.length === 0) return true // No roles required = any authenticated user\n return requiredRoles.some((role) => userRoles.includes(role))\n}\n\n/**\n * Check if user has all of the required roles\n */\nexport function hasAllRoles(userRoles: string[], requiredRoles: string[]): boolean {\n if (requiredRoles.length === 0) return true\n return requiredRoles.every((role) => userRoles.includes(role))\n}\n\n/**\n * Check if user has a specific role\n */\nexport function hasRole(userRoles: string[], role: string): boolean {\n return userRoles.includes(role)\n}\n\n","/**\n * Kernel Bridge Routes\n * Route matching and protection utilities\n */\n\nimport { getAuthUrl } from \"./jwt\"\nimport type { RouteConfig, RouteMatch, MicroFrontendConfig } from \"./types\"\n\n/**\n * Check if a pathname matches any route in the list\n */\nexport function matchesAnyRoute(pathname: string, routes: string[]): boolean {\n return routes.some((route) => pathname.startsWith(route))\n}\n\n/**\n * Find the first matching protected route for a pathname\n */\nexport function findMatchingProtectedRoute(\n pathname: string,\n protectedRoutes: RouteConfig\n): { route: string; requiredRoles: string[] } | undefined {\n for (const [route, roles] of Object.entries(protectedRoutes)) {\n if (pathname.startsWith(route)) {\n return { route, requiredRoles: roles }\n }\n }\n return undefined\n}\n\n/**\n * Match a pathname against route configuration\n *\n * @param pathname - The URL pathname to match\n * @param config - Micro-frontend configuration\n * @returns RouteMatch indicating the type of route\n *\n * @example\n * ```typescript\n * const match = matchRoute(\"/dashboard\", {\n * publicRoutes: [\"/\", \"/api/health\"],\n * protectedRoutes: { \"/dashboard\": [], \"/admin\": [\"organization.owner\"] },\n * })\n * // Returns: { type: \"protected\", requiredRoles: [] }\n * ```\n */\nexport function matchRoute(\n pathname: string,\n config: Pick<MicroFrontendConfig, \"publicRoutes\" | \"protectedRoutes\">\n): RouteMatch {\n // Check public routes first (if specified)\n if (config.publicRoutes && matchesAnyRoute(pathname, config.publicRoutes)) {\n return { type: \"public\" }\n }\n\n // Check protected routes\n const match = findMatchingProtectedRoute(pathname, config.protectedRoutes)\n if (match) {\n return { type: \"protected\", requiredRoles: match.requiredRoles }\n }\n\n // Not explicitly configured = unprotected\n return { type: \"unprotected\" }\n}\n\n/**\n * Create a default configuration for a micro-frontend\n */\nexport function createConfig(\n options: Partial<MicroFrontendConfig> & Pick<MicroFrontendConfig, \"basePath\">\n): MicroFrontendConfig {\n return {\n basePath: options.basePath,\n protectedRoutes: options.protectedRoutes || {\n \"/dashboard\": [],\n \"/admin\": [\"organization.owner\"],\n },\n publicRoutes: options.publicRoutes || [],\n }\n}\n\n/**\n * Build a sign-in redirect URL\n * Uses AUTH_URL environment variable internally.\n * \n * @param callbackUrl - Optional callback URL to return to after sign-in\n */\nexport function buildSignInUrl(callbackUrl?: string): URL {\n const authUrl = getAuthUrl()\n const url = new URL(\"/core/auth/signin\", authUrl)\n if (callbackUrl) {\n url.searchParams.set(\"callbackUrl\", callbackUrl)\n }\n return url\n}\n\n/**\n * Build a sign-out redirect URL\n * Uses AUTH_URL environment variable internally.\n * \n * @param callbackUrl - Optional callback URL to return to after sign-out\n */\nexport function buildSignOutUrl(callbackUrl?: string): URL {\n const authUrl = getAuthUrl()\n const url = new URL(\"/core/auth/signout\", authUrl)\n if (callbackUrl) {\n url.searchParams.set(\"callbackUrl\", callbackUrl)\n }\n return url\n}\n\n"]}
|
package/dist/index.js
CHANGED
|
@@ -85,7 +85,7 @@ async function fetchUserRoles(options) {
|
|
|
85
85
|
const cookieName = getCookieName(isSecure);
|
|
86
86
|
try {
|
|
87
87
|
const response = await fetchFn(
|
|
88
|
-
`${kernelUrl}/api/user/organizations/${organizationId}/roles`,
|
|
88
|
+
`${kernelUrl}/core/api/user/organizations/${organizationId}/roles`,
|
|
89
89
|
{
|
|
90
90
|
headers: {
|
|
91
91
|
Cookie: `${cookieName}=${sessionToken}`
|
|
@@ -155,7 +155,7 @@ function createConfig(options) {
|
|
|
155
155
|
}
|
|
156
156
|
function buildSignInUrl(callbackUrl) {
|
|
157
157
|
const authUrl = getAuthUrl();
|
|
158
|
-
const url = new URL("/auth/signin", authUrl);
|
|
158
|
+
const url = new URL("/core/auth/signin", authUrl);
|
|
159
159
|
if (callbackUrl) {
|
|
160
160
|
url.searchParams.set("callbackUrl", callbackUrl);
|
|
161
161
|
}
|
|
@@ -163,7 +163,7 @@ function buildSignInUrl(callbackUrl) {
|
|
|
163
163
|
}
|
|
164
164
|
function buildSignOutUrl(callbackUrl) {
|
|
165
165
|
const authUrl = getAuthUrl();
|
|
166
|
-
const url = new URL("/auth/signout", authUrl);
|
|
166
|
+
const url = new URL("/core/auth/signout", authUrl);
|
|
167
167
|
if (callbackUrl) {
|
|
168
168
|
url.searchParams.set("callbackUrl", callbackUrl);
|
|
169
169
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/jwt.ts","../src/roles.ts","../src/routes.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Kernel Bridge JWT\n * Framework-agnostic JWT decoding for AuthJS/NextAuth tokens\n */\n\nimport { decode } from \"@auth/core/jwt\"\nimport type { Session, SessionCookie, DecodeResult, DecodedToken } from \"./types\"\n\n/**\n * Get the base cookie name from AUTH_COOKIE env var or default to authjs.session-token\n * Strips __Secure- prefix if present, as it will be added automatically for secure cookies\n */\nfunction getBaseCookieName(): string {\n const cookieName = process.env.AUTH_COOKIE || \"authjs.session-token\"\n // Remove __Secure- prefix if present, as we'll add it back for secure cookies\n return cookieName.startsWith(\"__Secure-\") ? cookieName.slice(10) : cookieName\n}\n\n/**\n * Cookie names used by AuthJS/NextAuth\n * Defaults to authjs.session-token, can be overridden via AUTH_COOKIE env var\n */\nexport const COOKIE_NAMES = {\n get secure(): string {\n const baseName = getBaseCookieName()\n // Secure cookies must start with __Secure- prefix\n return `__Secure-${baseName}`\n },\n get dev(): string {\n return getBaseCookieName()\n },\n} as const\n\n/**\n * Get the AUTH_URL from environment variables\n * This is used internally for API calls to the kernel\n */\nexport function getAuthUrl(): string {\n return process.env.AUTH_URL || process.env.BASE_URL || \"http://localhost:3000\"\n}\n\n/**\n * Get the appropriate salt for JWT decoding based on cookie type\n */\nexport function getSalt(isSecure: boolean): string {\n return isSecure ? COOKIE_NAMES.secure : COOKIE_NAMES.dev\n}\n\n/**\n * Get the cookie name based on security mode\n */\nexport function getCookieName(isSecure: boolean): string {\n return isSecure ? COOKIE_NAMES.secure : COOKIE_NAMES.dev\n}\n\n/**\n * Check if a token is expired\n */\nexport function isTokenExpired(decoded: DecodedToken): boolean {\n if (!decoded.exp) return false\n return decoded.exp * 1000 < Date.now()\n}\n\n/**\n * Map decoded JWT payload to Session structure\n */\nexport function mapToSession(decoded: DecodedToken): Session {\n return {\n user: {\n id: decoded.sub as string,\n email: decoded.email as string,\n name: (decoded.name as string) || null,\n image: (decoded.picture as string) || null,\n },\n expires: decoded.exp\n ? new Date(decoded.exp * 1000).toISOString()\n : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // Default 30 days\n }\n}\n\n/**\n * Decode and validate a session token\n *\n * This is the core function - framework-agnostic JWT decoding.\n * The caller is responsible for extracting the token from their framework's\n * cookie/header mechanism.\n *\n * @param token - The JWT session token\n * @param secret - The AUTH_SECRET used to sign the token\n * @param isSecure - Whether the token came from a secure cookie\n * @returns DecodeResult with session data or error\n *\n * @example\n * ```typescript\n * const result = await decodeSessionToken(token, process.env.AUTH_SECRET, isSecure)\n * if (result.success) {\n * console.log(result.session.user.email)\n * } else {\n * console.error(result.error)\n * }\n * ```\n */\nexport async function decodeSessionToken(\n token: string | undefined,\n secret: string | undefined,\n isSecure: boolean = false\n): Promise<DecodeResult> {\n if (!token) {\n return { success: false, error: \"missing_token\" }\n }\n\n if (!secret) {\n return { success: false, error: \"missing_secret\" }\n }\n\n try {\n const salt = getSalt(isSecure)\n const decoded = await decode({ token, secret, salt })\n\n if (!decoded) {\n return { success: false, error: \"decode_error\" }\n }\n\n if (isTokenExpired(decoded as DecodedToken)) {\n return { success: false, error: \"expired\" }\n }\n\n const session = mapToSession(decoded as DecodedToken)\n return {\n success: true,\n session,\n decoded: decoded as DecodedToken,\n }\n } catch {\n return { success: false, error: \"decode_error\" }\n }\n}\n\n/**\n * Extract session cookie from a cookies object (generic interface)\n *\n * This works with any object that has a `get(name)` method returning\n * `{ value: string } | undefined`, like Next.js cookies() or similar.\n *\n * @param cookies - Object with get(name) method\n * @returns SessionCookie or undefined if no session cookie found\n */\nexport function extractSessionCookie(cookies: {\n get(name: string): { value: string } | undefined\n}): SessionCookie | undefined {\n // Try secure cookie first (production with HTTPS)\n const secureCookie = cookies.get(COOKIE_NAMES.secure)\n if (secureCookie?.value) {\n return { value: secureCookie.value, isSecure: true }\n }\n\n // Fall back to non-secure cookie (development)\n const devCookie = cookies.get(COOKIE_NAMES.dev)\n if (devCookie?.value) {\n return { value: devCookie.value, isSecure: false }\n }\n\n return undefined\n}\n","/**\n * Kernel Bridge Roles\n * Role checking and permission utilities\n */\n\nimport { getCookieName, getAuthUrl } from \"./jwt\"\n\n/**\n * Options for fetching user roles from the kernel\n */\nexport interface FetchRolesOptions {\n /**\n * Organization ID to fetch roles in\n */\n organizationId: string\n\n /**\n * Session token for authentication (user ID is determined from the token)\n */\n sessionToken: string\n\n /**\n * Whether using secure cookie\n * Defaults to NODE_ENV === \"production\" if not provided\n */\n isSecure?: boolean\n\n /**\n * Custom fetch function (optional, for testing or SSR)\n */\n fetchFn?: typeof fetch\n}\n\n/**\n * Result of role fetching operation\n */\nexport type FetchRolesResult =\n | { success: true; roles: string[] }\n | { success: false; error: string }\n\n/**\n * Fetch user roles from the kernel API\n *\n * This is a framework-agnostic function that makes a fetch request\n * to the kernel's roles endpoint. Uses AUTH_URL environment variable internally.\n * The user ID is determined from the session token.\n *\n * @param options - Configuration for fetching roles\n * @returns Promise with roles array or error\n *\n * @example\n * ```typescript\n * const result = await fetchUserRoles({\n * organizationId: \"org-123\",\n * sessionToken: token,\n * // isSecure defaults to NODE_ENV === \"production\"\n * })\n * if (result.success) {\n * console.log(result.roles)\n * }\n * ```\n */\nexport async function fetchUserRoles(\n options: FetchRolesOptions\n): Promise<FetchRolesResult> {\n const { organizationId, sessionToken, fetchFn = fetch } = options\n // Default isSecure to production mode\n const isSecure = options.isSecure ?? (process.env.NODE_ENV === \"production\")\n const kernelUrl = getAuthUrl()\n const cookieName = getCookieName(isSecure)\n\n try {\n const response = await fetchFn(\n `${kernelUrl}/api/user/organizations/${organizationId}/roles`,\n {\n headers: {\n Cookie: `${cookieName}=${sessionToken}`,\n },\n cache: \"no-store\",\n }\n )\n\n if (!response.ok) {\n return {\n success: false,\n error: `Failed to fetch roles: ${response.status}`,\n }\n }\n\n const data = await response.json()\n const roles = data.roles?.map((r: { roleName: string }) => r.roleName) || []\n\n return { success: true, roles }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n }\n }\n}\n\n/**\n * Check if user has at least one of the required roles\n */\nexport function hasAnyRole(userRoles: string[], requiredRoles: string[]): boolean {\n if (requiredRoles.length === 0) return true // No roles required = any authenticated user\n return requiredRoles.some((role) => userRoles.includes(role))\n}\n\n/**\n * Check if user has all of the required roles\n */\nexport function hasAllRoles(userRoles: string[], requiredRoles: string[]): boolean {\n if (requiredRoles.length === 0) return true\n return requiredRoles.every((role) => userRoles.includes(role))\n}\n\n/**\n * Check if user has a specific role\n */\nexport function hasRole(userRoles: string[], role: string): boolean {\n return userRoles.includes(role)\n}\n\n","/**\n * Kernel Bridge Routes\n * Route matching and protection utilities\n */\n\nimport { getAuthUrl } from \"./jwt\"\nimport type { RouteConfig, RouteMatch, MicroFrontendConfig } from \"./types\"\n\n/**\n * Check if a pathname matches any route in the list\n */\nexport function matchesAnyRoute(pathname: string, routes: string[]): boolean {\n return routes.some((route) => pathname.startsWith(route))\n}\n\n/**\n * Find the first matching protected route for a pathname\n */\nexport function findMatchingProtectedRoute(\n pathname: string,\n protectedRoutes: RouteConfig\n): { route: string; requiredRoles: string[] } | undefined {\n for (const [route, roles] of Object.entries(protectedRoutes)) {\n if (pathname.startsWith(route)) {\n return { route, requiredRoles: roles }\n }\n }\n return undefined\n}\n\n/**\n * Match a pathname against route configuration\n *\n * @param pathname - The URL pathname to match\n * @param config - Micro-frontend configuration\n * @returns RouteMatch indicating the type of route\n *\n * @example\n * ```typescript\n * const match = matchRoute(\"/dashboard\", {\n * publicRoutes: [\"/\", \"/api/health\"],\n * protectedRoutes: { \"/dashboard\": [], \"/admin\": [\"organization.owner\"] },\n * })\n * // Returns: { type: \"protected\", requiredRoles: [] }\n * ```\n */\nexport function matchRoute(\n pathname: string,\n config: Pick<MicroFrontendConfig, \"publicRoutes\" | \"protectedRoutes\">\n): RouteMatch {\n // Check public routes first (if specified)\n if (config.publicRoutes && matchesAnyRoute(pathname, config.publicRoutes)) {\n return { type: \"public\" }\n }\n\n // Check protected routes\n const match = findMatchingProtectedRoute(pathname, config.protectedRoutes)\n if (match) {\n return { type: \"protected\", requiredRoles: match.requiredRoles }\n }\n\n // Not explicitly configured = unprotected\n return { type: \"unprotected\" }\n}\n\n/**\n * Create a default configuration for a micro-frontend\n */\nexport function createConfig(\n options: Partial<MicroFrontendConfig> & Pick<MicroFrontendConfig, \"basePath\">\n): MicroFrontendConfig {\n return {\n basePath: options.basePath,\n protectedRoutes: options.protectedRoutes || {\n \"/dashboard\": [],\n \"/admin\": [\"organization.owner\"],\n },\n publicRoutes: options.publicRoutes || [],\n }\n}\n\n/**\n * Build a sign-in redirect URL\n * Uses AUTH_URL environment variable internally.\n * \n * @param callbackUrl - Optional callback URL to return to after sign-in\n */\nexport function buildSignInUrl(callbackUrl?: string): URL {\n const authUrl = getAuthUrl()\n const url = new URL(\"/auth/signin\", authUrl)\n if (callbackUrl) {\n url.searchParams.set(\"callbackUrl\", callbackUrl)\n }\n return url\n}\n\n/**\n * Build a sign-out redirect URL\n * Uses AUTH_URL environment variable internally.\n * \n * @param callbackUrl - Optional callback URL to return to after sign-out\n */\nexport function buildSignOutUrl(callbackUrl?: string): URL {\n const authUrl = getAuthUrl()\n const url = new URL(\"/auth/signout\", authUrl)\n if (callbackUrl) {\n url.searchParams.set(\"callbackUrl\", callbackUrl)\n }\n return url\n}\n\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/jwt.ts","../src/roles.ts","../src/routes.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Kernel Bridge JWT\n * Framework-agnostic JWT decoding for AuthJS/NextAuth tokens\n */\n\nimport { decode } from \"@auth/core/jwt\"\nimport type { Session, SessionCookie, DecodeResult, DecodedToken } from \"./types\"\n\n/**\n * Get the base cookie name from AUTH_COOKIE env var or default to authjs.session-token\n * Strips __Secure- prefix if present, as it will be added automatically for secure cookies\n */\nfunction getBaseCookieName(): string {\n const cookieName = process.env.AUTH_COOKIE || \"authjs.session-token\"\n // Remove __Secure- prefix if present, as we'll add it back for secure cookies\n return cookieName.startsWith(\"__Secure-\") ? cookieName.slice(10) : cookieName\n}\n\n/**\n * Cookie names used by AuthJS/NextAuth\n * Defaults to authjs.session-token, can be overridden via AUTH_COOKIE env var\n */\nexport const COOKIE_NAMES = {\n get secure(): string {\n const baseName = getBaseCookieName()\n // Secure cookies must start with __Secure- prefix\n return `__Secure-${baseName}`\n },\n get dev(): string {\n return getBaseCookieName()\n },\n} as const\n\n/**\n * Get the AUTH_URL from environment variables\n * This is used internally for API calls to the kernel\n */\nexport function getAuthUrl(): string {\n return process.env.AUTH_URL || process.env.BASE_URL || \"http://localhost:3000\"\n}\n\n/**\n * Get the appropriate salt for JWT decoding based on cookie type\n */\nexport function getSalt(isSecure: boolean): string {\n return isSecure ? COOKIE_NAMES.secure : COOKIE_NAMES.dev\n}\n\n/**\n * Get the cookie name based on security mode\n */\nexport function getCookieName(isSecure: boolean): string {\n return isSecure ? COOKIE_NAMES.secure : COOKIE_NAMES.dev\n}\n\n/**\n * Check if a token is expired\n */\nexport function isTokenExpired(decoded: DecodedToken): boolean {\n if (!decoded.exp) return false\n return decoded.exp * 1000 < Date.now()\n}\n\n/**\n * Map decoded JWT payload to Session structure\n */\nexport function mapToSession(decoded: DecodedToken): Session {\n return {\n user: {\n id: decoded.sub as string,\n email: decoded.email as string,\n name: (decoded.name as string) || null,\n image: (decoded.picture as string) || null,\n },\n expires: decoded.exp\n ? new Date(decoded.exp * 1000).toISOString()\n : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // Default 30 days\n }\n}\n\n/**\n * Decode and validate a session token\n *\n * This is the core function - framework-agnostic JWT decoding.\n * The caller is responsible for extracting the token from their framework's\n * cookie/header mechanism.\n *\n * @param token - The JWT session token\n * @param secret - The AUTH_SECRET used to sign the token\n * @param isSecure - Whether the token came from a secure cookie\n * @returns DecodeResult with session data or error\n *\n * @example\n * ```typescript\n * const result = await decodeSessionToken(token, process.env.AUTH_SECRET, isSecure)\n * if (result.success) {\n * console.log(result.session.user.email)\n * } else {\n * console.error(result.error)\n * }\n * ```\n */\nexport async function decodeSessionToken(\n token: string | undefined,\n secret: string | undefined,\n isSecure: boolean = false\n): Promise<DecodeResult> {\n if (!token) {\n return { success: false, error: \"missing_token\" }\n }\n\n if (!secret) {\n return { success: false, error: \"missing_secret\" }\n }\n\n try {\n const salt = getSalt(isSecure)\n const decoded = await decode({ token, secret, salt })\n\n if (!decoded) {\n return { success: false, error: \"decode_error\" }\n }\n\n if (isTokenExpired(decoded as DecodedToken)) {\n return { success: false, error: \"expired\" }\n }\n\n const session = mapToSession(decoded as DecodedToken)\n return {\n success: true,\n session,\n decoded: decoded as DecodedToken,\n }\n } catch {\n return { success: false, error: \"decode_error\" }\n }\n}\n\n/**\n * Extract session cookie from a cookies object (generic interface)\n *\n * This works with any object that has a `get(name)` method returning\n * `{ value: string } | undefined`, like Next.js cookies() or similar.\n *\n * @param cookies - Object with get(name) method\n * @returns SessionCookie or undefined if no session cookie found\n */\nexport function extractSessionCookie(cookies: {\n get(name: string): { value: string } | undefined\n}): SessionCookie | undefined {\n // Try secure cookie first (production with HTTPS)\n const secureCookie = cookies.get(COOKIE_NAMES.secure)\n if (secureCookie?.value) {\n return { value: secureCookie.value, isSecure: true }\n }\n\n // Fall back to non-secure cookie (development)\n const devCookie = cookies.get(COOKIE_NAMES.dev)\n if (devCookie?.value) {\n return { value: devCookie.value, isSecure: false }\n }\n\n return undefined\n}\n","/**\n * Kernel Bridge Roles\n * Role checking and permission utilities\n */\n\nimport { getCookieName, getAuthUrl } from \"./jwt\"\n\n/**\n * Options for fetching user roles from the kernel\n */\nexport interface FetchRolesOptions {\n /**\n * Organization ID to fetch roles in\n */\n organizationId: string\n\n /**\n * Session token for authentication (user ID is determined from the token)\n */\n sessionToken: string\n\n /**\n * Whether using secure cookie\n * Defaults to NODE_ENV === \"production\" if not provided\n */\n isSecure?: boolean\n\n /**\n * Custom fetch function (optional, for testing or SSR)\n */\n fetchFn?: typeof fetch\n}\n\n/**\n * Result of role fetching operation\n */\nexport type FetchRolesResult =\n | { success: true; roles: string[] }\n | { success: false; error: string }\n\n/**\n * Fetch user roles from the kernel API\n *\n * This is a framework-agnostic function that makes a fetch request\n * to the kernel's roles endpoint. Uses AUTH_URL environment variable internally.\n * The user ID is determined from the session token.\n *\n * @param options - Configuration for fetching roles\n * @returns Promise with roles array or error\n *\n * @example\n * ```typescript\n * const result = await fetchUserRoles({\n * organizationId: \"org-123\",\n * sessionToken: token,\n * // isSecure defaults to NODE_ENV === \"production\"\n * })\n * if (result.success) {\n * console.log(result.roles)\n * }\n * ```\n */\nexport async function fetchUserRoles(\n options: FetchRolesOptions\n): Promise<FetchRolesResult> {\n const { organizationId, sessionToken, fetchFn = fetch } = options\n // Default isSecure to production mode\n const isSecure = options.isSecure ?? (process.env.NODE_ENV === \"production\")\n const kernelUrl = getAuthUrl()\n const cookieName = getCookieName(isSecure)\n\n try {\n const response = await fetchFn(\n `${kernelUrl}/core/api/user/organizations/${organizationId}/roles`,\n {\n headers: {\n Cookie: `${cookieName}=${sessionToken}`,\n },\n cache: \"no-store\",\n }\n )\n\n if (!response.ok) {\n return {\n success: false,\n error: `Failed to fetch roles: ${response.status}`,\n }\n }\n\n const data = await response.json()\n const roles = data.roles?.map((r: { roleName: string }) => r.roleName) || []\n\n return { success: true, roles }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n }\n }\n}\n\n/**\n * Check if user has at least one of the required roles\n */\nexport function hasAnyRole(userRoles: string[], requiredRoles: string[]): boolean {\n if (requiredRoles.length === 0) return true // No roles required = any authenticated user\n return requiredRoles.some((role) => userRoles.includes(role))\n}\n\n/**\n * Check if user has all of the required roles\n */\nexport function hasAllRoles(userRoles: string[], requiredRoles: string[]): boolean {\n if (requiredRoles.length === 0) return true\n return requiredRoles.every((role) => userRoles.includes(role))\n}\n\n/**\n * Check if user has a specific role\n */\nexport function hasRole(userRoles: string[], role: string): boolean {\n return userRoles.includes(role)\n}\n\n","/**\n * Kernel Bridge Routes\n * Route matching and protection utilities\n */\n\nimport { getAuthUrl } from \"./jwt\"\nimport type { RouteConfig, RouteMatch, MicroFrontendConfig } from \"./types\"\n\n/**\n * Check if a pathname matches any route in the list\n */\nexport function matchesAnyRoute(pathname: string, routes: string[]): boolean {\n return routes.some((route) => pathname.startsWith(route))\n}\n\n/**\n * Find the first matching protected route for a pathname\n */\nexport function findMatchingProtectedRoute(\n pathname: string,\n protectedRoutes: RouteConfig\n): { route: string; requiredRoles: string[] } | undefined {\n for (const [route, roles] of Object.entries(protectedRoutes)) {\n if (pathname.startsWith(route)) {\n return { route, requiredRoles: roles }\n }\n }\n return undefined\n}\n\n/**\n * Match a pathname against route configuration\n *\n * @param pathname - The URL pathname to match\n * @param config - Micro-frontend configuration\n * @returns RouteMatch indicating the type of route\n *\n * @example\n * ```typescript\n * const match = matchRoute(\"/dashboard\", {\n * publicRoutes: [\"/\", \"/api/health\"],\n * protectedRoutes: { \"/dashboard\": [], \"/admin\": [\"organization.owner\"] },\n * })\n * // Returns: { type: \"protected\", requiredRoles: [] }\n * ```\n */\nexport function matchRoute(\n pathname: string,\n config: Pick<MicroFrontendConfig, \"publicRoutes\" | \"protectedRoutes\">\n): RouteMatch {\n // Check public routes first (if specified)\n if (config.publicRoutes && matchesAnyRoute(pathname, config.publicRoutes)) {\n return { type: \"public\" }\n }\n\n // Check protected routes\n const match = findMatchingProtectedRoute(pathname, config.protectedRoutes)\n if (match) {\n return { type: \"protected\", requiredRoles: match.requiredRoles }\n }\n\n // Not explicitly configured = unprotected\n return { type: \"unprotected\" }\n}\n\n/**\n * Create a default configuration for a micro-frontend\n */\nexport function createConfig(\n options: Partial<MicroFrontendConfig> & Pick<MicroFrontendConfig, \"basePath\">\n): MicroFrontendConfig {\n return {\n basePath: options.basePath,\n protectedRoutes: options.protectedRoutes || {\n \"/dashboard\": [],\n \"/admin\": [\"organization.owner\"],\n },\n publicRoutes: options.publicRoutes || [],\n }\n}\n\n/**\n * Build a sign-in redirect URL\n * Uses AUTH_URL environment variable internally.\n * \n * @param callbackUrl - Optional callback URL to return to after sign-in\n */\nexport function buildSignInUrl(callbackUrl?: string): URL {\n const authUrl = getAuthUrl()\n const url = new URL(\"/core/auth/signin\", authUrl)\n if (callbackUrl) {\n url.searchParams.set(\"callbackUrl\", callbackUrl)\n }\n return url\n}\n\n/**\n * Build a sign-out redirect URL\n * Uses AUTH_URL environment variable internally.\n * \n * @param callbackUrl - Optional callback URL to return to after sign-out\n */\nexport function buildSignOutUrl(callbackUrl?: string): URL {\n const authUrl = getAuthUrl()\n const url = new URL(\"/core/auth/signout\", authUrl)\n if (callbackUrl) {\n url.searchParams.set(\"callbackUrl\", callbackUrl)\n }\n return url\n}\n\n"]}
|
package/dist/next/components.cjs
CHANGED
|
@@ -445,14 +445,14 @@ function SidebarTrigger({
|
|
|
445
445
|
"data-slot": "sidebar-trigger",
|
|
446
446
|
variant: "ghost",
|
|
447
447
|
size: "icon",
|
|
448
|
-
className: cn("size-7"
|
|
448
|
+
className: cn(className, "size-7"),
|
|
449
449
|
onClick: (event) => {
|
|
450
450
|
onClick?.(event);
|
|
451
451
|
toggleSidebar();
|
|
452
452
|
},
|
|
453
453
|
...props,
|
|
454
454
|
children: [
|
|
455
|
-
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.
|
|
455
|
+
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.MenuIcon, {}),
|
|
456
456
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Toggle Sidebar" })
|
|
457
457
|
]
|
|
458
458
|
}
|
|
@@ -484,12 +484,11 @@ function SidebarRail({ className, ...props }) {
|
|
|
484
484
|
}
|
|
485
485
|
function SidebarInset({ className, ...props }) {
|
|
486
486
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
487
|
-
"
|
|
487
|
+
"div",
|
|
488
488
|
{
|
|
489
489
|
"data-slot": "sidebar-inset",
|
|
490
490
|
className: cn(
|
|
491
491
|
"bg-background relative flex w-full flex-1 flex-col",
|
|
492
|
-
"px-4",
|
|
493
492
|
"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
|
|
494
493
|
className
|
|
495
494
|
),
|
|
@@ -757,6 +756,7 @@ function getIconComponent(icon) {
|
|
|
757
756
|
}
|
|
758
757
|
function NavMain({ items }) {
|
|
759
758
|
const pathname = navigation.usePathname();
|
|
759
|
+
const { isMobile, setOpenMobile } = useSidebar();
|
|
760
760
|
if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
|
|
761
761
|
console.log("[NavMain] Received items:", {
|
|
762
762
|
itemsCount: items.length,
|
|
@@ -856,7 +856,18 @@ function NavMain({ items }) {
|
|
|
856
856
|
]
|
|
857
857
|
}
|
|
858
858
|
) }),
|
|
859
|
-
/* @__PURE__ */ jsxRuntime.jsx(CollapsibleContent2, { children: /* @__PURE__ */ jsxRuntime.jsx(SidebarMenuSub, { children: item.items?.map((subItem) => /* @__PURE__ */ jsxRuntime.jsx(SidebarMenuSubItem, { children: /* @__PURE__ */ jsxRuntime.jsx(SidebarMenuSubButton, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
859
|
+
/* @__PURE__ */ jsxRuntime.jsx(CollapsibleContent2, { children: /* @__PURE__ */ jsxRuntime.jsx(SidebarMenuSub, { children: item.items?.map((subItem) => /* @__PURE__ */ jsxRuntime.jsx(SidebarMenuSubItem, { children: /* @__PURE__ */ jsxRuntime.jsx(SidebarMenuSubButton, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
860
|
+
Link__default.default,
|
|
861
|
+
{
|
|
862
|
+
href: subItem.url,
|
|
863
|
+
onClick: () => {
|
|
864
|
+
if (isMobile) {
|
|
865
|
+
setOpenMobile(false);
|
|
866
|
+
}
|
|
867
|
+
},
|
|
868
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: subItem.title })
|
|
869
|
+
}
|
|
870
|
+
) }) }, subItem.title)) }) })
|
|
860
871
|
] })
|
|
861
872
|
},
|
|
862
873
|
item.title
|
|
@@ -868,10 +879,21 @@ function NavMain({ items }) {
|
|
|
868
879
|
asChild: true,
|
|
869
880
|
tooltip: item.title,
|
|
870
881
|
isActive,
|
|
871
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
882
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
883
|
+
Link__default.default,
|
|
884
|
+
{
|
|
885
|
+
href: item.url,
|
|
886
|
+
onClick: () => {
|
|
887
|
+
if (isMobile) {
|
|
888
|
+
setOpenMobile(false);
|
|
889
|
+
}
|
|
890
|
+
},
|
|
891
|
+
children: [
|
|
892
|
+
item.icon && /* @__PURE__ */ jsxRuntime.jsx(item.icon, {}),
|
|
893
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: item.title })
|
|
894
|
+
]
|
|
895
|
+
}
|
|
896
|
+
)
|
|
875
897
|
}
|
|
876
898
|
) }, item.title);
|
|
877
899
|
}) })
|
|
@@ -1042,7 +1064,7 @@ function NavUser({ user }) {
|
|
|
1042
1064
|
const displayName = user.name || user.email;
|
|
1043
1065
|
const handleSignOut = async () => {
|
|
1044
1066
|
await react.signOut({ redirect: false });
|
|
1045
|
-
router.push("/auth/signin");
|
|
1067
|
+
router.push("/core/auth/signin");
|
|
1046
1068
|
router.refresh();
|
|
1047
1069
|
};
|
|
1048
1070
|
return /* @__PURE__ */ jsxRuntime.jsx(SidebarMenu, { children: /* @__PURE__ */ jsxRuntime.jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu, { children: [
|
|
@@ -1109,11 +1131,19 @@ function TeamSwitcher({
|
|
|
1109
1131
|
const isAppOrgRoute = pathname.startsWith("/app/") && pathSegments.length > 2 && pathSegments[2] !== "user";
|
|
1110
1132
|
const pathOrgId = isAppOrgRoute ? pathSegments[2] : null;
|
|
1111
1133
|
const currentOrgId = organizationId || (pathOrgId && teams.some((team) => team.id === pathOrgId) ? pathOrgId : null);
|
|
1112
|
-
const activeTeam = currentOrgId ? teams.find((team) => team.id === currentOrgId) : null;
|
|
1113
1134
|
const handleTeamChange = (teamId) => {
|
|
1135
|
+
if (isMobile && sidebar?.setOpenMobile) {
|
|
1136
|
+
sidebar.setOpenMobile(false);
|
|
1137
|
+
}
|
|
1114
1138
|
router.push(`/app/${teamId}/home`);
|
|
1115
1139
|
router.refresh();
|
|
1116
1140
|
};
|
|
1141
|
+
const handleManageOrganizations = () => {
|
|
1142
|
+
if (isMobile && sidebar?.setOpenMobile) {
|
|
1143
|
+
sidebar.setOpenMobile(false);
|
|
1144
|
+
}
|
|
1145
|
+
router.push("/app/user/organizations");
|
|
1146
|
+
};
|
|
1117
1147
|
const teamsWithLogo = teams.map((org) => {
|
|
1118
1148
|
const logoIcon = org.logo ? getIconComponent(org.logo) : LucideIcons.GalleryVerticalEnd;
|
|
1119
1149
|
return {
|
|
@@ -1123,6 +1153,7 @@ function TeamSwitcher({
|
|
|
1123
1153
|
plan: org.plan || "Member"
|
|
1124
1154
|
};
|
|
1125
1155
|
});
|
|
1156
|
+
const activeTeamWithLogo = currentOrgId ? teamsWithLogo.find((team) => team.id === currentOrgId) : null;
|
|
1126
1157
|
if (teamsWithLogo.length === 0) {
|
|
1127
1158
|
return /* @__PURE__ */ jsxRuntime.jsx(SidebarMenu, { children: /* @__PURE__ */ jsxRuntime.jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxRuntime.jsxs(SidebarMenuButton, { size: "lg", className: "cursor-default", children: [
|
|
1128
1159
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-muted flex aspect-square size-8 items-center justify-center rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.GalleryVerticalEnd, { className: "size-4 text-muted-foreground" }) }),
|
|
@@ -1132,70 +1163,6 @@ function TeamSwitcher({
|
|
|
1132
1163
|
] })
|
|
1133
1164
|
] }) }) });
|
|
1134
1165
|
}
|
|
1135
|
-
if (!activeTeam) {
|
|
1136
|
-
return /* @__PURE__ */ jsxRuntime.jsx(SidebarMenu, { children: /* @__PURE__ */ jsxRuntime.jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu, { children: [
|
|
1137
|
-
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1138
|
-
SidebarMenuButton,
|
|
1139
|
-
{
|
|
1140
|
-
size: "lg",
|
|
1141
|
-
className: "data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground",
|
|
1142
|
-
children: [
|
|
1143
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-muted flex aspect-square size-8 items-center justify-center rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.GalleryVerticalEnd, { className: "size-4 text-muted-foreground" }) }),
|
|
1144
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid flex-1 text-left text-sm leading-tight", children: [
|
|
1145
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate font-medium", children: "Organizations" }),
|
|
1146
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "truncate text-xs text-muted-foreground", children: [
|
|
1147
|
-
teamsWithLogo.length,
|
|
1148
|
-
" available"
|
|
1149
|
-
] })
|
|
1150
|
-
] }),
|
|
1151
|
-
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronsUpDown, { className: "ml-auto" })
|
|
1152
|
-
]
|
|
1153
|
-
}
|
|
1154
|
-
) }),
|
|
1155
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1156
|
-
DropdownMenuContent,
|
|
1157
|
-
{
|
|
1158
|
-
className: "w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg",
|
|
1159
|
-
align: "start",
|
|
1160
|
-
side: isMobile ? "bottom" : "right",
|
|
1161
|
-
sideOffset: 4,
|
|
1162
|
-
children: [
|
|
1163
|
-
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuLabel, { className: "text-muted-foreground text-xs", children: "Select an organization" }),
|
|
1164
|
-
teamsWithLogo.map((team) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1165
|
-
DropdownMenuItem,
|
|
1166
|
-
{
|
|
1167
|
-
onClick: () => handleTeamChange(team.id),
|
|
1168
|
-
className: "gap-2 p-2",
|
|
1169
|
-
children: [
|
|
1170
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex size-6 items-center justify-center rounded-md border", children: /* @__PURE__ */ jsxRuntime.jsx(team.logo, { className: "size-3.5 shrink-0" }) }),
|
|
1171
|
-
team.name
|
|
1172
|
-
]
|
|
1173
|
-
},
|
|
1174
|
-
team.id
|
|
1175
|
-
)),
|
|
1176
|
-
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuSeparator, {}),
|
|
1177
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1178
|
-
DropdownMenuItem,
|
|
1179
|
-
{
|
|
1180
|
-
className: "gap-2 p-2",
|
|
1181
|
-
onClick: () => router.push("/app/user/organizations"),
|
|
1182
|
-
children: [
|
|
1183
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex size-6 items-center justify-center rounded-md border bg-transparent", children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.GalleryVerticalEnd, { className: "size-4" }) }),
|
|
1184
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-muted-foreground font-medium", children: "Manage organizations" })
|
|
1185
|
-
]
|
|
1186
|
-
}
|
|
1187
|
-
)
|
|
1188
|
-
]
|
|
1189
|
-
}
|
|
1190
|
-
)
|
|
1191
|
-
] }) }) });
|
|
1192
|
-
}
|
|
1193
|
-
const activeTeamWithLogo = teamsWithLogo.find(
|
|
1194
|
-
(team) => team.id === currentOrgId
|
|
1195
|
-
);
|
|
1196
|
-
if (!activeTeamWithLogo) {
|
|
1197
|
-
return null;
|
|
1198
|
-
}
|
|
1199
1166
|
return /* @__PURE__ */ jsxRuntime.jsx(SidebarMenu, { children: /* @__PURE__ */ jsxRuntime.jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu, { children: [
|
|
1200
1167
|
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1201
1168
|
SidebarMenuButton,
|
|
@@ -1203,10 +1170,18 @@ function TeamSwitcher({
|
|
|
1203
1170
|
size: "lg",
|
|
1204
1171
|
className: "data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground",
|
|
1205
1172
|
children: [
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate font-medium", children: activeTeamWithLogo.name })
|
|
1209
|
-
|
|
1173
|
+
activeTeamWithLogo ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1174
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsx(activeTeamWithLogo.logo, { className: "size-4" }) }),
|
|
1175
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid flex-1 text-left text-sm leading-tight", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate font-medium", children: activeTeamWithLogo.name }) })
|
|
1176
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1177
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-muted flex aspect-square size-8 items-center justify-center rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.GalleryVerticalEnd, { className: "size-4 text-muted-foreground" }) }),
|
|
1178
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid flex-1 text-left text-sm leading-tight", children: [
|
|
1179
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate font-medium", children: "Organizations" }),
|
|
1180
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "truncate text-xs text-muted-foreground", children: [
|
|
1181
|
+
teamsWithLogo.length,
|
|
1182
|
+
" available"
|
|
1183
|
+
] })
|
|
1184
|
+
] })
|
|
1210
1185
|
] }),
|
|
1211
1186
|
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronsUpDown, { className: "ml-auto" })
|
|
1212
1187
|
]
|
|
@@ -1220,7 +1195,7 @@ function TeamSwitcher({
|
|
|
1220
1195
|
side: isMobile ? "bottom" : "right",
|
|
1221
1196
|
sideOffset: 4,
|
|
1222
1197
|
children: [
|
|
1223
|
-
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuLabel, { className: "text-muted-foreground text-xs", children: "Organizations" }),
|
|
1198
|
+
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuLabel, { className: "text-muted-foreground text-xs", children: activeTeamWithLogo ? "Organizations" : "Select an organization" }),
|
|
1224
1199
|
teamsWithLogo.map((team) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1225
1200
|
DropdownMenuItem,
|
|
1226
1201
|
{
|
|
@@ -1239,7 +1214,7 @@ function TeamSwitcher({
|
|
|
1239
1214
|
DropdownMenuItem,
|
|
1240
1215
|
{
|
|
1241
1216
|
className: "gap-2 p-2",
|
|
1242
|
-
onClick:
|
|
1217
|
+
onClick: handleManageOrganizations,
|
|
1243
1218
|
children: [
|
|
1244
1219
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex size-6 items-center justify-center rounded-md border bg-transparent", children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.GalleryVerticalEnd, { className: "size-4" }) }),
|
|
1245
1220
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-muted-foreground font-medium", children: "Manage organizations" })
|
|
@@ -1253,7 +1228,7 @@ function TeamSwitcher({
|
|
|
1253
1228
|
}
|
|
1254
1229
|
function useNavigation({
|
|
1255
1230
|
organizationId,
|
|
1256
|
-
apiBaseUrl = "/api",
|
|
1231
|
+
apiBaseUrl = "/core/api",
|
|
1257
1232
|
enabled = true
|
|
1258
1233
|
} = {}) {
|
|
1259
1234
|
const [items, setItems] = React2.useState([]);
|
|
@@ -1267,16 +1242,12 @@ function useNavigation({
|
|
|
1267
1242
|
enabled,
|
|
1268
1243
|
organizationId,
|
|
1269
1244
|
apiBaseUrl,
|
|
1270
|
-
willFetch: enabled
|
|
1245
|
+
willFetch: enabled
|
|
1271
1246
|
});
|
|
1272
1247
|
}
|
|
1273
|
-
if (!enabled
|
|
1248
|
+
if (!enabled) {
|
|
1274
1249
|
if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
|
|
1275
|
-
console.log("[useNavigation] Skipping fetch:"
|
|
1276
|
-
reason: !enabled ? "disabled" : "no organizationId",
|
|
1277
|
-
enabled,
|
|
1278
|
-
organizationId
|
|
1279
|
-
});
|
|
1250
|
+
console.log("[useNavigation] Skipping fetch: disabled");
|
|
1280
1251
|
}
|
|
1281
1252
|
setItems([]);
|
|
1282
1253
|
setOrganizations([]);
|
|
@@ -1284,7 +1255,7 @@ function useNavigation({
|
|
|
1284
1255
|
return;
|
|
1285
1256
|
}
|
|
1286
1257
|
const fetchNavigation = async () => {
|
|
1287
|
-
const url = `${apiBaseUrl}/navigation/${organizationId}`;
|
|
1258
|
+
const url = organizationId ? `${apiBaseUrl}/navigation/${organizationId}` : `${apiBaseUrl}/navigation`;
|
|
1288
1259
|
if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
|
|
1289
1260
|
console.log("[useNavigation] Fetching navigation:", url);
|
|
1290
1261
|
}
|
|
@@ -1312,7 +1283,8 @@ function useNavigation({
|
|
|
1312
1283
|
console.log("[useNavigation] Navigation data received:", {
|
|
1313
1284
|
itemsCount: data.items?.length || 0,
|
|
1314
1285
|
organizationsCount: data.organizations?.length || 0,
|
|
1315
|
-
hasUser: !!data.user
|
|
1286
|
+
hasUser: !!data.user,
|
|
1287
|
+
organizationId: data.organizationId
|
|
1316
1288
|
});
|
|
1317
1289
|
}
|
|
1318
1290
|
setItems(data.items || []);
|
|
@@ -1464,6 +1436,22 @@ function AppSidebar({
|
|
|
1464
1436
|
/* @__PURE__ */ jsxRuntime.jsx(SidebarRail, {})
|
|
1465
1437
|
] });
|
|
1466
1438
|
}
|
|
1439
|
+
function AppHeader() {
|
|
1440
|
+
const { isMobile } = useSidebar();
|
|
1441
|
+
if (!isMobile) {
|
|
1442
|
+
return null;
|
|
1443
|
+
}
|
|
1444
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "p-2 relative flex items-center justify-center", children: [
|
|
1445
|
+
/* @__PURE__ */ jsxRuntime.jsx(SidebarTrigger, { className: "absolute left-2" }),
|
|
1446
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-sans text-xl", children: "Random Truffle" })
|
|
1447
|
+
] });
|
|
1448
|
+
}
|
|
1449
|
+
function SidebarContent2({ children }) {
|
|
1450
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1451
|
+
/* @__PURE__ */ jsxRuntime.jsx(AppHeader, {}),
|
|
1452
|
+
/* @__PURE__ */ jsxRuntime.jsx("main", { className: "px-4 py-0 md:py-6", children })
|
|
1453
|
+
] });
|
|
1454
|
+
}
|
|
1467
1455
|
function AppLayout({
|
|
1468
1456
|
user,
|
|
1469
1457
|
organizationId,
|
|
@@ -1479,10 +1467,7 @@ function AppLayout({
|
|
|
1479
1467
|
apiBaseUrl
|
|
1480
1468
|
}
|
|
1481
1469
|
),
|
|
1482
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1483
|
-
/* @__PURE__ */ jsxRuntime.jsx(SidebarTrigger, {}),
|
|
1484
|
-
children
|
|
1485
|
-
] })
|
|
1470
|
+
/* @__PURE__ */ jsxRuntime.jsx(SidebarInset, { children: /* @__PURE__ */ jsxRuntime.jsx(SidebarContent2, { children }) })
|
|
1486
1471
|
] });
|
|
1487
1472
|
}
|
|
1488
1473
|
|