From 055dcd4175b58dda313576bb02ec7346c87a3aeb Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Date: Thu, 2 Apr 2026 13:09:45 +0200 Subject: [PATCH] feat(admin): wire management modules to live backend and add UGC role --- src/app.css | 152 +++++++++++++++ src/components/dashboard/DashboardLayout.tsx | 150 ++++++++++++--- src/components/dashboard/DashboardWidgets.tsx | 181 ++++++++++++++++++ src/lib/auth-flow.ts | 13 +- src/lib/auth.ts | 57 ++++++ src/routes/admin/[...path].tsx | 28 --- src/routes/admin/approval-management.tsx | 143 -------------- src/routes/admin/approval.tsx | 1 - src/routes/admin/external-role-management.tsx | 115 ----------- src/routes/admin/internal-role-management.tsx | 133 ------------- src/routes/admin/verification-management.tsx | 142 -------------- src/routes/admin/verification.tsx | 1 - src/routes/auth/register/index.tsx | 31 ++- src/routes/dashboard/explore.tsx | 139 ++++++++++++-- src/routes/dashboard/index.tsx | 118 +++++++----- .../dashboard/jobs/[id]/applications.tsx | 41 +++- src/routes/dashboard/jobs/create.tsx | 41 +++- src/routes/dashboard/jobs/index.tsx | 17 +- src/routes/dashboard/leads/accepted.tsx | 95 ++++++++- src/routes/dashboard/marketplace/[id].tsx | 40 ++-- src/routes/dashboard/marketplace/index.tsx | 19 +- src/routes/dashboard/portfolio/index.tsx | 14 +- src/routes/dashboard/profile.tsx | 96 ++++++++-- src/routes/dashboard/requests.tsx | 136 ++++++++++++- src/routes/dashboard/requirements/[id].tsx | 14 +- src/routes/dashboard/requirements/index.tsx | 50 ++++- src/routes/dashboard/wallet/buy.tsx | 84 ++++++++ src/routes/dashboard/wallet/index.tsx | 9 +- src/routes/dashboard/wallet/ledger.tsx | 54 ++++-- src/routes/onboarding.tsx | 14 +- src/routes/pending.tsx | 4 +- 31 files changed, 1369 insertions(+), 763 deletions(-) create mode 100644 src/components/dashboard/DashboardWidgets.tsx delete mode 100644 src/routes/admin/[...path].tsx delete mode 100644 src/routes/admin/approval-management.tsx delete mode 100644 src/routes/admin/approval.tsx delete mode 100644 src/routes/admin/external-role-management.tsx delete mode 100644 src/routes/admin/internal-role-management.tsx delete mode 100644 src/routes/admin/verification-management.tsx delete mode 100644 src/routes/admin/verification.tsx create mode 100644 src/routes/dashboard/wallet/buy.tsx diff --git a/src/app.css b/src/app.css index b0ba600..4d8275a 100644 --- a/src/app.css +++ b/src/app.css @@ -5330,3 +5330,155 @@ body { flex-wrap: wrap; gap: 6px; } + +/* ── Dashboard Enhancements ────────────────────────────────────────────────── */ + +.dashboard-widgets-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); + gap: 20px; + margin-top: 24px; +} + +.widget-card { + background: #fff; + border: 1px solid rgba(16, 11, 47, 0.08); + border-radius: 20px; + padding: 20px; + box-shadow: 0 4px 20px -10px rgba(2, 6, 23, 0.1); + display: flex; + flex-direction: column; + gap: 16px; +} + +.widget-header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.widget-header h3 { + margin: 0; + font-size: 16px; + font-weight: 700; + color: var(--ink); +} + +.dashboard-tabs { + display: flex; + gap: 8px; + padding: 4px; + background: #f1f5f9; + border-radius: 12px; + width: fit-content; + margin-bottom: 24px; +} + +.dashboard-tab { + padding: 8px 16px; + font-size: 13px; + font-weight: 600; + color: #64748b; + border: none; + background: transparent; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s ease; +} + +.dashboard-tab--active { + background: #fff; + color: var(--brand-orange); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); +} + +.quick-actions-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; +} + +.quick-action-btn { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 16px; + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 16px; + text-decoration: none; + transition: all 0.2s ease; +} + +.quick-action-btn:hover { + background: #fff; + border-color: var(--brand-orange); + transform: translateY(-2px); + box-shadow: 0 8px 20px -10px rgba(253, 97, 22, 0.2); +} + +.action-icon { + font-size: 24px; +} + +.quick-action-btn span:not(.action-icon) { + font-size: 12px; + font-weight: 700; + color: #1e293b; + text-align: center; +} + +/* Activity Timeline */ +.activity-timeline { + display: flex; + flex-direction: column; + gap: 20px; + position: relative; + padding-left: 12px; +} + +.activity-timeline::before { + content: ''; + position: absolute; + left: 3px; + top: 8px; + bottom: 8px; + width: 2px; + background: #e2e8f0; +} + +.activity-item { + display: flex; + gap: 16px; + position: relative; +} + +.activity-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--brand-orange); + margin-top: 6px; + flex-shrink: 0; + z-index: 1; +} + +.activity-text p { + margin: 0; + font-size: 13px; + line-height: 1.4; + color: #334155; +} + +.activity-text small { + font-size: 11px; + color: #94a3b8; +} + +.empty-state { + text-align: center; + color: #94a3b8; + font-size: 13px; + padding: 20px 0; +} diff --git a/src/components/dashboard/DashboardLayout.tsx b/src/components/dashboard/DashboardLayout.tsx index a31d7eb..dc31bbc 100644 --- a/src/components/dashboard/DashboardLayout.tsx +++ b/src/components/dashboard/DashboardLayout.tsx @@ -1,7 +1,7 @@ import { Component, Show, createEffect, For, createSignal, onMount } from 'solid-js'; import { useNavigate, A, useSearchParams } from '@solidjs/router'; -import { authState, logout, switchRole, bootstrapAuth, setMockRuntimeConfig } from '~/lib/auth'; -import { shouldShowRoleSwitcher } from '~/lib/auth-flow'; +import { authState, logout, switchRole, bootstrapAuth, setMockRuntimeConfig, isModuleLocked } from '~/lib/auth'; +import { shouldShowRoleSwitcher, getRoleLabel } from '~/lib/auth-flow'; import { getRoleTourStorageKey, getWelcomeTourStorageKey, @@ -66,6 +66,10 @@ const IconWallet = () => ( +);const IconLock = () => ( + + + ); // ── Module → nav item mapping ───────────────────────────────────────────────── @@ -115,6 +119,28 @@ const MODULE_NAV_MAP: Record(null); const [tourStepIndex, setTourStepIndex] = createSignal(0); @@ -348,6 +374,38 @@ export default function DashboardLayout(props: { children: any }) { const navItems = () => { const role = String(activeRole() || rc()?.role || '').toUpperCase(); + const roleConfig = rc()?.role_config; + + // 1. If admin-defined sidebar_items exist, use them as priority + if (Array.isArray(roleConfig?.sidebar_items) && roleConfig.sidebar_items.length > 0) { + return roleConfig.sidebar_items + .map((label: string) => { + const key = mapSidebarLabelToNavKey(label); + const base = key ? MODULE_NAV_MAP[key] : null; + if (!base) return null; + const isLocked = isModuleLocked(key || ''); + let finalItem = { ...base, label, isLocked }; // Use the admin-saved label + + if (isLocked) { + finalItem.href = '#'; // Disable navigation + } + + if (key === 'MARKETPLACE' && role === 'CUSTOMER') { + finalItem = { ...finalItem, label: 'Post Requirement', href: '/users/requirements/new' }; + } + + if (role === 'COMPANY') { + if (key === 'profile') finalItem.href = '/companies/profile'; + if (key === 'support') finalItem.href = '/companies/support'; + if (key === 'settings') finalItem.href = '/companies/settings'; + } + + return finalItem; + }) + .filter((item: any): item is NonNullable => Boolean(item)); + } + + // 2. Fallback to module-based logic const moduleSet = new Set( (Array.isArray(rc()?.enabled_modules) ? rc()!.enabled_modules : []) .map((moduleKey) => String(moduleKey || '').toLowerCase()), @@ -362,20 +420,25 @@ export default function DashboardLayout(props: { children: any }) { .map((m) => { const base = MODULE_NAV_MAP[m]; if (!base) return null; + + const isLocked = isModuleLocked(m); + const finalItem = { ...base, isLocked }; + if (isLocked) { + finalItem.href = '#'; + } - // Match Next.js role override: customer "leads" is "Post Requirement" if (m === 'leads' && role === 'CUSTOMER') { - return { ...base, label: 'Post Requirement', href: '/users/requirements/new', tourId: 'requirements' }; + return { ...finalItem, label: 'Post Requirement', href: isLocked ? '#' : '/users/requirements/new', tourId: 'requirements' }; } - // Match Next.js company route overrides. if (role === 'COMPANY') { - if (m === 'profile') return { ...base, href: '/companies/profile' }; - if (m === 'support') return { ...base, href: '/companies/support' }; - if (m === 'settings') return { ...base, href: '/companies/settings' }; + const prefix = isLocked ? '#' : ''; + if (m === 'profile') return { ...finalItem, href: prefix + '/companies/profile' }; + if (m === 'support') return { ...finalItem, href: prefix + '/companies/support' }; + if (m === 'settings') return { ...finalItem, href: prefix + '/companies/settings' }; } - return base; + return finalItem; }) .filter((item): item is NonNullable => Boolean(item)) .sort((left, right) => (left.order ?? 999) - (right.order ?? 999)); @@ -452,7 +515,7 @@ export default function DashboardLayout(props: { children: any }) {