Compare commits
2 Commits
387e10b2fb
...
960dfeba7c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
960dfeba7c | ||
|
|
440f815df7 |
150
README.md
150
README.md
@@ -29,7 +29,10 @@ your app
|
||||
```ts
|
||||
import "@gsc/web-kit/css"; // CSS bundle (layout-3 + JetBrains Mono)
|
||||
|
||||
// Chrome
|
||||
// Chrome — unified header/footer/sidebar shell
|
||||
import { AdminShell } from "@gsc/web-kit/chrome";
|
||||
|
||||
// Lower-level layout primitives
|
||||
import { AppLayout } from "@gsc/web-kit/layout";
|
||||
import { useShell } from "@gsc/web-kit/shell";
|
||||
import { fetchShellConfig } from "@gsc/web-kit/shell/server";
|
||||
@@ -81,8 +84,149 @@ The `/api` sub-export is reserved for a future HTTP client helper; it currently
|
||||
| 2 | layout · auth · shell — usable end-to-end with shell-api | **done** |
|
||||
| 3 | data · forms — curated re-exports from limitless + validation | **done (v0.3.0)** |
|
||||
| 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 | chrome · AdminShell + headers + LogoutButton + nav migrations | **done (v0.4.0)** |
|
||||
| 5a | Roll out chrome to gscCRM / gscChronos / gscAdmin / gscPortal | in progress |
|
||||
| 6 | api · HTTP client helper (Bearer injection, 401 → signInRedirect) | planned |
|
||||
|
||||
---
|
||||
|
||||
## Chrome (`/chrome`) — v0.4.0
|
||||
|
||||
Unified `AdminShell` providing navbar, subbar, page-header, sidebar, footer,
|
||||
optional chat overlay and activity panel. Every app receives the same UI
|
||||
chrome and toggles features it doesn't use via `features` props.
|
||||
|
||||
### Adopting chrome in a new app
|
||||
|
||||
1. **Add the dep** (already a `file:` resolve to this kit) and import:
|
||||
|
||||
```tsx
|
||||
// app/[locale]/layout.tsx
|
||||
import { AdminShell } from "@gsc/web-kit/chrome";
|
||||
import "@gsc/web-kit/css";
|
||||
```
|
||||
|
||||
2. **Apply nav migrations** to your app's database:
|
||||
|
||||
```bash
|
||||
# In your app's migrations directory, copy the two kit-canonical files verbatim
|
||||
cp node_modules/@gsc/web-kit/migrations/nav-schema.up.sql apps/<your-app>/migrations/00X_nav_schema.up.sql
|
||||
cp node_modules/@gsc/web-kit/migrations/nav-apps-seed.up.sql apps/<your-app>/migrations/00Y_nav_apps_seed.up.sql
|
||||
# Then copy the menu-items template once and adapt to your app's menu
|
||||
cp node_modules/@gsc/web-kit/migrations/nav-menu-items-template.sql apps/<your-app>/migrations/00Z_nav_menu_items.up.sql
|
||||
# edit 00Z_… to replace example rows with your app's sidebar/topbar entries
|
||||
psql "$DATABASE_URL" -f apps/<your-app>/migrations/00X_nav_schema.up.sql
|
||||
psql "$DATABASE_URL" -f apps/<your-app>/migrations/00Y_nav_apps_seed.up.sql
|
||||
psql "$DATABASE_URL" -f apps/<your-app>/migrations/00Z_nav_menu_items.up.sql
|
||||
```
|
||||
|
||||
Re-copy `nav-schema` + `nav-apps-seed` on every kit upgrade. The
|
||||
menu-items file is yours after the first copy — the kit never touches it
|
||||
again.
|
||||
|
||||
3. **Add Prisma** to read the data:
|
||||
|
||||
```prisma
|
||||
// prisma/schema.prisma
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
schemas = ["nav"]
|
||||
}
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
previewFeatures = ["multiSchema"]
|
||||
}
|
||||
```
|
||||
|
||||
Add `"postinstall": "prisma generate"` to package.json. Copy `prisma/`
|
||||
into the Docker image *before* `npm install` so the generate step sees it.
|
||||
|
||||
4. **Wire the layout server component**:
|
||||
|
||||
```tsx
|
||||
const [sidebar, topbar, subbar, apps] = await Promise.all([
|
||||
getMenuItemsByType("sidebar"),
|
||||
getMenuItemsByType("topbar"),
|
||||
getMenuItemsByType("subbar"),
|
||||
getApps(),
|
||||
]);
|
||||
|
||||
return (
|
||||
<AdminShell
|
||||
menus={{ sidebar, topbar, subbar }}
|
||||
apps={apps}
|
||||
user={{ displayName, email, avatarUrl }}
|
||||
brand={{
|
||||
name: "MyApp",
|
||||
product: "GoSec MyApp",
|
||||
logoUrl: "https://assets.gosec.cloud/logos/logo.svg",
|
||||
websiteUrl: "https://gosec.cloud",
|
||||
supportUrl: "https://support.gosec.cloud/",
|
||||
docsUrl: "https://support.gosec.cloud/docs",
|
||||
copyrightStartYear: 2024,
|
||||
}}
|
||||
features={{ chat: false, activityPanel: false }}
|
||||
>
|
||||
{children}
|
||||
</AdminShell>
|
||||
);
|
||||
```
|
||||
|
||||
### Props reference
|
||||
|
||||
`<AdminShell>` is the kit's contract — see `src/chrome/types.ts` for the
|
||||
authoritative TypeScript definition. Summary:
|
||||
|
||||
- **Data**: `menus`, `apps`, `user`, `notificationCount?`, `activity?`
|
||||
- **Brand** (required): `name`, `product`, `logoUrl`, `websiteUrl`,
|
||||
`supportUrl`, `docsUrl`, `copyrightStartYear` (+ optional `logoSmallUrl`)
|
||||
- **`features`** (all optional booleans, sensible defaults):
|
||||
`search`, `searchHistory`, `searchOptions`, `browseApps`, `messages`,
|
||||
`notifications`, `subbar`, `subbarSupport`, `subbarSettings`,
|
||||
`pageHeader`, `pageHeaderCustomers`, `pageHeaderContacts`,
|
||||
`activityPanel`, `chat`, `footer`
|
||||
- **`slots`** (ReactNode overrides):
|
||||
`pageTitle`, `pageHeaderExtras`, `subbarExtras`, `activityPanel`,
|
||||
`navbarExtras`, `footerExtras`
|
||||
- **Behavior**: `onSignOut?` (default `next-auth signOut`),
|
||||
`labels?: Partial<ChromeLabels>` (override individual chrome strings)
|
||||
|
||||
### i18n
|
||||
|
||||
Chrome's own strings come from next-intl namespace `chrome` with English
|
||||
fallbacks. Add to your app's `common.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"chrome": {
|
||||
"navigation": "Navigation",
|
||||
"logout": "Logout",
|
||||
"support": "Support",
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Menu item labels (`menu.dashboard`, `menu.accounts`, …) live in your app's
|
||||
existing translation namespace.
|
||||
|
||||
### Semver
|
||||
|
||||
Public surface = `<AdminShell>` props + exported types + migration files +
|
||||
chrome CSS class names.
|
||||
|
||||
- **Major** — removed/renamed prop, changed prop shape, removed feature
|
||||
flag, renamed CSS class, changed migration DDL.
|
||||
- **Minor** — new optional prop, new feature flag (default off), new
|
||||
slot, new export, new migration file.
|
||||
- **Patch** — bugfix, internal refactor.
|
||||
|
||||
Deprecations land for one minor release with `console.warn` and a
|
||||
`CHANGELOG.md` note before removal in the next major. Pin
|
||||
`"@gsc/web-kit": "^X.Y.Z"` to receive minors automatically.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
30
migrations/nav-apps-seed.up.sql
Normal file
30
migrations/nav-apps-seed.up.sql
Normal file
@@ -0,0 +1,30 @@
|
||||
-- @gsc/web-kit nav.apps canonical seed (apply verbatim, never edit)
|
||||
--
|
||||
-- Replicated into every app's nav.apps table so the "browse apps" panel
|
||||
-- shows the same canonical list everywhere. Updates flow via kit version
|
||||
-- bumps: re-copy this file into each app's migrations directory and re-apply.
|
||||
--
|
||||
-- ON CONFLICT (key) DO UPDATE keeps the canonical list in sync — any local
|
||||
-- edits to these rows will be overwritten on next apply. Add new apps by
|
||||
-- editing this file in the kit and bumping the kit's minor version.
|
||||
|
||||
INSERT INTO nav.apps (key, name, description, url, icon_class, icon_url, icon_bg, sort_order, enabled)
|
||||
VALUES
|
||||
('gsc-crm', 'CRM', 'Customer relationship management', 'https://crm.gosec.internal', NULL, 'https://assets.gosec.cloud/logos/crm.svg', NULL, 10, TRUE),
|
||||
('gsc-chronos', 'Chronos', 'Time tracking and timesheets', 'https://chronos.gosec.internal', NULL, 'https://assets.gosec.cloud/logos/chronos.svg', NULL, 20, TRUE),
|
||||
('gsc-meet', 'GSC Meet', 'Video conferencing with AI features', '/apps/gsc-meet', NULL, '/images/demo/logos/1.svg', NULL, 30, TRUE),
|
||||
('gsc-voice', 'Voice', 'PBX and telephony management', '/apps/gsc-voice', 'ph-phone', NULL, 'bg-primary-lt', 40, TRUE),
|
||||
('gsc-ai-hub', 'AI Hub', 'AI models and services management', '/apps/gsc-ai-hub', NULL, '/images/demo/logos/2.svg', NULL, 50, TRUE),
|
||||
('gsc-surveillance', 'Surveillance', 'Video surveillance and security', '/apps/surveillance', NULL, '/images/demo/logos/3.svg', NULL, 60, TRUE),
|
||||
('gsc-archive', 'Archive', 'Email archiving and eDiscovery', '/apps/gsc-archive', 'ph-archive-box', NULL, 'bg-info-lt', 70, TRUE),
|
||||
('gsc-dam', 'Digital Asset Manager', 'Media and asset management platform', '/apps/gsc-dam', NULL, '/images/demo/logos/4.svg', NULL, 80, TRUE)
|
||||
ON CONFLICT (key) DO UPDATE
|
||||
SET name = EXCLUDED.name,
|
||||
description = EXCLUDED.description,
|
||||
url = EXCLUDED.url,
|
||||
icon_class = EXCLUDED.icon_class,
|
||||
icon_url = EXCLUDED.icon_url,
|
||||
icon_bg = EXCLUDED.icon_bg,
|
||||
sort_order = EXCLUDED.sort_order,
|
||||
enabled = EXCLUDED.enabled,
|
||||
updated_at = NOW();
|
||||
70
migrations/nav-menu-items-template.sql
Normal file
70
migrations/nav-menu-items-template.sql
Normal file
@@ -0,0 +1,70 @@
|
||||
-- @gsc/web-kit nav.menu_items TEMPLATE — copy once, then adapt per app
|
||||
--
|
||||
-- Each app curates its own sidebar/topbar/subbar items. Copy this file into
|
||||
-- your app's migrations directory (e.g. apps/gscFoo/migrations/00X_nav_menu_items.up.sql),
|
||||
-- replace the example rows below with your app's real menu, and apply.
|
||||
--
|
||||
-- The kit will NOT update this file in subsequent versions — once you've
|
||||
-- copied and adapted it, it's yours. The DDL (nav-schema.up.sql) and the
|
||||
-- canonical apps list (nav-apps-seed.up.sql) are kit-owned and re-copied
|
||||
-- on every kit upgrade; menu_items rows belong to the app.
|
||||
--
|
||||
-- Notes on columns:
|
||||
-- - key: unique stable identifier (used by ON CONFLICT)
|
||||
-- - translation_key: dotted i18n key, e.g. "menu.dashboard"
|
||||
-- (resolved via next-intl namespace "menu")
|
||||
-- - url: "/dashboard" — pass "#" for parent items with children
|
||||
-- - icon: Phosphor CSS class, e.g. "ph-house"
|
||||
-- - menu_type: 'sidebar' (left nav), 'topbar' (user dropdown), 'subbar' (settings dropdown)
|
||||
-- - parent_id: UUID of parent menu_item for nested items (or NULL)
|
||||
-- - sort_order: ascending; render order within type/parent
|
||||
-- - is_system_required: cannot be hidden/removed by user customization
|
||||
|
||||
-- ============================================================================
|
||||
-- SIDEBAR (left navigation)
|
||||
-- ============================================================================
|
||||
INSERT INTO nav.menu_items (menu_type, key, translation_key, url, icon, sort_order, is_active, is_system_required)
|
||||
VALUES
|
||||
-- Replace these example rows with your app's sidebar items.
|
||||
('sidebar', 'dashboard', 'menu.dashboard', '/dashboard', 'ph-house', 10, TRUE, TRUE),
|
||||
('sidebar', 'example-1', 'menu.example1', '/example-1', 'ph-folder-open', 20, TRUE, FALSE),
|
||||
('sidebar', 'example-2', 'menu.example2', '/example-2', 'ph-list', 30, TRUE, FALSE)
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
|
||||
-- ============================================================================
|
||||
-- TOPBAR (user dropdown in navbar)
|
||||
-- ============================================================================
|
||||
INSERT INTO nav.menu_items (menu_type, key, translation_key, url, icon, sort_order, is_active, is_system_required)
|
||||
VALUES
|
||||
('topbar', 'profile', 'menu.profile', '/profile', 'ph-user', 10, TRUE, TRUE),
|
||||
('topbar', 'settings', 'menu.settings', '/settings', 'ph-gear', 20, TRUE, TRUE)
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
|
||||
-- ============================================================================
|
||||
-- SUBBAR (Settings dropdown items)
|
||||
-- ============================================================================
|
||||
-- Uncomment / customize if your app exposes a Settings dropdown in the subbar.
|
||||
--
|
||||
-- INSERT INTO nav.menu_items (menu_type, key, translation_key, url, icon, sort_order, is_active, is_system_required)
|
||||
-- VALUES
|
||||
-- ('subbar', 'general-settings', 'menu.generalSettings', '/settings/general', 'ph-sliders', 10, TRUE, FALSE),
|
||||
-- ('subbar', 'notifications', 'menu.notifications', '/settings/notifications', 'ph-bell', 20, TRUE, FALSE)
|
||||
-- ON CONFLICT (key) DO NOTHING;
|
||||
|
||||
-- ============================================================================
|
||||
-- NESTED EXAMPLES (parent + children)
|
||||
-- ============================================================================
|
||||
-- For nested sidebar items, set url='#' on the parent and reference its id
|
||||
-- via parent_id on children. Example:
|
||||
--
|
||||
-- WITH parent AS (
|
||||
-- INSERT INTO nav.menu_items (menu_type, key, translation_key, url, icon, sort_order)
|
||||
-- VALUES ('sidebar', 'reports', 'menu.reports', '#', 'ph-chart-bar', 90)
|
||||
-- ON CONFLICT (key) DO UPDATE SET sort_order = EXCLUDED.sort_order
|
||||
-- RETURNING id
|
||||
-- )
|
||||
-- INSERT INTO nav.menu_items (menu_type, parent_id, key, translation_key, url, icon, sort_order)
|
||||
-- SELECT 'sidebar', parent.id, 'reports-sales', 'menu.reportsSales', '/reports/sales', 'ph-chart-line', 10 FROM parent
|
||||
-- UNION ALL
|
||||
-- SELECT 'sidebar', parent.id, 'reports-leads', 'menu.reportsLeads', '/reports/leads', 'ph-chart-pie', 20 FROM parent
|
||||
-- ON CONFLICT (key) DO NOTHING;
|
||||
117
migrations/nav-schema.up.sql
Normal file
117
migrations/nav-schema.up.sql
Normal file
@@ -0,0 +1,117 @@
|
||||
-- @gsc/web-kit nav schema (canonical, apply verbatim, never edit)
|
||||
--
|
||||
-- Creates the navigation tables consumed by @gsc/web-kit/chrome AdminShell:
|
||||
-- - nav.menu_items — per-app sidebar/topbar/subbar items
|
||||
-- - nav.menu_role_requirements — role gating (enforcement TBD)
|
||||
-- - nav.menu_permission_requirements
|
||||
-- - nav.menu_product_requirements
|
||||
-- - nav.apps — cross-app "browse apps" panel data
|
||||
--
|
||||
-- Per-app philosophy: each app applies this schema to its OWN database
|
||||
-- (gsc_crm, gsc_chronos, gsc_admin, ...). Menu items are app-specific;
|
||||
-- the apps list is replicated from nav-apps-seed.up.sql so every app's
|
||||
-- "browse apps" panel shows the same canonical list.
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
CREATE SCHEMA IF NOT EXISTS nav;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Enums
|
||||
-- ---------------------------------------------------------------------------
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_type t
|
||||
JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||
WHERE t.typname = 'menu_type' AND n.nspname = 'nav'
|
||||
) THEN
|
||||
CREATE TYPE nav.menu_type AS ENUM ('sidebar', 'topbar', 'subbar');
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_type t
|
||||
JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||
WHERE t.typname = 'requirement_logic' AND n.nspname = 'nav'
|
||||
) THEN
|
||||
CREATE TYPE nav.requirement_logic AS ENUM ('OR', 'AND');
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- menu_items
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS nav.menu_items (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
menu_type nav.menu_type NOT NULL,
|
||||
parent_id UUID NULL REFERENCES nav.menu_items(id) ON DELETE CASCADE,
|
||||
key VARCHAR(64) NOT NULL UNIQUE,
|
||||
translation_key VARCHAR(128) NOT NULL,
|
||||
url VARCHAR(256) NOT NULL,
|
||||
icon VARCHAR(64) NULL,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
is_system_required BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
role_logic nav.requirement_logic NOT NULL DEFAULT 'OR',
|
||||
permission_logic nav.requirement_logic NOT NULL DEFAULT 'OR',
|
||||
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_menu_items_type ON nav.menu_items (menu_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_menu_items_parent ON nav.menu_items (parent_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_menu_items_sort ON nav.menu_items (sort_order);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- menu requirement tables (role / permission / product)
|
||||
-- Cross-DB IDs stored as plain strings; no cross-DB FKs.
|
||||
-- Enforcement is not yet wired in v0.4.0 chrome.
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS nav.menu_role_requirements (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
menu_item_id UUID NOT NULL REFERENCES nav.menu_items(id) ON DELETE CASCADE,
|
||||
role_id VARCHAR(128) NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (menu_item_id, role_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS nav.menu_permission_requirements (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
menu_item_id UUID NOT NULL REFERENCES nav.menu_items(id) ON DELETE CASCADE,
|
||||
permission_id VARCHAR(128) NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (menu_item_id, permission_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS nav.menu_product_requirements (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
menu_item_id UUID NOT NULL REFERENCES nav.menu_items(id) ON DELETE CASCADE,
|
||||
product_id VARCHAR(128) NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (menu_item_id, product_id)
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- apps — cross-app browse-apps panel data
|
||||
-- Either icon_class (Phosphor / FA class) or icon_url (logo image) may be set.
|
||||
-- icon_bg styles the colored square behind icon_class (e.g. 'bg-primary-lt').
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS nav.apps (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
key VARCHAR(64) NOT NULL UNIQUE,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
description VARCHAR(256) NOT NULL DEFAULT '',
|
||||
url VARCHAR(512) NOT NULL,
|
||||
icon_class VARCHAR(64) NULL,
|
||||
icon_url VARCHAR(512) NULL,
|
||||
icon_bg VARCHAR(32) NULL,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_apps_sort_order ON nav.apps (sort_order);
|
||||
116
package-lock.json
generated
116
package-lock.json
generated
@@ -1,17 +1,15 @@
|
||||
{
|
||||
"name": "@gsc/web-kit",
|
||||
"version": "0.2.0",
|
||||
"version": "0.4.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@gsc/web-kit",
|
||||
"version": "0.2.0",
|
||||
"version": "0.4.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@limitless/ui": "file:../limitless-ui",
|
||||
"next-auth": "^5.0.0-beta.25",
|
||||
"next-intl": "^4.6.1",
|
||||
"zod": "^3.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -19,15 +17,23 @@
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"next": "16.1.1",
|
||||
"next-auth": "^5.0.0-beta.25",
|
||||
"next-intl": "^4.6.1",
|
||||
"typescript": "^5.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@gsc/chat": "*",
|
||||
"bootstrap": "^5.3.3",
|
||||
"next": ">=15.0.0",
|
||||
"next-auth": "^5.0.0-beta.25",
|
||||
"next-intl": "^4.6.0",
|
||||
"react": "^18.2.0 || ^19.0.0",
|
||||
"react-dom": "^18.2.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@gsc/chat": {
|
||||
"optional": true
|
||||
},
|
||||
"bootstrap": {
|
||||
"optional": true
|
||||
}
|
||||
@@ -62,6 +68,7 @@
|
||||
"version": "0.41.2",
|
||||
"resolved": "https://registry.npmjs.org/@auth/core/-/core-0.41.2.tgz",
|
||||
"integrity": "sha512-Hx5MNBxN2fJTbJKGUKAA0wca43D0Akl3TvufY54Gn8lop7F+34vU1zA1pn0vQfIoVuLIrpfc2nkyjwIaPJMW7w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@panva/hkdf": "^1.2.1",
|
||||
@@ -91,6 +98,7 @@
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -101,12 +109,14 @@
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.4.tgz",
|
||||
"integrity": "sha512-Lbke1aOrsygKKR09Ux0NrZgbTqpDmiwXOgzyDOJ8Owr1zd5qOKTauf62hH+Seeku3ju77rHWH9I5SfX2CN0vuA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@formatjs/icu-messageformat-parser": {
|
||||
"version": "3.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.7.tgz",
|
||||
"integrity": "sha512-wJxRZ+SiUCIMTL86bQlZU9bEKDQqqvgk2ezQ1BySUdWRfHqOzj4IKUVFeUZKS9w58M4e7wMSG0Sl86LAPb7Qww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/icu-skeleton-parser": "2.1.7"
|
||||
@@ -116,12 +126,14 @@
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.7.tgz",
|
||||
"integrity": "sha512-cIw1SFP0bi0CUBiJ2jzp99ws3OJNQDfStcHq9Z0iHWzItmiIikihFO+npR8C80yDlp7ZuBCLXCcKjgWjHicksA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@formatjs/intl-localematcher": {
|
||||
"version": "0.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.6.tgz",
|
||||
"integrity": "sha512-AZRgUxj0q93lyF7Z5lFS85bLINXuBLX4R3tCKicO6fSWo6cvh9GQfoR3B1WlsqQwefZ1QORTivhInx7gM6HUzQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/fast-memoize": "3.1.4"
|
||||
@@ -131,6 +143,7 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
||||
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
@@ -144,6 +157,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -166,6 +180,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -188,6 +203,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -204,6 +220,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -220,6 +237,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -236,6 +254,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -252,6 +271,7 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -268,6 +288,7 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -284,6 +305,7 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -300,6 +322,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -316,6 +339,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -332,6 +356,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -348,6 +373,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -370,6 +396,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -392,6 +419,7 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -414,6 +442,7 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -436,6 +465,7 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -458,6 +488,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -480,6 +511,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -502,6 +534,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -524,6 +557,7 @@
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -543,6 +577,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -562,6 +597,7 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -581,6 +617,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -601,6 +638,7 @@
|
||||
"version": "16.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.1.tgz",
|
||||
"integrity": "sha512-3oxyM97Sr2PqiVyMyrZUtrtM3jqqFxOQJVuKclDsgj/L728iZt/GyslkN4NwarledZATCenbk4Offjk1hQmaAA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
@@ -610,6 +648,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -626,6 +665,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -642,6 +682,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -658,6 +699,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -674,6 +716,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -690,6 +733,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -706,6 +750,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -722,6 +767,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -735,6 +781,7 @@
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz",
|
||||
"integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
@@ -744,6 +791,7 @@
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
|
||||
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -782,6 +830,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -802,6 +851,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -822,6 +872,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -842,6 +893,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -862,6 +914,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -882,6 +935,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -902,6 +956,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -922,6 +977,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -942,6 +998,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -962,6 +1019,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -982,6 +1040,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1002,6 +1061,7 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1022,6 +1082,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1051,6 +1112,7 @@
|
||||
"version": "1.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz",
|
||||
"integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@swc/core-darwin-arm64": {
|
||||
@@ -1060,6 +1122,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1076,6 +1139,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1092,6 +1156,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1108,6 +1173,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1124,6 +1190,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1140,6 +1207,7 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1156,6 +1224,7 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1172,6 +1241,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1188,6 +1258,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1204,6 +1275,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1220,6 +1292,7 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1236,6 +1309,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1249,12 +1323,14 @@
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
@@ -1264,6 +1340,7 @@
|
||||
"version": "0.1.26",
|
||||
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz",
|
||||
"integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3"
|
||||
@@ -1303,6 +1380,7 @@
|
||||
"version": "2.10.29",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz",
|
||||
"integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"baseline-browser-mapping": "dist/cli.cjs"
|
||||
@@ -1336,6 +1414,7 @@
|
||||
"version": "1.0.30001792",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz",
|
||||
"integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -1356,6 +1435,7 @@
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
@@ -1369,6 +1449,7 @@
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -1378,6 +1459,7 @@
|
||||
"version": "4.11.1",
|
||||
"resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.11.1.tgz",
|
||||
"integrity": "sha512-C0tsPVuvyNp+++qWJP+mty/KLLStjerOZqu3W1xWLJkChEDbDi9Taoj6blK7L/onxbuVzwgH6k9Sf+rOV6lOvw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -1393,6 +1475,7 @@
|
||||
"version": "11.2.4",
|
||||
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.4.tgz",
|
||||
"integrity": "sha512-iKP6+uJXn+XcfRgYfGPE3+mqCoODV2vATrXDLo/YkYgIdelJHJPBEbc0GZThipAYPuk+8QJFiPgOfblU085ABg==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@formatjs/fast-memoize": "3.1.4",
|
||||
@@ -1403,6 +1486,7 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -1412,6 +1496,7 @@
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
@@ -1424,6 +1509,7 @@
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz",
|
||||
"integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
@@ -1433,6 +1519,7 @@
|
||||
"version": "3.3.12",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -1451,6 +1538,7 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -1460,6 +1548,7 @@
|
||||
"version": "16.1.1",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-16.1.1.tgz",
|
||||
"integrity": "sha512-QI+T7xrxt1pF6SQ/JYFz95ro/mg/1Znk5vBebsWwbpejj1T0A23hO7GYEaVac9QUOT2BIMiuzm0L99ooq7k0/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/env": "16.1.1",
|
||||
@@ -1513,6 +1602,7 @@
|
||||
"version": "5.0.0-beta.31",
|
||||
"resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.31.tgz",
|
||||
"integrity": "sha512-1OBgCKPzo+S7UWWMp3xgvGvIJ0OpV7B3vR4ZDRqD9a4Ch+OT6dakLXG9ivhtmIWVa71nTSXattOHyCg8sNi8/Q==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@auth/core": "0.41.2"
|
||||
@@ -1540,6 +1630,7 @@
|
||||
"version": "4.11.1",
|
||||
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.11.1.tgz",
|
||||
"integrity": "sha512-s32lFFLXkxrW6fy+4IVaGD5J8xPpbEDFLfBbXV73CTbHAGhOGMjYN4/rftdsKOQ44AnPhnZ5Et+ZNMr5tRpsqA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -1571,12 +1662,14 @@
|
||||
"version": "4.11.1",
|
||||
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.11.1.tgz",
|
||||
"integrity": "sha512-jHKGij7NoYccy2y54+e/wHVMoRgNt4h/Kn0XS9c4GbKu3KgJyANLUN8sFcDixv6sqz4V2kh6CTWgrkIidQksUg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/next-intl/node_modules/@swc/core": {
|
||||
"version": "1.15.33",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.33.tgz",
|
||||
"integrity": "sha512-jOlwnFV2xhuuZeAUILGFULeR6vDPfijEJ57evfocwznQldLU3w2cZ9bSDryY9ip+AsM3r1NJKzf47V2NXebkeQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -1617,6 +1710,7 @@
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz",
|
||||
"integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
@@ -1628,12 +1722,14 @@
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/oauth4webapi": {
|
||||
"version": "3.8.6",
|
||||
"resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.6.tgz",
|
||||
"integrity": "sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
@@ -1643,12 +1739,14 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -1661,12 +1759,14 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz",
|
||||
"integrity": "sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -1695,6 +1795,7 @@
|
||||
"version": "10.24.3",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
|
||||
"integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -1705,6 +1806,7 @@
|
||||
"version": "6.5.11",
|
||||
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz",
|
||||
"integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"preact": ">=10"
|
||||
@@ -1744,6 +1846,7 @@
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
|
||||
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
@@ -1757,6 +1860,7 @@
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
|
||||
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
@@ -1802,6 +1906,7 @@
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -1811,6 +1916,7 @@
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
||||
"integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"client-only": "0.0.1"
|
||||
@@ -1834,6 +1940,7 @@
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
@@ -1861,6 +1968,7 @@
|
||||
"version": "4.11.1",
|
||||
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.11.1.tgz",
|
||||
"integrity": "sha512-/dqWSqUSbVMzC+fdy7io8enhGYHeGeHK1bFhTLrp0ZblqdzY4FkE+tkffW6IfCauqaIA2/z4DQae4XEn93+raw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
|
||||
27
package.json
27
package.json
@@ -1,12 +1,16 @@
|
||||
{
|
||||
"name": "@gsc/web-kit",
|
||||
"version": "0.3.0",
|
||||
"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.",
|
||||
"version": "0.4.0",
|
||||
"description": "GSC web app skeleton — layout, auth, data, forms, feedback, navigation, chrome. Built on @limitless/ui. Drop into a Next.js app and just write pages.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"migrations"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -37,6 +41,10 @@
|
||||
"types": "./dist/shell/server.d.ts",
|
||||
"import": "./dist/shell/server.js"
|
||||
},
|
||||
"./chrome": {
|
||||
"types": "./dist/chrome/index.d.ts",
|
||||
"import": "./dist/chrome/index.js"
|
||||
},
|
||||
"./data": {
|
||||
"types": "./dist/data/index.d.ts",
|
||||
"import": "./dist/data/index.js"
|
||||
@@ -60,7 +68,10 @@
|
||||
"./utils": {
|
||||
"types": "./dist/utils/index.d.ts",
|
||||
"import": "./dist/utils/index.js"
|
||||
}
|
||||
},
|
||||
"./migrations/nav-schema.up.sql": "./migrations/nav-schema.up.sql",
|
||||
"./migrations/nav-apps-seed.up.sql": "./migrations/nav-apps-seed.up.sql",
|
||||
"./migrations/nav-menu-items-template.sql": "./migrations/nav-menu-items-template.sql"
|
||||
},
|
||||
"sideEffects": [
|
||||
"**/*.css"
|
||||
@@ -72,24 +83,28 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@limitless/ui": "file:../limitless-ui",
|
||||
"next-auth": "^5.0.0-beta.25",
|
||||
"next-intl": "^4.6.1",
|
||||
"zod": "^3.23.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@gsc/chat": "*",
|
||||
"bootstrap": "^5.3.3",
|
||||
"next": ">=15.0.0",
|
||||
"next-auth": "^5.0.0-beta.25",
|
||||
"next-intl": "^4.6.0",
|
||||
"react": "^18.2.0 || ^19.0.0",
|
||||
"react-dom": "^18.2.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bootstrap": { "optional": true }
|
||||
"bootstrap": { "optional": true },
|
||||
"@gsc/chat": { "optional": true }
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"next": "16.1.1",
|
||||
"next-auth": "^5.0.0-beta.25",
|
||||
"next-intl": "^4.6.1",
|
||||
"typescript": "^5.4.0"
|
||||
}
|
||||
}
|
||||
1111
src/chrome/AdminShell.tsx
Normal file
1111
src/chrome/AdminShell.tsx
Normal file
File diff suppressed because it is too large
Load Diff
46
src/chrome/LogoutButton.tsx
Normal file
46
src/chrome/LogoutButton.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import { signOut } from "next-auth/react";
|
||||
|
||||
/**
|
||||
* Default flow (shared org Keycloak):
|
||||
* 1. GET /api/auth/logout — host app returns { logoutUrl } pointing at
|
||||
* Keycloak's end_session endpoint (id_token_hint included).
|
||||
* 2. next-auth signOut() locally — fires events.signOut for backchannel
|
||||
* revocation; redirect:false so we control the navigation.
|
||||
* 3. Navigate to logoutUrl — kills the SSO cookie at Keycloak.
|
||||
*
|
||||
* Apps without /api/auth/logout (or that need a different flow) pass
|
||||
* `onSignOut` to fully replace this behavior.
|
||||
*/
|
||||
type LogoutButtonProps = {
|
||||
label: string;
|
||||
onSignOut?: () => void | Promise<void>;
|
||||
};
|
||||
|
||||
export function LogoutButton({ label, onSignOut }: LogoutButtonProps) {
|
||||
const handleLogout = async () => {
|
||||
if (onSignOut) {
|
||||
await onSignOut();
|
||||
return;
|
||||
}
|
||||
|
||||
let logoutUrl = "/logged-out";
|
||||
try {
|
||||
const res = await fetch("/api/auth/logout");
|
||||
const body = await res.json();
|
||||
if (body?.logoutUrl) logoutUrl = body.logoutUrl;
|
||||
} catch {
|
||||
// fall through with local-only logout
|
||||
}
|
||||
await signOut({ redirect: false });
|
||||
window.location.href = logoutUrl;
|
||||
};
|
||||
|
||||
return (
|
||||
<button type="button" onClick={handleLogout} className="dropdown-item">
|
||||
<i className="ph-sign-out me-2"></i>
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
17
src/chrome/_ambient.d.ts
vendored
Normal file
17
src/chrome/_ambient.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// Ambient declarations for peer dependencies we don't install in the kit.
|
||||
// `@gsc/chat` is provided by the consuming app — declared here so the kit
|
||||
// can typecheck against the surface we use without owning a hard dep.
|
||||
|
||||
declare module "@gsc/chat" {
|
||||
import type { FC } from "react";
|
||||
|
||||
export const ChatBubble: FC<{
|
||||
isOpen: boolean;
|
||||
onClick: () => void;
|
||||
}>;
|
||||
|
||||
export const ChatOverlay: FC<{
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}>;
|
||||
}
|
||||
110
src/chrome/header/BrowseApps.tsx
Normal file
110
src/chrome/header/BrowseApps.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import type { AppListItem, ChromeLabels } from "../types";
|
||||
|
||||
type BrowseAppsProps = {
|
||||
show?: boolean;
|
||||
apps: AppListItem[];
|
||||
viewAllUrl?: string; // default "/apps"
|
||||
labels: Pick<ChromeLabels, "browseApps" | "viewAll">;
|
||||
};
|
||||
|
||||
export function BrowseApps({ show = true, apps, viewAllUrl = "/apps", labels }: BrowseAppsProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const close = () => {
|
||||
if (open) setOpen(false);
|
||||
};
|
||||
|
||||
const enabled = apps.filter((a) => a.enabled).sort((a, b) => a.sortOrder - b.sortOrder);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Mobile search icon (always rendered alongside browse-apps trigger) */}
|
||||
<li className="nav-item d-lg-none">
|
||||
<Link
|
||||
href="#navbar_search"
|
||||
className="navbar-nav-link navbar-nav-link-icon rounded-pill"
|
||||
>
|
||||
<i className="ph ph-magnifying-glass"></i>
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
{show && (
|
||||
<li
|
||||
className="nav-item nav-item-dropdown-lg dropdown"
|
||||
onMouseLeave={close}
|
||||
>
|
||||
<Link
|
||||
href="#"
|
||||
className="navbar-nav-link navbar-nav-link-icon rounded-pill"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setOpen(!open);
|
||||
}}
|
||||
>
|
||||
<i className="ph ph-squares-four"></i>
|
||||
</Link>
|
||||
|
||||
<div
|
||||
className={
|
||||
open
|
||||
? "dropdown-menu dropdown-menu-scrollable-sm wmin-lg-600 p-0 show"
|
||||
: "dropdown-menu dropdown-menu-scrollable-sm wmin-lg-600 p-0"
|
||||
}
|
||||
>
|
||||
<div className="d-flex align-items-center border-bottom p-3">
|
||||
<h6 className="mb-0">{labels.browseApps}</h6>
|
||||
<Link href={viewAllUrl} className="ms-auto">
|
||||
{labels.viewAll}
|
||||
<i className="ph ph-arrow-circle-right ms-1"></i>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="row row-cols-1 row-cols-sm-2 g-0">
|
||||
{enabled.map((app, idx) => {
|
||||
const isLast = idx === enabled.length - 1;
|
||||
const isEvenColumn = idx % 2 === 0;
|
||||
const itemClasses = [
|
||||
"dropdown-item",
|
||||
"text-wrap",
|
||||
"h-100",
|
||||
"align-items-start",
|
||||
isEvenColumn ? "border-end-sm" : "",
|
||||
isLast ? "" : "border-bottom",
|
||||
"p-3",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
|
||||
return (
|
||||
<div className="col" key={app.key}>
|
||||
<Link href={app.url} className={itemClasses}>
|
||||
<div>
|
||||
{app.iconUrl ? (
|
||||
<img src={app.iconUrl} className="h-40px mb-2" alt="" />
|
||||
) : app.iconClass ? (
|
||||
<div
|
||||
className={`d-flex align-items-center justify-content-center h-40px w-40px rounded mb-2 ${app.iconBg ?? "bg-secondary-lt"}`}
|
||||
>
|
||||
<i className={`${app.iconClass} fs-2`}></i>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="fw-semibold my-1">{app.name}</div>
|
||||
{app.description && (
|
||||
<div className="text-muted">{app.description}</div>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
12
src/chrome/header/HeaderContacts.tsx
Normal file
12
src/chrome/header/HeaderContacts.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
"use client";
|
||||
|
||||
// Placeholder for parity with gscAdmin's contacts header zone. Renders nothing
|
||||
// today; an app may pass slots.pageHeaderExtras to render its own contacts UI.
|
||||
type HeaderContactsProps = {
|
||||
show?: boolean;
|
||||
};
|
||||
|
||||
export function HeaderContacts({ show = true }: HeaderContactsProps) {
|
||||
if (!show) return null;
|
||||
return null;
|
||||
}
|
||||
98
src/chrome/header/HeaderCustomers.tsx
Normal file
98
src/chrome/header/HeaderCustomers.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
|
||||
export type CustomerOption = {
|
||||
id: number | string;
|
||||
name: string;
|
||||
logo: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
type HeaderCustomersProps = {
|
||||
show?: boolean;
|
||||
customers?: CustomerOption[];
|
||||
};
|
||||
|
||||
export function HeaderCustomers({ show = true, customers = [] }: HeaderCustomersProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedName, setSelectedName] = useState("");
|
||||
const [selectedLogo, setSelectedLogo] = useState("");
|
||||
|
||||
const toggle = () => setOpen((v) => !v);
|
||||
|
||||
const select = (c: CustomerOption) => {
|
||||
setSelectedName(c.name);
|
||||
setSelectedLogo(c.logo);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
if (!show || customers.length === 0) return null;
|
||||
|
||||
const current = customers[0];
|
||||
|
||||
return (
|
||||
<div className="dropdown w-100 w-sm-auto">
|
||||
<Link
|
||||
href="#"
|
||||
className="d-flex align-items-center text-body lh-1 dropdown-toggle py-sm-2"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
toggle();
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={selectedLogo || current.logo}
|
||||
className="w-32px h-32px me-2"
|
||||
alt=""
|
||||
/>
|
||||
<div className="me-auto me-lg-1">
|
||||
<div className="fs-sm text-muted mb-1">Customer</div>
|
||||
<div className="fw-semibold">{selectedName || current.name}</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div
|
||||
className={
|
||||
open
|
||||
? "dropdown-menu dropdown-menu-lg-end w-100 w-lg-auto wmin-300 wmin-sm-350 pt-0 show"
|
||||
: "dropdown-menu dropdown-menu-lg-end w-100 w-lg-auto wmin-300 wmin-sm-350 pt-0"
|
||||
}
|
||||
onMouseLeave={toggle}
|
||||
>
|
||||
<div className="d-flex align-items-center p-3">
|
||||
<h6 className="fw-semibold mb-0">Customers</h6>
|
||||
<Link href="#" className="ms-auto">
|
||||
View all
|
||||
<i className="ph ph-arrow-circle-right ms-1"></i>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{customers.map((c) => (
|
||||
<Link
|
||||
href="#"
|
||||
className={
|
||||
selectedName === c.name
|
||||
? "dropdown-item py-2 active"
|
||||
: "dropdown-item py-2"
|
||||
}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
select(c);
|
||||
}}
|
||||
key={c.id}
|
||||
>
|
||||
<img src={c.logo} className="w-32px h-32px me-2" alt="" />
|
||||
<div>
|
||||
<div className="fw-semibold">{c.name}</div>
|
||||
{c.description && (
|
||||
<div className="fs-sm text-muted">{c.description}</div>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
24
src/chrome/header/Messages.tsx
Normal file
24
src/chrome/header/Messages.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
"use client";
|
||||
|
||||
type MessagesProps = {
|
||||
show?: boolean;
|
||||
onOpenChat?: () => void;
|
||||
};
|
||||
|
||||
export function Messages({ show = true, onOpenChat }: MessagesProps) {
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
className="navbar-nav-link navbar-nav-link-icon rounded-pill border-0 bg-transparent position-relative"
|
||||
title="Open chat"
|
||||
aria-label="Open chat"
|
||||
onClick={onOpenChat}
|
||||
>
|
||||
<i className="ph ph-chat-circle"></i>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
26
src/chrome/header/Search.tsx
Normal file
26
src/chrome/header/Search.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import { SearchHistory } from "./SearchHistory";
|
||||
import { SearchOptions } from "./SearchOptions";
|
||||
|
||||
type SearchProps = {
|
||||
show?: boolean;
|
||||
showHistory?: boolean;
|
||||
showOptions?: boolean;
|
||||
};
|
||||
|
||||
export function Search({ show = true, showHistory = true, showOptions = true }: SearchProps) {
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="navbar-collapse justify-content-center flex-lg-1 order-2 order-lg-1 collapse"
|
||||
id="navbar_search"
|
||||
>
|
||||
<div className="navbar-search flex-fill position-relative mt-2 mt-lg-0 mx-lg-3">
|
||||
{showHistory && <SearchHistory />}
|
||||
{showOptions && <SearchOptions />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
39
src/chrome/header/SearchHistory.tsx
Normal file
39
src/chrome/header/SearchHistory.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
export function SearchHistory() {
|
||||
const [showSearchHistory, setShowSearchHistory] = useState(false);
|
||||
|
||||
const close = () => {
|
||||
if (showSearchHistory) setShowSearchHistory(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="form-control-feedback form-control-feedback-start flex-grow-1"
|
||||
data-color-theme="dark"
|
||||
onMouseLeave={close}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control bg-transparent rounded-pill"
|
||||
placeholder="Search"
|
||||
onClick={() => setShowSearchHistory(!showSearchHistory)}
|
||||
/>
|
||||
<div className="form-control-feedback-icon">
|
||||
<i className="ph ph-magnifying-glass"></i>
|
||||
</div>
|
||||
{showSearchHistory && (
|
||||
<div className="dropdown-menu w-100 show" data-color-theme="light">
|
||||
<div className="dropdown-item text-muted">
|
||||
<div className="text-center w-32px me-3">
|
||||
<i className="ph ph-magnifying-glass"></i>
|
||||
</div>
|
||||
<span>Type to search...</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
103
src/chrome/header/SearchOptions.tsx
Normal file
103
src/chrome/header/SearchOptions.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
|
||||
export function SearchOptions() {
|
||||
const [showSearchOptions, setShowSearchOptions] = useState(false);
|
||||
|
||||
const close = () => {
|
||||
if (showSearchOptions) setShowSearchOptions(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Link
|
||||
href="#"
|
||||
className="navbar-nav-link align-items-center justify-content-center w-40px h-32px rounded-pill position-absolute end-0 top-50 translate-middle-y p-0 me-1"
|
||||
onClick={() => setShowSearchOptions(!showSearchOptions)}
|
||||
>
|
||||
<i className="ph ph-faders-horizontal"></i>
|
||||
</Link>
|
||||
|
||||
<div
|
||||
className={
|
||||
showSearchOptions
|
||||
? "dropdown-menu w-100 p-3 show"
|
||||
: "dropdown-menu w-100 p-3"
|
||||
}
|
||||
onMouseLeave={close}
|
||||
>
|
||||
<div className="d-flex align-items-center mb-3">
|
||||
<h6 className="mb-0">Search options</h6>
|
||||
<Link href="#" className="text-body rounded-pill ms-auto">
|
||||
<i className="ph ph-clock-counter-clockwise"></i>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<span className="d-block form-label">Category</span>
|
||||
<label className="form-check form-check-inline">
|
||||
<input type="checkbox" className="form-check-input" />
|
||||
<span className="form-check-label">Invoices</span>
|
||||
</label>
|
||||
<label className="form-check form-check-inline">
|
||||
<input type="checkbox" className="form-check-input" />
|
||||
<span className="form-check-label">Files</span>
|
||||
</label>
|
||||
<label className="form-check form-check-inline">
|
||||
<input type="checkbox" className="form-check-input" />
|
||||
<span className="form-check-label">Users</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Addition</label>
|
||||
<div className="input-group">
|
||||
<select className="form-select w-auto flex-grow-0">
|
||||
<option value="1">has</option>
|
||||
<option value="2">has not</option>
|
||||
</select>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Enter the word(s)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Status</label>
|
||||
<div className="input-group">
|
||||
<select className="form-select w-auto flex-grow-0">
|
||||
<option value="1">is</option>
|
||||
<option value="2">is not</option>
|
||||
</select>
|
||||
<select className="form-select">
|
||||
<option value="1">Active</option>
|
||||
<option value="2">Inactive</option>
|
||||
<option value="3">New</option>
|
||||
<option value="4">Expired</option>
|
||||
<option value="5">Pending</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="d-flex">
|
||||
<button type="button" className="btn btn-light">
|
||||
Reset
|
||||
</button>
|
||||
|
||||
<div className="ms-auto">
|
||||
<button type="button" className="btn btn-light">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" className="btn btn-primary ms-2">
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
7
src/chrome/header/index.ts
Normal file
7
src/chrome/header/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export { Search } from "./Search";
|
||||
export { SearchHistory } from "./SearchHistory";
|
||||
export { SearchOptions } from "./SearchOptions";
|
||||
export { Messages } from "./Messages";
|
||||
export { BrowseApps } from "./BrowseApps";
|
||||
export { HeaderContacts } from "./HeaderContacts";
|
||||
export { HeaderCustomers, type CustomerOption } from "./HeaderCustomers";
|
||||
25
src/chrome/index.ts
Normal file
25
src/chrome/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export { AdminShell } from "./AdminShell";
|
||||
export { LogoutButton } from "./LogoutButton";
|
||||
export {
|
||||
Search,
|
||||
SearchHistory,
|
||||
SearchOptions,
|
||||
Messages,
|
||||
BrowseApps,
|
||||
HeaderContacts,
|
||||
HeaderCustomers,
|
||||
type CustomerOption,
|
||||
} from "./header";
|
||||
export { useChromeLabels, DEFAULT_CHROME_LABELS } from "./labels";
|
||||
export type {
|
||||
AdminShellProps,
|
||||
ActivityFeedItem,
|
||||
AppListItem,
|
||||
Brand,
|
||||
ChromeFeatures,
|
||||
ChromeLabels,
|
||||
ChromeSlots,
|
||||
ChromeUser,
|
||||
DbMenuItem,
|
||||
MenuData,
|
||||
} from "./types";
|
||||
72
src/chrome/labels.ts
Normal file
72
src/chrome/labels.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { ChromeLabels } from "./types";
|
||||
|
||||
/**
|
||||
* Default English chrome labels. Used as fallback when next-intl namespace
|
||||
* "chrome" doesn't resolve a key and no per-key override is passed in props.
|
||||
*/
|
||||
export const DEFAULT_CHROME_LABELS: ChromeLabels = {
|
||||
navigation: "Navigation",
|
||||
main: "MAIN",
|
||||
dashboard: "Dashboard",
|
||||
support: "Support",
|
||||
settings: "Settings",
|
||||
allSettings: "All Settings",
|
||||
logout: "Logout",
|
||||
docs: "Docs",
|
||||
browseApps: "Browse apps",
|
||||
viewAll: "View all",
|
||||
activityTitle: "Activity",
|
||||
newNotifications: "New notifications",
|
||||
olderNotifications: "Older notifications",
|
||||
noOlderNotifications: "No older notifications",
|
||||
noNewNotifications: "No new notifications",
|
||||
expandSidebar: "Expand sidebar",
|
||||
collapseSidebar: "Collapse sidebar",
|
||||
closeSidebar: "Close sidebar",
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve chrome labels with three-level precedence:
|
||||
* 1. `overrides` prop (per-key escape hatch)
|
||||
* 2. next-intl namespace "chrome" message
|
||||
* 3. hardcoded English default
|
||||
*
|
||||
* Apps add `"chrome": { ... }` to their next-intl messages to translate.
|
||||
*/
|
||||
export function useChromeLabels(overrides?: Partial<ChromeLabels>): ChromeLabels {
|
||||
let t: ((key: string) => string) | null = null;
|
||||
try {
|
||||
t = useTranslations("chrome");
|
||||
} catch {
|
||||
t = null;
|
||||
}
|
||||
|
||||
const resolved = { ...DEFAULT_CHROME_LABELS };
|
||||
|
||||
if (t) {
|
||||
for (const key of Object.keys(resolved) as (keyof ChromeLabels)[]) {
|
||||
try {
|
||||
const v = t(key);
|
||||
// next-intl returns the key path when the message is missing — treat
|
||||
// that as "no translation" so we fall back to the English default.
|
||||
if (v && v !== key && !v.startsWith("chrome.")) {
|
||||
resolved[key] = v;
|
||||
}
|
||||
} catch {
|
||||
// Missing key — keep the default.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (overrides) {
|
||||
for (const k of Object.keys(overrides) as (keyof ChromeLabels)[]) {
|
||||
const v = overrides[k];
|
||||
if (v != null) resolved[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
174
src/chrome/types.ts
Normal file
174
src/chrome/types.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
// ============================================================================
|
||||
// Menu data (Prisma-shaped, returned by app's getMenuItemsByType())
|
||||
// ============================================================================
|
||||
|
||||
export type DbMenuItem = {
|
||||
id: string;
|
||||
key: string;
|
||||
translationKey: string;
|
||||
url: string;
|
||||
icon: string | null;
|
||||
sortOrder: number;
|
||||
isActive: boolean;
|
||||
isSystemRequired: boolean;
|
||||
children?: DbMenuItem[];
|
||||
};
|
||||
|
||||
export type MenuData = {
|
||||
sidebar: DbMenuItem[];
|
||||
topbar: DbMenuItem[];
|
||||
subbar: DbMenuItem[];
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Browse-apps panel data (returned by app's getApps())
|
||||
// ============================================================================
|
||||
|
||||
export type AppListItem = {
|
||||
key: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
url: string;
|
||||
iconClass?: string | null; // e.g. "ph-phone"
|
||||
iconUrl?: string | null; // image URL (preferred when set)
|
||||
iconBg?: string | null; // background utility class for iconClass square
|
||||
sortOrder: number;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Activity panel data (when features.activityPanel:true)
|
||||
// Optional — apps may pass `slots.activityPanel` to render their own body.
|
||||
// ============================================================================
|
||||
|
||||
export type ActivityFeedItem = {
|
||||
id: string;
|
||||
actorName: string;
|
||||
actorAvatarUrl?: string;
|
||||
message: ReactNode; // freeform body, may contain JSX
|
||||
timestamp: string; // pre-formatted display string (e.g. "2 hours ago")
|
||||
group?: "new" | "older"; // default "new"
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Brand (replaces site-informations.json)
|
||||
// ============================================================================
|
||||
|
||||
export type Brand = {
|
||||
name: string; // "CRM"
|
||||
product: string; // "GoSec CRM"
|
||||
logoUrl: string; // full navbar logo
|
||||
logoSmallUrl?: string; // optional compact logo
|
||||
websiteUrl: string; // footer brand link
|
||||
supportUrl: string; // subbar Support + footer Support link
|
||||
docsUrl: string; // footer docs link
|
||||
copyrightStartYear: number; // e.g. 2023
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// User
|
||||
// ============================================================================
|
||||
|
||||
export type ChromeUser = {
|
||||
displayName: string;
|
||||
email?: string;
|
||||
avatarUrl?: string;
|
||||
roles?: string[]; // future: role-gated menu filtering
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Feature toggles
|
||||
// ============================================================================
|
||||
|
||||
export type ChromeFeatures = {
|
||||
// Navbar
|
||||
search?: boolean; // default true
|
||||
searchHistory?: boolean; // default true
|
||||
searchOptions?: boolean; // default true
|
||||
browseApps?: boolean; // default true
|
||||
messages?: boolean; // default true
|
||||
notifications?: boolean; // default true
|
||||
// Subbar
|
||||
subbar?: boolean; // default true
|
||||
subbarSupport?: boolean; // default true
|
||||
subbarSettings?: boolean; // default true
|
||||
// Page header
|
||||
pageHeader?: boolean; // default true
|
||||
pageHeaderCustomers?: boolean; // default false (CRM-specific)
|
||||
pageHeaderContacts?: boolean; // default false (CRM-specific)
|
||||
// Overlays
|
||||
activityPanel?: boolean; // default false
|
||||
chat?: boolean; // default false
|
||||
// Footer
|
||||
footer?: boolean; // default true
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Slots (app-rendered content; overrides built-in renderers)
|
||||
// ============================================================================
|
||||
|
||||
export type ChromeSlots = {
|
||||
pageTitle?: ReactNode;
|
||||
pageHeaderExtras?: ReactNode;
|
||||
subbarExtras?: ReactNode;
|
||||
activityPanel?: ReactNode; // overrides built-in activity body
|
||||
navbarExtras?: ReactNode;
|
||||
footerExtras?: ReactNode;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// i18n labels (chrome strings, overridable per-key)
|
||||
// Defaults shipped in labels.ts; apps may also set keys under next-intl
|
||||
// namespace "chrome".
|
||||
// ============================================================================
|
||||
|
||||
export type ChromeLabels = {
|
||||
navigation: string; // sidebar header "Navigation"
|
||||
main: string; // sidebar group header "MAIN"
|
||||
dashboard: string; // default page title for "/"
|
||||
support: string; // subbar / footer "Support"
|
||||
settings: string; // subbar "Settings"
|
||||
allSettings: string; // subbar dropdown "All Settings"
|
||||
logout: string; // user dropdown "Logout"
|
||||
docs: string; // footer "Docs"
|
||||
browseApps: string; // browse-apps "Browse apps"
|
||||
viewAll: string; // browse-apps "View all"
|
||||
activityTitle: string; // activity panel header
|
||||
newNotifications: string; // activity panel "New notifications"
|
||||
olderNotifications: string; // activity panel "Older notifications"
|
||||
noOlderNotifications: string; // activity panel empty older state
|
||||
noNewNotifications: string; // activity panel empty new state
|
||||
expandSidebar: string; // aria-label
|
||||
collapseSidebar: string; // aria-label
|
||||
closeSidebar: string; // aria-label
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// AdminShell props
|
||||
// ============================================================================
|
||||
|
||||
export type AdminShellProps = {
|
||||
// Data
|
||||
menus: MenuData;
|
||||
apps: AppListItem[];
|
||||
user: ChromeUser;
|
||||
notificationCount?: number;
|
||||
activity?: ActivityFeedItem[];
|
||||
|
||||
// Brand (required)
|
||||
brand: Brand;
|
||||
|
||||
// Feature toggles
|
||||
features?: ChromeFeatures;
|
||||
|
||||
// Slots
|
||||
slots?: ChromeSlots;
|
||||
|
||||
// Behavior
|
||||
onSignOut?: () => void | Promise<void>;
|
||||
labels?: Partial<ChromeLabels>;
|
||||
|
||||
children: ReactNode;
|
||||
};
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
export * from "./layout/index";
|
||||
export * from "./shell/index";
|
||||
export * from "./chrome/index";
|
||||
export * from "./data/index";
|
||||
export * from "./forms/index";
|
||||
export * from "./feedback/index";
|
||||
|
||||
Reference in New Issue
Block a user