@logickernel/bridge 0.9.0 → 0.9.1
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 +29 -31
- package/dist/next/components.cjs +80 -6
- package/dist/next/components.cjs.map +1 -1
- package/dist/next/components.js +80 -6
- package/dist/next/components.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,15 +35,20 @@ const nextConfig: NextConfig = {
|
|
|
35
35
|
export default nextConfig
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
Use webpack instead of Turbopack (the default) in your `package.json` scripts:
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"scripts": {
|
|
43
|
+
"dev": "next dev --webpack",
|
|
44
|
+
"build": "next build --webpack"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
### 3. Use the Layout Component
|
|
45
50
|
|
|
46
|
-
Create
|
|
51
|
+
Create a wrapper component to establish the client/server boundary:
|
|
47
52
|
|
|
48
53
|
```typescript
|
|
49
54
|
"use client"
|
|
@@ -77,11 +82,9 @@ export function AppLayoutWrapper({
|
|
|
77
82
|
}
|
|
78
83
|
```
|
|
79
84
|
|
|
80
|
-
|
|
85
|
+
Use in your layout:
|
|
81
86
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
87
|
+
```tsx
|
|
85
88
|
import { redirect } from "next/navigation"
|
|
86
89
|
import { auth } from "@/lib/next-auth" // or your auth setup
|
|
87
90
|
import { AppLayoutWrapper } from "@/components/app-layout-wrapper"
|
|
@@ -105,11 +108,11 @@ export default async function Layout({
|
|
|
105
108
|
}
|
|
106
109
|
```
|
|
107
110
|
|
|
108
|
-
The
|
|
111
|
+
The layout automatically fetches user information from the navigation API endpoint.
|
|
109
112
|
|
|
110
113
|
### 4. Navigation API Endpoint
|
|
111
114
|
|
|
112
|
-
The layout
|
|
115
|
+
The layout automatically loads navigation items from `/api/navigation/[organization_id]`.
|
|
113
116
|
|
|
114
117
|
**Required Endpoint:** `GET /api/navigation/[organization_id]`
|
|
115
118
|
|
|
@@ -147,25 +150,22 @@ interface NavigationOrganization {
|
|
|
147
150
|
}
|
|
148
151
|
```
|
|
149
152
|
|
|
150
|
-
|
|
153
|
+
The endpoint should be protected and only return data the authenticated user has access to. The `{organizationId}` placeholder in item URLs is automatically replaced with the current organization ID.
|
|
151
154
|
|
|
152
155
|
## Features
|
|
153
156
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
-
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
-
|
|
160
|
-
- ✅ **Automatic navigation loading** - Fetches from your API endpoint
|
|
161
|
-
- ✅ **Consistent UI** - Same look and feel across all microfrontends using the library
|
|
162
|
-
- ✅ **No extra components required** - All UI components are included in the library
|
|
157
|
+
- Built-in sidebar navigation with collapsible sections
|
|
158
|
+
- Organization switcher
|
|
159
|
+
- User menu with profile and sign-out
|
|
160
|
+
- Responsive design (mobile and desktop)
|
|
161
|
+
- Automatic navigation loading from API
|
|
162
|
+
- Consistent UI across microfrontends
|
|
163
163
|
|
|
164
164
|
## Next.js Utilities
|
|
165
165
|
|
|
166
166
|
### Server Components / Pages
|
|
167
167
|
|
|
168
|
-
```
|
|
168
|
+
```tsx
|
|
169
169
|
import { getSession } from "@logickernel/bridge/next"
|
|
170
170
|
import { redirect } from "next/navigation"
|
|
171
171
|
|
|
@@ -182,7 +182,7 @@ export default async function DashboardPage() {
|
|
|
182
182
|
|
|
183
183
|
### Role-Based Page Protection
|
|
184
184
|
|
|
185
|
-
```
|
|
185
|
+
```tsx
|
|
186
186
|
import { checkPageAuth } from "@logickernel/bridge/next"
|
|
187
187
|
import { redirect, notFound } from "next/navigation"
|
|
188
188
|
|
|
@@ -267,8 +267,6 @@ export const config = {
|
|
|
267
267
|
|
|
268
268
|
## Import Paths
|
|
269
269
|
|
|
270
|
-
The library uses top-level exports (no subpath exports) for better bundler compatibility:
|
|
271
|
-
|
|
272
270
|
```typescript
|
|
273
271
|
// Core utilities (framework-agnostic)
|
|
274
272
|
import { decodeSessionToken, fetchUserRoles } from "@logickernel/bridge"
|
|
@@ -277,7 +275,7 @@ import { decodeSessionToken, fetchUserRoles } from "@logickernel/bridge"
|
|
|
277
275
|
import { getSession, withAuth, checkPageAuth } from "@logickernel/bridge/next"
|
|
278
276
|
|
|
279
277
|
// Layout components (client components)
|
|
280
|
-
import { AppLayout, type User } from "@logickernel/bridge/next
|
|
278
|
+
import { AppLayout, type User } from "@logickernel/bridge/next/components"
|
|
281
279
|
```
|
|
282
280
|
|
|
283
281
|
## API Reference
|
|
@@ -318,10 +316,10 @@ import { AppLayout, type User } from "@logickernel/bridge/next-components"
|
|
|
318
316
|
|
|
319
317
|
```typescript
|
|
320
318
|
interface AppLayoutProps {
|
|
321
|
-
user?: User // Optional:
|
|
319
|
+
user?: User // Optional: Auto-fetched if not provided
|
|
322
320
|
organizationId?: string // Optional: Current organization ID
|
|
323
|
-
apiBaseUrl?: string // Optional:
|
|
324
|
-
children: React.ReactNode
|
|
321
|
+
apiBaseUrl?: string // Optional: Defaults to "/api"
|
|
322
|
+
children: React.ReactNode
|
|
325
323
|
}
|
|
326
324
|
```
|
|
327
325
|
|
|
@@ -349,7 +347,7 @@ import type {
|
|
|
349
347
|
User,
|
|
350
348
|
NavigationItem,
|
|
351
349
|
NavigationOrganization,
|
|
352
|
-
} from "@logickernel/bridge/next
|
|
350
|
+
} from "@logickernel/bridge/next/components"
|
|
353
351
|
```
|
|
354
352
|
|
|
355
353
|
## Architecture
|
package/dist/next/components.cjs
CHANGED
|
@@ -1217,18 +1217,37 @@ function useNavigation({
|
|
|
1217
1217
|
const [loading, setLoading] = React2.useState(false);
|
|
1218
1218
|
const [error, setError] = React2.useState(null);
|
|
1219
1219
|
React2.useEffect(() => {
|
|
1220
|
+
if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
|
|
1221
|
+
console.log("[useNavigation] Hook called:", {
|
|
1222
|
+
enabled,
|
|
1223
|
+
organizationId,
|
|
1224
|
+
apiBaseUrl,
|
|
1225
|
+
willFetch: enabled && !!organizationId
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1220
1228
|
if (!enabled || !organizationId) {
|
|
1229
|
+
if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
|
|
1230
|
+
console.log("[useNavigation] Skipping fetch:", {
|
|
1231
|
+
reason: !enabled ? "disabled" : "no organizationId",
|
|
1232
|
+
enabled,
|
|
1233
|
+
organizationId
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1221
1236
|
setItems([]);
|
|
1222
1237
|
setOrganizations([]);
|
|
1223
1238
|
setUser(null);
|
|
1224
1239
|
return;
|
|
1225
1240
|
}
|
|
1226
1241
|
const fetchNavigation = async () => {
|
|
1242
|
+
const url = `${apiBaseUrl}/navigation/${organizationId}`;
|
|
1243
|
+
if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
|
|
1244
|
+
console.log("[useNavigation] Fetching navigation:", url);
|
|
1245
|
+
}
|
|
1227
1246
|
setLoading(true);
|
|
1228
1247
|
setError(null);
|
|
1229
1248
|
try {
|
|
1230
1249
|
const response = await fetch(
|
|
1231
|
-
|
|
1250
|
+
url,
|
|
1232
1251
|
{
|
|
1233
1252
|
credentials: "include"
|
|
1234
1253
|
// Include cookies for auth
|
|
@@ -1244,11 +1263,25 @@ function useNavigation({
|
|
|
1244
1263
|
throw new Error(`Failed to fetch navigation: ${response.statusText}`);
|
|
1245
1264
|
}
|
|
1246
1265
|
const data = await response.json();
|
|
1266
|
+
if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
|
|
1267
|
+
console.log("[useNavigation] Navigation data received:", {
|
|
1268
|
+
itemsCount: data.items?.length || 0,
|
|
1269
|
+
organizationsCount: data.organizations?.length || 0,
|
|
1270
|
+
hasUser: !!data.user
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1247
1273
|
setItems(data.items || []);
|
|
1248
1274
|
setOrganizations(data.organizations || []);
|
|
1249
1275
|
setUser(data.user || null);
|
|
1250
1276
|
} catch (err) {
|
|
1251
1277
|
const error2 = err instanceof Error ? err : new Error("Unknown error");
|
|
1278
|
+
if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
|
|
1279
|
+
console.error("[useNavigation] Fetch error:", {
|
|
1280
|
+
error: error2.message,
|
|
1281
|
+
url,
|
|
1282
|
+
organizationId
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1252
1285
|
setError(error2);
|
|
1253
1286
|
setItems([]);
|
|
1254
1287
|
setOrganizations([]);
|
|
@@ -1268,19 +1301,60 @@ function AppSidebar({
|
|
|
1268
1301
|
...props
|
|
1269
1302
|
}) {
|
|
1270
1303
|
const currentPathname = navigation.usePathname();
|
|
1271
|
-
const pathSegments = currentPathname.split("/");
|
|
1272
|
-
|
|
1273
|
-
|
|
1304
|
+
const pathSegments = currentPathname.split("/").filter(Boolean);
|
|
1305
|
+
let pathOrgId = null;
|
|
1306
|
+
if (pathSegments.length >= 2 && pathSegments[0] === "app") {
|
|
1307
|
+
const potentialOrgId = pathSegments[1];
|
|
1308
|
+
if (potentialOrgId && potentialOrgId !== "user") {
|
|
1309
|
+
pathOrgId = potentialOrgId;
|
|
1310
|
+
}
|
|
1311
|
+
} else if (pathSegments.length >= 1) {
|
|
1312
|
+
const potentialOrgId = pathSegments[0];
|
|
1313
|
+
const skipSegments = ["user", "api", "auth", "dashboard", "settings", "app"];
|
|
1314
|
+
if (potentialOrgId && !skipSegments.includes(potentialOrgId)) {
|
|
1315
|
+
if (potentialOrgId.includes("-") || potentialOrgId.length >= 8) {
|
|
1316
|
+
pathOrgId = potentialOrgId;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
const detectedOrgId = organizationId || pathOrgId || void 0;
|
|
1321
|
+
if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
|
|
1322
|
+
console.log("[AppSidebar] Debug:", {
|
|
1323
|
+
currentPathname,
|
|
1324
|
+
pathSegments,
|
|
1325
|
+
pathOrgId,
|
|
1326
|
+
organizationId,
|
|
1327
|
+
detectedOrgId,
|
|
1328
|
+
apiBaseUrl
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1274
1331
|
const {
|
|
1275
1332
|
items: apiNavigationItems,
|
|
1276
1333
|
organizations: apiOrganizations,
|
|
1277
1334
|
user: navigationUser,
|
|
1278
|
-
loading: navigationLoading
|
|
1335
|
+
loading: navigationLoading,
|
|
1336
|
+
error: navigationError
|
|
1279
1337
|
} = useNavigation({
|
|
1280
|
-
organizationId:
|
|
1338
|
+
organizationId: detectedOrgId,
|
|
1281
1339
|
apiBaseUrl,
|
|
1282
1340
|
enabled: true
|
|
1283
1341
|
});
|
|
1342
|
+
React2__namespace.useEffect(() => {
|
|
1343
|
+
if (process.env.NODE_ENV === "development") {
|
|
1344
|
+
console.log("[AppSidebar] Navigation state:", {
|
|
1345
|
+
currentPathname,
|
|
1346
|
+
pathOrgId,
|
|
1347
|
+
organizationId,
|
|
1348
|
+
detectedOrgId,
|
|
1349
|
+
apiBaseUrl,
|
|
1350
|
+
itemsCount: apiNavigationItems.length,
|
|
1351
|
+
organizationsCount: apiOrganizations.length,
|
|
1352
|
+
loading: navigationLoading,
|
|
1353
|
+
error: navigationError?.message,
|
|
1354
|
+
navigationUrl: detectedOrgId ? `${apiBaseUrl}/navigation/${detectedOrgId}` : "N/A"
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
}, [currentPathname, pathOrgId, organizationId, detectedOrgId, apiBaseUrl, apiNavigationItems.length, apiOrganizations.length, navigationLoading, navigationError]);
|
|
1284
1358
|
const user = navigationUser || userProp;
|
|
1285
1359
|
const currentOrgId = organizationId || (pathOrgId && apiOrganizations.some((org) => org.id === pathOrgId) ? pathOrgId : void 0);
|
|
1286
1360
|
let navigationItems = apiNavigationItems;
|