From 562ad4f8de94c036c61111b3de7e02addf4e638b Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Date: Mon, 16 Mar 2026 23:30:37 +0100 Subject: [PATCH] feat(admin): add manage and edit pages for runtime configs --- src/app.css | 43 +++++++++ src/components/AdminSidebar.tsx | 22 +++-- src/lib/runtime/storage.ts | 54 ++++++++--- .../admin/onboarding-schemas/[schemaId].tsx | 81 +++++++++++++++++ src/routes/admin/onboarding-schemas/index.tsx | 48 ++++++++++ .../admin/role-ui-configs/[roleKey].tsx | 89 +++++++++++++++++++ src/routes/admin/role-ui-configs/index.tsx | 48 ++++++++++ src/routes/admin/runtime-roles/[roleKey].tsx | 81 +++++++++++++++++ src/routes/admin/runtime-roles/index.tsx | 48 ++++++++++ 9 files changed, 499 insertions(+), 15 deletions(-) create mode 100644 src/routes/admin/onboarding-schemas/[schemaId].tsx create mode 100644 src/routes/admin/onboarding-schemas/index.tsx create mode 100644 src/routes/admin/role-ui-configs/[roleKey].tsx create mode 100644 src/routes/admin/role-ui-configs/index.tsx create mode 100644 src/routes/admin/runtime-roles/[roleKey].tsx create mode 100644 src/routes/admin/runtime-roles/index.tsx diff --git a/src/app.css b/src/app.css index cf82633..92c5991 100644 --- a/src/app.css +++ b/src/app.css @@ -186,3 +186,46 @@ body { color: #0f766e; font-weight: 600; } + +.list-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 14px; +} + +.list-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.list-item { + border: 1px solid #dbe1ec; + border-radius: 12px; + padding: 12px; + background: #fff; +} + +.list-item h3 { + margin: 0 0 8px; + font-size: 16px; +} + +.list-item p { + margin: 4px 0; + font-size: 13px; + color: #475569; +} + +@media (max-width: 1000px) { + .list-grid { + grid-template-columns: 1fr; + } + + .list-header { + flex-direction: column; + align-items: flex-start; + } +} diff --git a/src/components/AdminSidebar.tsx b/src/components/AdminSidebar.tsx index 2f75bd9..fe0d350 100644 --- a/src/components/AdminSidebar.tsx +++ b/src/components/AdminSidebar.tsx @@ -2,8 +2,11 @@ import { A, useLocation } from '@solidjs/router'; const links = [ { href: '/admin/runtime-roles/new', label: 'Create Role' }, + { href: '/admin/runtime-roles', label: 'Manage Roles' }, { href: '/admin/role-ui-configs/new', label: 'Create Dashboard' }, + { href: '/admin/role-ui-configs', label: 'Manage Dashboards' }, { href: '/admin/onboarding-schemas/new', label: 'Create Onboarding Flow' }, + { href: '/admin/onboarding-schemas', label: 'Manage Onboarding Flows' }, ]; export default function AdminSidebar() { @@ -14,11 +17,20 @@ export default function AdminSidebar() {
NXTGAUGE
- {links.map((item) => ( - - {item.label} - - ))} + {links.map((item) => { + const isActive = + location.pathname === item.href || + (item.href !== '/admin/runtime-roles' && + item.href !== '/admin/role-ui-configs' && + item.href !== '/admin/onboarding-schemas' && + location.pathname.startsWith(item.href.replace('/new', ''))); + + return ( + + {item.label} + + ); + })}

Same UI flow, simpler builder experience.

); diff --git a/src/lib/runtime/storage.ts b/src/lib/runtime/storage.ts index 9de987d..dc63a64 100644 --- a/src/lib/runtime/storage.ts +++ b/src/lib/runtime/storage.ts @@ -4,17 +4,30 @@ export type RuntimeRecordStatus = 'draft' | 'published'; const KEY = 'nxtgauge_admin_runtime_builder_v1'; type RuntimeStore = { - role: Record; - dashboard: Record; - onboarding: Record; + role: Record>; + dashboard: Record>; + onboarding: Record>; }; +export type RuntimeStoredRecord = { + status: RuntimeRecordStatus; + updatedAt: string; + payload: T; +}; + +export type RuntimeListItem = RuntimeStoredRecord & { + key: string; +}; + +function emptyStore(): RuntimeStore { + return { role: {}, dashboard: {}, onboarding: {} }; +} + function readStore(): RuntimeStore { - if (typeof window === 'undefined') { - return { role: {}, dashboard: {}, onboarding: {} }; - } + if (typeof window === 'undefined') return emptyStore(); const raw = window.localStorage.getItem(KEY); - if (!raw) return { role: {}, dashboard: {}, onboarding: {} }; + if (!raw) return emptyStore(); + try { const parsed = JSON.parse(raw) as Partial; return { @@ -23,7 +36,7 @@ function readStore(): RuntimeStore { onboarding: parsed.onboarding || {}, }; } catch { - return { role: {}, dashboard: {}, onboarding: {} }; + return emptyStore(); } } @@ -33,9 +46,11 @@ function writeStore(store: RuntimeStore) { } export function saveRuntimeConfig(type: RuntimeRecordType, key: string, payload: unknown, status: RuntimeRecordStatus) { + const normalizedKey = key.trim(); + if (!normalizedKey) return; + const store = readStore(); - const bucket = store[type]; - bucket[key] = { + store[type][normalizedKey] = { status, updatedAt: new Date().toISOString(), payload, @@ -43,3 +58,22 @@ export function saveRuntimeConfig(type: RuntimeRecordType, key: string, payload: writeStore(store); } +export function listRuntimeConfigs(type: RuntimeRecordType): Array> { + const store = readStore(); + return Object.entries(store[type]) + .map(([key, value]) => ({ key, ...(value as RuntimeStoredRecord) })) + .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)); +} + +export function getRuntimeConfig(type: RuntimeRecordType, key: string): RuntimeStoredRecord | null { + const record = readStore()[type][key]; + return (record as RuntimeStoredRecord) || null; +} + +export function deleteRuntimeConfig(type: RuntimeRecordType, key: string): boolean { + const store = readStore(); + if (!store[type][key]) return false; + delete store[type][key]; + writeStore(store); + return true; +} diff --git a/src/routes/admin/onboarding-schemas/[schemaId].tsx b/src/routes/admin/onboarding-schemas/[schemaId].tsx new file mode 100644 index 0000000..4ede867 --- /dev/null +++ b/src/routes/admin/onboarding-schemas/[schemaId].tsx @@ -0,0 +1,81 @@ +import { useParams } from '@solidjs/router'; +import { createSignal, onMount } from 'solid-js'; +import AdminShell from '~/components/AdminShell'; +import { getRuntimeConfig, saveRuntimeConfig } from '~/lib/runtime/storage'; +import type { RuntimeOnboardingConfig } from '~/lib/runtime/types'; + +export default function EditOnboardingPage() { + const params = useParams(); + const [config, setConfig] = createSignal(null); + const [statusMessage, setStatusMessage] = createSignal(''); + + onMount(() => { + const existing = getRuntimeConfig('onboarding', params.schemaId); + setConfig(existing?.payload || null); + }); + + const persist = (status: 'draft' | 'published') => { + const payload = config(); + if (!payload) return; + saveRuntimeConfig('onboarding', payload.schemaId, payload, status); + setStatusMessage(status === 'draft' ? 'Draft saved in runtime storage.' : 'Onboarding schema published in runtime storage.'); + }; + + return ( + +

Edit Onboarding Flow

+

Update this runtime onboarding schema without changing source code.

+ {!config() ? ( +

Onboarding schema not found in local runtime storage.

+ ) : ( +
+
+

Onboarding Builder

+
+ + setConfig({ ...config()!, schemaId: e.currentTarget.value })} /> +
+
+ + setConfig({ ...config()!, roleKey: e.currentTarget.value.toUpperCase() })} /> +
+
+ + setConfig({ ...config()!, version: Number(e.currentTarget.value || 1) })} /> +
+
+ +