Compare commits
4 Commits
1f2141118d
...
387e10b2fb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
387e10b2fb | ||
|
|
360b611ae6 | ||
|
|
b0e2c21d0a | ||
|
|
d430680df5 |
65
README.md
65
README.md
@@ -1,6 +1,6 @@
|
|||||||
# `@gsc/web-kit`
|
# `@gsc/web-kit`
|
||||||
|
|
||||||
App skeleton for GSC Next.js frontends. Wraps `@limitless/ui` primitives into a pre-configured layout + auth + data/forms/feedback stack so apps just write their domain pages.
|
App skeleton for GSC Next.js frontends. Curates `@limitless/ui` primitives behind a pre-configured layout + auth + data/forms/feedback/navigation stack so apps just write their domain pages.
|
||||||
|
|
||||||
See the implementation plan in the parent repo for the full module map. This is a `file:` dep consumed by every GSC frontend.
|
See the implementation plan in the parent repo for the full module map. This is a `file:` dep consumed by every GSC frontend.
|
||||||
|
|
||||||
@@ -28,23 +28,64 @@ your app
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
import "@gsc/web-kit/css"; // CSS bundle (layout-3 + JetBrains Mono)
|
import "@gsc/web-kit/css"; // CSS bundle (layout-3 + JetBrains Mono)
|
||||||
|
|
||||||
|
// Chrome
|
||||||
import { AppLayout } from "@gsc/web-kit/layout";
|
import { AppLayout } from "@gsc/web-kit/layout";
|
||||||
import { createAuth } from "@gsc/web-kit/auth/server";
|
import { useShell } from "@gsc/web-kit/shell";
|
||||||
import { fetchShellConfig } from "@gsc/web-kit/shell/server";
|
import { fetchShellConfig } from "@gsc/web-kit/shell/server";
|
||||||
import { EntityList } from "@gsc/web-kit/data";
|
|
||||||
import { Form, TextField } from "@gsc/web-kit/forms";
|
// Auth (NextAuth v5 + Keycloak)
|
||||||
import { useToast } from "@gsc/web-kit/feedback";
|
import { createAuth } from "@gsc/web-kit/auth/server";
|
||||||
import { Breadcrumbs } from "@gsc/web-kit/navigation";
|
import { createAuthMiddleware } from "@gsc/web-kit/auth/middleware";
|
||||||
import { createApiClient } from "@gsc/web-kit/api";
|
import { signInRedirect } from "@gsc/web-kit/auth";
|
||||||
import { formatCurrency } from "@gsc/web-kit/utils";
|
|
||||||
|
// Building blocks — curated re-exports from @limitless/ui
|
||||||
|
import {
|
||||||
|
Table, DataTable, Pagination, TreeView, Timeline,
|
||||||
|
Calendar, Gallery, Sortable, ListGroup,
|
||||||
|
StatWidget, ProgressWidget, ChartWidget, ContentWidget,
|
||||||
|
} from "@gsc/web-kit/data";
|
||||||
|
|
||||||
|
import {
|
||||||
|
FormGroup, FormControl, FormCheck, Select, InputGroup,
|
||||||
|
SelectSingle, MultiSelect, TagsSelect, AsyncSelect,
|
||||||
|
DatePicker, ColorPicker, TagInput, FileUpload,
|
||||||
|
Slider, Rating, DualListBox, ImageCropper, Wizard, Stepper,
|
||||||
|
// validation
|
||||||
|
useValidation, useFieldValidation, required, email, password,
|
||||||
|
noInjection, europeanAddress, /* …etc */
|
||||||
|
} from "@gsc/web-kit/forms";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Alert, Toast, Notification, Modal, Offcanvas,
|
||||||
|
Popover, Tooltip, SweetAlert, Spinner,
|
||||||
|
Progress, ProgressStacked, IdleTimeout, FAB,
|
||||||
|
} from "@gsc/web-kit/feedback";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Breadcrumbs, Nav, Tabs, Pills, Dropdown, ContextMenu,
|
||||||
|
Scrollspy, PageHeader, Accordion, Collapse, Carousel,
|
||||||
|
Embed, SyntaxHighlighter, Card, Badge, Button, Media,
|
||||||
|
} from "@gsc/web-kit/navigation";
|
||||||
|
|
||||||
|
import { useDisclosure } from "@gsc/web-kit/utils";
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `/api` sub-export is reserved for a future HTTP client helper; it currently re-exports nothing.
|
||||||
|
|
||||||
## Phases
|
## Phases
|
||||||
|
|
||||||
| Phase | Scope | Status |
|
| Phase | Scope | Status |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 1 | Package scaffold + CSS bundle + sub-export stubs | **in progress** |
|
| 1 | Package scaffold + CSS bundle + sub-export stubs | **done** |
|
||||||
| 2 | layout · auth · shell — usable end-to-end with shell-api | planned |
|
| 2 | layout · auth · shell — usable end-to-end with shell-api | **done** |
|
||||||
| 3 | data · forms — EntityList, Form, DefineAction | planned |
|
| 3 | data · forms — curated re-exports from limitless + validation | **done (v0.3.0)** |
|
||||||
| 4 | feedback · navigation · api · utils | planned |
|
| 4 | feedback · navigation · utils — curated re-exports from limitless | **done (v0.3.0)** |
|
||||||
|
| 4a | api · HTTP client helper (Bearer injection, 401 → signInRedirect) | planned |
|
||||||
| 5 | Roll out to gscCRM / gscChronos / gscAdmin / gscPortal | planned |
|
| 5 | Roll out to gscCRM / gscChronos / gscAdmin / gscPortal | planned |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- `AppLayout` is a thin wrapper around `<AppShell>` from `@limitless/ui` — they share the `ShellConfig` DTO, so the kit owns the consumer-facing surface without duplicating chrome code.
|
||||||
|
- All form, data, feedback, and navigation modules are **curated re-exports**: apps should never need to reach into `@limitless/ui` directly.
|
||||||
|
- The `Widget` family in `/data` is intentionally narrow (`StatWidget`, `ProgressWidget`, `ChartWidget`, `ContentWidget`) — the installed limitless dist has a duplicate `Widget` file/folder collision, so only names exported by both are passed through.
|
||||||
|
|||||||
148
package-lock.json
generated
148
package-lock.json
generated
@@ -18,6 +18,7 @@
|
|||||||
"@types/node": "^20.11.0",
|
"@types/node": "^20.11.0",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"next": "16.1.1",
|
||||||
"typescript": "^5.4.0"
|
"typescript": "^5.4.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -25,6 +26,11 @@
|
|||||||
"next": ">=15.0.0",
|
"next": ">=15.0.0",
|
||||||
"react": "^18.2.0 || ^19.0.0",
|
"react": "^18.2.0 || ^19.0.0",
|
||||||
"react-dom": "^18.2.0 || ^19.0.0"
|
"react-dom": "^18.2.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bootstrap": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"../limitless-ui": {
|
"../limitless-ui": {
|
||||||
@@ -87,7 +93,6 @@
|
|||||||
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
@@ -128,7 +133,6 @@
|
|||||||
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
@@ -145,7 +149,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -168,7 +171,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -191,7 +193,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -208,7 +209,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -242,7 +241,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -259,7 +257,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -276,7 +273,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -293,7 +289,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -310,7 +305,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -327,7 +321,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -344,7 +337,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -361,7 +353,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -384,7 +375,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -407,7 +397,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -430,7 +419,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -453,7 +441,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -476,7 +463,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -499,7 +485,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -522,7 +507,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -542,7 +526,6 @@
|
|||||||
],
|
],
|
||||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emnapi/runtime": "^1.7.0"
|
"@emnapi/runtime": "^1.7.0"
|
||||||
},
|
},
|
||||||
@@ -565,7 +548,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -585,7 +567,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -605,7 +586,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -618,16 +598,15 @@
|
|||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "16.2.6",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.1.tgz",
|
||||||
"integrity": "sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==",
|
"integrity": "sha512-3oxyM97Sr2PqiVyMyrZUtrtM3jqqFxOQJVuKclDsgj/L728iZt/GyslkN4NwarledZATCenbk4Offjk1hQmaAA==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-arm64": {
|
"node_modules/@next/swc-darwin-arm64": {
|
||||||
"version": "16.2.6",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.1.tgz",
|
||||||
"integrity": "sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==",
|
"integrity": "sha512-JS3m42ifsVSJjSTzh27nW+Igfha3NdBOFScr9C80hHGrWx55pTrVL23RJbqir7k7/15SKlrLHhh/MQzqBBYrQA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -636,15 +615,14 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-x64": {
|
"node_modules/@next/swc-darwin-x64": {
|
||||||
"version": "16.2.6",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.1.tgz",
|
||||||
"integrity": "sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==",
|
"integrity": "sha512-hbyKtrDGUkgkyQi1m1IyD3q4I/3m9ngr+V93z4oKHrPcmxwNL5iMWORvLSGAf2YujL+6HxgVvZuCYZfLfb4bGw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -653,15 +631,14 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||||
"version": "16.2.6",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.1.tgz",
|
||||||
"integrity": "sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==",
|
"integrity": "sha512-/fvHet+EYckFvRLQ0jPHJCUI5/B56+2DpI1xDSvi80r/3Ez+Eaa2Yq4tJcRTaB1kqj/HrYKn8Yplm9bNoMJpwQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -670,15 +647,14 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-musl": {
|
"node_modules/@next/swc-linux-arm64-musl": {
|
||||||
"version": "16.2.6",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.1.tgz",
|
||||||
"integrity": "sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==",
|
"integrity": "sha512-MFHrgL4TXNQbBPzkKKur4Fb5ICEJa87HM7fczFs2+HWblM7mMLdco3dvyTI+QmLBU9xgns/EeeINSZD6Ar+oLg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -687,15 +663,14 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-gnu": {
|
"node_modules/@next/swc-linux-x64-gnu": {
|
||||||
"version": "16.2.6",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.1.tgz",
|
||||||
"integrity": "sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==",
|
"integrity": "sha512-20bYDfgOQAPUkkKBnyP9PTuHiJGM7HzNBbuqmD0jiFVZ0aOldz+VnJhbxzjcSabYsnNjMPsE0cyzEudpYxsrUQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -704,15 +679,14 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-musl": {
|
"node_modules/@next/swc-linux-x64-musl": {
|
||||||
"version": "16.2.6",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.1.tgz",
|
||||||
"integrity": "sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==",
|
"integrity": "sha512-9pRbK3M4asAHQRkwaXwu601oPZHghuSC8IXNENgbBSyImHv/zY4K5udBusgdHkvJ/Tcr96jJwQYOll0qU8+fPA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -721,15 +695,14 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||||
"version": "16.2.6",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.1.tgz",
|
||||||
"integrity": "sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==",
|
"integrity": "sha512-bdfQkggaLgnmYrFkSQfsHfOhk/mCYmjnrbRCGgkMcoOBZ4n+TRRSLmT/CU5SATzlBJ9TpioUyBW/vWFXTqQRiA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -738,15 +711,14 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-x64-msvc": {
|
"node_modules/@next/swc-win32-x64-msvc": {
|
||||||
"version": "16.2.6",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.1.tgz",
|
||||||
"integrity": "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==",
|
"integrity": "sha512-Ncwbw2WJ57Al5OX0k4chM68DKhEPlrXBaSXDCi2kPi5f4d8b3ejr3RRJGfKBLrn2YJL5ezNS7w2TZLHSti8CMw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -755,7 +727,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
@@ -1069,6 +1040,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -1284,7 +1256,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||||
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
|
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.8.0"
|
"tslib": "^2.8.0"
|
||||||
}
|
}
|
||||||
@@ -1333,7 +1304,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz",
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz",
|
||||||
"integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==",
|
"integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"baseline-browser-mapping": "dist/cli.cjs"
|
"baseline-browser-mapping": "dist/cli.cjs"
|
||||||
},
|
},
|
||||||
@@ -1356,6 +1326,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@popperjs/core": "^2.11.8"
|
"@popperjs/core": "^2.11.8"
|
||||||
@@ -1379,15 +1350,13 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "CC-BY-4.0",
|
"license": "CC-BY-4.0"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/client-only": {
|
"node_modules/client-only": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
@@ -1471,7 +1440,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"nanoid": "bin/nanoid.cjs"
|
"nanoid": "bin/nanoid.cjs"
|
||||||
},
|
},
|
||||||
@@ -1489,15 +1457,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "16.2.6",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-16.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-16.1.1.tgz",
|
||||||
"integrity": "sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==",
|
"integrity": "sha512-QI+T7xrxt1pF6SQ/JYFz95ro/mg/1Znk5vBebsWwbpejj1T0A23hO7GYEaVac9QUOT2BIMiuzm0L99ooq7k0/w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "16.2.6",
|
"@next/env": "16.1.1",
|
||||||
"@swc/helpers": "0.5.15",
|
"@swc/helpers": "0.5.15",
|
||||||
"baseline-browser-mapping": "^2.9.19",
|
"baseline-browser-mapping": "^2.8.3",
|
||||||
"caniuse-lite": "^1.0.30001579",
|
"caniuse-lite": "^1.0.30001579",
|
||||||
"postcss": "8.4.31",
|
"postcss": "8.4.31",
|
||||||
"styled-jsx": "5.1.6"
|
"styled-jsx": "5.1.6"
|
||||||
@@ -1509,15 +1476,15 @@
|
|||||||
"node": ">=20.9.0"
|
"node": ">=20.9.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@next/swc-darwin-arm64": "16.2.6",
|
"@next/swc-darwin-arm64": "16.1.1",
|
||||||
"@next/swc-darwin-x64": "16.2.6",
|
"@next/swc-darwin-x64": "16.1.1",
|
||||||
"@next/swc-linux-arm64-gnu": "16.2.6",
|
"@next/swc-linux-arm64-gnu": "16.1.1",
|
||||||
"@next/swc-linux-arm64-musl": "16.2.6",
|
"@next/swc-linux-arm64-musl": "16.1.1",
|
||||||
"@next/swc-linux-x64-gnu": "16.2.6",
|
"@next/swc-linux-x64-gnu": "16.1.1",
|
||||||
"@next/swc-linux-x64-musl": "16.2.6",
|
"@next/swc-linux-x64-musl": "16.1.1",
|
||||||
"@next/swc-win32-arm64-msvc": "16.2.6",
|
"@next/swc-win32-arm64-msvc": "16.1.1",
|
||||||
"@next/swc-win32-x64-msvc": "16.2.6",
|
"@next/swc-win32-x64-msvc": "16.1.1",
|
||||||
"sharp": "^0.34.5"
|
"sharp": "^0.34.4"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@opentelemetry/api": "^1.1.0",
|
"@opentelemetry/api": "^1.1.0",
|
||||||
@@ -1676,8 +1643,7 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
"license": "ISC",
|
"license": "ISC"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
@@ -1716,7 +1682,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.6",
|
"nanoid": "^3.3.6",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
@@ -1781,7 +1746,6 @@
|
|||||||
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
},
|
},
|
||||||
@@ -1796,7 +1760,6 @@
|
|||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@img/colour": "^1.0.0",
|
"@img/colour": "^1.0.0",
|
||||||
"detect-libc": "^2.1.2",
|
"detect-libc": "^2.1.2",
|
||||||
@@ -1840,7 +1803,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -1850,7 +1812,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
||||||
"integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
|
"integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"client-only": "0.0.1"
|
"client-only": "0.0.1"
|
||||||
},
|
},
|
||||||
@@ -1873,8 +1834,7 @@
|
|||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"license": "0BSD",
|
"license": "0BSD"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
|
|||||||
75
package.json
75
package.json
@@ -1,27 +1,66 @@
|
|||||||
{
|
{
|
||||||
"name": "@gsc/web-kit",
|
"name": "@gsc/web-kit",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"description": "GSC web app skeleton — layout, auth, data, forms, feedback, navigation. Built on @limitless/ui. Drop into a Next.js app and just write pages.",
|
"description": "GSC web app skeleton \u2014 layout, auth, data, forms, feedback, navigation. Built on @limitless/ui. Drop into a Next.js app and just write pages.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"module": "dist/index.js",
|
"module": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" },
|
".": {
|
||||||
"./css": "./dist/styles/index.css",
|
"types": "./dist/index.d.ts",
|
||||||
"./layout": { "import": "./dist/layout/index.js", "types": "./dist/layout/index.d.ts" },
|
"import": "./dist/index.js"
|
||||||
"./auth": { "import": "./dist/auth/index.js", "types": "./dist/auth/index.d.ts" },
|
},
|
||||||
"./auth/server":{ "import": "./dist/auth/server.js", "types": "./dist/auth/server.d.ts" },
|
"./css": "./dist/styles/index.css",
|
||||||
"./auth/middleware": { "import": "./dist/auth/middleware.js", "types": "./dist/auth/middleware.d.ts" },
|
"./layout": {
|
||||||
"./shell": { "import": "./dist/shell/index.js", "types": "./dist/shell/index.d.ts" },
|
"types": "./dist/layout/index.d.ts",
|
||||||
"./shell/server":{ "import": "./dist/shell/server.js", "types": "./dist/shell/server.d.ts" },
|
"import": "./dist/layout/index.js"
|
||||||
"./data": { "import": "./dist/data/index.js", "types": "./dist/data/index.d.ts" },
|
},
|
||||||
"./forms": { "import": "./dist/forms/index.js", "types": "./dist/forms/index.d.ts" },
|
"./auth": {
|
||||||
"./feedback": { "import": "./dist/feedback/index.js", "types": "./dist/feedback/index.d.ts" },
|
"types": "./dist/auth/index.d.ts",
|
||||||
"./navigation": { "import": "./dist/navigation/index.js", "types": "./dist/navigation/index.d.ts" },
|
"import": "./dist/auth/index.js"
|
||||||
"./api": { "import": "./dist/api/index.js", "types": "./dist/api/index.d.ts" },
|
},
|
||||||
"./utils": { "import": "./dist/utils/index.js", "types": "./dist/utils/index.d.ts" }
|
"./auth/server": {
|
||||||
|
"types": "./dist/auth/server.d.ts",
|
||||||
|
"import": "./dist/auth/server.js"
|
||||||
|
},
|
||||||
|
"./auth/middleware": {
|
||||||
|
"types": "./dist/auth/middleware.d.ts",
|
||||||
|
"import": "./dist/auth/middleware.js"
|
||||||
|
},
|
||||||
|
"./shell": {
|
||||||
|
"types": "./dist/shell/index.d.ts",
|
||||||
|
"import": "./dist/shell/index.js"
|
||||||
|
},
|
||||||
|
"./shell/server": {
|
||||||
|
"types": "./dist/shell/server.d.ts",
|
||||||
|
"import": "./dist/shell/server.js"
|
||||||
|
},
|
||||||
|
"./data": {
|
||||||
|
"types": "./dist/data/index.d.ts",
|
||||||
|
"import": "./dist/data/index.js"
|
||||||
|
},
|
||||||
|
"./forms": {
|
||||||
|
"types": "./dist/forms/index.d.ts",
|
||||||
|
"import": "./dist/forms/index.js"
|
||||||
|
},
|
||||||
|
"./feedback": {
|
||||||
|
"types": "./dist/feedback/index.d.ts",
|
||||||
|
"import": "./dist/feedback/index.js"
|
||||||
|
},
|
||||||
|
"./navigation": {
|
||||||
|
"types": "./dist/navigation/index.d.ts",
|
||||||
|
"import": "./dist/navigation/index.js"
|
||||||
|
},
|
||||||
|
"./api": {
|
||||||
|
"types": "./dist/api/index.d.ts",
|
||||||
|
"import": "./dist/api/index.js"
|
||||||
|
},
|
||||||
|
"./utils": {
|
||||||
|
"types": "./dist/utils/index.d.ts",
|
||||||
|
"import": "./dist/utils/index.js"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"sideEffects": [
|
"sideEffects": [
|
||||||
"**/*.css"
|
"**/*.css"
|
||||||
@@ -43,10 +82,14 @@
|
|||||||
"react": "^18.2.0 || ^19.0.0",
|
"react": "^18.2.0 || ^19.0.0",
|
||||||
"react-dom": "^18.2.0 || ^19.0.0"
|
"react-dom": "^18.2.0 || ^19.0.0"
|
||||||
},
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bootstrap": { "optional": true }
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.11.0",
|
"@types/node": "^20.11.0",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"next": "16.1.1",
|
||||||
"typescript": "^5.4.0"
|
"typescript": "^5.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,18 @@ function copyDir(src, dst) {
|
|||||||
}
|
}
|
||||||
if (fs.existsSync(srcStyles)) {
|
if (fs.existsSync(srcStyles)) {
|
||||||
copyDir(srcStyles, distStyles);
|
copyDir(srcStyles, distStyles);
|
||||||
|
|
||||||
|
// Rewrite ../images/... references in any copied CSS to ./images/...
|
||||||
|
// The source CSS assumes a sibling src/images/ dir; in dist/ the images
|
||||||
|
// live at dist/styles/images/ alongside the CSS, so the relative path
|
||||||
|
// is one level shorter.
|
||||||
|
for (const f of fs.readdirSync(distStyles)) {
|
||||||
|
if (!f.endsWith(".css")) continue;
|
||||||
|
const p = path.join(distStyles, f);
|
||||||
|
const c = fs.readFileSync(p, "utf8");
|
||||||
|
const fixed = c.replace(/\.\.\/images\//g, "./images/");
|
||||||
|
if (fixed !== c) fs.writeFileSync(p, fixed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Fix ESM relative imports: append `.js` to bare specifiers like
|
// 2. Fix ESM relative imports: append `.js` to bare specifiers like
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ export type { SessionUser } from "./types";
|
|||||||
*/
|
*/
|
||||||
export function signInRedirect(callbackUrl?: string): void {
|
export function signInRedirect(callbackUrl?: string): void {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
const target = "/api/auth/signin/keycloak";
|
// Match the server-side default. /api/auth/signin/<provider> is POST
|
||||||
|
// only in NextAuth v5 — a GET navigation there returns UnknownAction.
|
||||||
|
const target = "/api/auth/signin";
|
||||||
const url = new URL(target, window.location.origin);
|
const url = new URL(target, window.location.origin);
|
||||||
url.searchParams.set("callbackUrl", callbackUrl ?? window.location.pathname);
|
url.searchParams.set("callbackUrl", callbackUrl ?? window.location.pathname);
|
||||||
window.location.href = url.toString();
|
window.location.href = url.toString();
|
||||||
|
|||||||
@@ -1,4 +1,17 @@
|
|||||||
import { NextResponse, type NextRequest } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
// Loose local typing for NextRequest. Importing next's official type
|
||||||
|
// here pulls in `next/server` from THIS package's node_modules, which
|
||||||
|
// causes a "two copies of next" type conflict with the consumer's
|
||||||
|
// next. The shape below is the surface area the middleware actually
|
||||||
|
// uses; structural typing is enough at the call site.
|
||||||
|
interface NextRequestLike {
|
||||||
|
nextUrl: URL;
|
||||||
|
cookies: {
|
||||||
|
get(name: string): { value: string } | undefined;
|
||||||
|
getAll(): Array<{ name: string; value: string }>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface AuthMiddlewareOptions {
|
export interface AuthMiddlewareOptions {
|
||||||
/**
|
/**
|
||||||
@@ -36,9 +49,11 @@ export interface AuthMiddlewareOptions {
|
|||||||
*/
|
*/
|
||||||
export function createAuthMiddleware(opts: AuthMiddlewareOptions = {}) {
|
export function createAuthMiddleware(opts: AuthMiddlewareOptions = {}) {
|
||||||
const publicRoutes = opts.publicRoutes ?? [];
|
const publicRoutes = opts.publicRoutes ?? [];
|
||||||
const signInPath = opts.signInPath ?? "/api/auth/signin/keycloak";
|
// See createAuth for why /api/auth/signin (not /api/auth/signin/keycloak):
|
||||||
|
// provider-specific paths are POST-only in NextAuth v5.
|
||||||
|
const signInPath = opts.signInPath ?? "/api/auth/signin";
|
||||||
|
|
||||||
return function middleware(req: NextRequest) {
|
return function middleware(req: NextRequestLike) {
|
||||||
const { pathname } = req.nextUrl;
|
const { pathname } = req.nextUrl;
|
||||||
|
|
||||||
if (isAlwaysAllowed(pathname)) {
|
if (isAlwaysAllowed(pathname)) {
|
||||||
@@ -48,10 +63,7 @@ export function createAuthMiddleware(opts: AuthMiddlewareOptions = {}) {
|
|||||||
return NextResponse.next();
|
return NextResponse.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionCookie =
|
if (hasSessionCookie(req)) {
|
||||||
req.cookies.get("authjs.session-token") ??
|
|
||||||
req.cookies.get("__Secure-authjs.session-token");
|
|
||||||
if (sessionCookie) {
|
|
||||||
return NextResponse.next();
|
return NextResponse.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +73,32 @@ export function createAuthMiddleware(opts: AuthMiddlewareOptions = {}) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NextAuth v5 chunks the session cookie when the JWT payload exceeds
|
||||||
|
* ~4 KB (claims + accessToken + roles add up fast). When chunked, the
|
||||||
|
* bare `authjs.session-token` cookie is *removed* in favour of
|
||||||
|
* `authjs.session-token.0`, `.1`, etc. Looking up only the bare name
|
||||||
|
* misses the chunked form and the middleware loops the user back to
|
||||||
|
* signin even though they have a valid session.
|
||||||
|
*
|
||||||
|
* Match presence-only on any cookie whose name starts with either
|
||||||
|
* canonical prefix. Token validity is still verified server-side by
|
||||||
|
* NextAuth on every RSC render — this check only gates the redirect.
|
||||||
|
*/
|
||||||
|
function hasSessionCookie(req: NextRequestLike): boolean {
|
||||||
|
for (const c of req.cookies.getAll()) {
|
||||||
|
if (
|
||||||
|
c.name === "authjs.session-token" ||
|
||||||
|
c.name === "__Secure-authjs.session-token" ||
|
||||||
|
c.name.startsWith("authjs.session-token.") ||
|
||||||
|
c.name.startsWith("__Secure-authjs.session-token.")
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function isAlwaysAllowed(pathname: string): boolean {
|
function isAlwaysAllowed(pathname: string): boolean {
|
||||||
return (
|
return (
|
||||||
pathname.startsWith("/api/auth/") ||
|
pathname.startsWith("/api/auth/") ||
|
||||||
|
|||||||
@@ -6,13 +6,31 @@ import type { CreateAuthOptions, SessionUser } from "./types";
|
|||||||
|
|
||||||
export type { SessionUser, CreateAuthOptions };
|
export type { SessionUser, CreateAuthOptions };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shape of the GET/POST route handlers NextAuth returns. Declared
|
||||||
|
* structurally (Request → Response) rather than via
|
||||||
|
* `NextAuthResult["handlers"]` — that type embeds `NextRequest` from
|
||||||
|
* the kit's own copy of `next`, which conflicts with the consumer's
|
||||||
|
* `next` and produces a spurious `RouteHandlerConfig` mismatch in
|
||||||
|
* `.next/types/validator.ts` for every app's `[...nextauth]/route.ts`.
|
||||||
|
*
|
||||||
|
* Web-standard `Request`/`Response` are valid for Next.js route
|
||||||
|
* handlers (NextRequest extends Request), and TS function-parameter
|
||||||
|
* contravariance makes this assignable wherever the validator wants
|
||||||
|
* `(request: NextRequest, ctx) => ...`.
|
||||||
|
*/
|
||||||
|
export type AuthRouteHandlers = {
|
||||||
|
GET: (request: Request) => Promise<Response>;
|
||||||
|
POST: (request: Request) => Promise<Response>;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Result of `createAuth()`. Mirrors NextAuth's return value plus
|
* Result of `createAuth()`. Mirrors NextAuth's return value plus
|
||||||
* `requireAuth` and the resolved `signInPath` so consumers don't have to
|
* `requireAuth` and the resolved `signInPath` so consumers don't have to
|
||||||
* thread these around.
|
* thread these around.
|
||||||
*/
|
*/
|
||||||
export interface AuthBundle {
|
export interface AuthBundle {
|
||||||
handlers: NextAuthResult["handlers"];
|
handlers: AuthRouteHandlers;
|
||||||
signIn: NextAuthResult["signIn"];
|
signIn: NextAuthResult["signIn"];
|
||||||
signOut: NextAuthResult["signOut"];
|
signOut: NextAuthResult["signOut"];
|
||||||
auth: NextAuthResult["auth"];
|
auth: NextAuthResult["auth"];
|
||||||
@@ -45,7 +63,13 @@ export interface AuthBundle {
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function createAuth(opts: CreateAuthOptions): AuthBundle {
|
export function createAuth(opts: CreateAuthOptions): AuthBundle {
|
||||||
const signInPath = opts.signInPath ?? "/api/auth/signin/keycloak";
|
// NextAuth v5: provider-specific paths like /api/auth/signin/keycloak
|
||||||
|
// are POST-only (CSRF-protected form submit). A GET redirect there
|
||||||
|
// bounces to /api/auth/error?error=Configuration ("UnknownAction").
|
||||||
|
// /api/auth/signin (no provider) is the GET-accessible page that
|
||||||
|
// lists configured providers. Apps wanting one-click Keycloak can
|
||||||
|
// override signInPath with a custom page that calls signIn('keycloak').
|
||||||
|
const signInPath = opts.signInPath ?? "/api/auth/signin";
|
||||||
const defaultTenantId =
|
const defaultTenantId =
|
||||||
opts.defaultTenantId ?? "00000000-0000-0000-0000-000000000000";
|
opts.defaultTenantId ?? "00000000-0000-0000-0000-000000000000";
|
||||||
|
|
||||||
@@ -114,7 +138,12 @@ export function createAuth(opts: CreateAuthOptions): AuthBundle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handlers: na.handlers,
|
// Cast through unknown: NextAuth's handler param type references its
|
||||||
|
// own NextRequest; the AuthRouteHandlers shape is structurally
|
||||||
|
// compatible (NextRequest extends Request), but TS can't prove the
|
||||||
|
// function-parameter contravariance across the two `next` copies on
|
||||||
|
// its own.
|
||||||
|
handlers: na.handlers as unknown as AuthRouteHandlers,
|
||||||
signIn: na.signIn,
|
signIn: na.signIn,
|
||||||
signOut: na.signOut,
|
signOut: na.signOut,
|
||||||
auth: na.auth,
|
auth: na.auth,
|
||||||
|
|||||||
@@ -1,2 +1,37 @@
|
|||||||
// @gsc/web-kit/data — Phase 1 stub. Real surface lands in later phases.
|
/**
|
||||||
export {};
|
* @gsc/web-kit/data — data display primitives.
|
||||||
|
*
|
||||||
|
* Curated re-export from @limitless/ui. Use these for entity lists,
|
||||||
|
* detail panels, and timeline/calendar views.
|
||||||
|
*
|
||||||
|
* The Widget family is intentionally narrow: only names that exist in
|
||||||
|
* both Widget.d.ts and Widget/index.d.ts are re-exported, because the
|
||||||
|
* installed limitless dist has a duplicate-file collision there. If
|
||||||
|
* apps need IconWidget / UserWidget / etc., import from @limitless/ui
|
||||||
|
* directly until upstream is fixed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export {
|
||||||
|
Table,
|
||||||
|
DataTable,
|
||||||
|
Pagination,
|
||||||
|
TreeView,
|
||||||
|
Timeline,
|
||||||
|
Calendar,
|
||||||
|
Gallery,
|
||||||
|
Sortable,
|
||||||
|
ListGroup,
|
||||||
|
// Widget family (intersection of the two Widget files in dist)
|
||||||
|
StatWidget,
|
||||||
|
ProgressWidget,
|
||||||
|
ChartWidget,
|
||||||
|
ContentWidget,
|
||||||
|
} from "@limitless/ui";
|
||||||
|
|
||||||
|
export type {
|
||||||
|
DataTableProps,
|
||||||
|
StatWidgetProps,
|
||||||
|
ProgressWidgetProps,
|
||||||
|
ChartWidgetProps,
|
||||||
|
ContentWidgetProps,
|
||||||
|
} from "@limitless/ui";
|
||||||
|
|||||||
@@ -1,2 +1,25 @@
|
|||||||
// @gsc/web-kit/feedback — Phase 1 stub. Real surface lands in later phases.
|
/**
|
||||||
export {};
|
* @gsc/web-kit/feedback — overlays, toasts, status indicators.
|
||||||
|
*
|
||||||
|
* Curated re-export from @limitless/ui.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export {
|
||||||
|
Alert,
|
||||||
|
Toast,
|
||||||
|
Notification,
|
||||||
|
Modal,
|
||||||
|
Offcanvas,
|
||||||
|
Popover,
|
||||||
|
Tooltip,
|
||||||
|
SweetAlert,
|
||||||
|
Spinner,
|
||||||
|
Progress,
|
||||||
|
ProgressStacked,
|
||||||
|
IdleTimeout,
|
||||||
|
FAB,
|
||||||
|
} from "@limitless/ui";
|
||||||
|
|
||||||
|
export type {
|
||||||
|
ToastProps,
|
||||||
|
} from "@limitless/ui";
|
||||||
|
|||||||
@@ -1,2 +1,146 @@
|
|||||||
// @gsc/web-kit/forms — Phase 1 stub. Real surface lands in later phases.
|
/**
|
||||||
export {};
|
* @gsc/web-kit/forms — form primitives + validation.
|
||||||
|
*
|
||||||
|
* Curated re-export from @limitless/ui. Apps import everything here
|
||||||
|
* instead of reaching into the lower layer:
|
||||||
|
*
|
||||||
|
* import { Form, FormGroup, FormControl, Select, SelectSingle,
|
||||||
|
* useValidation, required, email } from "@gsc/web-kit/forms";
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Form primitives
|
||||||
|
export {
|
||||||
|
FormGroup,
|
||||||
|
FormControl,
|
||||||
|
FormCheck,
|
||||||
|
Select,
|
||||||
|
InputGroup,
|
||||||
|
} from "@limitless/ui";
|
||||||
|
export type {
|
||||||
|
FormGroupProps,
|
||||||
|
FormControlProps,
|
||||||
|
FormCheckProps,
|
||||||
|
SelectOption,
|
||||||
|
SelectProps,
|
||||||
|
InputGroupProps,
|
||||||
|
} from "@limitless/ui";
|
||||||
|
|
||||||
|
// Rich selects (react-select family — AdvancedSelect module)
|
||||||
|
export {
|
||||||
|
SelectSingle,
|
||||||
|
MultiSelect,
|
||||||
|
TagsSelect,
|
||||||
|
AsyncSelect,
|
||||||
|
} from "@limitless/ui";
|
||||||
|
export type {
|
||||||
|
SelectSingleProps,
|
||||||
|
SelectMultiProps,
|
||||||
|
CreatableSelectProps,
|
||||||
|
AsyncSelectProps,
|
||||||
|
} from "@limitless/ui";
|
||||||
|
|
||||||
|
// Rich inputs
|
||||||
|
export {
|
||||||
|
DatePicker,
|
||||||
|
ColorPicker,
|
||||||
|
TagInput,
|
||||||
|
FileUpload,
|
||||||
|
Slider,
|
||||||
|
Rating,
|
||||||
|
DualListBox,
|
||||||
|
ImageCropper,
|
||||||
|
} from "@limitless/ui";
|
||||||
|
|
||||||
|
// Multi-step
|
||||||
|
export { Wizard, Stepper } from "@limitless/ui";
|
||||||
|
|
||||||
|
// Validation — hooks (re-exported via @limitless/ui root: `export * from './validation'`)
|
||||||
|
export {
|
||||||
|
useValidation,
|
||||||
|
useFieldValidation,
|
||||||
|
useAddressAutocomplete,
|
||||||
|
loadGoogleMapsScript,
|
||||||
|
} from "@limitless/ui";
|
||||||
|
|
||||||
|
// Validation — format validators
|
||||||
|
export {
|
||||||
|
required,
|
||||||
|
minLength,
|
||||||
|
maxLength,
|
||||||
|
pattern,
|
||||||
|
matches,
|
||||||
|
email,
|
||||||
|
url,
|
||||||
|
tel,
|
||||||
|
number,
|
||||||
|
range,
|
||||||
|
date,
|
||||||
|
time,
|
||||||
|
datetimeLocal,
|
||||||
|
month,
|
||||||
|
week,
|
||||||
|
color,
|
||||||
|
search,
|
||||||
|
password,
|
||||||
|
getPasswordStrength,
|
||||||
|
file,
|
||||||
|
checkbox,
|
||||||
|
radio,
|
||||||
|
} from "@limitless/ui";
|
||||||
|
|
||||||
|
// Validation — security
|
||||||
|
export {
|
||||||
|
noInjection,
|
||||||
|
noSqlInjection,
|
||||||
|
noScriptInjection,
|
||||||
|
noHtmlInjection,
|
||||||
|
noPromptInjection,
|
||||||
|
detectInjectionType,
|
||||||
|
SQL_INJECTION_PATTERNS,
|
||||||
|
SCRIPT_INJECTION_PATTERNS,
|
||||||
|
HTML_INJECTION_PATTERNS,
|
||||||
|
PROMPT_INJECTION_PATTERNS,
|
||||||
|
} from "@limitless/ui";
|
||||||
|
|
||||||
|
// Validation — address
|
||||||
|
export {
|
||||||
|
postalCode,
|
||||||
|
europeanPostalCode,
|
||||||
|
city,
|
||||||
|
streetAddress,
|
||||||
|
houseNumber,
|
||||||
|
europeanCountry,
|
||||||
|
europeanAddress,
|
||||||
|
getCountryName,
|
||||||
|
getPostalCodeHint,
|
||||||
|
POSTAL_CODE_PATTERNS,
|
||||||
|
EU_COUNTRIES,
|
||||||
|
EEA_COUNTRIES,
|
||||||
|
EUROPEAN_COUNTRIES,
|
||||||
|
} from "@limitless/ui";
|
||||||
|
|
||||||
|
// Validation — types
|
||||||
|
export type {
|
||||||
|
ValidationStatus,
|
||||||
|
ValidationResult,
|
||||||
|
FieldState,
|
||||||
|
FormState,
|
||||||
|
ValidatorFn,
|
||||||
|
FieldSchema,
|
||||||
|
FormSchema,
|
||||||
|
EuropeanAddress,
|
||||||
|
AddressSuggestion,
|
||||||
|
SecurityValidationOptions,
|
||||||
|
PasswordStrength,
|
||||||
|
PasswordOptions,
|
||||||
|
PhoneOptions,
|
||||||
|
NumberOptions,
|
||||||
|
DateOptions,
|
||||||
|
TimeOptions,
|
||||||
|
FileOptions,
|
||||||
|
UseValidationOptions,
|
||||||
|
UseFieldValidationOptions,
|
||||||
|
UseAddressAutocompleteOptions,
|
||||||
|
ServerValidationSchema,
|
||||||
|
ServerValidationResult,
|
||||||
|
} from "@limitless/ui";
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React from "react";
|
||||||
|
import { AppShell } from "@limitless/ui";
|
||||||
|
|
||||||
import { ShellProvider } from "../shell/index";
|
import type { ShellConfig } from "../shell/types";
|
||||||
import type {
|
|
||||||
ShellConfig,
|
|
||||||
ShellMenuItem,
|
|
||||||
} from "../shell/types";
|
|
||||||
|
|
||||||
const SIDEBAR_COLLAPSED_KEY = "gsc-web-kit-sidebar-collapsed";
|
|
||||||
|
|
||||||
export interface AppLayoutProps {
|
export interface AppLayoutProps {
|
||||||
/** Pre-resolved chrome config. Fetch with `fetchShellConfig()` server-side. */
|
/** Pre-resolved chrome config. Fetch with `fetchShellConfig()` server-side. */
|
||||||
@@ -26,378 +21,11 @@ export interface AppLayoutProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The chrome. Renders a Bootstrap-Layout-3 navbar / sidebar / footer
|
* GSC app chrome. Thin wrapper around `<AppShell>` from `@limitless/ui` —
|
||||||
* shell from a `ShellConfig`. Apps wrap their `[locale]/layout.tsx`
|
* the two share the same `ShellConfig` DTO. Kept as a separate export so
|
||||||
* children in this and get a consistent app look for free.
|
* the kit owns the consumer-facing surface and can add GSC-only props
|
||||||
|
* (telemetry, feature flags, etc.) without forking limitless.
|
||||||
*/
|
*/
|
||||||
export function AppLayout({
|
export function AppLayout(props: AppLayoutProps) {
|
||||||
config,
|
return <AppShell {...props} />;
|
||||||
currentPath,
|
|
||||||
translate,
|
|
||||||
onSignOut,
|
|
||||||
navbarExtras,
|
|
||||||
children,
|
|
||||||
}: AppLayoutProps) {
|
|
||||||
const t = translate ?? ((k: string) => k);
|
|
||||||
const path =
|
|
||||||
currentPath ??
|
|
||||||
(typeof window !== "undefined" ? window.location.pathname : "/");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ShellProvider value={config}>
|
|
||||||
<AdminNavbar
|
|
||||||
config={config}
|
|
||||||
t={t}
|
|
||||||
onSignOut={onSignOut}
|
|
||||||
navbarExtras={navbarExtras}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="page-content d-flex align-items-start p-3 gap-3"
|
|
||||||
style={{ minHeight: 0 }}
|
|
||||||
>
|
|
||||||
<AdminSidebar config={config} t={t} pathname={path} />
|
|
||||||
<main className="flex-grow-1" style={{ minWidth: 0 }}>
|
|
||||||
{children}
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
<AdminFooter config={config} t={t} />
|
|
||||||
</ShellProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Navbar ────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
function AdminNavbar({
|
|
||||||
config,
|
|
||||||
t,
|
|
||||||
onSignOut,
|
|
||||||
navbarExtras,
|
|
||||||
}: {
|
|
||||||
config: ShellConfig;
|
|
||||||
t: (k: string) => string;
|
|
||||||
onSignOut?: () => void;
|
|
||||||
navbarExtras?: React.ReactNode;
|
|
||||||
}) {
|
|
||||||
const [showUserMenu, setShowUserMenu] = useState(false);
|
|
||||||
|
|
||||||
const initials =
|
|
||||||
config.user.displayName
|
|
||||||
?.split(" ")
|
|
||||||
.map((n) => n[0])
|
|
||||||
.filter(Boolean)
|
|
||||||
.join("")
|
|
||||||
.slice(0, 2)
|
|
||||||
.toUpperCase() || "?";
|
|
||||||
|
|
||||||
const userMenuItems = config.menus["user-menu"] ?? [];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="navbar navbar-dark navbar-expand-lg navbar-static">
|
|
||||||
<div className="container-fluid">
|
|
||||||
<div className="navbar-brand wmin-200">
|
|
||||||
<a href={config.app.baseUrl} className="d-inline-block">
|
|
||||||
<img
|
|
||||||
src={config.branding.logoUrl}
|
|
||||||
className="h-36px"
|
|
||||||
alt={config.branding.productName}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{navbarExtras}
|
|
||||||
|
|
||||||
<ul className="nav flex-row justify-content-end order-1 order-lg-2 ms-auto">
|
|
||||||
<li
|
|
||||||
className="nav-item nav-item-dropdown-lg dropdown ms-lg-2"
|
|
||||||
onMouseLeave={() => setShowUserMenu(false)}
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
className="navbar-nav-link align-items-center rounded-pill p-1"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setShowUserMenu((open) => !open);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="status-indicator-container">
|
|
||||||
<span
|
|
||||||
className="w-32px h-32px rounded-pill bg-primary bg-opacity-20 text-primary d-inline-flex align-items-center justify-content-center fw-semibold"
|
|
||||||
style={{ fontSize: "0.75rem" }}
|
|
||||||
>
|
|
||||||
{initials}
|
|
||||||
</span>
|
|
||||||
<span className="status-indicator bg-success" />
|
|
||||||
</div>
|
|
||||||
<span className="d-none d-lg-inline-block mx-lg-2">
|
|
||||||
{config.user.displayName || config.user.email || ""}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
showUserMenu
|
|
||||||
? "dropdown-menu dropdown-menu-end show"
|
|
||||||
: "dropdown-menu dropdown-menu-end"
|
|
||||||
}
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
inset: "0px 0px auto auto",
|
|
||||||
margin: "0px",
|
|
||||||
transform: "translate3d(0px, 44px, 0px)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{userMenuItems.map((m) => {
|
|
||||||
const isLogout = m.key === "logout";
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
key={m.id}
|
|
||||||
href={m.href}
|
|
||||||
target={m.isExternal ? "_blank" : undefined}
|
|
||||||
rel={m.isExternal ? "noopener noreferrer" : undefined}
|
|
||||||
className="dropdown-item"
|
|
||||||
onClick={
|
|
||||||
isLogout && onSignOut
|
|
||||||
? (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
onSignOut();
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{m.icon ? <i className={`${m.icon} me-2`} /> : null}
|
|
||||||
{t(m.translationKey)}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Sidebar ──────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
function AdminSidebar({
|
|
||||||
config,
|
|
||||||
t,
|
|
||||||
pathname,
|
|
||||||
}: {
|
|
||||||
config: ShellConfig;
|
|
||||||
t: (k: string) => string;
|
|
||||||
pathname: string;
|
|
||||||
}) {
|
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (typeof window === "undefined") return;
|
|
||||||
const saved = window.localStorage.getItem(SIDEBAR_COLLAPSED_KEY);
|
|
||||||
if (saved === "true") setCollapsed(true);
|
|
||||||
}, []);
|
|
||||||
useEffect(() => {
|
|
||||||
if (typeof window === "undefined") return;
|
|
||||||
window.localStorage.setItem(SIDEBAR_COLLAPSED_KEY, String(collapsed));
|
|
||||||
}, [collapsed]);
|
|
||||||
|
|
||||||
const sidebarClasses = [
|
|
||||||
"sidebar",
|
|
||||||
"sidebar-light",
|
|
||||||
"sidebar-main",
|
|
||||||
"sidebar-expand-lg",
|
|
||||||
"align-self-start",
|
|
||||||
collapsed ? "sidebar-main-resized" : "",
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(" ");
|
|
||||||
|
|
||||||
const items = config.menus.sidebar ?? [];
|
|
||||||
const strippedPath = stripLocale(pathname);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={sidebarClasses}>
|
|
||||||
<div className="sidebar-content">
|
|
||||||
<div className="sidebar-section">
|
|
||||||
<div className="sidebar-section-body d-flex justify-content-center">
|
|
||||||
{!collapsed && (
|
|
||||||
<h5 className="sidebar-resize-hide flex-grow-1 my-auto">
|
|
||||||
{config.branding.productName}
|
|
||||||
</h5>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-light btn-icon btn-sm rounded-pill border-transparent sidebar-control sidebar-main-resize d-none d-lg-inline-flex"
|
|
||||||
onClick={() => setCollapsed((c) => !c)}
|
|
||||||
aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
|
|
||||||
>
|
|
||||||
<i className="ph-arrows-left-right" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="sidebar-section">
|
|
||||||
<ul className="nav nav-sidebar" data-nav-type="accordion">
|
|
||||||
{items.map((item) => (
|
|
||||||
<SidebarNavItem
|
|
||||||
key={item.id}
|
|
||||||
item={item}
|
|
||||||
pathname={strippedPath}
|
|
||||||
collapsed={collapsed}
|
|
||||||
t={t}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function SidebarNavItem({
|
|
||||||
item,
|
|
||||||
pathname,
|
|
||||||
collapsed,
|
|
||||||
t,
|
|
||||||
}: {
|
|
||||||
item: ShellMenuItem;
|
|
||||||
pathname: string;
|
|
||||||
collapsed: boolean;
|
|
||||||
t: (k: string) => string;
|
|
||||||
}) {
|
|
||||||
const hasChildren = !!item.children?.length;
|
|
||||||
const active = isActiveHref(item.href, pathname);
|
|
||||||
const childActive = !!item.children?.some((c) =>
|
|
||||||
isActiveHref(c.href, pathname),
|
|
||||||
);
|
|
||||||
const [open, setOpen] = useState(childActive);
|
|
||||||
const [hovered, setHovered] = useState(false);
|
|
||||||
|
|
||||||
const icon = item.icon ? <i className={item.icon} /> : null;
|
|
||||||
|
|
||||||
if (hasChildren) {
|
|
||||||
const showFlyout = collapsed && hovered;
|
|
||||||
const showInline = !collapsed && open;
|
|
||||||
const navItemClasses = [
|
|
||||||
"nav-item",
|
|
||||||
"nav-item-submenu",
|
|
||||||
showInline ? "nav-item-open" : "",
|
|
||||||
showFlyout ? "nav-group-sub-visible" : "",
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(" ");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
className={navItemClasses}
|
|
||||||
onMouseEnter={collapsed ? () => setHovered(true) : undefined}
|
|
||||||
onMouseLeave={collapsed ? () => setHovered(false) : undefined}
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
className={`nav-link ${active || childActive ? "active" : ""}`}
|
|
||||||
title={collapsed ? t(item.translationKey) : undefined}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setOpen((o) => !o);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{icon}
|
|
||||||
{!collapsed && <span>{t(item.translationKey)}</span>}
|
|
||||||
</a>
|
|
||||||
<ul
|
|
||||||
className={
|
|
||||||
collapsed
|
|
||||||
? "nav-group-sub nav-group-sub-flyout collapse"
|
|
||||||
: `nav-group-sub collapse ${open ? "show" : ""}`
|
|
||||||
}
|
|
||||||
data-submenu-title={t(item.translationKey)}
|
|
||||||
>
|
|
||||||
{item.children!.map((c) => (
|
|
||||||
<li className="nav-item" key={c.id}>
|
|
||||||
<a
|
|
||||||
href={c.href}
|
|
||||||
target={c.isExternal ? "_blank" : undefined}
|
|
||||||
rel={c.isExternal ? "noopener noreferrer" : undefined}
|
|
||||||
className={`nav-link ${isActiveHref(c.href, pathname) ? "active" : ""}`}
|
|
||||||
>
|
|
||||||
{c.icon && <i className={c.icon} />}
|
|
||||||
{t(c.translationKey)}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li className="nav-item">
|
|
||||||
<a
|
|
||||||
href={item.href}
|
|
||||||
target={item.isExternal ? "_blank" : undefined}
|
|
||||||
rel={item.isExternal ? "noopener noreferrer" : undefined}
|
|
||||||
className={`nav-link ${active ? "active" : ""}`}
|
|
||||||
title={collapsed ? t(item.translationKey) : undefined}
|
|
||||||
>
|
|
||||||
{icon}
|
|
||||||
{!collapsed && <span>{t(item.translationKey)}</span>}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Footer ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
function AdminFooter({
|
|
||||||
config,
|
|
||||||
t,
|
|
||||||
}: {
|
|
||||||
config: ShellConfig;
|
|
||||||
t: (k: string) => string;
|
|
||||||
}) {
|
|
||||||
const items = config.menus.footer ?? [];
|
|
||||||
return (
|
|
||||||
<div className="navbar navbar-sm navbar-footer border-top">
|
|
||||||
<div className="container-fluid">
|
|
||||||
<span>
|
|
||||||
{config.branding.footerHtml ? (
|
|
||||||
<span
|
|
||||||
dangerouslySetInnerHTML={{ __html: config.branding.footerHtml }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<>© {new Date().getFullYear()} GoSec Cloud</>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
{items.length > 0 && (
|
|
||||||
<ul className="navbar-nav flex-row gap-3 ms-auto">
|
|
||||||
{items.map((m) => (
|
|
||||||
<li className="nav-item" key={m.id}>
|
|
||||||
<a
|
|
||||||
href={m.href}
|
|
||||||
target={m.isExternal ? "_blank" : undefined}
|
|
||||||
rel={m.isExternal ? "noopener noreferrer" : undefined}
|
|
||||||
className="navbar-nav-link"
|
|
||||||
>
|
|
||||||
{m.icon && <i className={`${m.icon} me-1`} />}
|
|
||||||
{t(m.translationKey)}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── helpers ──────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
function isActiveHref(href: string, currentPath: string): boolean {
|
|
||||||
if (!href || href === "#") return false;
|
|
||||||
if (href === "/") return currentPath === "/";
|
|
||||||
return currentPath === href || currentPath.startsWith(`${href}/`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripLocale(p: string): string {
|
|
||||||
return p.replace(/^\/[a-z]{2}(?=\/|$)/, "") || "/";
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,26 @@
|
|||||||
// @gsc/web-kit/navigation — Phase 1 stub. Real surface lands in later phases.
|
/**
|
||||||
export {};
|
* @gsc/web-kit/navigation — nav, breadcrumbs, page chrome, content
|
||||||
|
* organisers.
|
||||||
|
*
|
||||||
|
* Curated re-export from @limitless/ui.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export {
|
||||||
|
Breadcrumbs,
|
||||||
|
Nav,
|
||||||
|
Tabs,
|
||||||
|
Pills,
|
||||||
|
Dropdown,
|
||||||
|
ContextMenu,
|
||||||
|
Scrollspy,
|
||||||
|
PageHeader,
|
||||||
|
Accordion,
|
||||||
|
Collapse,
|
||||||
|
Carousel,
|
||||||
|
Embed,
|
||||||
|
SyntaxHighlighter,
|
||||||
|
Card,
|
||||||
|
Badge,
|
||||||
|
Button,
|
||||||
|
Media,
|
||||||
|
} from "@limitless/ui";
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { createContext, useContext } from "react";
|
// Client surface for @gsc/web-kit/shell.
|
||||||
|
//
|
||||||
|
// The context provider lives inside @limitless/ui's <AppShell>; we just
|
||||||
|
// re-export the `useShell()` hook so pages rendered under <AppLayout>
|
||||||
|
// (which delegates to AppShell) can read the config.
|
||||||
|
|
||||||
import type { ShellConfig } from "./types";
|
export { useShell } from "@limitless/ui";
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
ShellApp,
|
ShellApp,
|
||||||
@@ -12,17 +16,3 @@ export type {
|
|||||||
ShellMenuZone,
|
ShellMenuZone,
|
||||||
ShellUser,
|
ShellUser,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
const ShellContext = createContext<ShellConfig | null>(null);
|
|
||||||
|
|
||||||
/** Provider used by `<AppLayout>`; rarely needed directly. */
|
|
||||||
export const ShellProvider = ShellContext.Provider;
|
|
||||||
|
|
||||||
/** Read the current ShellConfig anywhere inside `<AppLayout>`. */
|
|
||||||
export function useShell(): ShellConfig {
|
|
||||||
const cfg = useContext(ShellContext);
|
|
||||||
if (!cfg) {
|
|
||||||
throw new Error("useShell must be used inside <AppLayout>");
|
|
||||||
}
|
|
||||||
return cfg;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,48 +1,11 @@
|
|||||||
/**
|
// Re-export from @limitless/ui so the kit and the underlying AppShell
|
||||||
* Shape of what gsc-shell-api returns. Mirror of the Go service's DTO.
|
// share one canonical type for ShellConfig. Diverging here would mean
|
||||||
* If you change one, change both — there's a runtime contract in
|
// the kit's <AppLayout> can't be fed by limitless and vice versa.
|
||||||
* between, not a code-generator.
|
export type {
|
||||||
*/
|
ShellApp,
|
||||||
|
ShellBranding,
|
||||||
export type ShellMenuZone = "topbar" | "sidebar" | "footer" | "user-menu";
|
ShellConfig,
|
||||||
|
ShellMenuItem,
|
||||||
export interface ShellMenuItem {
|
ShellMenuZone,
|
||||||
id: string;
|
ShellUser,
|
||||||
key: string;
|
} from "@limitless/ui";
|
||||||
translationKey: string;
|
|
||||||
href: string;
|
|
||||||
icon?: string;
|
|
||||||
isExternal?: boolean;
|
|
||||||
children?: ShellMenuItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ShellApp {
|
|
||||||
key: string;
|
|
||||||
displayName: string;
|
|
||||||
baseUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ShellBranding {
|
|
||||||
logoUrl: string;
|
|
||||||
productName: string;
|
|
||||||
footerHtml?: string;
|
|
||||||
brandColor?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ShellUser {
|
|
||||||
id: string;
|
|
||||||
email?: string;
|
|
||||||
displayName: string;
|
|
||||||
givenName?: string;
|
|
||||||
familyName?: string;
|
|
||||||
tenantId?: string;
|
|
||||||
roles: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ShellConfig {
|
|
||||||
version: number;
|
|
||||||
app: ShellApp;
|
|
||||||
branding: ShellBranding;
|
|
||||||
user: ShellUser;
|
|
||||||
menus: Partial<Record<ShellMenuZone, ShellMenuItem[]>>;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,2 +1,7 @@
|
|||||||
// @gsc/web-kit/utils — Phase 1 stub. Real surface lands in later phases.
|
/**
|
||||||
export {};
|
* @gsc/web-kit/utils — shared hooks and helpers.
|
||||||
|
*
|
||||||
|
* Curated re-export from @limitless/ui.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { useDisclosure } from "@limitless/ui";
|
||||||
|
|||||||
Reference in New Issue
Block a user