feat(admin): Phase 0 — Tailwind v4 foundation, shell rewrite, modern dashboard

- Install Tailwind CSS v4 via @tailwindcss/vite; configure vite.config.ts
- Rewrite app.css: Tailwind base, Exo 2 font, brand tokens (orange #fd6216, navy #050026), scrollbar utility; fix @import order
- Rewrite AdminShell.tsx: fixed header, fixed inset body grid (sidebar + main), session check, sub-tab system, logout, admin avatar/name/role
- Rewrite AdminSidebar.tsx: collapsible w-64/w-20, orange active rail + badge/dot, CSS filter for SVG icon tinting, min-h-0 flex fix
- Replace 84 route stub CSS classes (page-title, card, btn, table-wrap, etc.) with Tailwind equivalents via safe class-attr-only regex script
- Rewrite admin dashboard: Lucide icons in colored chip backgrounds, 4-col KPI grid, Control Plane 6-module grid, hover lift animations
- Disable SSR (ssr: false) to fix Vinxi dev manifest error; clear stale .vinxi cache
- Add lucide-solid icon library
- Add scripts/cleanup-css.mjs for class migration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ashwin Kumar 2026-03-23 23:00:21 +01:00
parent 0050277922
commit a272276055
92 changed files with 1879 additions and 2871 deletions

View file

@ -4,9 +4,8 @@
{ {
"name": "admin-solid", "name": "admin-solid",
"runtimeExecutable": "sh", "runtimeExecutable": "sh",
"runtimeArgs": ["-c", "cd /Users/ashwin/workspace/nxtgauge-admin-solid && npm run dev -- --port $PORT"], "runtimeArgs": ["-c", "cd /Users/ashwin/workspace/nxtgauge-admin-solid && npm run dev -- --port 3002"],
"port": 3002, "port": 3002
"autoPort": true
} }
] ]
} }

327
package-lock.json generated
View file

@ -9,7 +9,10 @@
"@solidjs/meta": "^0.29.4", "@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.15.0", "@solidjs/router": "^0.15.0",
"@solidjs/start": "^1.3.2", "@solidjs/start": "^1.3.2",
"@tailwindcss/vite": "^4.2.2",
"lucide-solid": "^1.0.1",
"solid-js": "^1.9.5", "solid-js": "^1.9.5",
"tailwindcss": "^4.2.2",
"vinxi": "^0.5.7" "vinxi": "^0.5.7"
}, },
"devDependencies": { "devDependencies": {
@ -355,7 +358,6 @@
"integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==", "integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"@emnapi/wasi-threads": "1.2.0", "@emnapi/wasi-threads": "1.2.0",
"tslib": "^2.4.0" "tslib": "^2.4.0"
@ -367,7 +369,6 @@
"integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
@ -378,7 +379,6 @@
"integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
@ -968,7 +968,6 @@
"integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"@emnapi/core": "^1.7.1", "@emnapi/core": "^1.7.1",
"@emnapi/runtime": "^1.7.1", "@emnapi/runtime": "^1.7.1",
@ -2311,6 +2310,272 @@
"integrity": "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==", "integrity": "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==",
"license": "CC0-1.0" "license": "CC0-1.0"
}, },
"node_modules/@tailwindcss/node": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz",
"integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==",
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.5",
"enhanced-resolve": "^5.19.0",
"jiti": "^2.6.1",
"lightningcss": "1.32.0",
"magic-string": "^0.30.21",
"source-map-js": "^1.2.1",
"tailwindcss": "4.2.2"
}
},
"node_modules/@tailwindcss/node/node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/@tailwindcss/oxide": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz",
"integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==",
"license": "MIT",
"engines": {
"node": ">= 20"
},
"optionalDependencies": {
"@tailwindcss/oxide-android-arm64": "4.2.2",
"@tailwindcss/oxide-darwin-arm64": "4.2.2",
"@tailwindcss/oxide-darwin-x64": "4.2.2",
"@tailwindcss/oxide-freebsd-x64": "4.2.2",
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2",
"@tailwindcss/oxide-linux-arm64-gnu": "4.2.2",
"@tailwindcss/oxide-linux-arm64-musl": "4.2.2",
"@tailwindcss/oxide-linux-x64-gnu": "4.2.2",
"@tailwindcss/oxide-linux-x64-musl": "4.2.2",
"@tailwindcss/oxide-wasm32-wasi": "4.2.2",
"@tailwindcss/oxide-win32-arm64-msvc": "4.2.2",
"@tailwindcss/oxide-win32-x64-msvc": "4.2.2"
}
},
"node_modules/@tailwindcss/oxide-android-arm64": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz",
"integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-darwin-arm64": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz",
"integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-darwin-x64": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz",
"integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-freebsd-x64": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz",
"integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz",
"integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz",
"integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz",
"integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz",
"integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz",
"integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz",
"integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==",
"bundleDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
"@emnapi/runtime",
"@tybys/wasm-util",
"@emnapi/wasi-threads",
"tslib"
],
"cpu": [
"wasm32"
],
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.8.1",
"@emnapi/runtime": "^1.8.1",
"@emnapi/wasi-threads": "^1.1.0",
"@napi-rs/wasm-runtime": "^1.1.1",
"@tybys/wasm-util": "^0.10.1",
"tslib": "^2.8.1"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz",
"integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz",
"integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/vite": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.2.tgz",
"integrity": "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==",
"license": "MIT",
"dependencies": {
"@tailwindcss/node": "4.2.2",
"@tailwindcss/oxide": "4.2.2",
"tailwindcss": "4.2.2"
},
"peerDependencies": {
"vite": "^5.2.0 || ^6 || ^7 || ^8"
}
},
"node_modules/@tanstack/directive-functions-plugin": { "node_modules/@tanstack/directive-functions-plugin": {
"version": "1.121.21", "version": "1.121.21",
"resolved": "https://registry.npmjs.org/@tanstack/directive-functions-plugin/-/directive-functions-plugin-1.121.21.tgz", "resolved": "https://registry.npmjs.org/@tanstack/directive-functions-plugin/-/directive-functions-plugin-1.121.21.tgz",
@ -2391,7 +2656,6 @@
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
@ -4009,6 +4273,19 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/enhanced-resolve": {
"version": "5.20.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz",
"integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.3.0"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/entities": { "node_modules/entities": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
@ -5019,7 +5296,6 @@
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
"license": "MPL-2.0", "license": "MPL-2.0",
"peer": true,
"dependencies": { "dependencies": {
"detect-libc": "^2.0.3" "detect-libc": "^2.0.3"
}, },
@ -5056,7 +5332,6 @@
"os": [ "os": [
"android" "android"
], ],
"peer": true,
"engines": { "engines": {
"node": ">= 12.0.0" "node": ">= 12.0.0"
}, },
@ -5077,7 +5352,6 @@
"os": [ "os": [
"darwin" "darwin"
], ],
"peer": true,
"engines": { "engines": {
"node": ">= 12.0.0" "node": ">= 12.0.0"
}, },
@ -5098,7 +5372,6 @@
"os": [ "os": [
"darwin" "darwin"
], ],
"peer": true,
"engines": { "engines": {
"node": ">= 12.0.0" "node": ">= 12.0.0"
}, },
@ -5119,7 +5392,6 @@
"os": [ "os": [
"freebsd" "freebsd"
], ],
"peer": true,
"engines": { "engines": {
"node": ">= 12.0.0" "node": ">= 12.0.0"
}, },
@ -5140,7 +5412,6 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">= 12.0.0" "node": ">= 12.0.0"
}, },
@ -5161,7 +5432,6 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">= 12.0.0" "node": ">= 12.0.0"
}, },
@ -5182,7 +5452,6 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">= 12.0.0" "node": ">= 12.0.0"
}, },
@ -5203,7 +5472,6 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">= 12.0.0" "node": ">= 12.0.0"
}, },
@ -5224,7 +5492,6 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">= 12.0.0" "node": ">= 12.0.0"
}, },
@ -5245,7 +5512,6 @@
"os": [ "os": [
"win32" "win32"
], ],
"peer": true,
"engines": { "engines": {
"node": ">= 12.0.0" "node": ">= 12.0.0"
}, },
@ -5266,7 +5532,6 @@
"os": [ "os": [
"win32" "win32"
], ],
"peer": true,
"engines": { "engines": {
"node": ">= 12.0.0" "node": ">= 12.0.0"
}, },
@ -5401,6 +5666,15 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"node_modules/lucide-solid": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/lucide-solid/-/lucide-solid-1.0.1.tgz",
"integrity": "sha512-fpo0UmEHdEmdvydbFS9lQUqQ9rqSqWXMY4w3n9tVr9FqBBWJB0YI/lTMSxCspHs706ZzHIS/1YEnDhLOIP+m+A==",
"license": "ISC",
"peerDependencies": {
"solid-js": "^1.4.7"
}
},
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.21", "version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@ -7355,6 +7629,25 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/tailwindcss": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
"integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==",
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.1.tgz",
"integrity": "sha512-b+u3CEM6FjDHru+nhUSjDofpWSBp2rINziJWgApm72wwGasQ/wKXftZe4tI2Y5HPv6OpzXSZHOFq87H4vfsgsw==",
"license": "MIT",
"engines": {
"node": ">=6"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/tar": { "node_modules/tar": {
"version": "7.5.11", "version": "7.5.11",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz", "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz",

View file

@ -13,7 +13,10 @@
"@solidjs/meta": "^0.29.4", "@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.15.0", "@solidjs/router": "^0.15.0",
"@solidjs/start": "^1.3.2", "@solidjs/start": "^1.3.2",
"@tailwindcss/vite": "^4.2.2",
"lucide-solid": "^1.0.1",
"solid-js": "^1.9.5", "solid-js": "^1.9.5",
"tailwindcss": "^4.2.2",
"vinxi": "^0.5.7" "vinxi": "^0.5.7"
}, },
"engines": { "engines": {

167
scripts/cleanup-css.mjs Normal file
View file

@ -0,0 +1,167 @@
#!/usr/bin/env node
/**
* Replace old hand-written CSS class names with Tailwind v4 equivalents.
* SAFE version: only replaces inside class="..." attribute values.
*
* Run: node scripts/cleanup-css.mjs
*/
import { readFileSync, writeFileSync } from 'fs';
import { globSync } from 'glob';
// Order matters: most-specific (multi-word) patterns first.
// Each entry: [old-class-string, new-tailwind-classes]
const REPLACEMENTS = [
// ── Page structure ──────────────────────────────────────────────
['page-hero-card page-actions', 'mb-6 flex items-start justify-between gap-4'],
['page-actions-right', 'flex items-center gap-2'],
['page-actions', 'mb-6 flex items-start justify-between gap-4'],
['page-title-block', 'flex flex-col gap-1'],
['page-title', 'text-2xl font-bold text-gray-900'],
['page-subtitle', 'mt-1 text-sm text-gray-500'],
// ── Card ────────────────────────────────────────────────────────
['card', 'rounded-xl border border-gray-200 bg-white shadow-sm'],
// ── Buttons (specific before generic) ───────────────────────────
['btn navy', 'inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors'],
['btn primary', 'inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors'],
['btn danger', 'inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors'],
['btn sm', 'inline-flex items-center rounded-md border border-gray-200 bg-white px-3 py-1.5 text-xs font-medium text-gray-700 hover:bg-gray-50 transition-colors'],
['btn', 'inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors'],
// ── Table ────────────────────────────────────────────────────────
['table-wrap', 'overflow-x-auto'],
['list-table', 'w-full text-sm'],
['perm-table', 'w-full text-sm'],
['table-actions', 'flex items-center justify-end gap-1'],
['align-right', 'text-right'],
['row-selected', 'bg-orange-50'],
// ── Icon action buttons ──────────────────────────────────────────
['action-icon-btn danger', 'rounded p-1.5 text-red-500 hover:bg-red-50 hover:text-red-700 text-sm'],
['action-icon-btn', 'rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm'],
// ── Status chips (specific before generic) ───────────────────────
['status-chip active', 'inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700'],
['status-chip pending', 'inline-flex items-center rounded-full bg-amber-100 px-2.5 py-0.5 text-xs font-medium text-amber-700'],
['status-chip inactive', 'inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-500'],
['status-chip', 'inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600'],
// ── Error / notice ───────────────────────────────────────────────
['error-box', 'mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700'],
// ── Form sections ────────────────────────────────────────────────
['role-form-section', 'mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm'],
['field-grid-2', 'mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2'],
['module-picker', 'mt-3 flex flex-wrap gap-2'],
['module-chip selected', 'flex cursor-pointer items-center gap-1.5 rounded-full border border-orange-300 bg-orange-50 px-3 py-1.5 text-xs font-medium text-orange-700'],
['module-chip', 'flex cursor-pointer items-center gap-1.5 rounded-full border border-gray-200 bg-white px-3 py-1.5 text-xs font-medium text-gray-600 hover:border-gray-300'],
// ── Legacy tab nav (now handled by AdminShell) ────────────────────
['admin-link-tabs', 'hidden'],
['admin-link-tab active', 'hidden'],
['admin-link-tab', 'hidden'],
];
/**
* Replace old CSS class names only inside JSX class attribute values.
* Handles:
* class="foo bar baz"
* class={'foo bar baz'}
* class={`foo bar baz`}
* class={condition ? 'foo' : 'bar'}
*
* We do this by extracting the string portion of class attributes and
* replacing only within those extracted portions.
*/
function replaceInClassAttributes(src) {
let changed = false;
// Process class="..." and class='...' (static strings)
src = src.replace(/class=["']([^"']*)["']/g, (match, classes) => {
let replaced = classes;
for (const [from, to] of REPLACEMENTS) {
// Replace whole-word occurrences within the class string
// Use word boundaries to avoid partial matches
const re = new RegExp(`(?<![\\w-])${escapeRe(from)}(?![\\w-])`, 'g');
replaced = replaced.replace(re, to);
}
if (replaced !== classes) changed = true;
const quote = match[6] === '"' ? '"' : "'"; // char after `class=`
return `class=${quote}${replaced}${quote}`;
});
// Process class={`...`} (template literals with static content)
src = src.replace(/class=\{`([^`]*)`\}/g, (match, classes) => {
let replaced = classes;
for (const [from, to] of REPLACEMENTS) {
const re = new RegExp(`(?<![\\w-])${escapeRe(from)}(?![\\w-])`, 'g');
replaced = replaced.replace(re, to);
}
if (replaced !== classes) changed = true;
return `class={\`${replaced}\`}`;
});
// Process class={'...'} and class={"..."} (single-expression strings)
src = src.replace(/class=\{(['"])([^'"]*)\1\}/g, (match, q, classes) => {
let replaced = classes;
for (const [from, to] of REPLACEMENTS) {
const re = new RegExp(`(?<![\\w-])${escapeRe(from)}(?![\\w-])`, 'g');
replaced = replaced.replace(re, to);
}
if (replaced !== classes) changed = true;
return `class={${q}${replaced}${q}}`;
});
// Process ternary class values: class={cond ? 'foo' : 'bar'}
// Match string literals inside class={...} expressions
src = src.replace(/class=\{[^}]+\}/g, (match) => {
// Replace inside single/double-quoted string literals within the expression
const replaced = match.replace(/'([^']*)'/g, (m, inner) => {
let r = inner;
for (const [from, to] of REPLACEMENTS) {
const re = new RegExp(`(?<![\\w-])${escapeRe(from)}(?![\\w-])`, 'g');
r = r.replace(re, to);
}
if (r !== inner) changed = true;
return `'${r}'`;
}).replace(/"([^"]*)"/g, (m, inner) => {
// skip JSX attributes within the expression (avoid double replacement)
let r = inner;
for (const [from, to] of REPLACEMENTS) {
const re = new RegExp(`(?<![\\w-])${escapeRe(from)}(?![\\w-])`, 'g');
r = r.replace(re, to);
}
if (r !== inner) changed = true;
return `"${r}"`;
});
return replaced;
});
return { src, changed };
}
function escapeRe(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
const files = globSync('src/routes/admin/**/*.tsx', {
cwd: '/Users/ashwin/workspace/nxtgauge-admin-solid',
absolute: true,
});
let totalChanged = 0;
for (const file of files) {
const original = readFileSync(file, 'utf8');
const { src, changed } = replaceInClassAttributes(original);
if (changed) {
writeFileSync(file, src, 'utf8');
totalChanged++;
console.log(` updated: ${file.replace('/Users/ashwin/workspace/nxtgauge-admin-solid/', '')}`);
}
}
console.log(`\nDone. Updated ${totalChanged} / ${files.length} files.`);

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,6 @@ import { createMemo, createSignal, onMount, type JSX } from 'solid-js';
import AdminSidebar from './AdminSidebar'; import AdminSidebar from './AdminSidebar';
import { isExternalIdentity } from '~/lib/admin-auth'; import { isExternalIdentity } from '~/lib/admin-auth';
import { clearAdminSession, hasAdminSession, setAdminSession } from '~/lib/admin-session'; import { clearAdminSession, hasAdminSession, setAdminSession } from '~/lib/admin-session';
import { sidebarCollapsed } from '~/lib/sidebar-state';
type Tab = { href: string; label: string; exact?: boolean }; type Tab = { href: string; label: string; exact?: boolean };
@ -52,42 +51,45 @@ const TAB_SETS: Array<{ prefixes: string[]; tabs: Tab[] }> = [
]; ];
const PAGE_TITLES: Array<{ prefix: string; title: string }> = [ const PAGE_TITLES: Array<{ prefix: string; title: string }> = [
{ prefix: '/admin/workspace', title: 'Dashboard Workspace' }, { prefix: '/admin/employees', title: 'Employee Management' },
{ prefix: '/admin/settings', title: 'Settings' }, { prefix: '/admin/department', title: 'Department Management' },
{ prefix: '/admin/role-modules', title: 'Role Modules' }, { prefix: '/admin/designation', title: 'Designation Management' },
{ prefix: '/admin/modules', title: 'Module Management' }, { prefix: '/admin/roles', title: 'Internal Role Management' },
{ prefix: '/admin/responses', title: 'Lead Responses' }, { prefix: '/admin/runtime-roles', title: 'External Role Management' },
{ prefix: '/admin/applications', title: 'Applications' }, { prefix: '/admin/onboarding-schemas', title: 'External Onboarding Management' },
{ prefix: '/admin/financial', title: 'Financial Management' }, { prefix: '/admin/internal-dashboard-management', title: 'Internal Dashboard Management' },
{ prefix: '/admin/help', title: 'Support Management' }, { prefix: '/admin/external-dashboard-management', title: 'External Dashboard Management' },
{ prefix: '/admin/verification-status', title: 'Verification Status' }, { prefix: '/admin/role-ui-configs', title: 'External Dashboard Management' },
{ prefix: '/admin/verification', title: 'Verification Review' },
{ prefix: '/admin/approval', title: 'Approval Management' }, { prefix: '/admin/approval', title: 'Approval Management' },
{ prefix: '/admin/users', title: 'Users Management' }, { prefix: '/admin/users', title: 'Users Management' },
{ prefix: '/admin/company', title: 'Company Management' }, { prefix: '/admin/company', title: 'Company Management' },
{ prefix: '/admin/customer', title: 'Customer Management' },
{ prefix: '/admin/candidate', title: 'Candidate Management' }, { prefix: '/admin/candidate', title: 'Candidate Management' },
{ prefix: '/admin/customer', title: 'Customer Management' },
{ prefix: '/admin/photographer', title: 'Photographer Management' }, { prefix: '/admin/photographer', title: 'Photographer Management' },
{ prefix: '/admin/makeup-artist', title: 'Makeup Artist Management' }, { prefix: '/admin/makeup-artist', title: 'Makeup Artist Management' },
{ prefix: '/admin/tutors', title: 'Tutors Management' }, { prefix: '/admin/tutors', title: 'Tutors Management' },
{ prefix: '/admin/developers', title: 'Developers Management' }, { prefix: '/admin/developers', title: 'Developers Management' },
{ prefix: '/admin/video-editors', title: 'Video Editor Management' },
{ prefix: '/admin/fitness-trainers', title: 'Fitness Trainer Management' },
{ prefix: '/admin/catering-services', title: 'Catering Services Management' },
{ prefix: '/admin/graphic-designers', title: 'Graphics Designer Management' }, { prefix: '/admin/graphic-designers', title: 'Graphics Designer Management' },
{ prefix: '/admin/social-media-managers', title: 'Social Media Manager Management' },
{ prefix: '/admin/jobs', title: 'Jobs Management' }, { prefix: '/admin/jobs', title: 'Jobs Management' },
{ prefix: '/admin/leads', title: 'Leads Management' }, { prefix: '/admin/leads', title: 'Leads Management' },
{ prefix: '/admin/requirements', title: 'Requirement Request' },
{ prefix: '/admin/pricing', title: 'Pricing Management' }, { prefix: '/admin/pricing', title: 'Pricing Management' },
{ prefix: '/admin/invoice', title: 'Invoice Management' },
{ prefix: '/admin/credit', title: 'Credit Management' }, { prefix: '/admin/credit', title: 'Credit Management' },
{ prefix: '/admin/ledger', title: 'Ledger Management' }, { prefix: '/admin/coupon', title: 'Coupon Management' },
{ prefix: '/admin/discount', title: 'Discount Management' },
{ prefix: '/admin/tax', title: 'Tax Management' },
{ prefix: '/admin/order', title: 'Order Management' },
{ prefix: '/admin/invoice', title: 'Invoice Management' },
{ prefix: '/admin/review', title: 'Review Management' },
{ prefix: '/admin/support', title: 'Support Management' },
{ prefix: '/admin/kb', title: 'Knowledge Base Management' },
{ prefix: '/admin/notifications', title: 'Notifications' },
{ prefix: '/admin/report', title: 'Report Management' }, { prefix: '/admin/report', title: 'Report Management' },
{ prefix: '/admin/employees', title: 'Employee Management' }, { prefix: '/admin/ledger', title: 'Ledger Management' },
{ prefix: '/admin/roles', title: 'Internal Role Management' }, { prefix: '/admin/workspace', title: 'Dashboard Workspace' },
{ prefix: '/admin/external-role-management', title: 'External Role Management' },
{ prefix: '/admin/internal-role-management', title: 'Internal Role Management' },
{ prefix: '/admin/runtime-roles', title: 'External Role Management' },
{ prefix: '/admin/onboarding-management', title: 'External Onboarding Management' },
{ prefix: '/admin/onboarding-schemas', title: 'External Onboarding Management' },
{ prefix: '/admin/kb', title: 'KB Management' },
{ prefix: '/admin', title: 'Dashboard' }, { prefix: '/admin', title: 'Dashboard' },
]; ];
@ -96,6 +98,8 @@ export default function AdminShell(props: { children: JSX.Element }) {
const navigate = useNavigate(); const navigate = useNavigate();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const [checkedSession, setCheckedSession] = createSignal(false); const [checkedSession, setCheckedSession] = createSignal(false);
const [adminName, setAdminName] = createSignal('Admin');
const [adminRole, setAdminRole] = createSignal('Super Admin');
const tabs = createMemo<Tab[]>(() => { const tabs = createMemo<Tab[]>(() => {
const path = location.pathname; const path = location.pathname;
@ -121,9 +125,6 @@ export default function AdminShell(props: { children: JSX.Element }) {
}); });
onMount(() => { onMount(() => {
// ?_preview=1 or sessionStorage flag — bypass auth for UI testing without a live backend.
// Sets the session cookie AND a sessionStorage flag so all subsequent pages in this tab
// also skip the API check without needing ?_preview=1 in every URL.
const isLocalDev = const isLocalDev =
typeof window !== 'undefined' && typeof window !== 'undefined' &&
(window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'); (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');
@ -144,7 +145,6 @@ export default function AdminShell(props: { children: JSX.Element }) {
navigate(`/login?from=${from}`, { replace: true }); navigate(`/login?from=${from}`, { replace: true });
return; return;
} }
try { try {
const accessToken = const accessToken =
typeof sessionStorage !== 'undefined' typeof sessionStorage !== 'undefined'
@ -160,9 +160,9 @@ export default function AdminShell(props: { children: JSX.Element }) {
credentials: 'include', credentials: 'include',
}); });
const payload = await response.json().catch(() => ({})); const payload = await response.json().catch(() => ({}));
if (!response.ok || isExternalIdentity(payload)) { if (!response.ok || isExternalIdentity(payload)) throw new Error('Unauthorized');
throw new Error('Unauthorized'); if (payload?.full_name) setAdminName(payload.full_name);
} if (payload?.role?.name) setAdminRole(payload.role.name);
setCheckedSession(true); setCheckedSession(true);
} catch { } catch {
clearAdminSession(); clearAdminSession();
@ -177,77 +177,109 @@ export default function AdminShell(props: { children: JSX.Element }) {
const onLogout = async () => { const onLogout = async () => {
await fetch('/api/gateway/users/auth/logout', { await fetch('/api/gateway/users/auth/logout', {
method: 'POST', method: 'POST',
headers: { headers: { Accept: 'application/json', 'x-portal-target': 'admin' },
Accept: 'application/json',
'x-portal-target': 'admin',
},
credentials: 'include', credentials: 'include',
}).catch(() => {}); }).catch(() => {});
clearAdminSession(); clearAdminSession();
if (typeof sessionStorage !== 'undefined') { if (typeof sessionStorage !== 'undefined') {
sessionStorage.removeItem('nxtgauge_admin_access_token'); sessionStorage.removeItem('nxtgauge_admin_access_token');
sessionStorage.removeItem('nxtgauge_admin_preview');
} }
navigate('/login', { replace: true }); navigate('/login', { replace: true });
}; };
const initials = () => adminName().charAt(0).toUpperCase() || 'A';
return ( return (
<div class="admin-root"> <div class="min-h-screen bg-gray-50">
<header class="admin-header"> {/* ── Fixed Header ── */}
<div class="admin-header-left"> <header class="fixed top-0 z-50 flex h-16 w-full items-center justify-between border-b border-gray-200 bg-white px-6 shadow-sm">
<div class="admin-brand"> {/* Left: logo + page title */}
<img src="/nxtgauge-logo.png" alt="NXTGAUGE" /> <div class="flex items-center gap-8">
<div class="flex h-10 items-center">
<img src="/nxtgauge-logo.png" alt="NXTGAUGE" class="h-8 w-auto object-contain" />
</div> </div>
<h1 class="admin-page-heading">{pageTitle()}</h1> <h1 class="ml-28 text-base font-semibold text-gray-800">{pageTitle()}</h1>
</div> </div>
<div class="admin-header-actions">
<button class="admin-notification-btn" type="button" aria-label="Notifications"> {/* Right: notifications + avatar + logout */}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> <div class="flex items-center gap-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" /> {/* Notification bell */}
<path strokeLinecap="round" strokeLinejoin="round" d="M13.73 21a2 2 0 0 1-3.46 0" /> <button
</svg> type="button"
</button> aria-label="Notifications"
<button class="admin-avatar-btn" type="button" aria-label="Admin profile"> class="relative rounded-full p-2 text-gray-500 transition-colors hover:bg-gray-100"
<span class="admin-avatar">A</span> >
<span class="admin-avatar-meta"> <svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<span class="admin-avatar-name">Admin</span> <path stroke-linecap="round" stroke-linejoin="round" d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
<span class="admin-avatar-role">Super Admin</span> <path stroke-linecap="round" stroke-linejoin="round" d="M13.73 21a2 2 0 0 1-3.46 0" />
</span>
</button>
<button class="admin-logout-btn" type="button" onClick={onLogout} aria-label="Logout">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path strokeLinecap="round" strokeLinejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg> </svg>
</button> </button>
{/* Avatar + name */}
<div class="flex items-center gap-2">
<button
type="button"
class="flex items-center gap-3 rounded-lg p-1 pr-2 transition-colors hover:bg-gray-100"
>
<div class="flex h-8 w-8 items-center justify-center overflow-hidden rounded-full border border-orange-200 bg-orange-100 text-sm font-semibold text-orange-700">
{initials()}
</div>
<div class="flex flex-col items-start">
<span class="mb-0.5 text-xs font-semibold leading-none text-gray-700">{adminName()}</span>
<span class="text-[10px] leading-none text-gray-500">{adminRole()}</span>
</div>
</button>
{/* Logout */}
<button
type="button"
onClick={onLogout}
aria-label="Logout"
class="rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600"
>
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
</button>
</div>
</div> </div>
</header> </header>
{/* ── Body: sidebar + main (fixed, below header) ── */}
{checkedSession() ? ( {checkedSession() ? (
<div class={`shell${sidebarCollapsed() ? ' sidebar-collapsed' : ''}`}> <div class="fixed inset-0 top-16 grid grid-cols-[auto_1fr]">
<aside class="sidebar-wrap"> {/* Sidebar */}
<AdminSidebar /> <AdminSidebar />
</aside>
<main class="main"> {/* Main content */}
{tabs().length > 0 ? ( <main class="scrollbar min-w-0 overflow-y-auto bg-gray-50 p-6">
<div class="admin-tab-wrap"> {/* Sub-tabs (shown for multi-tab sections) */}
<nav class="admin-tabs"> {tabs().length > 0 && (
{tabs().map((tab) => ( <div class="mb-6 flex gap-6 border-b border-gray-200">
<A href={tab.href} class={`admin-tab ${isTabActive(tab) ? 'active' : ''}`}> {tabs().map((tab) => (
{tab.label} <A
</A> href={tab.href}
))} class={`pb-3 text-sm font-medium transition-colors ${
</nav> isTabActive(tab)
? 'border-b-2 border-[#fd6216] text-gray-900'
: 'text-gray-500 hover:text-gray-700'
}`}
>
{tab.label}
</A>
))}
</div> </div>
) : null} )}
<div class="main-inner">{props.children}</div> {props.children}
</main> </main>
</div> </div>
) : ( ) : (
<div class="shell"> /* Session check loading state */
<main class="main"> <div class="fixed inset-0 top-16 grid grid-cols-[auto_1fr]">
<div class="card"> <div class="w-64 border-r border-slate-200 bg-[#fcfcfd]" />
<p class="notice">Checking session...</p> <main class="flex items-center justify-center bg-gray-50">
</div> <p class="text-sm text-gray-400">Checking session</p>
</main> </main>
</div> </div>
)} )}

View file

@ -1,94 +1,140 @@
import { A, useLocation } from '@solidjs/router'; import { A, useLocation } from '@solidjs/router';
import { sidebarCollapsed, toggleSidebar } from '~/lib/sidebar-state'; import { createSignal } from 'solid-js';
type LinkItem = { legacyHref: string; href: string; label: string; icon: string; aliasPrefix?: string }; type LinkItem = {
href: string;
label: string;
icon: string;
aliasPrefix?: string;
};
const links: LinkItem[] = [ const links: LinkItem[] = [
{ legacyHref: '/', href: '/admin', label: 'Dashboard', icon: 'dashboard.svg' }, { href: '/admin', label: 'Dashboard', icon: 'dashboard.svg' },
{ legacyHref: '/roles?scope=internal', href: '/admin/roles', label: 'Internal Role Management', icon: 'role.svg' }, { href: '/admin/department', label: 'Department Management', icon: 'department.svg' },
{ legacyHref: '/runtime-roles', href: '/admin/runtime-roles', label: 'External Role Management', icon: 'role.svg' }, { href: '/admin/designation', label: 'Designation Management', icon: 'designation.svg' },
{ legacyHref: '/onboarding-management', href: '/admin/onboarding-schemas', label: 'External Onboarding Management', icon: 'reviews.svg' }, { href: '/admin/employees', label: 'Employee Management', icon: 'users.svg' },
{ legacyHref: '/internal-dashboard-management', href: '/admin/internal-dashboard-management', label: 'Internal Dashboard Management', icon: 'dashboard.svg' }, { href: '/admin/roles', label: 'Internal Role Management', icon: 'role.svg' },
{ legacyHref: '/external-dashboard-management', href: '/admin/external-dashboard-management', label: 'External Dashboard Management', icon: 'dashboard.svg', aliasPrefix: '/admin/role-ui-configs' }, { href: '/admin/runtime-roles', label: 'External Role Management', icon: 'role.svg' },
{ legacyHref: '/approval', href: '/admin/approval', label: 'Approval Management', icon: 'approval.svg' }, { href: '/admin/onboarding-schemas', label: 'External Onboarding Management', icon: 'reviews.svg' },
{ legacyHref: '/department', href: '/admin/department', label: 'Department Management', icon: 'department.svg' }, { href: '/admin/internal-dashboard-management', label: 'Internal Dashboard Management', icon: 'dashboard.svg' },
{ legacyHref: '/designation', href: '/admin/designation', label: 'Designation Management', icon: 'designation.svg' }, { href: '/admin/external-dashboard-management', label: 'External Dashboard Management', icon: 'dashboard.svg', aliasPrefix: '/admin/role-ui-configs' },
{ legacyHref: '/employees', href: '/admin/employees', label: 'Employee Management', icon: 'users.svg' }, { href: '/admin/approval', label: 'Approval Management', icon: 'approval.svg' },
{ legacyHref: '/users', href: '/admin/users', label: 'Users Management', icon: 'users.svg' }, { href: '/admin/users', label: 'Users Management', icon: 'users.svg' },
{ legacyHref: '/company', href: '/admin/company', label: 'Company Management', icon: 'company.svg' }, { href: '/admin/company', label: 'Company Management', icon: 'company.svg' },
{ legacyHref: '/candidate', href: '/admin/candidate', label: 'Candidate Management', icon: 'candidate.svg' }, { href: '/admin/candidate', label: 'Candidate Management', icon: 'candidate.svg' },
{ legacyHref: '/customer', href: '/admin/customer', label: 'Customer Management', icon: 'users.svg' }, { href: '/admin/customer', label: 'Customer Management', icon: 'users.svg' },
{ legacyHref: '/photographer', href: '/admin/photographer', label: 'Photographer Management', icon: 'photographer.svg' }, { href: '/admin/photographer', label: 'Photographer Management', icon: 'photographer.svg' },
{ legacyHref: '/makeup-artist', href: '/admin/makeup-artist', label: 'Makeup Artist Management', icon: 'makeup-artist.svg' }, { href: '/admin/makeup-artist', label: 'Makeup Artist Management', icon: 'makeup-artist.svg' },
{ legacyHref: '/tutors', href: '/admin/tutors', label: 'Tutors Management', icon: 'tutor.svg' }, { href: '/admin/tutors', label: 'Tutors Management', icon: 'tutor.svg' },
{ legacyHref: '/developers', href: '/admin/developers', label: 'Developers Management', icon: 'developers.svg' }, { href: '/admin/developers', label: 'Developers Management', icon: 'developers.svg' },
{ legacyHref: '/video-editors', href: '/admin/video-editors', label: 'Video Editor Management', icon: 'developers.svg' }, { href: '/admin/video-editors', label: 'Video Editor Management', icon: 'developers.svg' },
{ legacyHref: '/fitness-trainers', href: '/admin/fitness-trainers', label: 'Fitness Trainer Management', icon: 'tutor.svg' }, { href: '/admin/fitness-trainers', label: 'Fitness Trainer Management', icon: 'tutor.svg' },
{ legacyHref: '/catering-services', href: '/admin/catering-services', label: 'Catering Services Management', icon: 'company.svg' }, { href: '/admin/catering-services', label: 'Catering Services Management', icon: 'company.svg' },
{ legacyHref: '/graphic-designers', href: '/admin/graphic-designers', label: 'Graphics Designer Management', icon: 'developers.svg' }, { href: '/admin/graphic-designers', label: 'Graphics Designer Management', icon: 'developers.svg' },
{ legacyHref: '/social-media-managers', href: '/admin/social-media-managers', label: 'Social Media Manager Management', icon: 'developers.svg' }, { href: '/admin/social-media-managers', label: 'Social Media Manager Management', icon: 'developers.svg' },
{ legacyHref: '/jobs', href: '/admin/jobs', label: 'Jobs Management', icon: 'jobs.svg' }, { href: '/admin/jobs', label: 'Jobs Management', icon: 'jobs.svg' },
{ legacyHref: '/leads', href: '/admin/leads', label: 'Leads Management', icon: 'leads.svg' }, { href: '/admin/leads', label: 'Leads Management', icon: 'leads.svg' },
{ legacyHref: '/pricing', href: '/admin/pricing', label: 'Pricing Management', icon: 'pricing.svg' }, { href: '/admin/pricing', label: 'Pricing Management', icon: 'pricing.svg' },
{ legacyHref: '/credit', href: '/admin/credit', label: 'Credit Management', icon: 'credits.svg' }, { href: '/admin/credit', label: 'Credit Management', icon: 'credits.svg' },
{ legacyHref: '/coupon', href: '/admin/coupon', label: 'Coupon Management', icon: 'coupon.svg' }, { href: '/admin/coupon', label: 'Coupon Management', icon: 'coupon.svg' },
{ legacyHref: '/discount', href: '/admin/discount', label: 'Discount Management', icon: 'discount.svg' }, { href: '/admin/discount', label: 'Discount Management', icon: 'discount.svg' },
{ legacyHref: '/tax', href: '/admin/tax', label: 'Tax Management', icon: 'tax.svg' }, { href: '/admin/tax', label: 'Tax Management', icon: 'tax.svg' },
{ legacyHref: '/order', href: '/admin/order', label: 'Order Management', icon: 'order.svg' }, { href: '/admin/order', label: 'Order Management', icon: 'order.svg' },
{ legacyHref: '/invoice', href: '/admin/invoice', label: 'Invoice Management', icon: 'invoice.svg' }, { href: '/admin/invoice', label: 'Invoice Management', icon: 'invoice.svg' },
{ legacyHref: '/review', href: '/admin/review', label: 'Review Management', icon: 'reviews.svg' }, { href: '/admin/review', label: 'Review Management', icon: 'reviews.svg' },
{ legacyHref: '/help', href: '/admin/support', label: 'Support Management', icon: 'support.svg' }, { href: '/admin/support', label: 'Support Management', icon: 'support.svg' },
{ legacyHref: '/report', href: '/admin/report', label: 'Report Management', icon: 'report.svg' }, { href: '/admin/kb', label: 'Knowledge Base Management', icon: 'reviews.svg' },
{ legacyHref: '/ledger', href: '/admin/ledger', label: 'Ledger Management', icon: 'ledger.svg' }, { href: '/admin/notifications', label: 'Notifications', icon: 'reviews.svg' },
{ legacyHref: '/kb', href: '/admin/kb', label: 'KB Management', icon: 'reviews.svg' }, { href: '/admin/report', label: 'Report Management', icon: 'report.svg' },
{ legacyHref: '/notifications', href: '/admin/notifications', label: 'Notifications', icon: 'reviews.svg' }, { href: '/admin/ledger', label: 'Ledger Management', icon: 'ledger.svg' },
]; ];
export default function AdminSidebar() { export default function AdminSidebar() {
const location = useLocation(); const location = useLocation();
const collapsed = sidebarCollapsed; const [collapsed, setCollapsed] = createSignal(false);
const isLinkActive = (href: string, aliasPrefix?: string) => { const isActive = (href: string, aliasPrefix?: string) => {
const pathOnly = href.split('?')[0] || href; if (href === '/admin') return location.pathname === '/admin';
if (pathOnly === '/admin') return location.pathname === '/admin';
if (aliasPrefix && location.pathname.startsWith(aliasPrefix)) return true; if (aliasPrefix && location.pathname.startsWith(aliasPrefix)) return true;
return location.pathname === pathOnly || location.pathname.startsWith(`${pathOnly}/`); return location.pathname === href || location.pathname.startsWith(`${href}/`);
}; };
return ( return (
<aside class={`sidebar${collapsed() ? ' sidebar-collapsed' : ''}`}> <aside
<div class="sidebar-toggle-row"> class={`flex h-full flex-col border-r border-slate-200 bg-[#fcfcfd] px-3 pb-3 pt-5 transition-all duration-300 ${
collapsed() ? 'w-20' : 'w-64'
}`}
>
{/* Collapse toggle */}
<div class="mb-4 flex justify-end px-2">
<button <button
type="button" type="button"
class="sidebar-toggle-btn" onClick={() => setCollapsed((v) => !v)}
aria-label={collapsed() ? 'Expand sidebar' : 'Collapse sidebar'} aria-label={collapsed() ? 'Expand sidebar' : 'Collapse sidebar'}
onClick={toggleSidebar} class="rounded-lg p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-700"
> >
<span class={`sidebar-chevron${collapsed() ? ' collapsed' : ''}`}></span> <svg
class={`h-4 w-4 transition-transform ${collapsed() ? 'rotate-180' : ''}`}
fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
</svg>
</button> </button>
</div> </div>
<nav class="sidebar-nav scrollbar">
{/* Nav items */}
<nav class="scrollbar min-h-0 flex-1 space-y-1.5 overflow-y-auto pr-1">
{links.map((item) => { {links.map((item) => {
const active = isLinkActive(item.href, item.aliasPrefix); const active = isActive(item.href, item.aliasPrefix);
return ( return (
<A <A
href={item.href} href={item.href}
class={`nav-item ${active ? 'active' : ''}`}
activeClass="" activeClass=""
inactiveClass="" inactiveClass=""
data-legacy-href={item.legacyHref}
title={collapsed() ? item.label : undefined} title={collapsed() ? item.label : undefined}
class={`group relative flex items-center gap-3 rounded-xl border px-3 py-3 text-[15px] leading-5 transition-all ${
collapsed() ? 'justify-center px-2' : ''
} ${
active
? 'border-orange-200 bg-gradient-to-r from-orange-50 to-orange-100/70 text-slate-900 shadow-[inset_3px_0_0_0_#FD6216]'
: 'border-transparent text-slate-500 hover:border-slate-200 hover:bg-white hover:text-slate-800'
}`}
> >
<span class="nav-active-rail" /> {/* Active left rail */}
<img class="nav-icon" src={`/sidebar-icons/${item.icon}`} alt="" /> <span
class={`absolute bottom-2 left-0 top-2 w-[3px] rounded-r-full bg-orange-500 transition-opacity ${
active ? 'opacity-100' : 'opacity-0'
}`}
/>
{/* Icon */}
<img
src={`/sidebar-icons/${item.icon}`}
alt=""
class="h-[18px] w-[18px] shrink-0 transition-all"
style={active
? 'filter: invert(42%) sepia(78%) saturate(1200%) hue-rotate(360deg) brightness(95%) contrast(95%); opacity:1'
: 'opacity:0.45'
}
/>
{/* Label */}
{!collapsed() && ( {!collapsed() && (
<span class="nav-title">{item.label}</span> <span class={`truncate font-medium ${active ? 'text-slate-900' : 'text-slate-600 group-hover:text-slate-800'}`}>
{item.label}
</span>
)} )}
{/* Active badge (expanded) */}
{!collapsed() && active && ( {!collapsed() && active && (
<span class="active-badge">Active</span> <span class="ml-auto rounded-full border border-orange-200 bg-orange-100 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-orange-700">
Active
</span>
)} )}
{/* Active dot (collapsed) */}
{collapsed() && active && ( {collapsed() && active && (
<span class="collapsed-dot" /> <span class="absolute -right-0.5 top-1/2 h-2 w-2 -translate-y-1/2 rounded-full bg-orange-500" />
)} )}
</A> </A>
); );

View file

@ -47,13 +47,13 @@ export default function LegacyModuleShellPage() {
return ( return (
<AdminShell> <AdminShell>
<h1 class="page-title">{moduleName()}</h1> <h1 class="text-2xl font-bold text-gray-900">{moduleName()}</h1>
<p class="page-subtitle"> <p class="mt-1 text-sm text-gray-500">
Live legacy module embedded for exact design and functionality parity during migration. Live legacy module embedded for exact design and functionality parity during migration.
</p> </p>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<div class="actions"> <div class="actions">
<A class="btn" href={legacyUrl()} target="_blank">Open Module In New Tab</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={legacyUrl()} target="_blank">Open Module In New Tab</A>
</div> </div>
<iframe <iframe
src={legacyUrl()} src={legacyUrl()}

View file

@ -84,20 +84,20 @@ export default function ApplicationsPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card"> <div class="page-hero-card">
<h1 class="page-title">Applications</h1> <h1 class="text-2xl font-bold text-gray-900">Applications</h1>
<p class="page-subtitle">Review submitted applications and update acceptance status.</p> <p class="mt-1 text-sm text-gray-500">Review submitted applications and update acceptance status.</p>
</div> </div>
<Show when={payload.loading}> <Show when={payload.loading}>
<div class="card"><p class="notice">Loading applications...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading applications...</p></div>
</Show> </Show>
<Show when={error()}> <Show when={error()}>
<div class="card"><p class="error-note">{error()}</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="error-note">{error()}</p></div>
</Show> </Show>
<Show when={!payload.loading && applications().length === 0}> <Show when={!payload.loading && applications().length === 0}>
<div class="card"><p class="notice">No applications found.</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">No applications found.</p></div>
</Show> </Show>
<div class="list-grid" style="grid-template-columns:1fr;gap:14px"> <div class="list-grid" style="grid-template-columns:1fr;gap:14px">
@ -105,7 +105,7 @@ export default function ApplicationsPage() {
{(app) => { {(app) => {
const req = createMemo(() => requirementFor(app.requirementId)); const req = createMemo(() => requirementFor(app.requirementId));
return ( return (
<article class="card"> <article class="rounded-xl border border-gray-200 bg-white shadow-sm">
<div style="display:flex;justify-content:space-between;gap:12px;align-items:flex-start;flex-wrap:wrap"> <div style="display:flex;justify-content:space-between;gap:12px;align-items:flex-start;flex-wrap:wrap">
<div> <div>
<h2 style="margin:0;font-size:19px">{req()?.title || 'Unknown Requirement'}</h2> <h2 style="margin:0;font-size:19px">{req()?.title || 'Unknown Requirement'}</h2>
@ -119,7 +119,7 @@ export default function ApplicationsPage() {
<p class="kv-value" style="font-weight:500">{app.message || 'No message provided.'}</p> <p class="kv-value" style="font-weight:500">{app.message || 'No message provided.'}</p>
</div> </div>
<div class="field-grid-2" style="margin-top:12px"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2" style="margin-top:12px">
<div class="kv-item"> <div class="kv-item">
<p class="kv-label">Quote</p> <p class="kv-label">Quote</p>
<p class="kv-value"> {app.quote || 0}</p> <p class="kv-value"> {app.quote || 0}</p>
@ -140,15 +140,15 @@ export default function ApplicationsPage() {
<div class="actions"> <div class="actions">
<Show when={app.status === 'SUBMITTED'}> <Show when={app.status === 'SUBMITTED'}>
<button class="btn primary" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'ACCEPTED')}>Accept Bid</button> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'ACCEPTED')}>Accept Bid</button>
<button class="btn danger" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'REJECTED')}>Decline</button> <button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'REJECTED')}>Decline</button>
</Show> </Show>
<Show when={app.status === 'SHORTLISTED'}> <Show when={app.status === 'SHORTLISTED'}>
<button class="btn primary" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'ACCEPTED')}>Accept Bid</button> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'ACCEPTED')}>Accept Bid</button>
<button class="btn danger" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'REJECTED')}>Decline</button> <button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'REJECTED')}>Decline</button>
</Show> </Show>
<Show when={app.status === 'SUBMITTED' || app.status === 'SHORTLISTED'}> <Show when={app.status === 'SUBMITTED' || app.status === 'SHORTLISTED'}>
<button class="btn" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'WITHDRAWN')}>Withdraw</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'WITHDRAWN')}>Withdraw</button>
</Show> </Show>
</div> </div>
</article> </article>

View file

@ -7,7 +7,7 @@ export default function ApprovalManagementAliasPage() {
onMount(() => navigate('/admin/approval', { replace: true })); onMount(() => navigate('/admin/approval', { replace: true }));
return ( return (
<AdminShell> <AdminShell>
<div class="card"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
<p class="notice">Redirecting to Approval Management...</p> <p class="notice">Redirecting to Approval Management...</p>
</div> </div>
</AdminShell> </AdminShell>

View file

@ -254,11 +254,11 @@ function RoleTypeBadge(props: { type?: RoleType }) {
function StatusBadge(props: { status: string; isDocRequest?: boolean }) { function StatusBadge(props: { status: string; isDocRequest?: boolean }) {
const s = props.status.toUpperCase(); const s = props.status.toUpperCase();
const label = props.isDocRequest ? 'MORE DOCS REQUIRED' : s.replace(/_/g, ' '); const label = props.isDocRequest ? 'MORE DOCS REQUIRED' : s.replace(/_/g, ' ');
if (s === 'APPROVED') return <span class="status-chip active">{label}</span>; if (s === 'APPROVED') return <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">{label}</span>;
if (s === 'REJECTED') return <span class="status-chip" style="background:#ef4444;color:#fff;border-color:#ef4444">{label}</span>; if (s === 'REJECTED') return <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#ef4444;color:#fff;border-color:#ef4444">{label}</span>;
if (s === 'CHANGES_REQUESTED') return <span class="status-chip" style={`background:${props.isDocRequest ? '#3b82f6' : '#f97316'};color:#fff;border-color:currentColor`}>{label}</span>; if (s === 'CHANGES_REQUESTED') return <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style={`background:${props.isDocRequest ? '#3b82f6' : '#f97316'};color:#fff;border-color:currentColor`}>{label}</span>;
if (s === 'CANCELLED') return <span class="status-chip" style="background:#94a3b8;color:#fff;border-color:#94a3b8">{label}</span>; if (s === 'CANCELLED') return <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#94a3b8;color:#fff;border-color:#94a3b8">{label}</span>;
return <span class="status-chip" style="background:#f59e0b;color:#fff;border-color:#f59e0b">{label}</span>; return <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#f59e0b;color:#fff;border-color:#f59e0b">{label}</span>;
} }
// ────────────────────────────── API calls ────────────────────────────── // ────────────────────────────── API calls ──────────────────────────────
@ -676,10 +676,10 @@ export default function ApprovalPage() {
return ( return (
<AdminShell> <AdminShell>
{/* Page header */} {/* Page header */}
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Approval Management</h1> <h1 class="text-2xl font-bold text-gray-900">Approval Management</h1>
<p class="page-subtitle">Review, approve, reject and configure approval workflows.</p> <p class="mt-1 text-sm text-gray-500">Review, approve, reject and configure approval workflows.</p>
</div> </div>
</div> </div>
@ -713,22 +713,22 @@ export default function ApprovalPage() {
</div> </div>
<Show when={actionError()}> <Show when={actionError()}>
<div class="error-box" style="margin-bottom:12px">{actionError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{actionError()}</div>
</Show> </Show>
<Show when={approvals.error}> <Show when={approvals.error}>
<div class="error-box" style="margin-bottom:12px">{String((approvals.error as any)?.message || approvals.error)}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{String((approvals.error as any)?.message || approvals.error)}</div>
</Show> </Show>
<Show when={!approvals.loading && !snapshot.loading}> <Show when={!approvals.loading && !snapshot.loading}>
<div class="card" style="margin-bottom:12px;background:#f8fafc;border-color:#e2e8f0"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:12px;background:#f8fafc;border-color:#e2e8f0">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap"> <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap">
<div style="display:flex;gap:8px;flex-wrap:wrap"> <div style="display:flex;gap:8px;flex-wrap:wrap">
<span class="status-chip" style="background:#e0f2fe;color:#075985;border-color:#bae6fd">Pending Jobs: {summary().jobs}</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#e0f2fe;color:#075985;border-color:#bae6fd">Pending Jobs: {summary().jobs}</span>
<span class="status-chip" style="background:#ecfeff;color:#155e75;border-color:#a5f3fc">Pending Requirements: {summary().requirements}</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#ecfeff;color:#155e75;border-color:#a5f3fc">Pending Requirements: {summary().requirements}</span>
<span class="status-chip" style="background:#eef2ff;color:#3730a3;border-color:#c7d2fe">Pending Profiles: {summary().profilePending}</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#eef2ff;color:#3730a3;border-color:#c7d2fe">Pending Profiles: {summary().profilePending}</span>
<span class="status-chip" style="background:#fef3c7;color:#92400e;border-color:#fde68a">Total Pending: {summary().totalPending}</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fef3c7;color:#92400e;border-color:#fde68a">Total Pending: {summary().totalPending}</span>
</div> </div>
<button class="btn" type="button" onClick={() => { refetchApprovals(); refetchSnapshot(); }}>Refresh</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" onClick={() => { refetchApprovals(); refetchSnapshot(); }}>Refresh</button>
</div> </div>
<Show when={summary().totalPending === 0}> <Show when={summary().totalPending === 0}>
<p style="margin:10px 0 0;color:#64748b;font-size:13px"> <p style="margin:10px 0 0;color:#64748b;font-size:13px">
@ -747,7 +747,7 @@ export default function ApprovalPage() {
<Show when={activeTab() !== 'rules'}> <Show when={activeTab() !== 'rules'}>
<Show when={!showDetail()}> <Show when={!showDetail()}>
{/* Filter bar */} {/* Filter bar */}
<div class="card" style="margin-bottom:12px;display:flex;gap:10px;flex-wrap:wrap;align-items:center"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:12px;display:flex;gap:10px;flex-wrap:wrap;align-items:center">
<input <input
type="text" type="text"
placeholder="Search requester, email, type or ID..." placeholder="Search requester, email, type or ID..."
@ -773,9 +773,9 @@ export default function ApprovalPage() {
<span style="font-size:12px;color:#64748b;margin-left:auto">{filteredApprovals().length} record{filteredApprovals().length !== 1 ? 's' : ''}</span> <span style="font-size:12px;color:#64748b;margin-left:auto">{filteredApprovals().length} record{filteredApprovals().length !== 1 ? 's' : ''}</span>
</div> </div>
<section class="card" style="padding:0;overflow:hidden"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Requester</th> <th>Requester</th>
@ -784,7 +784,7 @@ export default function ApprovalPage() {
<th>Document Request</th> <th>Document Request</th>
<th>Status</th> <th>Status</th>
<th>Submitted</th> <th>Submitted</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -837,10 +837,10 @@ export default function ApprovalPage() {
{(item.createdAt || item.created_at) ? new Date((item.createdAt || item.created_at)!).toLocaleDateString() : '—'} {(item.createdAt || item.created_at) ? new Date((item.createdAt || item.created_at)!).toLocaleDateString() : '—'}
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
{/* View detail */} {/* View detail */}
<button <button
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
type="button" type="button"
title="View Request" title="View Request"
style="font-size:12px;padding:4px 10px" style="font-size:12px;padding:4px 10px"
@ -849,12 +849,12 @@ export default function ApprovalPage() {
View View
</button> </button>
<Show when={item._viewHref}> <Show when={item._viewHref}>
<A class="btn" href={item._viewHref!} style="font-size:12px;padding:4px 10px" title="Open full request page"> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={item._viewHref!} style="font-size:12px;padding:4px 10px" title="Open full request page">
Full Request Full Request
</A> </A>
</Show> </Show>
<Show when={item._supportsSubmissionView}> <Show when={item._supportsSubmissionView}>
<A class="btn" href={`/admin/approval/${item.id}`} style="font-size:12px;padding:4px 10px" title="Open full profile review"> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/approval/${item.id}`} style="font-size:12px;padding:4px 10px" title="Open full profile review">
Profile Review Profile Review
</A> </A>
</Show> </Show>
@ -862,7 +862,7 @@ export default function ApprovalPage() {
<Show when={status === 'PENDING'}> <Show when={status === 'PENDING'}>
{/* Request More Documents */} {/* Request More Documents */}
<button <button
class="action-icon-btn" class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm"
type="button" type="button"
title="Request More Documents" title="Request More Documents"
disabled={isActing} disabled={isActing}
@ -873,7 +873,7 @@ export default function ApprovalPage() {
</button> </button>
{/* Request Changes */} {/* Request Changes */}
<button <button
class="action-icon-btn" class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm"
type="button" type="button"
title="Request Changes" title="Request Changes"
disabled={isActing} disabled={isActing}
@ -884,7 +884,7 @@ export default function ApprovalPage() {
</button> </button>
{/* Approve */} {/* Approve */}
<button <button
class="action-icon-btn" class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm"
type="button" type="button"
title="Approve" title="Approve"
disabled={isActing} disabled={isActing}
@ -895,7 +895,7 @@ export default function ApprovalPage() {
</button> </button>
{/* Reject */} {/* Reject */}
<button <button
class="action-icon-btn danger" class="rounded p-1.5 text-red-500 hover:bg-red-50 hover:text-red-700 text-sm"
type="button" type="button"
title="Reject" title="Reject"
disabled={isActing} disabled={isActing}
@ -906,7 +906,7 @@ export default function ApprovalPage() {
</Show> </Show>
<Show when={status === 'CHANGES_REQUESTED'}> <Show when={status === 'CHANGES_REQUESTED'}>
<button <button
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
type="button" type="button"
title="Update requested documents" title="Update requested documents"
style="font-size:12px;padding:4px 10px;background:#eff6ff;color:#1d4ed8;border-color:#bfdbfe" style="font-size:12px;padding:4px 10px;background:#eff6ff;color:#1d4ed8;border-color:#bfdbfe"
@ -921,7 +921,7 @@ export default function ApprovalPage() {
<Show when={status === 'APPROVED'}> <Show when={status === 'APPROVED'}>
<A <A
href={dest.href} href={dest.href}
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
style="font-size:11px;padding:3px 8px;background:#f0fdf4;color:#15803d;border-color:#bbf7d0;white-space:nowrap" style="font-size:11px;padding:3px 8px;background:#f0fdf4;color:#15803d;border-color:#bbf7d0;white-space:nowrap"
title={`Open ${dest.label}`} title={`Open ${dest.label}`}
> >
@ -939,9 +939,9 @@ export default function ApprovalPage() {
</table> </table>
</div> </div>
<div class="admin-pagination"> <div class="admin-pagination">
<button class="btn" type="button" disabled={currentPage() <= 1} onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}> Prev</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" disabled={currentPage() <= 1} onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}> Prev</button>
<span>Page {currentPage()} of {totalPages()}</span> <span>Page {currentPage()} of {totalPages()}</span>
<button class="btn" type="button" disabled={currentPage() >= totalPages()} onClick={() => setCurrentPage((p) => Math.min(totalPages(), p + 1))}>Next </button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" disabled={currentPage() >= totalPages()} onClick={() => setCurrentPage((p) => Math.min(totalPages(), p + 1))}>Next </button>
</div> </div>
</section> </section>
</Show> </Show>
@ -963,20 +963,20 @@ export default function ApprovalPage() {
{/* ── Rules Tab ── */} {/* ── Rules Tab ── */}
<Show when={activeTab() === 'rules'}> <Show when={activeTab() === 'rules'}>
<Show when={ruleError()}> <Show when={ruleError()}>
<div class="error-box" style="margin-bottom:12px">{ruleError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{ruleError()}</div>
</Show> </Show>
<div class="page-actions" style="margin-bottom:16px"> <div class="mb-6 flex items-start justify-between gap-4" style="margin-bottom:16px">
<div /> <div />
<button class="btn navy" onClick={() => setShowAddRule((v) => !v)}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={() => setShowAddRule((v) => !v)}>
{showAddRule() ? 'Cancel' : '+ Add Rule'} {showAddRule() ? 'Cancel' : '+ Add Rule'}
</button> </button>
</div> </div>
<Show when={showAddRule()}> <Show when={showAddRule()}>
<div class="card" style="margin-bottom:16px"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px">
<h3 style="font-size:15px;font-weight:600;color:#0f172a;margin:0 0 14px">New Approval Rule</h3> <h3 style="font-size:15px;font-weight:600;color:#0f172a;margin:0 0 14px">New Approval Rule</h3>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label>Rule Name <span style="color:#ef4444">*</span></label> <label>Rule Name <span style="color:#ef4444">*</span></label>
<input <input
@ -1003,24 +1003,24 @@ export default function ApprovalPage() {
</div> </div>
</div> </div>
<div class="actions" style="margin-top:14px"> <div class="actions" style="margin-top:14px">
<button class="btn navy" disabled={submittingRule()} onClick={handleAddRule}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={submittingRule()} onClick={handleAddRule}>
{submittingRule() ? 'Saving...' : 'Save Rule'} {submittingRule() ? 'Saving...' : 'Save Rule'}
</button> </button>
<button class="btn" onClick={() => setShowAddRule(false)}>Cancel</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => setShowAddRule(false)}>Cancel</button>
</div> </div>
</div> </div>
</Show> </Show>
<section class="card" style="padding:0;overflow:hidden"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Rule Name</th> <th>Rule Name</th>
<th>Entity Type</th> <th>Entity Type</th>
<th>Approver Type</th> <th>Approver Type</th>
<th>Priority</th> <th>Priority</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -1039,9 +1039,9 @@ export default function ApprovalPage() {
<td style="color:#475569">{rule.approverType || rule.approver_type || '—'}</td> <td style="color:#475569">{rule.approverType || rule.approver_type || '—'}</td>
<td style="color:#475569">{rule.priority ?? '—'}</td> <td style="color:#475569">{rule.priority ?? '—'}</td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<button <button
class="btn danger" class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
disabled={deletingRule() === rule.id} disabled={deletingRule() === rule.id}
onClick={() => handleDeleteRule(rule.id)} onClick={() => handleDeleteRule(rule.id)}
> >
@ -1083,19 +1083,19 @@ function ApprovalDetailPanel(props: {
return ( return (
<Show when={a()} fallback={ <Show when={a()} fallback={
<div class="card"><p class="notice">Select an approval from the list.</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Select an approval from the list.</p></div>
}> }>
<div class="page-hero-card page-actions" style="margin-bottom:12px"> <div class="mb-6 flex items-start justify-between gap-4" style="margin-bottom:12px">
<div> <div>
<h2 class="page-title" style="font-size:18px">Approval Detail</h2> <h2 class="text-2xl font-bold text-gray-900" style="font-size:18px">Approval Detail</h2>
<p class="page-subtitle">{a()!._typeLabel || 'Request'}</p> <p class="mt-1 text-sm text-gray-500">{a()!._typeLabel || 'Request'}</p>
</div> </div>
<button class="btn" type="button" onClick={props.onBack}> Back to List</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" onClick={props.onBack}> Back to List</button>
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px">
{/* Request info */} {/* Request info */}
<div class="card"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">Request Summary</h3> <h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">Request Summary</h3>
<table style="width:100%;border-collapse:collapse;font-size:13px"> <table style="width:100%;border-collapse:collapse;font-size:13px">
<tbody> <tbody>
@ -1110,7 +1110,7 @@ function ApprovalDetailPanel(props: {
</div> </div>
{/* Requester info */} {/* Requester info */}
<div class="card"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">Requester</h3> <h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">Requester</h3>
<table style="width:100%;border-collapse:collapse;font-size:13px"> <table style="width:100%;border-collapse:collapse;font-size:13px">
<tbody> <tbody>
@ -1124,21 +1124,21 @@ function ApprovalDetailPanel(props: {
{/* Actions */} {/* Actions */}
<div style="margin-top:16px;display:flex;flex-wrap:wrap;gap:8px"> <div style="margin-top:16px;display:flex;flex-wrap:wrap;gap:8px">
<Show when={isPending()}> <Show when={isPending()}>
<button class="btn" style="background:#eff6ff;color:#1d4ed8;border-color:#bfdbfe;font-size:13px" disabled={isActing()} onClick={() => props.onRequestMoreDocuments(a()!.id)}>📄 Request Documents</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" style="background:#eff6ff;color:#1d4ed8;border-color:#bfdbfe;font-size:13px" disabled={isActing()} onClick={() => props.onRequestMoreDocuments(a()!.id)}>📄 Request Documents</button>
<button class="btn" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;font-size:13px" disabled={isActing()} onClick={() => props.onRequestChanges(a()!.id)}> Request Changes</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;font-size:13px" disabled={isActing()} onClick={() => props.onRequestChanges(a()!.id)}> Request Changes</button>
<button class="btn" style="background:#f0fdf4;color:#15803d;border-color:#bbf7d0;font-size:13px" disabled={isActing()} onClick={() => props.onApprove(a()!.id)}> Approve</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" style="background:#f0fdf4;color:#15803d;border-color:#bbf7d0;font-size:13px" disabled={isActing()} onClick={() => props.onApprove(a()!.id)}> Approve</button>
<button class="btn danger" style="font-size:13px" disabled={isActing()} onClick={() => props.onReject(a()!.id)}> Reject</button> <button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" style="font-size:13px" disabled={isActing()} onClick={() => props.onReject(a()!.id)}> Reject</button>
</Show> </Show>
<Show when={status() === 'APPROVED'}> <Show when={status() === 'APPROVED'}>
<A href={dest().href} class="btn" style="background:#f0fdf4;color:#15803d;border-color:#bbf7d0;font-size:13px"> <A href={dest().href} class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" style="background:#f0fdf4;color:#15803d;border-color:#bbf7d0;font-size:13px">
Open {dest().label} Open {dest().label}
</A> </A>
</Show> </Show>
<Show when={a()!._viewHref}> <Show when={a()!._viewHref}>
<A href={a()!._viewHref!} class="btn" style="font-size:13px">Open Full Request</A> <A href={a()!._viewHref!} class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" style="font-size:13px">Open Full Request</A>
</Show> </Show>
<Show when={a()!._supportsSubmissionView}> <Show when={a()!._supportsSubmissionView}>
<A href={`/admin/approval/${a()!.id}`} class="btn" style="font-size:13px">Open Profile Review</A> <A href={`/admin/approval/${a()!.id}`} class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" style="font-size:13px">Open Profile Review</A>
</Show> </Show>
</div> </div>
</div> </div>
@ -1146,7 +1146,7 @@ function ApprovalDetailPanel(props: {
{/* Submitted fields */} {/* Submitted fields */}
<Show when={a()!._parsedReason?.values && Object.keys(a()!._parsedReason!.values!).length > 0}> <Show when={a()!._parsedReason?.values && Object.keys(a()!._parsedReason!.values!).length > 0}>
<div class="card" style="margin-bottom:16px"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px">
<h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">Submitted Data</h3> <h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">Submitted Data</h3>
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px"> <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px">
{Object.entries(a()!._parsedReason!.values!).map(([k, v]) => ( {Object.entries(a()!._parsedReason!.values!).map(([k, v]) => (
@ -1161,7 +1161,7 @@ function ApprovalDetailPanel(props: {
{/* Document request spotlight (USP) */} {/* Document request spotlight (USP) */}
<Show when={docRemark()}> <Show when={docRemark()}>
<div class="card" style="margin-bottom:16px;border:1px solid #bfdbfe;background:#eff6ff"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px;border:1px solid #bfdbfe;background:#eff6ff">
<h3 style="margin:0 0 10px;font-size:15px;font-weight:700;color:#1d4ed8">Requested Documents</h3> <h3 style="margin:0 0 10px;font-size:15px;font-weight:700;color:#1d4ed8">Requested Documents</h3>
<p style="margin:0;font-size:13px;color:#0f172a">{docRemark()!.comment}</p> <p style="margin:0;font-size:13px;color:#0f172a">{docRemark()!.comment}</p>
<Show when={(docRemark()!.fields || []).length > 0}> <Show when={(docRemark()!.fields || []).length > 0}>
@ -1174,7 +1174,7 @@ function ApprovalDetailPanel(props: {
{/* Admin remarks history */} {/* Admin remarks history */}
<Show when={remarks().length > 0}> <Show when={remarks().length > 0}>
<div class="card"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">Admin Remarks History</h3> <h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">Admin Remarks History</h3>
<div style="display:flex;flex-direction:column;gap:8px"> <div style="display:flex;flex-direction:column;gap:8px">
<For each={remarks()}> <For each={remarks()}>

View file

@ -247,20 +247,20 @@ export default function ApprovalDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Submission Review</h1> <h1 class="text-2xl font-bold text-gray-900">Submission Review</h1>
<p class="page-subtitle">Review a user's onboarding form submission and take action.</p> <p class="mt-1 text-sm text-gray-500">Review a user's onboarding form submission and take action.</p>
</div> </div>
<A class="btn" href="/admin/approval"> Back to Approvals</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/approval"> Back to Approvals</A>
</div> </div>
<Show when={actionError()}> <Show when={actionError()}>
<div class="error-box" style="margin-bottom:12px">{actionError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{actionError()}</div>
</Show> </Show>
<Show when={actionDone()}> <Show when={actionDone()}>
<div class="card" style="margin-bottom:12px;border-left:4px solid #22c55e;padding:12px 16px"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:12px;border-left:4px solid #22c55e;padding:12px 16px">
<p style="margin:0;font-weight:600;color:#166534"> <p style="margin:0;font-weight:600;color:#166534">
{actionDone() === 'APPROVED' ? '✓ Profile approved successfully.' : '✕ Profile rejected.'} {actionDone() === 'APPROVED' ? '✓ Profile approved successfully.' : '✕ Profile rejected.'}
</p> </p>
@ -271,11 +271,11 @@ export default function ApprovalDetailPage() {
</Show> </Show>
<Show when={data.loading}> <Show when={data.loading}>
<div class="card"><p class="notice">Loading submission...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading submission...</p></div>
</Show> </Show>
<Show when={!data.loading && !data()}> <Show when={!data.loading && !data()}>
<div class="card"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
<p class="notice">Submission not found or user does not have an onboarding record for this role.</p> <p class="notice">Submission not found or user does not have an onboarding record for this role.</p>
<p style="font-size:13px;color:#64748b;margin-top:8px"> <p style="font-size:13px;color:#64748b;margin-top:8px">
Make sure the URL includes <code>?roleKey=PHOTOGRAPHER</code> (or the relevant role key). Make sure the URL includes <code>?roleKey=PHOTOGRAPHER</code> (or the relevant role key).
@ -286,7 +286,7 @@ export default function ApprovalDetailPage() {
<Show when={data()}> <Show when={data()}>
{/* ── Action bar ── */} {/* ── Action bar ── */}
<Show when={!actionDone()}> <Show when={!actionDone()}>
<div class="card" style="display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:16px;padding:12px 16px"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:16px;padding:12px 16px">
<span style={`display:inline-block;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600;${ROLE_COLORS[roleType()]}`}> <span style={`display:inline-block;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600;${ROLE_COLORS[roleType()]}`}>
{(roleKey() || 'UNKNOWN').replace(/_/g, ' ')} {(roleKey() || 'UNKNOWN').replace(/_/g, ' ')}
</span> </span>
@ -298,7 +298,7 @@ export default function ApprovalDetailPage() {
<div style="flex:1" /> <div style="flex:1" />
<Show when={data()!.onboarding?.status === 'COMPLETED' && !actionDone()}> <Show when={data()!.onboarding?.status === 'COMPLETED' && !actionDone()}>
<button <button
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
style="background:#f0fdf4;color:#15803d;border-color:#bbf7d0" style="background:#f0fdf4;color:#15803d;border-color:#bbf7d0"
disabled={!!acting()} disabled={!!acting()}
onClick={handleApprove} onClick={handleApprove}
@ -306,7 +306,7 @@ export default function ApprovalDetailPage() {
{acting() === 'APPROVE' ? 'Approving...' : '✓ Approve Profile'} {acting() === 'APPROVE' ? 'Approving...' : '✓ Approve Profile'}
</button> </button>
<button <button
class="btn danger" class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
disabled={!!acting()} disabled={!!acting()}
onClick={handleReject} onClick={handleReject}
> >
@ -318,7 +318,7 @@ export default function ApprovalDetailPage() {
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px">
{/* User info */} {/* User info */}
<div class="card"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">User Info</h3> <h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">User Info</h3>
<table style="width:100%;border-collapse:collapse;font-size:13px"> <table style="width:100%;border-collapse:collapse;font-size:13px">
<tbody> <tbody>
@ -328,7 +328,7 @@ export default function ApprovalDetailPage() {
<tr> <tr>
<td style="color:#64748b;padding:5px 10px 5px 0">Account Status</td> <td style="color:#64748b;padding:5px 10px 5px 0">Account Status</td>
<td> <td>
<span class={`status-chip${data()!.user.status === 'ACTIVE' ? ' active' : ''}`}> <span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600${data()!.user.status === 'ACTIVE' ? ' active' : ''}`}>
{data()!.user.status} {data()!.user.status}
</span> </span>
</td> </td>
@ -346,7 +346,7 @@ export default function ApprovalDetailPage() {
</div> </div>
{/* Submission status */} {/* Submission status */}
<div class="card"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">Submission Info</h3> <h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">Submission Info</h3>
<Show when={data()!.onboarding} fallback={ <Show when={data()!.onboarding} fallback={
<div style="background:#fef2f2;border:1px solid #fecaca;border-radius:8px;padding:14px"> <div style="background:#fef2f2;border:1px solid #fecaca;border-radius:8px;padding:14px">
@ -383,7 +383,7 @@ export default function ApprovalDetailPage() {
{/* ── No form data fallback ── */} {/* ── No form data fallback ── */}
<Show when={data()!.onboarding && submittedRows().length === 0}> <Show when={data()!.onboarding && submittedRows().length === 0}>
<div class="card" style="margin-bottom:16px"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px">
<h3 style="margin:0 0 10px;font-size:15px;font-weight:600;color:#0f172a">Submitted Form Answers</h3> <h3 style="margin:0 0 10px;font-size:15px;font-weight:600;color:#0f172a">Submitted Form Answers</h3>
<p class="notice">Onboarding state is present but contains no displayable field data.</p> <p class="notice">Onboarding state is present but contains no displayable field data.</p>
</div> </div>
@ -417,7 +417,7 @@ function SubmissionViewer(props: { rows: Array<{ key: string; value: string }> }
<> <>
{/* ── Text / data fields ── */} {/* ── Text / data fields ── */}
<Show when={textFields().length > 0}> <Show when={textFields().length > 0}>
<div class="card" style="margin-bottom:16px"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px">
<h3 style="margin:0 0 14px;font-size:15px;font-weight:600;color:#0f172a"> <h3 style="margin:0 0 14px;font-size:15px;font-weight:600;color:#0f172a">
Submitted Form Data Submitted Form Data
<span style="margin-left:8px;font-size:12px;font-weight:400;color:#64748b">{textFields().length} fields</span> <span style="margin-left:8px;font-size:12px;font-weight:400;color:#64748b">{textFields().length} fields</span>
@ -449,7 +449,7 @@ function SubmissionViewer(props: { rows: Array<{ key: string; value: string }> }
{/* ── Documents & Images ── */} {/* ── Documents & Images ── */}
<Show when={mediaFields().length > 0}> <Show when={mediaFields().length > 0}>
<div class="card" style="margin-bottom:16px"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px">
<h3 style="margin:0 0 14px;font-size:15px;font-weight:600;color:#0f172a"> <h3 style="margin:0 0 14px;font-size:15px;font-weight:600;color:#0f172a">
Documents & Media Documents & Media
<span style="margin-left:8px;font-size:12px;font-weight:400;color:#64748b">{mediaFields().length} file{mediaFields().length !== 1 ? 's' : ''}</span> <span style="margin-left:8px;font-size:12px;font-weight:400;color:#64748b">{mediaFields().length} file{mediaFields().length !== 1 ? 's' : ''}</span>
@ -595,7 +595,7 @@ function SubmissionViewer(props: { rows: Array<{ key: string; value: string }> }
</a> </a>
<button <button
type="button" type="button"
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
style="padding:4px 10px;font-size:13px" style="padding:4px 10px;font-size:13px"
onClick={() => setPdfViewer(null)} onClick={() => setPdfViewer(null)}
> >

View file

@ -7,7 +7,7 @@ export default function ApprovalsAliasPage() {
onMount(() => navigate('/admin/approval', { replace: true })); onMount(() => navigate('/admin/approval', { replace: true }));
return ( return (
<AdminShell> <AdminShell>
<div class="card"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
<p class="notice">Redirecting to Approval Management...</p> <p class="notice">Redirecting to Approval Management...</p>
</div> </div>
</AdminShell> </AdminShell>

View file

@ -36,14 +36,14 @@ export default function CandidatePage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Candidate Management</h1> <h1 class="text-2xl font-bold text-gray-900">Candidate Management</h1>
<p class="page-subtitle">Manage all job seeker accounts on the platform.</p> <p class="mt-1 text-sm text-gray-500">Manage all job seeker accounts on the platform.</p>
</div> </div>
</div> </div>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;"> <div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
<input <input
type="text" type="text"
@ -64,15 +64,15 @@ export default function CandidatePage() {
</select> </select>
</div> </div>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Email</th> <th>Email</th>
<th>Status</th> <th>Status</th>
<th>Registered</th> <th>Registered</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -93,22 +93,22 @@ export default function CandidatePage() {
<td style="color:#475569">{item.email}</td> <td style="color:#475569">{item.email}</td>
<td> <td>
{item.status?.toUpperCase() === 'ACTIVE' && ( {item.status?.toUpperCase() === 'ACTIVE' && (
<span class="status-chip active">ACTIVE</span> <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'INACTIVE' && ( {item.status?.toUpperCase() === 'INACTIVE' && (
<span class="status-chip">INACTIVE</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'PENDING' && ( {item.status?.toUpperCase() === 'PENDING' && (
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
)} )}
{!item.status && <span class="status-chip"></span>} {!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600"></span>}
</td> </td>
<td style="color:#475569"> <td style="color:#475569">
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'} {item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/users/${item.id}`}>View</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -36,14 +36,14 @@ export default function CateringServicesPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Catering Services Management</h1> <h1 class="text-2xl font-bold text-gray-900">Catering Services Management</h1>
<p class="page-subtitle">Manage all catering services accounts on the platform.</p> <p class="mt-1 text-sm text-gray-500">Manage all catering services accounts on the platform.</p>
</div> </div>
</div> </div>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;"> <div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
<input <input
type="text" type="text"
@ -64,15 +64,15 @@ export default function CateringServicesPage() {
</select> </select>
</div> </div>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Email</th> <th>Email</th>
<th>Status</th> <th>Status</th>
<th>Registered</th> <th>Registered</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -93,22 +93,22 @@ export default function CateringServicesPage() {
<td style="color:#475569">{item.email}</td> <td style="color:#475569">{item.email}</td>
<td> <td>
{item.status?.toUpperCase() === 'ACTIVE' && ( {item.status?.toUpperCase() === 'ACTIVE' && (
<span class="status-chip active">ACTIVE</span> <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'INACTIVE' && ( {item.status?.toUpperCase() === 'INACTIVE' && (
<span class="status-chip">INACTIVE</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'PENDING' && ( {item.status?.toUpperCase() === 'PENDING' && (
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
)} )}
{!item.status && <span class="status-chip"></span>} {!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600"></span>}
</td> </td>
<td style="color:#475569"> <td style="color:#475569">
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'} {item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/users/${item.id}`}>View</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -30,12 +30,12 @@ async function fetchCompanies(): Promise<Company[]> {
function StatusBadge(props: { status: string }) { function StatusBadge(props: { status: string }) {
if (props.status === 'ACTIVE') { if (props.status === 'ACTIVE') {
return <span class="status-chip active">ACTIVE</span>; return <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>;
} }
if (props.status === 'PENDING') { if (props.status === 'PENDING') {
return <span class="status-chip" style="background:#f59e0b;color:#fff">PENDING</span>; return <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#f59e0b;color:#fff">PENDING</span>;
} }
return <span class="status-chip">{props.status}</span>; return <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">{props.status}</span>;
} }
export default function CompanyPage() { export default function CompanyPage() {
@ -72,20 +72,20 @@ export default function CompanyPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Company Management</h1> <h1 class="text-2xl font-bold text-gray-900">Company Management</h1>
<p class="page-subtitle">Manage all company accounts on the platform.</p> <p class="mt-1 text-sm text-gray-500">Manage all company accounts on the platform.</p>
</div> </div>
<A class="btn navy" href="/admin/company/create">Create Company</A> <A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/company/create">Create Company</A>
</div> </div>
<Show when={actionError()}> <Show when={actionError()}>
<div class="error-box">{actionError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{actionError()}</div>
</Show> </Show>
{/* Search */} {/* Search */}
<div class="card" style="margin-bottom:16px;display:flex;gap:12px;flex-wrap:wrap;align-items:center;"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px;display:flex;gap:12px;flex-wrap:wrap;align-items:center;">
<input <input
type="text" type="text"
placeholder="Search by name or email..." placeholder="Search by name or email..."
@ -95,9 +95,9 @@ export default function CompanyPage() {
/> />
</div> </div>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Company Name</th> <th>Company Name</th>
@ -105,7 +105,7 @@ export default function CompanyPage() {
<th>City</th> <th>City</th>
<th>Email</th> <th>Email</th>
<th>Status</th> <th>Status</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -132,10 +132,10 @@ export default function CompanyPage() {
<StatusBadge status={item.status} /> <StatusBadge status={item.status} />
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/company/${item.id}`}>View</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/company/${item.id}`}>View</A>
<button <button
class="btn danger" class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
disabled={deleting() === item.id} disabled={deleting() === item.id}
onClick={() => handleDelete(item.id, displayName)} onClick={() => handleDelete(item.id, displayName)}
> >

View file

@ -96,39 +96,39 @@ export default function CompanyDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">{name()}</h1> <h1 class="text-2xl font-bold text-gray-900">{name()}</h1>
<p class="page-subtitle">{companyId()}</p> <p class="mt-1 text-sm text-gray-500">{companyId()}</p>
</div> </div>
<div class="page-actions-right"> <div class="flex items-center gap-2">
<span class={`status-pill ${isVerified() ? 'status-approved' : 'status-pending'}`}> <span class={`status-pill ${isVerified() ? 'status-approved' : 'status-pending'}`}>
{isVerified() ? 'Verified - Can Post Jobs' : 'Not Verified'} {isVerified() ? 'Verified - Can Post Jobs' : 'Not Verified'}
</span> </span>
<A class="btn" href="/admin/company">Back to Companies</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/company">Back to Companies</A>
<A class="btn navy" href="/admin/approval">Open Approval Management</A> <A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/approval">Open Approval Management</A>
</div> </div>
</div> </div>
<Show when={bundle.loading}> <Show when={bundle.loading}>
<div class="card"><p class="notice">Loading company...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading company...</p></div>
</Show> </Show>
<Show when={!bundle.loading && !company()}> <Show when={!bundle.loading && !company()}>
<div class="card"><p class="notice">Company not found.</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Company not found.</p></div>
</Show> </Show>
<Show when={company()}> <Show when={company()}>
<> <>
<div class="field-grid-2" style="margin-bottom:16px"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2" style="margin-bottom:16px">
<div class="card"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
<p class="kv-label">Email</p> <p class="kv-label">Email</p>
<p class="kv-value">{company()!.email || '—'}</p> <p class="kv-value">{company()!.email || '—'}</p>
</div> </div>
<div class="card"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
<p class="kv-label">Phone</p> <p class="kv-label">Phone</p>
<p class="kv-value">{company()!.phone || '—'}</p> <p class="kv-value">{company()!.phone || '—'}</p>
</div> </div>
<div class="card" style="grid-column:1 / -1"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="grid-column:1 / -1">
<p class="kv-label">Website</p> <p class="kv-label">Website</p>
<p class="kv-value"> <p class="kv-value">
<Show when={website()} fallback={'—'}> <Show when={website()} fallback={'—'}>
@ -140,9 +140,9 @@ export default function CompanyDetailPage() {
<div class="detail-layout"> <div class="detail-layout">
<div style="display:flex;flex-direction:column;gap:16px"> <div style="display:flex;flex-direction:column;gap:16px">
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h2 style="margin-bottom:8px">Company Information</h2> <h2 style="margin-bottom:8px">Company Information</h2>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="kv-item"> <div class="kv-item">
<p class="kv-label">Company Name</p> <p class="kv-label">Company Name</p>
<p class="kv-value">{name()}</p> <p class="kv-value">{name()}</p>
@ -186,9 +186,9 @@ export default function CompanyDetailPage() {
</div> </div>
</section> </section>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h2 style="margin-bottom:8px">Contact Details</h2> <h2 style="margin-bottom:8px">Contact Details</h2>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="kv-item"> <div class="kv-item">
<p class="kv-label">City</p> <p class="kv-label">City</p>
<p class="kv-value">{contact()?.city || company()!.city || '—'}</p> <p class="kv-value">{contact()?.city || company()!.city || '—'}</p>
@ -216,9 +216,9 @@ export default function CompanyDetailPage() {
</div> </div>
</section> </section>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h2 style="margin-bottom:8px">Banking Details</h2> <h2 style="margin-bottom:8px">Banking Details</h2>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="kv-item"> <div class="kv-item">
<p class="kv-label">Account Holder</p> <p class="kv-label">Account Holder</p>
<p class="kv-value">{banking()?.accountHolderName || '—'}</p> <p class="kv-value">{banking()?.accountHolderName || '—'}</p>
@ -248,7 +248,7 @@ export default function CompanyDetailPage() {
</div> </div>
<div style="display:flex;flex-direction:column;gap:16px"> <div style="display:flex;flex-direction:column;gap:16px">
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h2 style="margin-bottom:8px">Verification Status</h2> <h2 style="margin-bottom:8px">Verification Status</h2>
<div class={`sub-card ${isVerified() ? 'sub-card-success' : 'sub-card-warning'}`} style="margin-top:0"> <div class={`sub-card ${isVerified() ? 'sub-card-success' : 'sub-card-warning'}`} style="margin-top:0">
<p class="kv-label">Overall Status</p> <p class="kv-label">Overall Status</p>
@ -273,7 +273,7 @@ export default function CompanyDetailPage() {
</div> </div>
</section> </section>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h2 style="margin-bottom:8px">Metadata</h2> <h2 style="margin-bottom:8px">Metadata</h2>
<div class="kv-item"> <div class="kv-item">
<p class="kv-label">Created At</p> <p class="kv-label">Created At</p>

View file

@ -52,20 +52,20 @@ export default function CreateCompanyPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Create Company</h1> <h1 class="text-2xl font-bold text-gray-900">Create Company</h1>
<p class="page-subtitle">Add a new organization profile to the admin company catalog.</p> <p class="mt-1 text-sm text-gray-500">Add a new organization profile to the admin company catalog.</p>
</div> </div>
<A class="btn" href="/admin/company">Back to Companies</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/company">Back to Companies</A>
</div> </div>
<Show when={error()}> <Show when={error()}>
<div class="error-box">{error()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
</Show> </Show>
<form class="card" onSubmit={submit}> <form class="rounded-xl border border-gray-200 bg-white shadow-sm" onSubmit={submit}>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label>Company Name *</label> <label>Company Name *</label>
<input value={form().companyName} onInput={(e) => setField('companyName', e.currentTarget.value)} /> <input value={form().companyName} onInput={(e) => setField('companyName', e.currentTarget.value)} />
@ -100,8 +100,8 @@ export default function CreateCompanyPage() {
</div> </div>
</div> </div>
<div class="actions" style="justify-content:flex-end"> <div class="actions" style="justify-content:flex-end">
<A class="btn" href="/admin/company">Cancel</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/company">Cancel</A>
<button class="btn navy" type="submit" disabled={saving()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
{saving() ? 'Creating...' : 'Create Company'} {saving() ? 'Creating...' : 'Create Company'}
</button> </button>
</div> </div>

View file

@ -142,10 +142,10 @@ export default function CouponPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Coupon Management</h1> <h1 class="text-2xl font-bold text-gray-900">Coupon Management</h1>
<p class="page-subtitle">Reusable coupon codes for package checkout</p> <p class="mt-1 text-sm text-gray-500">Reusable coupon codes for package checkout</p>
</div> </div>
</div> </div>
@ -168,9 +168,9 @@ export default function CouponPage() {
</div> </div>
<Show when={activeTab() === 'list'}> <Show when={activeTab() === 'list'}>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Code</th> <th>Code</th>
@ -179,7 +179,7 @@ export default function CouponPage() {
<th>Value</th> <th>Value</th>
<th>Max Uses</th> <th>Max Uses</th>
<th>Status</th> <th>Status</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -202,15 +202,15 @@ export default function CouponPage() {
<td style="color:#475569">{item.type === 'PERCENT' ? `${item.value}%` : `${item.value}`}</td> <td style="color:#475569">{item.type === 'PERCENT' ? `${item.value}%` : `${item.value}`}</td>
<td style="color:#475569">{item.usage_limit != null ? item.usage_limit : '—'}</td> <td style="color:#475569">{item.usage_limit != null ? item.usage_limit : '—'}</td>
<td> <td>
<span class={`status-chip ${item.is_active ? 'active' : ''}`}> <span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${item.is_active ? 'active' : ''}`}>
{item.is_active ? 'Active' : 'Inactive'} {item.is_active ? 'Active' : 'Inactive'}
</span> </span>
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<button class="btn" onClick={() => startEdit(item)}>Edit</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => startEdit(item)}>Edit</button>
<button <button
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
disabled={toggling() === item.id} disabled={toggling() === item.id}
onClick={() => handleToggle(item)} onClick={() => handleToggle(item)}
> >
@ -229,10 +229,10 @@ export default function CouponPage() {
</Show> </Show>
<Show when={activeTab() === 'create'}> <Show when={activeTab() === 'create'}>
<section class="card" style="max-width:520px"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:520px">
<h2 style="margin:0 0 20px;font-size:16px;font-weight:700">{form().id ? 'Edit Coupon' : 'Create Coupon'}</h2> <h2 style="margin:0 0 20px;font-size:16px;font-weight:700">{form().id ? 'Edit Coupon' : 'Create Coupon'}</h2>
<Show when={formError()}> <Show when={formError()}>
<div class="error-box" style="margin-bottom:12px">{formError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{formError()}</div>
</Show> </Show>
<form onSubmit={handleSave} style="display:flex;flex-direction:column;gap:14px"> <form onSubmit={handleSave} style="display:flex;flex-direction:column;gap:14px">
<div class="field"> <div class="field">
@ -256,7 +256,7 @@ export default function CouponPage() {
placeholder="e.g. 10% off for companies" placeholder="e.g. 10% off for companies"
/> />
</div> </div>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label>Type</label> <label>Type</label>
<select <select
@ -278,7 +278,7 @@ export default function CouponPage() {
/> />
</div> </div>
</div> </div>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label>Min Order Amount ()</label> <label>Min Order Amount ()</label>
<input <input
@ -320,11 +320,11 @@ export default function CouponPage() {
</div> </div>
</div> </div>
<div class="actions"> <div class="actions">
<button class="btn navy" type="submit" disabled={saving()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
{saving() ? 'Saving...' : (form().id ? 'Update Coupon' : 'Save Coupon')} {saving() ? 'Saving...' : (form().id ? 'Update Coupon' : 'Save Coupon')}
</button> </button>
<Show when={form().id}> <Show when={form().id}>
<button type="button" class="btn" onClick={resetForm}>Cancel Edit</button> <button type="button" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={resetForm}>Cancel Edit</button>
</Show> </Show>
</div> </div>
</form> </form>

View file

@ -136,10 +136,10 @@ export default function CreditPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Credit Management</h1> <h1 class="text-2xl font-bold text-gray-900">Credit Management</h1>
<p class="page-subtitle">Audit TraceCoin balances and adjust credits</p> <p class="mt-1 text-sm text-gray-500">Audit TraceCoin balances and adjust credits</p>
</div> </div>
</div> </div>
@ -161,7 +161,7 @@ export default function CreditPage() {
{/* Balance & Ledger Tab */} {/* Balance & Ledger Tab */}
<Show when={activeTab() === 'ledger'}> <Show when={activeTab() === 'ledger'}>
<div style="display:flex;flex-direction:column;gap:24px"> <div style="display:flex;flex-direction:column;gap:24px">
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h2 style="margin:0 0 16px;font-size:16px;font-weight:700;color:#1e293b">Search Account Balance</h2> <h2 style="margin:0 0 16px;font-size:16px;font-weight:700;color:#1e293b">Search Account Balance</h2>
<div style="display:flex;gap:10px"> <div style="display:flex;gap:10px">
<input <input
@ -173,7 +173,7 @@ export default function CreditPage() {
style="flex:1;padding:8px 12px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px" style="flex:1;padding:8px 12px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px"
/> />
<button <button
class="btn navy" class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
onClick={handleSearch} onClick={handleSearch}
disabled={searchLoading()} disabled={searchLoading()}
> >
@ -181,7 +181,7 @@ export default function CreditPage() {
</button> </button>
</div> </div>
<Show when={searchError()}> <Show when={searchError()}>
<div class="error-box" style="margin-top:10px">{searchError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-top:10px">{searchError()}</div>
</Show> </Show>
</section> </section>
@ -193,14 +193,14 @@ export default function CreditPage() {
<p style="font-size:12px;color:#bfdbfe;margin:8px 0 0">User: {searchedUserId()}</p> <p style="font-size:12px;color:#bfdbfe;margin:8px 0 0">User: {searchedUserId()}</p>
</div> </div>
<section class="card" style="padding:20px;overflow:hidden"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:20px;overflow:hidden">
<h3 style="margin:0 0 16px;font-size:15px;font-weight:700;color:#0f172a">TraceCoin Ledger</h3> <h3 style="margin:0 0 16px;font-size:15px;font-weight:700;color:#0f172a">TraceCoin Ledger</h3>
<Show when={ledger().length === 0}> <Show when={ledger().length === 0}>
<p style="text-align:center;padding:32px;color:#94a3b8;font-style:italic">No transactions found for this account.</p> <p style="text-align:center;padding:32px;color:#94a3b8;font-style:italic">No transactions found for this account.</p>
</Show> </Show>
<Show when={ledger().length > 0}> <Show when={ledger().length > 0}>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Type</th> <th>Type</th>
@ -248,7 +248,7 @@ export default function CreditPage() {
{/* Reward / Deduct Tab */} {/* Reward / Deduct Tab */}
<Show when={activeTab() === 'adjust'}> <Show when={activeTab() === 'adjust'}>
<section class="card" style="max-width:460px"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:460px">
<h2 style="margin:0 0 6px;font-size:16px;font-weight:700;color:#1e293b">Reward or Adjust TraceCoins</h2> <h2 style="margin:0 0 6px;font-size:16px;font-weight:700;color:#1e293b">Reward or Adjust TraceCoins</h2>
<p style="margin:0 0 20px;font-size:13px;color:#64748b"> <p style="margin:0 0 20px;font-size:13px;color:#64748b">
Use this to reward TraceCoins for a valid support case, process a refund, or correct a balance manually. Use this to reward TraceCoins for a valid support case, process a refund, or correct a balance manually.
@ -259,7 +259,7 @@ export default function CreditPage() {
</div> </div>
</Show> </Show>
<Show when={adjError()}> <Show when={adjError()}>
<div class="error-box" style="margin-bottom:14px">{adjError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:14px">{adjError()}</div>
</Show> </Show>
<form onSubmit={handleAdjust} style="display:flex;flex-direction:column;gap:14px"> <form onSubmit={handleAdjust} style="display:flex;flex-direction:column;gap:14px">
<div class="field"> <div class="field">
@ -272,7 +272,7 @@ export default function CreditPage() {
style="width:100%;padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;box-sizing:border-box" style="width:100%;padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;box-sizing:border-box"
/> />
</div> </div>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:4px">Amount</label> <label style="display:block;font-size:13px;font-weight:600;margin-bottom:4px">Amount</label>
<input <input
@ -316,7 +316,7 @@ export default function CreditPage() {
/> />
</div> </div>
<div> <div>
<button class="btn navy" type="submit" disabled={adjLoading()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={adjLoading()}>
{adjLoading() ? 'Adjusting...' : 'Apply Adjustment'} {adjLoading() ? 'Adjusting...' : 'Apply Adjustment'}
</button> </button>
</div> </div>
@ -327,13 +327,13 @@ export default function CreditPage() {
{/* Reconcile Tab */} {/* Reconcile Tab */}
<Show when={activeTab() === 'reconcile'}> <Show when={activeTab() === 'reconcile'}>
<div style="display:flex;flex-direction:column;gap:20px"> <div style="display:flex;flex-direction:column;gap:20px">
<section class="card" style="max-width:460px"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:460px">
<h2 style="margin:0 0 16px;font-size:16px;font-weight:700;color:#1e293b">Ledger Reconciliation</h2> <h2 style="margin:0 0 16px;font-size:16px;font-weight:700;color:#1e293b">Ledger Reconciliation</h2>
<Show when={reconError()}> <Show when={reconError()}>
<div class="error-box" style="margin-bottom:14px">{reconError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:14px">{reconError()}</div>
</Show> </Show>
<form onSubmit={handleReconcile} style="display:flex;flex-direction:column;gap:14px"> <form onSubmit={handleReconcile} style="display:flex;flex-direction:column;gap:14px">
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:4px">Start Date</label> <label style="display:block;font-size:13px;font-weight:600;margin-bottom:4px">Start Date</label>
<input <input
@ -356,7 +356,7 @@ export default function CreditPage() {
</div> </div>
</div> </div>
<div> <div>
<button class="btn navy" type="submit" disabled={reconLoading()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={reconLoading()}>
{reconLoading() ? 'Running...' : 'Run Reconciliation'} {reconLoading() ? 'Running...' : 'Run Reconciliation'}
</button> </button>
</div> </div>
@ -368,9 +368,9 @@ export default function CreditPage() {
<p style="color:#16a34a;font-weight:600;font-size:14px">No discrepancies found.</p> <p style="color:#16a34a;font-weight:600;font-size:14px">No discrepancies found.</p>
</Show> </Show>
<Show when={(reconResults()?.length ?? 0) > 0}> <Show when={(reconResults()?.length ?? 0) > 0}>
<section class="card" style="padding:0;overflow:hidden"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>User ID</th> <th>User ID</th>

View file

@ -36,14 +36,14 @@ export default function CustomerPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Customer Management</h1> <h1 class="text-2xl font-bold text-gray-900">Customer Management</h1>
<p class="page-subtitle">Manage all customer accounts on the platform.</p> <p class="mt-1 text-sm text-gray-500">Manage all customer accounts on the platform.</p>
</div> </div>
</div> </div>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;"> <div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
<input <input
type="text" type="text"
@ -64,15 +64,15 @@ export default function CustomerPage() {
</select> </select>
</div> </div>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Email</th> <th>Email</th>
<th>Status</th> <th>Status</th>
<th>Registered</th> <th>Registered</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -93,22 +93,22 @@ export default function CustomerPage() {
<td style="color:#475569">{item.email}</td> <td style="color:#475569">{item.email}</td>
<td> <td>
{item.status?.toUpperCase() === 'ACTIVE' && ( {item.status?.toUpperCase() === 'ACTIVE' && (
<span class="status-chip active">ACTIVE</span> <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'INACTIVE' && ( {item.status?.toUpperCase() === 'INACTIVE' && (
<span class="status-chip">INACTIVE</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'PENDING' && ( {item.status?.toUpperCase() === 'PENDING' && (
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
)} )}
{!item.status && <span class="status-chip"></span>} {!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600"></span>}
</td> </td>
<td style="color:#475569"> <td style="color:#475569">
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'} {item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/users/${item.id}`}>View</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -207,13 +207,13 @@ export default function DepartmentPage() {
return ( return (
<AdminShell> <AdminShell>
{/* Header */} {/* Header */}
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Departments</h1> <h1 class="text-2xl font-bold text-gray-900">Departments</h1>
<p class="page-subtitle">Manage organization departments</p> <p class="mt-1 text-sm text-gray-500">Manage organization departments</p>
</div> </div>
<button <button
class="btn navy" class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
onClick={() => { onClick={() => {
setShowCreate((v) => !v); setShowCreate((v) => !v);
setCreateError(''); setCreateError('');
@ -225,9 +225,9 @@ export default function DepartmentPage() {
{/* Create form */} {/* Create form */}
<Show when={showCreate()}> <Show when={showCreate()}>
<section class="card role-form-section"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
<form onSubmit={handleCreate}> <form onSubmit={handleCreate}>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label>Name *</label> <label>Name *</label>
<input <input
@ -249,12 +249,12 @@ export default function DepartmentPage() {
</div> </div>
</div> </div>
<Show when={createError()}> <Show when={createError()}>
<p class="error-box" style="margin-top:10px">{createError()}</p> <p class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-top:10px">{createError()}</p>
</Show> </Show>
<div class="actions" style="margin-top:16px"> <div class="actions" style="margin-top:16px">
<button <button
type="button" type="button"
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
onClick={() => { onClick={() => {
setShowCreate(false); setShowCreate(false);
setCreateError(''); setCreateError('');
@ -262,7 +262,7 @@ export default function DepartmentPage() {
> >
Cancel Cancel
</button> </button>
<button type="submit" class="btn navy" disabled={creating()}> <button type="submit" class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={creating()}>
{creating() ? 'Saving...' : 'Save'} {creating() ? 'Saving...' : 'Save'}
</button> </button>
</div> </div>
@ -288,19 +288,19 @@ export default function DepartmentPage() {
{/* Action error */} {/* Action error */}
<Show when={actionError()}> <Show when={actionError()}>
<div class="error-box" style="margin-bottom:12px">{actionError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{actionError()}</div>
</Show> </Show>
{/* Table */} {/* Table */}
<section class="card" style="padding:0;overflow:hidden"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Description</th> <th>Description</th>
<th>Created At</th> <th>Created At</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -334,16 +334,16 @@ export default function DepartmentPage() {
<td style="color:#475569">{item.description || '—'}</td> <td style="color:#475569">{item.description || '—'}</td>
<td style="color:#475569">{fmtDate(item.createdAt || item.created_at)}</td> <td style="color:#475569">{fmtDate(item.createdAt || item.created_at)}</td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<button <button
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
onClick={() => startEdit(item)} onClick={() => startEdit(item)}
> >
Edit Edit
</button> </button>
<Show when={tab() === 'active'}> <Show when={tab() === 'active'}>
<button <button
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
disabled={busy() === item.id} disabled={busy() === item.id}
onClick={() => handleArchive(item.id)} onClick={() => handleArchive(item.id)}
> >
@ -352,7 +352,7 @@ export default function DepartmentPage() {
</Show> </Show>
<Show when={tab() === 'archived'}> <Show when={tab() === 'archived'}>
<button <button
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
disabled={busy() === item.id} disabled={busy() === item.id}
onClick={() => handleRestore(item.id)} onClick={() => handleRestore(item.id)}
> >
@ -360,7 +360,7 @@ export default function DepartmentPage() {
</button> </button>
</Show> </Show>
<button <button
class="btn danger" class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
disabled={busy() === item.id} disabled={busy() === item.id}
onClick={() => handleDelete(item.id, deptLabel(item))} onClick={() => handleDelete(item.id, deptLabel(item))}
> >
@ -373,7 +373,7 @@ export default function DepartmentPage() {
<Show when={editingId() === item.id}> <Show when={editingId() === item.id}>
<tr> <tr>
<td colspan="4" style="background:#f8fafc;padding:16px"> <td colspan="4" style="background:#f8fafc;padding:16px">
<div class="field-grid-2" style="margin-bottom:10px"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2" style="margin-bottom:10px">
<div class="field"> <div class="field">
<label>Name *</label> <label>Name *</label>
<input <input
@ -393,14 +393,14 @@ export default function DepartmentPage() {
</div> </div>
</div> </div>
<Show when={editError()}> <Show when={editError()}>
<p class="error-box" style="margin-bottom:8px">{editError()}</p> <p class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:8px">{editError()}</p>
</Show> </Show>
<div class="actions"> <div class="actions">
<button class="btn" type="button" onClick={cancelEdit}> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" onClick={cancelEdit}>
Cancel Cancel
</button> </button>
<button <button
class="btn navy" class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
type="button" type="button"
disabled={saving()} disabled={saving()}
onClick={() => handleUpdate(item.id)} onClick={() => handleUpdate(item.id)}

View file

@ -189,13 +189,13 @@ export default function DesignationPage() {
return ( return (
<AdminShell> <AdminShell>
{/* Header */} {/* Header */}
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Designations</h1> <h1 class="text-2xl font-bold text-gray-900">Designations</h1>
<p class="page-subtitle">Manage job designations</p> <p class="mt-1 text-sm text-gray-500">Manage job designations</p>
</div> </div>
<button <button
class="btn navy" class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
onClick={() => { onClick={() => {
setShowCreate((v) => !v); setShowCreate((v) => !v);
setCreateError(''); setCreateError('');
@ -210,9 +210,9 @@ export default function DesignationPage() {
{/* Create form */} {/* Create form */}
<Show when={showCreate()}> <Show when={showCreate()}>
<section class="card role-form-section"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
<form onSubmit={handleCreate}> <form onSubmit={handleCreate}>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label>Name *</label> <label>Name *</label>
<input <input
@ -250,12 +250,12 @@ export default function DesignationPage() {
</div> </div>
</div> </div>
<Show when={createError()}> <Show when={createError()}>
<p class="error-box" style="margin-top:10px">{createError()}</p> <p class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-top:10px">{createError()}</p>
</Show> </Show>
<div class="actions" style="margin-top:16px"> <div class="actions" style="margin-top:16px">
<button <button
type="button" type="button"
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
onClick={() => { onClick={() => {
setShowCreate(false); setShowCreate(false);
setCreateError(''); setCreateError('');
@ -265,7 +265,7 @@ export default function DesignationPage() {
</button> </button>
<button <button
type="submit" type="submit"
class="btn navy" class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
disabled={creating() || (departments() ?? []).length === 0} disabled={creating() || (departments() ?? []).length === 0}
> >
{creating() ? 'Saving...' : 'Save'} {creating() ? 'Saving...' : 'Save'}
@ -293,19 +293,19 @@ export default function DesignationPage() {
{/* Action error */} {/* Action error */}
<Show when={actionError()}> <Show when={actionError()}>
<div class="error-box" style="margin-bottom:12px">{actionError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{actionError()}</div>
</Show> </Show>
{/* Table */} {/* Table */}
<section class="card" style="padding:0;overflow:hidden"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Department</th> <th>Department</th>
<th>Description</th> <th>Description</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -339,15 +339,15 @@ export default function DesignationPage() {
<td style="color:#475569">{deptDisplay(item)}</td> <td style="color:#475569">{deptDisplay(item)}</td>
<td style="color:#475569">{item.description || '—'}</td> <td style="color:#475569">{item.description || '—'}</td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<button <button
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
onClick={() => startEdit(item)} onClick={() => startEdit(item)}
> >
Edit Edit
</button> </button>
<button <button
class="btn danger" class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
disabled={deleting() === item.id} disabled={deleting() === item.id}
onClick={() => handleDelete(item.id, item.name)} onClick={() => handleDelete(item.id, item.name)}
> >
@ -360,7 +360,7 @@ export default function DesignationPage() {
<Show when={editingId() === item.id}> <Show when={editingId() === item.id}>
<tr> <tr>
<td colspan="4" style="background:#f8fafc;padding:16px"> <td colspan="4" style="background:#f8fafc;padding:16px">
<div class="field-grid-2" style="margin-bottom:10px"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2" style="margin-bottom:10px">
<div class="field"> <div class="field">
<label>Name *</label> <label>Name *</label>
<input <input
@ -394,14 +394,14 @@ export default function DesignationPage() {
</div> </div>
</div> </div>
<Show when={editError()}> <Show when={editError()}>
<p class="error-box" style="margin-bottom:8px">{editError()}</p> <p class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:8px">{editError()}</p>
</Show> </Show>
<div class="actions"> <div class="actions">
<button class="btn" type="button" onClick={cancelEdit}> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" onClick={cancelEdit}>
Cancel Cancel
</button> </button>
<button <button
class="btn navy" class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
type="button" type="button"
disabled={saving()} disabled={saving()}
onClick={() => handleUpdate(item.id)} onClick={() => handleUpdate(item.id)}

View file

@ -36,14 +36,14 @@ export default function DevelopersPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Developers Management</h1> <h1 class="text-2xl font-bold text-gray-900">Developers Management</h1>
<p class="page-subtitle">Manage all developer accounts on the platform.</p> <p class="mt-1 text-sm text-gray-500">Manage all developer accounts on the platform.</p>
</div> </div>
</div> </div>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;"> <div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
<input <input
type="text" type="text"
@ -64,15 +64,15 @@ export default function DevelopersPage() {
</select> </select>
</div> </div>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Email</th> <th>Email</th>
<th>Status</th> <th>Status</th>
<th>Registered</th> <th>Registered</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -93,22 +93,22 @@ export default function DevelopersPage() {
<td style="color:#475569">{item.email}</td> <td style="color:#475569">{item.email}</td>
<td> <td>
{item.status?.toUpperCase() === 'ACTIVE' && ( {item.status?.toUpperCase() === 'ACTIVE' && (
<span class="status-chip active">ACTIVE</span> <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'INACTIVE' && ( {item.status?.toUpperCase() === 'INACTIVE' && (
<span class="status-chip">INACTIVE</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'PENDING' && ( {item.status?.toUpperCase() === 'PENDING' && (
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
)} )}
{!item.status && <span class="status-chip"></span>} {!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600"></span>}
</td> </td>
<td style="color:#475569"> <td style="color:#475569">
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'} {item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/users/${item.id}`}>View</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -146,10 +146,10 @@ export default function DiscountPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Discount Management</h1> <h1 class="text-2xl font-bold text-gray-900">Discount Management</h1>
<p class="page-subtitle">Automatic discounts applied before coupons</p> <p class="mt-1 text-sm text-gray-500">Automatic discounts applied before coupons</p>
</div> </div>
</div> </div>
@ -172,9 +172,9 @@ export default function DiscountPage() {
</div> </div>
<Show when={activeTab() === 'list'}> <Show when={activeTab() === 'list'}>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Title</th> <th>Title</th>
@ -183,7 +183,7 @@ export default function DiscountPage() {
<th>Type</th> <th>Type</th>
<th>Value</th> <th>Value</th>
<th>Status</th> <th>Status</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -206,14 +206,14 @@ export default function DiscountPage() {
<td style="color:#475569">{item.type}</td> <td style="color:#475569">{item.type}</td>
<td style="color:#475569">{item.type === 'PERCENT' ? `${item.value}%` : `${item.value}`}</td> <td style="color:#475569">{item.type === 'PERCENT' ? `${item.value}%` : `${item.value}`}</td>
<td> <td>
<span class={`status-chip ${item.is_active ? 'active' : ''}`}> <span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${item.is_active ? 'active' : ''}`}>
{item.is_active ? 'Active' : 'Inactive'} {item.is_active ? 'Active' : 'Inactive'}
</span> </span>
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<button <button
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
disabled={toggling() === item.id} disabled={toggling() === item.id}
onClick={() => handleToggle(item)} onClick={() => handleToggle(item)}
> >
@ -232,10 +232,10 @@ export default function DiscountPage() {
</Show> </Show>
<Show when={activeTab() === 'create'}> <Show when={activeTab() === 'create'}>
<section class="card" style="max-width:520px"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:520px">
<h2 style="margin:0 0 20px;font-size:16px;font-weight:700">{form().id ? 'Edit Discount' : 'Create Discount'}</h2> <h2 style="margin:0 0 20px;font-size:16px;font-weight:700">{form().id ? 'Edit Discount' : 'Create Discount'}</h2>
<Show when={formError()}> <Show when={formError()}>
<div class="error-box" style="margin-bottom:12px">{formError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{formError()}</div>
</Show> </Show>
<form onSubmit={handleSave} style="display:flex;flex-direction:column;gap:14px"> <form onSubmit={handleSave} style="display:flex;flex-direction:column;gap:14px">
<div class="field"> <div class="field">
@ -301,7 +301,7 @@ export default function DiscountPage() {
</select> </select>
</Show> </Show>
</div> </div>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label>Type</label> <label>Type</label>
<select <select
@ -324,11 +324,11 @@ export default function DiscountPage() {
</div> </div>
</div> </div>
<div class="actions"> <div class="actions">
<button class="btn navy" type="submit" disabled={saving()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
{saving() ? 'Saving...' : (form().id ? 'Update Discount' : 'Save Discount')} {saving() ? 'Saving...' : (form().id ? 'Update Discount' : 'Save Discount')}
</button> </button>
<Show when={form().id}> <Show when={form().id}>
<button type="button" class="btn" onClick={resetForm}>Cancel Edit</button> <button type="button" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={resetForm}>Cancel Edit</button>
</Show> </Show>
</div> </div>
</form> </form>

View file

@ -174,9 +174,9 @@ export default function EmployeesPage(props: EmployeesPageProps = {}) {
return ( return (
<AdminShell> <AdminShell>
{/* Header */} {/* Header */}
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Internal User Management</h1> <h1 class="text-2xl font-bold text-gray-900">Internal User Management</h1>
</div> </div>
</div> </div>
@ -207,19 +207,19 @@ export default function EmployeesPage(props: EmployeesPageProps = {}) {
{/* ===== LIST VIEW ===== */} {/* ===== LIST VIEW ===== */}
<Show when={view() === 'list'}> <Show when={view() === 'list'}>
<Show when={actionError()}> <Show when={actionError()}>
<div class="error-box" style="margin-bottom:12px">{actionError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{actionError()}</div>
</Show> </Show>
<section class="card" style="padding:0;overflow:hidden"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Email</th> <th>Email</th>
<th>Role</th> <th>Role</th>
<th>Status</th> <th>Status</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -252,24 +252,24 @@ export default function EmployeesPage(props: EmployeesPageProps = {}) {
<td style="color:#475569">{item.email}</td> <td style="color:#475569">{item.email}</td>
<td style="color:#475569">{roleName(item)}</td> <td style="color:#475569">{roleName(item)}</td>
<td> <td>
<span class={`status-chip${isActive(item) ? ' active' : ''}`}> <span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600${isActive(item) ? ' active' : ''}`}>
{isActive(item) ? 'ACTIVE' : 'INACTIVE'} {isActive(item) ? 'ACTIVE' : 'INACTIVE'}
</span> </span>
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/employees/${item.id}/edit`}> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/employees/${item.id}/edit`}>
Edit Edit
</A> </A>
<button <button
class={isActive(item) ? 'btn danger' : 'btn navy'} class={isActive(item) ? 'inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors' : 'inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors'}
disabled={toggling() === item.id} disabled={toggling() === item.id}
onClick={() => handleToggleStatus(item.id, isActive(item))} onClick={() => handleToggleStatus(item.id, isActive(item))}
> >
{toggling() === item.id ? '...' : isActive(item) ? 'Deactivate' : 'Activate'} {toggling() === item.id ? '...' : isActive(item) ? 'Deactivate' : 'Activate'}
</button> </button>
<button <button
class="btn danger" class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
disabled={deleting() === item.id} disabled={deleting() === item.id}
onClick={() => handleDelete(item.id, employeeName(item))} onClick={() => handleDelete(item.id, employeeName(item))}
> >
@ -289,9 +289,9 @@ export default function EmployeesPage(props: EmployeesPageProps = {}) {
{/* ===== CREATE VIEW ===== */} {/* ===== CREATE VIEW ===== */}
<Show when={view() === 'create'}> <Show when={view() === 'create'}>
<section class="card role-form-section"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
<form onSubmit={handleCreate}> <form onSubmit={handleCreate}>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label>Full Name *</label> <label>Full Name *</label>
<input <input
@ -339,12 +339,12 @@ export default function EmployeesPage(props: EmployeesPageProps = {}) {
</div> </div>
</div> </div>
<Show when={createError()}> <Show when={createError()}>
<p class="error-box" style="margin-top:10px">{createError()}</p> <p class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-top:10px">{createError()}</p>
</Show> </Show>
<div class="actions" style="margin-top:16px"> <div class="actions" style="margin-top:16px">
<button <button
type="button" type="button"
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
onClick={() => { onClick={() => {
resetCreateForm(); resetCreateForm();
setView('list'); setView('list');
@ -352,7 +352,7 @@ export default function EmployeesPage(props: EmployeesPageProps = {}) {
> >
Cancel Cancel
</button> </button>
<button type="submit" class="btn navy" disabled={creating()}> <button type="submit" class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={creating()}>
{creating() ? 'Creating...' : 'Create Internal User'} {creating() ? 'Creating...' : 'Create Internal User'}
</button> </button>
</div> </div>

View file

@ -77,28 +77,28 @@ export default function EditEmployeePage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Edit Employee</h1> <h1 class="text-2xl font-bold text-gray-900">Edit Employee</h1>
<p class="page-subtitle">Update internal employee profile and role assignment.</p> <p class="mt-1 text-sm text-gray-500">Update internal employee profile and role assignment.</p>
</div> </div>
<A class="btn" href="/admin/employees">Back to Employees</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/employees">Back to Employees</A>
</div> </div>
<Show when={error()}> <Show when={error()}>
<div class="error-box">{error()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
</Show> </Show>
<Show when={employee.loading}> <Show when={employee.loading}>
<div class="card"><p class="notice">Loading employee...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading employee...</p></div>
</Show> </Show>
<Show when={!employee.loading && !employee()}> <Show when={!employee.loading && !employee()}>
<div class="card"><p class="notice">Employee not found.</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Employee not found.</p></div>
</Show> </Show>
<Show when={employee()}> <Show when={employee()}>
<form class="card" onSubmit={submit}> <form class="rounded-xl border border-gray-200 bg-white shadow-sm" onSubmit={submit}>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label>Full Name</label> <label>Full Name</label>
<input value={name()} onInput={(e) => setName(e.currentTarget.value)} /> <input value={name()} onInput={(e) => setName(e.currentTarget.value)} />
@ -120,8 +120,8 @@ export default function EditEmployeePage() {
</div> </div>
</div> </div>
<div class="actions" style="justify-content:flex-end"> <div class="actions" style="justify-content:flex-end">
<A class="btn" href="/admin/employees">Cancel</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/employees">Cancel</A>
<button class="btn navy" type="submit" disabled={saving()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
{saving() ? 'Saving...' : 'Save Changes'} {saving() ? 'Saving...' : 'Save Changes'}
</button> </button>
</div> </div>

View file

@ -127,7 +127,7 @@ function renderModuleContent(module: Module | null) {
<textarea rows={4} placeholder="Describe the request" style="width:100%;border:1px solid #cbd5e1;border-radius:12px;padding:10px 12px;font-size:13px;outline:none" /> <textarea rows={4} placeholder="Describe the request" style="width:100%;border:1px solid #cbd5e1;border-radius:12px;padding:10px 12px;font-size:13px;outline:none" />
</div> </div>
<div style="display:flex;justify-content:flex-end;margin-top:12px"> <div style="display:flex;justify-content:flex-end;margin-top:12px">
<button class="btn navy" type="button">Submit</button> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="button">Submit</button>
</div> </div>
</div> </div>
); );
@ -454,36 +454,36 @@ export default function ExternalDashboardManagementPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">External Dashboard Management</h1> <h1 class="text-2xl font-bold text-gray-900">External Dashboard Management</h1>
<p class="page-subtitle">Open one external dashboard at a time from the list below and edit it using simple tabs.</p> <p class="mt-1 text-sm text-gray-500">Open one external dashboard at a time from the list below and edit it using simple tabs.</p>
</div> </div>
<a class="btn" href="/admin">Back to Dashboard</a> <a class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin">Back to Dashboard</a>
</div> </div>
<div class="admin-link-tabs" style="margin-bottom:14px"> <div class="hidden" style="margin-bottom:14px">
<a class="admin-link-tab active" href="#builder">View Dashboards</a> <a class="hidden" href="#builder">View Dashboards</a>
<a class="admin-link-tab" href="#builder">Open Builder</a> <a class="hidden" href="#builder">Open Builder</a>
</div> </div>
<div id="builder" /> <div id="builder" />
{/* ---------- List View ---------- */} {/* ---------- List View ---------- */}
<Show when={!selected()}> <Show when={!selected()}>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h2 class="page-title" style="font-size:20px">External Dashboard List</h2> <h2 class="text-2xl font-bold text-gray-900" style="font-size:20px">External Dashboard List</h2>
<p class="page-subtitle">Choose one external role dashboard to open in the builder, or create a new one.</p> <p class="mt-1 text-sm text-gray-500">Choose one external role dashboard to open in the builder, or create a new one.</p>
</div> </div>
<button class="btn primary" onClick={createDashboard} disabled={creating()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={createDashboard} disabled={creating()}>
{creating() ? 'Creating...' : 'Create External Dashboard'} {creating() ? 'Creating...' : 'Create External Dashboard'}
</button> </button>
</div> </div>
<Show when={error()}><div class="error-box">{error()}</div></Show> <Show when={error()}><div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div></Show>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table list-table-soft-head"> <table class="w-full text-sm list-table-soft-head">
<thead> <thead>
<tr> <tr>
<th>Role</th> <th>Role</th>
@ -491,7 +491,7 @@ export default function ExternalDashboardManagementPage() {
<th>Status</th> <th>Status</th>
<th>Version</th> <th>Version</th>
<th>Modules</th> <th>Modules</th>
<th class="align-right">Action</th> <th class="text-right">Action</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -506,12 +506,12 @@ export default function ExternalDashboardManagementPage() {
<tr> <tr>
<td style="font-weight:600;color:#0f172a">{d.roleKey || 'No role selected'}</td> <td style="font-weight:600;color:#0f172a">{d.roleKey || 'No role selected'}</td>
<td style="color:#475569">{d.title}</td> <td style="color:#475569">{d.title}</td>
<td><span class={`status-chip ${d.status === 'published' ? 'active' : ''}`}>{d.status}</span></td> <td><span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${d.status === 'published' ? 'active' : ''}`}>{d.status}</span></td>
<td style="color:#64748b">v{d.version}</td> <td style="color:#64748b">v{d.version}</td>
<td style="color:#64748b">{d.modules.length} pages</td> <td style="color:#64748b">{d.modules.length} pages</td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<button class="btn" onClick={() => void openDashboard(d.id)}>View Builder</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => void openDashboard(d.id)}>View Builder</button>
</div> </div>
</td> </td>
</tr> </tr>
@ -531,14 +531,14 @@ export default function ExternalDashboardManagementPage() {
<p>Edit menu labels, page names, and opening page for this role dashboard.</p> <p>Edit menu labels, page names, and opening page for this role dashboard.</p>
</div> </div>
<div class="builder-header-actions"> <div class="builder-header-actions">
<button class="btn" onClick={() => setSelectedId('')}>Back to List</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => setSelectedId('')}>Back to List</button>
<button class="btn primary" onClick={saveSelected} disabled={saving()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={saveSelected} disabled={saving()}>
{saving() ? 'Saving...' : 'Save Dashboard'} {saving() ? 'Saving...' : 'Save Dashboard'}
</button> </button>
</div> </div>
</div> </div>
<Show when={error()}><div class="error-box">{error()}</div></Show> <Show when={error()}><div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div></Show>
{/* Tab bar */} {/* Tab bar */}
<div class="builder-tab-bar"> <div class="builder-tab-bar">
@ -551,7 +551,7 @@ export default function ExternalDashboardManagementPage() {
{/* Overview */} {/* Overview */}
<Show when={activeTab() === 'overview'}> <Show when={activeTab() === 'overview'}>
<div class="card"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
<div class="field"> <div class="field">
<label>Role</label> <label>Role</label>
<select <select
@ -592,7 +592,7 @@ export default function ExternalDashboardManagementPage() {
<div class="builder-section"> <div class="builder-section">
<div class="sub-card-header"> <div class="sub-card-header">
<h4>Menu Items</h4> <h4>Menu Items</h4>
<button class="btn orange" onClick={addSidebarItem}>Add Menu Item</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors orange" onClick={addSidebarItem}>Add Menu Item</button>
</div> </div>
<For each={selected()!.sidebar}> <For each={selected()!.sidebar}>
{(item, idx) => ( {(item, idx) => (
@ -624,7 +624,7 @@ export default function ExternalDashboardManagementPage() {
/> />
Show Show
</label> </label>
<button class="btn danger" onClick={() => removeSidebarItem(item.key)}>Remove</button> <button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" onClick={() => removeSidebarItem(item.key)}>Remove</button>
</div> </div>
)} )}
</For> </For>
@ -639,7 +639,7 @@ export default function ExternalDashboardManagementPage() {
<div class="builder-section"> <div class="builder-section">
<div class="sub-card-header"> <div class="sub-card-header">
<h4>Pages</h4> <h4>Pages</h4>
<button class="btn orange" onClick={addModule}>Add Page</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors orange" onClick={addModule}>Add Page</button>
</div> </div>
<For each={selected()!.modules}> <For each={selected()!.modules}>
{(module) => ( {(module) => (
@ -675,7 +675,7 @@ export default function ExternalDashboardManagementPage() {
<textarea rows={2} value={module.summary} onInput={(e) => update({ modules: selected()!.modules.map((m) => m.key === module.key ? { ...m, summary: e.currentTarget.value } : m) })} /> <textarea rows={2} value={module.summary} onInput={(e) => update({ modules: selected()!.modules.map((m) => m.key === module.key ? { ...m, summary: e.currentTarget.value } : m) })} />
</div> </div>
<div style="display:flex;justify-content:flex-end"> <div style="display:flex;justify-content:flex-end">
<button class="btn danger" onClick={() => removeModule(module.key)}>Remove Page</button> <button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" onClick={() => removeModule(module.key)}>Remove Page</button>
</div> </div>
</div> </div>
)} )}
@ -719,7 +719,7 @@ export default function ExternalDashboardManagementPage() {
> >
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:10px"> <div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:10px">
<span class="meta-chip">Role: {livePreviewRoleKey()}</span> <span class="meta-chip">Role: {livePreviewRoleKey()}</span>
<a class="btn" href={livePreviewUrl()} target="_blank" rel="noreferrer">Open Full Page Preview</a> <a class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={livePreviewUrl()} target="_blank" rel="noreferrer">Open Full Page Preview</a>
</div> </div>
<iframe <iframe
src={livePreviewUrl()} src={livePreviewUrl()}

View file

@ -7,7 +7,7 @@ export default function ExternalRoleManagementAliasPage() {
onMount(() => navigate('/admin/runtime-roles', { replace: true })); onMount(() => navigate('/admin/runtime-roles', { replace: true }));
return ( return (
<AdminShell> <AdminShell>
<div class="card"><p class="notice">Redirecting to external role management...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to external role management...</p></div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -5,5 +5,5 @@ import AdminShell from '~/components/AdminShell';
export default function AdjustCreditAliasPage() { export default function AdjustCreditAliasPage() {
const navigate = useNavigate(); const navigate = useNavigate();
onMount(() => navigate('/admin/credit', { replace: true })); onMount(() => navigate('/admin/credit', { replace: true }));
return <AdminShell><div class="card"><p class="notice">Redirecting to credit management...</p></div></AdminShell>; return <AdminShell><div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to credit management...</p></div></AdminShell>;
} }

View file

@ -5,5 +5,5 @@ import AdminShell from '~/components/AdminShell';
export default function ReconcileAliasPage() { export default function ReconcileAliasPage() {
const navigate = useNavigate(); const navigate = useNavigate();
onMount(() => navigate('/admin/ledger', { replace: true })); onMount(() => navigate('/admin/ledger', { replace: true }));
return <AdminShell><div class="card"><p class="notice">Redirecting to ledger management...</p></div></AdminShell>; return <AdminShell><div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to ledger management...</p></div></AdminShell>;
} }

View file

@ -36,14 +36,14 @@ export default function FitnessTrainersPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Fitness Trainer Management</h1> <h1 class="text-2xl font-bold text-gray-900">Fitness Trainer Management</h1>
<p class="page-subtitle">Manage all fitness trainer accounts on the platform.</p> <p class="mt-1 text-sm text-gray-500">Manage all fitness trainer accounts on the platform.</p>
</div> </div>
</div> </div>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;"> <div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
<input <input
type="text" type="text"
@ -64,15 +64,15 @@ export default function FitnessTrainersPage() {
</select> </select>
</div> </div>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Email</th> <th>Email</th>
<th>Status</th> <th>Status</th>
<th>Registered</th> <th>Registered</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -93,22 +93,22 @@ export default function FitnessTrainersPage() {
<td style="color:#475569">{item.email}</td> <td style="color:#475569">{item.email}</td>
<td> <td>
{item.status?.toUpperCase() === 'ACTIVE' && ( {item.status?.toUpperCase() === 'ACTIVE' && (
<span class="status-chip active">ACTIVE</span> <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'INACTIVE' && ( {item.status?.toUpperCase() === 'INACTIVE' && (
<span class="status-chip">INACTIVE</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'PENDING' && ( {item.status?.toUpperCase() === 'PENDING' && (
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
)} )}
{!item.status && <span class="status-chip"></span>} {!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600"></span>}
</td> </td>
<td style="color:#475569"> <td style="color:#475569">
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'} {item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/users/${item.id}`}>View</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -36,14 +36,14 @@ export default function GraphicDesignersPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Graphic Designer Management</h1> <h1 class="text-2xl font-bold text-gray-900">Graphic Designer Management</h1>
<p class="page-subtitle">Manage all graphic designer accounts on the platform.</p> <p class="mt-1 text-sm text-gray-500">Manage all graphic designer accounts on the platform.</p>
</div> </div>
</div> </div>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;"> <div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
<input <input
type="text" type="text"
@ -64,15 +64,15 @@ export default function GraphicDesignersPage() {
</select> </select>
</div> </div>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Email</th> <th>Email</th>
<th>Status</th> <th>Status</th>
<th>Registered</th> <th>Registered</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -93,22 +93,22 @@ export default function GraphicDesignersPage() {
<td style="color:#475569">{item.email}</td> <td style="color:#475569">{item.email}</td>
<td> <td>
{item.status?.toUpperCase() === 'ACTIVE' && ( {item.status?.toUpperCase() === 'ACTIVE' && (
<span class="status-chip active">ACTIVE</span> <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'INACTIVE' && ( {item.status?.toUpperCase() === 'INACTIVE' && (
<span class="status-chip">INACTIVE</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'PENDING' && ( {item.status?.toUpperCase() === 'PENDING' && (
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
)} )}
{!item.status && <span class="status-chip"></span>} {!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600"></span>}
</td> </td>
<td style="color:#475569"> <td style="color:#475569">
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'} {item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/users/${item.id}`}>View</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -7,7 +7,7 @@ export default function HelpAliasPage() {
onMount(() => navigate('/admin/support', { replace: true })); onMount(() => navigate('/admin/support', { replace: true }));
return ( return (
<AdminShell> <AdminShell>
<div class="card"><p class="notice">Redirecting to support management...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to support management...</p></div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -5,14 +5,14 @@ export default function HelpArticlePage() {
const params = useParams(); const params = useParams();
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Help Article</h1> <h1 class="text-2xl font-bold text-gray-900">Help Article</h1>
<p class="page-subtitle">Legacy help article route preserved for migration compatibility.</p> <p class="mt-1 text-sm text-gray-500">Legacy help article route preserved for migration compatibility.</p>
</div> </div>
<A class="btn" href="/admin/support">Back to Support</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/support">Back to Support</A>
</div> </div>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<p class="notice" style="margin:0"> <p class="notice" style="margin:0">
Article ID: <strong>{params.id}</strong> Article ID: <strong>{params.id}</strong>
</p> </p>

View file

@ -5,14 +5,14 @@ export default function HelpSupportBridgePage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card"> <div class="page-hero-card">
<h1 class="page-title">Support Bridge</h1> <h1 class="text-2xl font-bold text-gray-900">Support Bridge</h1>
<p class="page-subtitle" style="margin-top:8px"> <p class="mt-1 text-sm text-gray-500" style="margin-top:8px">
This legacy help bridge now routes through the unified Support Management module. This legacy help bridge now routes through the unified Support Management module.
</p> </p>
</div> </div>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<div class="actions"> <div class="actions">
<A class="btn navy" href="/admin/support">Open Support Management</A> <A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/support">Open Support Management</A>
</div> </div>
</section> </section>
</AdminShell> </AdminShell>

View file

@ -1,25 +1,165 @@
import { A } from '@solidjs/router'; import { A } from '@solidjs/router';
import AdminShell from '~/components/AdminShell'; import AdminShell from '~/components/AdminShell';
import {
Users, Building2, Briefcase, Clock, Ticket, Receipt,
Package, Megaphone, Shield, UserCog, FormInput, LayoutDashboard,
BadgeCheck, ChevronRight, TrendingUp,
} from 'lucide-solid';
import type { Component } from 'solid-js';
export default function AdminIndex() { type KpiCard = {
label: string;
value: string;
href: string;
icon: Component<any>;
bg: string;
fg: string;
};
type ControlLink = {
label: string;
href: string;
desc: string;
icon: Component<any>;
accent: string;
};
const KPI: KpiCard[] = [
{ label: 'Total Users', value: '—', href: '/admin/users', icon: Users, bg: 'bg-blue-50', fg: 'text-blue-600' },
{ label: 'Companies', value: '—', href: '/admin/company', icon: Building2, bg: 'bg-violet-50', fg: 'text-violet-600' },
{ label: 'Open Jobs', value: '—', href: '/admin/jobs', icon: Briefcase, bg: 'bg-amber-50', fg: 'text-amber-600' },
{ label: 'Pending Approvals', value: '—', href: '/admin/approval', icon: Clock, bg: 'bg-orange-50', fg: 'text-orange-600' },
{ label: 'Open Tickets', value: '—', href: '/admin/support', icon: Ticket, bg: 'bg-rose-50', fg: 'text-rose-600' },
{ label: 'Invoices', value: '—', href: '/admin/invoice', icon: Receipt, bg: 'bg-teal-50', fg: 'text-teal-600' },
{ label: 'Active Orders', value: '—', href: '/admin/order', icon: Package, bg: 'bg-sky-50', fg: 'text-sky-600' },
{ label: 'Active Leads', value: '—', href: '/admin/leads', icon: Megaphone, bg: 'bg-pink-50', fg: 'text-pink-600' },
];
const CONTROLS: ControlLink[] = [
{
label: 'Internal Roles',
href: '/admin/roles',
desc: 'Define permissions and access levels for internal staff roles.',
icon: Shield,
accent: 'bg-blue-50 text-blue-600',
},
{
label: 'External Roles',
href: '/admin/runtime-roles',
desc: 'Configure modules, credit rules, and capabilities per external role.',
icon: UserCog,
accent: 'bg-violet-50 text-violet-600',
},
{
label: 'Onboarding Flows',
href: '/admin/onboarding-schemas',
desc: 'Build schema-driven onboarding forms for each external role.',
icon: FormInput,
accent: 'bg-amber-50 text-amber-600',
},
{
label: 'External Dashboards',
href: '/admin/external-dashboard-management',
desc: 'Configure the runtimeConfig, sidebar, and widgets per external role.',
icon: LayoutDashboard,
accent: 'bg-teal-50 text-teal-600',
},
{
label: 'Internal Dashboards',
href: '/admin/internal-dashboard-management',
desc: 'Design home widgets and KPI panels for internal staff dashboards.',
icon: LayoutDashboard,
accent: 'bg-orange-50 text-orange-600',
},
{
label: 'Approval Queue',
href: '/admin/approval',
desc: 'Review, approve, or reject pending platform action requests.',
icon: BadgeCheck,
accent: 'bg-rose-50 text-rose-600',
},
];
export default function AdminDashboard() {
return ( return (
<AdminShell> <AdminShell>
<h1 class="page-title">Admin Builder</h1> <div class="space-y-8">
<p class="page-subtitle">Pick what you want to configure.</p>
<section class="card"> {/* ── Page header ── */}
<div class="actions"> <div class="flex items-center justify-between">
<A class="btn primary" href="/admin/runtime-roles/new">Create Role</A> <div>
<A class="btn" href="/admin/runtime-roles">Manage Roles</A> <h1 class="text-[22px] font-bold tracking-tight text-gray-900">Platform Overview</h1>
<p class="mt-0.5 text-sm text-gray-400">Real-time snapshot of the Nxtgauge platform.</p>
</div>
<div class="flex items-center gap-2 rounded-xl border border-gray-200 bg-white px-3 py-1.5 shadow-sm">
<TrendingUp class="h-4 w-4 text-emerald-500" />
<span class="text-xs font-medium text-gray-500">Live data</span>
</div>
</div> </div>
<div class="actions">
<A class="btn primary" href="/admin/role-ui-configs/new">Create Dashboard</A> {/* ── KPI grid ── */}
<A class="btn" href="/admin/role-ui-configs">Manage Dashboards</A> <div class="grid grid-cols-2 gap-4 lg:grid-cols-4">
{KPI.map((kpi) => {
const Icon = kpi.icon;
return (
<A
href={kpi.href}
class="group relative overflow-hidden rounded-2xl border border-gray-100 bg-white p-5 shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:border-gray-200 hover:shadow-md"
>
{/* Subtle background circle */}
<div class="absolute -right-4 -top-4 h-20 w-20 rounded-full bg-gray-50 opacity-60" />
<div class="relative flex flex-col gap-4">
{/* Icon */}
<div class={`flex h-10 w-10 items-center justify-center rounded-xl ${kpi.bg}`}>
<Icon class={`h-5 w-5 ${kpi.fg}`} />
</div>
{/* Metric */}
<div>
<p class="text-[28px] font-bold leading-none tracking-tight text-gray-900">{kpi.value}</p>
<p class="mt-1.5 text-[13px] font-medium text-gray-400">{kpi.label}</p>
</div>
</div>
{/* Hover arrow */}
<ChevronRight class="absolute bottom-4 right-4 h-4 w-4 text-gray-300 opacity-0 transition-opacity group-hover:opacity-100" />
</A>
);
})}
</div> </div>
<div class="actions">
<A class="btn primary" href="/admin/onboarding-schemas/new">Create Onboarding</A> {/* ── Control Plane ── */}
<A class="btn" href="/admin/onboarding-schemas">Manage Onboarding</A> <div>
<div class="mb-4 flex items-center justify-between">
<h2 class="text-[15px] font-semibold text-gray-700">Control Plane</h2>
<span class="rounded-full border border-gray-200 bg-white px-2.5 py-0.5 text-[11px] font-medium text-gray-400 shadow-sm">
{CONTROLS.length} modules
</span>
</div>
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
{CONTROLS.map((item) => {
const Icon = item.icon;
return (
<A
href={item.href}
class="group flex items-start gap-4 rounded-2xl border border-gray-100 bg-white p-5 shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:border-orange-100 hover:shadow-md"
>
<div class={`mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-xl ${item.accent}`}>
<Icon class="h-4 w-4" />
</div>
<div class="min-w-0 flex-1">
<p class="text-[14px] font-semibold text-gray-800 group-hover:text-[#fd6216] transition-colors">{item.label}</p>
<p class="mt-0.5 text-[12px] leading-relaxed text-gray-400">{item.desc}</p>
</div>
<ChevronRight class="mt-1 h-4 w-4 shrink-0 text-gray-300 transition-transform group-hover:translate-x-0.5 group-hover:text-orange-400" />
</A>
);
})}
</div>
</div> </div>
</section>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -413,35 +413,35 @@ export default function InternalDashboardManagementPage() {
// ---------- List view ---------- // ---------- List view ----------
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Internal Dashboard Management</h1> <h1 class="text-2xl font-bold text-gray-900">Internal Dashboard Management</h1>
<p class="page-subtitle">Open one internal dashboard at a time from the list below and edit it using simple tabs.</p> <p class="mt-1 text-sm text-gray-500">Open one internal dashboard at a time from the list below and edit it using simple tabs.</p>
</div> </div>
<a class="btn" href="/admin">Back to Dashboard</a> <a class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin">Back to Dashboard</a>
</div> </div>
<div class="admin-link-tabs" style="margin-bottom:14px"> <div class="hidden" style="margin-bottom:14px">
<a class="admin-link-tab active" href="#builder">View Dashboards</a> <a class="hidden" href="#builder">View Dashboards</a>
<a class="admin-link-tab" href="#builder">Open Builder</a> <a class="hidden" href="#builder">Open Builder</a>
</div> </div>
<div id="builder" /> <div id="builder" />
<Show when={!selected()}> <Show when={!selected()}>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h2 class="page-title" style="font-size:20px">Internal Dashboard List</h2> <h2 class="text-2xl font-bold text-gray-900" style="font-size:20px">Internal Dashboard List</h2>
<p class="page-subtitle">Choose one internal dashboard to open in the builder, or create a new dashboard for an internal role.</p> <p class="mt-1 text-sm text-gray-500">Choose one internal dashboard to open in the builder, or create a new dashboard for an internal role.</p>
</div> </div>
<button class="btn navy" onClick={createDashboard} disabled={creating()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={createDashboard} disabled={creating()}>
{creating() ? 'Creating...' : 'Create Internal Dashboard'} {creating() ? 'Creating...' : 'Create Internal Dashboard'}
</button> </button>
</div> </div>
<Show when={error()}><div class="error-box">{error()}</div></Show> <Show when={error()}><div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div></Show>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table list-table-soft-head"> <table class="w-full text-sm list-table-soft-head">
<thead> <thead>
<tr> <tr>
<th>Role</th> <th>Role</th>
@ -449,7 +449,7 @@ export default function InternalDashboardManagementPage() {
<th>Dashboard</th> <th>Dashboard</th>
<th>Status</th> <th>Status</th>
<th>Version</th> <th>Version</th>
<th class="align-right">Action</th> <th class="text-right">Action</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -465,11 +465,11 @@ export default function InternalDashboardManagementPage() {
<td style="color:#475569">{d.roleName || 'Not linked'}</td> <td style="color:#475569">{d.roleName || 'Not linked'}</td>
<td style="color:#475569">{d.roleId || 'Not linked'}</td> <td style="color:#475569">{d.roleId || 'Not linked'}</td>
<td style="font-weight:600;color:#0f172a">{d.title}</td> <td style="font-weight:600;color:#0f172a">{d.title}</td>
<td><span class={`status-chip ${d.status === 'published' ? 'active' : ''}`}>{d.status}</span></td> <td><span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${d.status === 'published' ? 'active' : ''}`}>{d.status}</span></td>
<td style="color:#64748b">v{d.version}</td> <td style="color:#64748b">v{d.version}</td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<button class="btn" onClick={() => void openDashboard(d.id)}>View Builder</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => void openDashboard(d.id)}>View Builder</button>
</div> </div>
</td> </td>
</tr> </tr>
@ -489,14 +489,14 @@ export default function InternalDashboardManagementPage() {
<p>Manage menu items, sections, tabs, form fields, and summary cards from one place.</p> <p>Manage menu items, sections, tabs, form fields, and summary cards from one place.</p>
</div> </div>
<div class="builder-header-actions"> <div class="builder-header-actions">
<button class="btn" onClick={() => setSelectedId('')}>Back to List</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => setSelectedId('')}>Back to List</button>
<button class="btn primary" onClick={saveSelected} disabled={saving()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={saveSelected} disabled={saving()}>
{saving() ? 'Saving...' : 'Save Dashboard'} {saving() ? 'Saving...' : 'Save Dashboard'}
</button> </button>
</div> </div>
</div> </div>
<Show when={error()}><div class="error-box">{error()}</div></Show> <Show when={error()}><div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div></Show>
{/* Tab bar */} {/* Tab bar */}
<div class="builder-tab-bar"> <div class="builder-tab-bar">
@ -513,7 +513,7 @@ export default function InternalDashboardManagementPage() {
{/* Overview */} {/* Overview */}
<Show when={activeTab() === 'overview'}> <Show when={activeTab() === 'overview'}>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label>Team Role</label> <label>Team Role</label>
<select <select
@ -548,7 +548,7 @@ export default function InternalDashboardManagementPage() {
<div class="builder-section"> <div class="builder-section">
<div class="sub-card-header"> <div class="sub-card-header">
<h4>Menu</h4> <h4>Menu</h4>
<button class="btn orange" onClick={addSidebarItem}>Add Menu Item</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors orange" onClick={addSidebarItem}>Add Menu Item</button>
</div> </div>
<For each={selected()!.sidebar}> <For each={selected()!.sidebar}>
{(item, idx) => ( {(item, idx) => (
@ -572,7 +572,7 @@ export default function InternalDashboardManagementPage() {
/> />
Show Show
</label> </label>
<button class="btn danger" onClick={() => removeSidebarItem(item.key)}>Remove</button> <button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" onClick={() => removeSidebarItem(item.key)}>Remove</button>
</div> </div>
)} )}
</For> </For>
@ -586,7 +586,7 @@ export default function InternalDashboardManagementPage() {
<Show when={activeTab() === 'sections'}> <Show when={activeTab() === 'sections'}>
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px"> <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px">
<h3 style="margin:0;font-size:15px;font-weight:700">Sections</h3> <h3 style="margin:0;font-size:15px;font-weight:700">Sections</h3>
<button class="btn orange" onClick={addSection}>Add Section</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors orange" onClick={addSection}>Add Section</button>
</div> </div>
<For each={selected()!.sections}> <For each={selected()!.sections}>
{(section) => ( {(section) => (
@ -597,14 +597,14 @@ export default function InternalDashboardManagementPage() {
onInput={(e) => updateSection(section.id, { title: e.currentTarget.value })} onInput={(e) => updateSection(section.id, { title: e.currentTarget.value })}
placeholder="Section title" placeholder="Section title"
/> />
<button class="btn danger" onClick={() => removeSection(section.id)}>Remove</button> <button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" onClick={() => removeSection(section.id)}>Remove</button>
</div> </div>
{/* Tabs */} {/* Tabs */}
<div class="sub-card"> <div class="sub-card">
<div class="sub-card-header"> <div class="sub-card-header">
<h4>Tabs and Form Fields</h4> <h4>Tabs and Form Fields</h4>
<button class="btn orange" onClick={() => addTab(section.id)}>Add Tab</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors orange" onClick={() => addTab(section.id)}>Add Tab</button>
</div> </div>
<For each={section.tabs}> <For each={section.tabs}>
{(tab) => ( {(tab) => (
@ -615,10 +615,10 @@ export default function InternalDashboardManagementPage() {
onInput={(e) => updateTab(section.id, tab.id, { title: e.currentTarget.value })} onInput={(e) => updateTab(section.id, tab.id, { title: e.currentTarget.value })}
placeholder="Tab title" placeholder="Tab title"
/> />
<button class="btn danger" onClick={() => removeTab(section.id, tab.id)}>Remove</button> <button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" onClick={() => removeTab(section.id, tab.id)}>Remove</button>
</div> </div>
<div style="display:flex;justify-content:flex-end;margin-bottom:8px"> <div style="display:flex;justify-content:flex-end;margin-bottom:8px">
<button class="btn orange" onClick={() => addField(section.id, tab.id)}>Add Field</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors orange" onClick={() => addField(section.id, tab.id)}>Add Field</button>
</div> </div>
<For each={tab.fields}> <For each={tab.fields}>
{(field) => ( {(field) => (
@ -651,7 +651,7 @@ export default function InternalDashboardManagementPage() {
/> />
Required Required
</label> </label>
<button class="btn danger" onClick={() => removeField(section.id, tab.id, field.id)}>Remove</button> <button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" onClick={() => removeField(section.id, tab.id, field.id)}>Remove</button>
</div> </div>
)} )}
</For> </For>
@ -670,7 +670,7 @@ export default function InternalDashboardManagementPage() {
<div class="sub-card" style="margin-top:8px"> <div class="sub-card" style="margin-top:8px">
<div class="sub-card-header"> <div class="sub-card-header">
<h4>Widgets</h4> <h4>Widgets</h4>
<button class="btn orange" onClick={() => addWidget(section.id)}>Add Widget</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors orange" onClick={() => addWidget(section.id)}>Add Widget</button>
</div> </div>
<For each={section.widgets}> <For each={section.widgets}>
{(widget) => ( {(widget) => (
@ -679,7 +679,7 @@ export default function InternalDashboardManagementPage() {
<input value={widget.metric} onInput={(e) => updateWidget(section.id, widget.id, { metric: e.currentTarget.value })} placeholder="Metric value e.g. 42" /> <input value={widget.metric} onInput={(e) => updateWidget(section.id, widget.id, { metric: e.currentTarget.value })} placeholder="Metric value e.g. 42" />
<textarea rows={2} value={widget.description || ''} onInput={(e) => updateWidget(section.id, widget.id, { description: e.currentTarget.value })} placeholder="Widget description" /> <textarea rows={2} value={widget.description || ''} onInput={(e) => updateWidget(section.id, widget.id, { description: e.currentTarget.value })} placeholder="Widget description" />
<div style="display:flex;justify-content:flex-end"> <div style="display:flex;justify-content:flex-end">
<button class="btn danger" style="font-size:12px;padding:5px 10px" onClick={() => removeWidget(section.id, widget.id)}>Remove Widget</button> <button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" style="font-size:12px;padding:5px 10px" onClick={() => removeWidget(section.id, widget.id)}>Remove Widget</button>
</div> </div>
</div> </div>
)} )}
@ -728,7 +728,7 @@ export default function InternalDashboardManagementPage() {
<Show when={selected()!.roleId}> <Show when={selected()!.roleId}>
<span class="meta-chip">Role ID: {selected()!.roleId}</span> <span class="meta-chip">Role ID: {selected()!.roleId}</span>
</Show> </Show>
<a class="btn" href={livePreviewUrl()} target="_blank" rel="noreferrer">Open Full Page Preview</a> <a class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={livePreviewUrl()} target="_blank" rel="noreferrer">Open Full Page Preview</a>
</div> </div>
<iframe <iframe
src={livePreviewUrl()} src={livePreviewUrl()}

View file

@ -7,7 +7,7 @@ export default function InternalRoleManagementAliasPage() {
onMount(() => navigate('/admin/roles', { replace: true })); onMount(() => navigate('/admin/roles', { replace: true }));
return ( return (
<AdminShell> <AdminShell>
<div class="card"><p class="notice">Redirecting to internal role management...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to internal role management...</p></div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -32,10 +32,10 @@ export default function InvoicePage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Invoice Management</h1> <h1 class="text-2xl font-bold text-gray-900">Invoice Management</h1>
<p class="page-subtitle">View and download all platform invoices.</p> <p class="mt-1 text-sm text-gray-500">View and download all platform invoices.</p>
</div> </div>
</div> </div>
@ -49,9 +49,9 @@ export default function InvoicePage() {
/> />
</div> </div>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Invoice #</th> <th>Invoice #</th>
@ -61,7 +61,7 @@ export default function InvoicePage() {
<th>Tax ()</th> <th>Tax ()</th>
<th>Status</th> <th>Status</th>
<th>Date</th> <th>Date</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -83,16 +83,16 @@ export default function InvoicePage() {
<td style="color:#475569">{item.total != null ? (item.total / 100).toFixed(2) : '—'}</td> <td style="color:#475569">{item.total != null ? (item.total / 100).toFixed(2) : '—'}</td>
<td style="color:#475569">{item.tax != null ? (item.tax / 100).toFixed(2) : '—'}</td> <td style="color:#475569">{item.tax != null ? (item.tax / 100).toFixed(2) : '—'}</td>
<td> <td>
<span class={`status-chip ${item.status === 'PAID' || item.status === 'ISSUED' ? 'active' : ''}`}> <span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${item.status === 'PAID' || item.status === 'ISSUED' ? 'active' : ''}`}>
{item.status || '—'} {item.status || '—'}
</span> </span>
</td> </td>
<td style="color:#475569">{item.created_at ? new Date(item.created_at).toLocaleString() : '—'}</td> <td style="color:#475569">{item.created_at ? new Date(item.created_at).toLocaleString() : '—'}</td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<Show when={item.download_url || item.pdf_url}> <Show when={item.download_url || item.pdf_url}>
<a <a
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
href={item.download_url || item.pdf_url} href={item.download_url || item.pdf_url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"

View file

@ -63,10 +63,10 @@ export default function JobsPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Jobs Management</h1> <h1 class="text-2xl font-bold text-gray-900">Jobs Management</h1>
<p class="page-subtitle">Review live company job postings</p> <p class="mt-1 text-sm text-gray-500">Review live company job postings</p>
</div> </div>
</div> </div>
@ -87,9 +87,9 @@ export default function JobsPage() {
</select> </select>
</div> </div>
<section class="card" style="padding:0;overflow:hidden"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Title</th> <th>Title</th>
@ -98,7 +98,7 @@ export default function JobsPage() {
<th>Rate</th> <th>Rate</th>
<th>Location</th> <th>Location</th>
<th>Status</th> <th>Status</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -137,10 +137,10 @@ export default function JobsPage() {
<span class={statusChipClass(job.status)}>{job.status || '—'}</span> <span class={statusChipClass(job.status)}>{job.status || '—'}</span>
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/jobs/${job.id}`}>View</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/jobs/${job.id}`}>View</A>
<Show when={job.status === 'PENDING_APPROVAL'}> <Show when={job.status === 'PENDING_APPROVAL'}>
<A class="btn" href="/admin/approval">Review</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/approval">Review</A>
</Show> </Show>
</div> </div>
</td> </td>

View file

@ -51,25 +51,25 @@ export default function JobDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Job Management</h1> <h1 class="text-2xl font-bold text-gray-900">Job Management</h1>
<p class="page-subtitle">Review one live backend job in the same detail-first style as other admin modules.</p> <p class="mt-1 text-sm text-gray-500">Review one live backend job in the same detail-first style as other admin modules.</p>
</div> </div>
<A class="btn" href="/admin/jobs">Back to Jobs</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/jobs">Back to Jobs</A>
</div> </div>
<Show when={job.loading}> <Show when={job.loading}>
<div class="card"><p class="notice">Loading job...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading job...</p></div>
</Show> </Show>
<Show when={!job.loading && !job()}> <Show when={!job.loading && !job()}>
<div class="card"><p class="notice">Job not found.</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Job not found.</p></div>
</Show> </Show>
<Show when={job()}> <Show when={job()}>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div> <div>
<p class="hint">Title</p> <p class="hint">Title</p>
<p style="margin:6px 0 0;font-weight:700;color:#0f172a">{job()!.title || '—'}</p> <p style="margin:6px 0 0;font-weight:700;color:#0f172a">{job()!.title || '—'}</p>

View file

@ -215,10 +215,10 @@ export default function KbPage(props: KbPageProps = {}) {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Knowledge Base</h1> <h1 class="text-2xl font-bold text-gray-900">Knowledge Base</h1>
<p class="page-subtitle">Manage help articles and categories</p> <p class="mt-1 text-sm text-gray-500">Manage help articles and categories</p>
</div> </div>
</div> </div>
@ -249,22 +249,22 @@ export default function KbPage(props: KbPageProps = {}) {
{/* Categories Tab */} {/* Categories Tab */}
<Show when={tab() === 'categories'}> <Show when={tab() === 'categories'}>
<div class="page-actions" style="margin-bottom:16px"> <div class="mb-6 flex items-start justify-between gap-4" style="margin-bottom:16px">
<div /> <div />
<button class="btn navy" onClick={() => setShowCatForm(!showCatForm())}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={() => setShowCatForm(!showCatForm())}>
{showCatForm() ? 'Cancel' : 'Add Category'} {showCatForm() ? 'Cancel' : 'Add Category'}
</button> </button>
</div> </div>
<Show when={actionError()}> <Show when={actionError()}>
<div class="error-box" style="margin-bottom:12px">{actionError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{actionError()}</div>
</Show> </Show>
<Show when={showCatForm()}> <Show when={showCatForm()}>
<section class="card" style="margin-bottom:16px;max-width:480px"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px;max-width:480px">
<h2 style="margin:0 0 16px;font-size:15px;font-weight:700">New Category</h2> <h2 style="margin:0 0 16px;font-size:15px;font-weight:700">New Category</h2>
<Show when={catError()}> <Show when={catError()}>
<div class="error-box" style="margin-bottom:10px">{catError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:10px">{catError()}</div>
</Show> </Show>
<form onSubmit={handleAddCategory} style="display:flex;flex-direction:column;gap:12px"> <form onSubmit={handleAddCategory} style="display:flex;flex-direction:column;gap:12px">
<div class="field"> <div class="field">
@ -300,7 +300,7 @@ export default function KbPage(props: KbPageProps = {}) {
/> />
</div> </div>
<div> <div>
<button class="btn navy" type="submit" disabled={catSaving()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={catSaving()}>
{catSaving() ? 'Saving...' : 'Create Category'} {catSaving() ? 'Saving...' : 'Create Category'}
</button> </button>
</div> </div>
@ -308,15 +308,15 @@ export default function KbPage(props: KbPageProps = {}) {
</section> </section>
</Show> </Show>
<section class="card" style="padding:0;overflow:hidden"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Slug</th> <th>Slug</th>
<th>Article Count</th> <th>Article Count</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -338,10 +338,10 @@ export default function KbPage(props: KbPageProps = {}) {
<td style="color:#475569;font-family:monospace;font-size:13px">{cat.slug}</td> <td style="color:#475569;font-family:monospace;font-size:13px">{cat.slug}</td>
<td style="color:#475569">{cat.article_count ?? '—'}</td> <td style="color:#475569">{cat.article_count ?? '—'}</td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<button class="btn" onClick={() => startEditCat(cat)}>Edit</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => startEditCat(cat)}>Edit</button>
<button <button
class="btn danger" class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
disabled={deletingCatId() === cat.id} disabled={deletingCatId() === cat.id}
onClick={() => deleteCategory(cat.id, cat.name)} onClick={() => deleteCategory(cat.id, cat.name)}
> >
@ -354,7 +354,7 @@ export default function KbPage(props: KbPageProps = {}) {
<tr> <tr>
<td colspan="4" style="background:#f8fafc;padding:14px"> <td colspan="4" style="background:#f8fafc;padding:14px">
<Show when={editCatError()}> <Show when={editCatError()}>
<div class="error-box" style="margin-bottom:8px">{editCatError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:8px">{editCatError()}</div>
</Show> </Show>
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end"> <div style="display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end">
<div class="field"> <div class="field">
@ -376,10 +376,10 @@ export default function KbPage(props: KbPageProps = {}) {
/> />
</div> </div>
<div style="display:flex;gap:8px"> <div style="display:flex;gap:8px">
<button class="btn navy" disabled={editCatSaving()} onClick={() => saveEditCat(cat.id)}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={editCatSaving()} onClick={() => saveEditCat(cat.id)}>
{editCatSaving() ? 'Saving...' : 'Save'} {editCatSaving() ? 'Saving...' : 'Save'}
</button> </button>
<button class="btn" onClick={cancelEditCat}>Cancel</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={cancelEditCat}>Cancel</button>
</div> </div>
</div> </div>
</td> </td>
@ -398,7 +398,7 @@ export default function KbPage(props: KbPageProps = {}) {
{/* Articles Tab */} {/* Articles Tab */}
<Show when={tab() === 'articles'}> <Show when={tab() === 'articles'}>
<Show when={articleActionError()}> <Show when={articleActionError()}>
<div class="error-box" style="margin-bottom:12px">{articleActionError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{articleActionError()}</div>
</Show> </Show>
<div style="margin-bottom:16px"> <div style="margin-bottom:16px">
@ -411,16 +411,16 @@ export default function KbPage(props: KbPageProps = {}) {
/> />
</div> </div>
<section class="card" style="padding:0;overflow:hidden"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Title</th> <th>Title</th>
<th>Category</th> <th>Category</th>
<th>Status</th> <th>Status</th>
<th>Updated At</th> <th>Updated At</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -440,7 +440,7 @@ export default function KbPage(props: KbPageProps = {}) {
<td style="font-weight:600;color:#0f172a">{article.title}</td> <td style="font-weight:600;color:#0f172a">{article.title}</td>
<td style="color:#475569">{article.category || '—'}</td> <td style="color:#475569">{article.category || '—'}</td>
<td> <td>
<span class={`status-chip ${article.status === 'PUBLISHED' ? 'active' : 'draft'}`}> <span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${article.status === 'PUBLISHED' ? 'active' : 'draft'}`}>
{article.status || '—'} {article.status || '—'}
</span> </span>
</td> </td>
@ -448,10 +448,10 @@ export default function KbPage(props: KbPageProps = {}) {
{article.updated_at ? new Date(article.updated_at).toLocaleString() : '—'} {article.updated_at ? new Date(article.updated_at).toLocaleString() : '—'}
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/kb/articles/${article.id}/edit`}>Edit</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/kb/articles/${article.id}/edit`}>Edit</A>
<button <button
class="btn danger" class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
disabled={deletingArticleId() === article.id} disabled={deletingArticleId() === article.id}
onClick={() => deleteArticle(article.id, article.title)} onClick={() => deleteArticle(article.id, article.title)}
> >
@ -471,10 +471,10 @@ export default function KbPage(props: KbPageProps = {}) {
{/* Create Article Tab */} {/* Create Article Tab */}
<Show when={tab() === 'create-article'}> <Show when={tab() === 'create-article'}>
<section class="card" style="max-width:640px"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:640px">
<h2 style="margin:0 0 20px;font-size:16px;font-weight:700">New Article</h2> <h2 style="margin:0 0 20px;font-size:16px;font-weight:700">New Article</h2>
<Show when={artError()}> <Show when={artError()}>
<div class="error-box" style="margin-bottom:14px">{artError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:14px">{artError()}</div>
</Show> </Show>
<form onSubmit={handleCreateArticle} style="display:flex;flex-direction:column;gap:14px"> <form onSubmit={handleCreateArticle} style="display:flex;flex-direction:column;gap:14px">
<div class="field"> <div class="field">
@ -536,7 +536,7 @@ export default function KbPage(props: KbPageProps = {}) {
</select> </select>
</div> </div>
<div> <div>
<button class="btn navy" type="submit" disabled={artSaving()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={artSaving()}>
{artSaving() ? 'Creating...' : 'Create Article'} {artSaving() ? 'Creating...' : 'Create Article'}
</button> </button>
</div> </div>

View file

@ -33,30 +33,30 @@ export default function KbArticleDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">KB Article Detail</h1> <h1 class="text-2xl font-bold text-gray-900">KB Article Detail</h1>
<p class="page-subtitle">Metadata and safe content preview for this article.</p> <p class="mt-1 text-sm text-gray-500">Metadata and safe content preview for this article.</p>
</div> </div>
<div class="page-actions-right"> <div class="flex items-center gap-2">
<A class="btn" href="/admin/kb/articles">Back to Articles</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/kb/articles">Back to Articles</A>
<A class="btn primary" href={`/admin/kb/articles/${params.id}/edit`}>Edit Article</A> <A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/kb/articles/${params.id}/edit`}>Edit Article</A>
</div> </div>
</div> </div>
<Show when={article.loading}> <Show when={article.loading}>
<section class="card"><p class="notice">Loading article...</p></section> <section class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading article...</p></section>
</Show> </Show>
<Show when={!article.loading && !article()}> <Show when={!article.loading && !article()}>
<section class="card"><p class="notice">Article not found.</p></section> <section class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Article not found.</p></section>
</Show> </Show>
<Show when={article()}> <Show when={article()}>
<div class="detail-layout"> <div class="detail-layout">
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h2 style="margin-bottom:10px">Metadata</h2> <h2 style="margin-bottom:10px">Metadata</h2>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="kv-item"><p class="kv-label">Title</p><p class="kv-value">{article()!.title || '—'}</p></div> <div class="kv-item"><p class="kv-label">Title</p><p class="kv-value">{article()!.title || '—'}</p></div>
<div class="kv-item"><p class="kv-label">Status</p><p class="kv-value">{article()!.status || '—'}</p></div> <div class="kv-item"><p class="kv-label">Status</p><p class="kv-value">{article()!.status || '—'}</p></div>
<div class="kv-item"><p class="kv-label">Slug</p><p class="kv-value">{article()!.slug || '—'}</p></div> <div class="kv-item"><p class="kv-label">Slug</p><p class="kv-value">{article()!.slug || '—'}</p></div>
@ -66,7 +66,7 @@ export default function KbArticleDetailPage() {
</div> </div>
</section> </section>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h2 style="margin-bottom:10px">Content</h2> <h2 style="margin-bottom:10px">Content</h2>
<pre class="json" style="max-height:720px;white-space:pre-wrap">{article()!.content || article()!.body || 'No content'}</pre> <pre class="json" style="max-height:720px;white-space:pre-wrap">{article()!.content || article()!.body || 'No content'}</pre>
</section> </section>

View file

@ -75,28 +75,28 @@ export default function KbArticleEditPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Edit KB Article</h1> <h1 class="text-2xl font-bold text-gray-900">Edit KB Article</h1>
<p class="page-subtitle">Update article metadata, status, and content.</p> <p class="mt-1 text-sm text-gray-500">Update article metadata, status, and content.</p>
</div> </div>
<div class="page-actions-right"> <div class="flex items-center gap-2">
<A class="btn" href={`/admin/kb/articles/${params.id}`}>Back to Detail</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/kb/articles/${params.id}`}>Back to Detail</A>
<A class="btn" href="/admin/kb/articles">Back to Articles</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/kb/articles">Back to Articles</A>
</div> </div>
</div> </div>
<Show when={article.loading}> <Show when={article.loading}>
<section class="card"><p class="notice">Loading article...</p></section> <section class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading article...</p></section>
</Show> </Show>
<Show when={!article.loading && !article()}> <Show when={!article.loading && !article()}>
<section class="card"><p class="notice">Article not found.</p></section> <section class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Article not found.</p></section>
</Show> </Show>
<Show when={article() && loaded()}> <Show when={article() && loaded()}>
<form class="card" onSubmit={save}> <form class="rounded-xl border border-gray-200 bg-white shadow-sm" onSubmit={save}>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label>Title</label> <label>Title</label>
<input value={title()} onInput={(e) => setTitle(e.currentTarget.value)} required /> <input value={title()} onInput={(e) => setTitle(e.currentTarget.value)} required />
@ -127,7 +127,7 @@ export default function KbArticleEditPage() {
</Show> </Show>
<div class="actions" style="justify-content:flex-end"> <div class="actions" style="justify-content:flex-end">
<button class="btn primary" type="submit" disabled={saving()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
{saving() ? 'Saving...' : 'Save Article'} {saving() ? 'Saving...' : 'Save Article'}
</button> </button>
</div> </div>

View file

@ -49,10 +49,10 @@ export default function LeadsPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Leads Management</h1> <h1 class="text-2xl font-bold text-gray-900">Leads Management</h1>
<p class="page-subtitle">View all requirements and lead requests from customers.</p> <p class="mt-1 text-sm text-gray-500">View all requirements and lead requests from customers.</p>
</div> </div>
</div> </div>
@ -92,9 +92,9 @@ export default function LeadsPage() {
</Show> </Show>
</div> </div>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Title</th> <th>Title</th>
@ -102,7 +102,7 @@ export default function LeadsPage() {
<th>Budget</th> <th>Budget</th>
<th>Location</th> <th>Location</th>
<th>Status</th> <th>Status</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -133,13 +133,13 @@ export default function LeadsPage() {
</td> </td>
<td style="color:#475569">{item.location || '—'}</td> <td style="color:#475569">{item.location || '—'}</td>
<td> <td>
<span class={`status-chip ${(item.status === 'ACTIVE' || item.status === 'OPEN') ? 'active' : ''}`}> <span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${(item.status === 'ACTIVE' || item.status === 'OPEN') ? 'active' : ''}`}>
{item.status || '—'} {item.status || '—'}
</span> </span>
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/leads/${item.id}`}>View</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/leads/${item.id}`}>View</A>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -73,24 +73,24 @@ export default function LeadDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Lead Detail</h1> <h1 class="text-2xl font-bold text-gray-900">Lead Detail</h1>
<p class="page-subtitle">Review one lead and its linked requirement identifiers.</p> <p class="mt-1 text-sm text-gray-500">Review one lead and its linked requirement identifiers.</p>
</div> </div>
<A class="btn" href="/admin/leads">Back to Leads</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/leads">Back to Leads</A>
</div> </div>
<Show when={bundle.loading}> <Show when={bundle.loading}>
<div class="card"><p class="notice">Loading lead...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading lead...</p></div>
</Show> </Show>
<Show when={!bundle.loading && !lead()}> <Show when={!bundle.loading && !lead()}>
<div class="card"><p class="notice">Lead not found.</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Lead not found.</p></div>
</Show> </Show>
<Show when={lead()}> <Show when={lead()}>
<section class="card" style="padding:20px"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:20px">
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="kv-item"> <div class="kv-item">
<p class="kv-label">Lead ID</p> <p class="kv-label">Lead ID</p>
<p class="kv-value">{lead()!.id}</p> <p class="kv-value">{lead()!.id}</p>
@ -128,7 +128,7 @@ export default function LeadDetailPage() {
</div> </div>
<div class="actions"> <div class="actions">
<Show when={requirementId()}> <Show when={requirementId()}>
<A class="btn" href={`/admin/jobs/${requirementId()}`}>Open Linked Requirement/Job</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/jobs/${requirementId()}`}>Open Linked Requirement/Job</A>
</Show> </Show>
</div> </div>
@ -139,7 +139,7 @@ export default function LeadDetailPage() {
</div> </div>
<p style="margin:0;font-size:17px;font-weight:700;color:#111827">{requirement()!.title || 'Untitled Requirement'}</p> <p style="margin:0;font-size:17px;font-weight:700;color:#111827">{requirement()!.title || 'Untitled Requirement'}</p>
<p class="notice" style="margin-top:8px">{requirement()!.description || 'No description available.'}</p> <p class="notice" style="margin-top:8px">{requirement()!.description || 'No description available.'}</p>
<div class="field-grid-2" style="margin-top:12px"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2" style="margin-top:12px">
<div class="kv-item"> <div class="kv-item">
<p class="kv-label">Customer</p> <p class="kv-label">Customer</p>
<p class="kv-value">{requirement()!.customerName || 'Customer'}</p> <p class="kv-value">{requirement()!.customerName || 'Customer'}</p>

View file

@ -56,16 +56,16 @@ export default function LedgerPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Ledger Management</h1> <h1 class="text-2xl font-bold text-gray-900">Ledger Management</h1>
<p class="page-subtitle">Platform financial ledger</p> <p class="mt-1 text-sm text-gray-500">Platform financial ledger</p>
</div> </div>
</div> </div>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Type</th> <th>Type</th>

View file

@ -36,14 +36,14 @@ export default function MakeupArtistPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Makeup Artist Management</h1> <h1 class="text-2xl font-bold text-gray-900">Makeup Artist Management</h1>
<p class="page-subtitle">Manage all makeup artist accounts on the platform.</p> <p class="mt-1 text-sm text-gray-500">Manage all makeup artist accounts on the platform.</p>
</div> </div>
</div> </div>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;"> <div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
<input <input
type="text" type="text"
@ -64,15 +64,15 @@ export default function MakeupArtistPage() {
</select> </select>
</div> </div>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Email</th> <th>Email</th>
<th>Status</th> <th>Status</th>
<th>Registered</th> <th>Registered</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -93,22 +93,22 @@ export default function MakeupArtistPage() {
<td style="color:#475569">{item.email}</td> <td style="color:#475569">{item.email}</td>
<td> <td>
{item.status?.toUpperCase() === 'ACTIVE' && ( {item.status?.toUpperCase() === 'ACTIVE' && (
<span class="status-chip active">ACTIVE</span> <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'INACTIVE' && ( {item.status?.toUpperCase() === 'INACTIVE' && (
<span class="status-chip">INACTIVE</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'PENDING' && ( {item.status?.toUpperCase() === 'PENDING' && (
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
)} )}
{!item.status && <span class="status-chip"></span>} {!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600"></span>}
</td> </td>
<td style="color:#475569"> <td style="color:#475569">
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'} {item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/users/${item.id}`}>View</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -107,32 +107,32 @@ export default function ModulesPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Module Registry</h1> <h1 class="text-2xl font-bold text-gray-900">Module Registry</h1>
<p class="page-subtitle">Manage internal module definitions and activation state.</p> <p class="mt-1 text-sm text-gray-500">Manage internal module definitions and activation state.</p>
</div> </div>
<button class="btn primary" onClick={() => openModal()}>Add Module</button> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={() => openModal()}>Add Module</button>
</div> </div>
<Show when={error() && !isModalOpen()}> <Show when={error() && !isModalOpen()}>
<div class="card"><p class="error-note">{error()}</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="error-note">{error()}</p></div>
</Show> </Show>
<Show when={modules.loading}> <Show when={modules.loading}>
<div class="card"><p class="notice">Loading modules...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading modules...</p></div>
</Show> </Show>
<div class="card"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Key</th> <th>Key</th>
<th>Description</th> <th>Description</th>
<th>Status</th> <th>Status</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -150,10 +150,10 @@ export default function ModulesPage() {
{item.isActive ? 'Active' : 'Inactive'} {item.isActive ? 'Active' : 'Inactive'}
</span> </span>
</td> </td>
<td class="align-right"> <td class="text-right">
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<button class="btn" onClick={() => openModal(item)}>Edit</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => openModal(item)}>Edit</button>
<button class="btn danger" onClick={() => removeModule(item.id)}>Delete</button> <button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" onClick={() => removeModule(item.id)}>Delete</button>
</div> </div>
</td> </td>
</tr> </tr>
@ -211,8 +211,8 @@ export default function ModulesPage() {
</Show> </Show>
<div class="actions" style="justify-content:flex-end"> <div class="actions" style="justify-content:flex-end">
<button type="button" class="btn" onClick={closeModal}>Cancel</button> <button type="button" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={closeModal}>Cancel</button>
<button type="submit" class="btn primary" disabled={submitting()}> <button type="submit" class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={submitting()}>
{editing() ? 'Save Changes' : 'Create'} {editing() ? 'Save Changes' : 'Create'}
</button> </button>
</div> </div>

View file

@ -83,13 +83,13 @@ export default function NotificationsPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Notifications</h1> <h1 class="text-2xl font-bold text-gray-900">Notifications</h1>
<p class="page-subtitle">Approval outcomes and action-required updates</p> <p class="mt-1 text-sm text-gray-500">Approval outcomes and action-required updates</p>
</div> </div>
<button <button
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
onClick={handleMarkAllRead} onClick={handleMarkAllRead}
disabled={markingAllRead()} disabled={markingAllRead()}
> >
@ -115,16 +115,16 @@ export default function NotificationsPage() {
</button> </button>
</div> </div>
<section class="card" style="padding:0;overflow:hidden"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Title</th> <th>Title</th>
<th>Message</th> <th>Message</th>
<th>Event Type</th> <th>Event Type</th>
<th>Created At</th> <th>Created At</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -148,10 +148,10 @@ export default function NotificationsPage() {
{item.created_at ? new Date(item.created_at).toLocaleString() : '—'} {item.created_at ? new Date(item.created_at).toLocaleString() : '—'}
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<Show when={item.read_at === null}> <Show when={item.read_at === null}>
<button <button
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
disabled={markingId() === item.id} disabled={markingId() === item.id}
onClick={() => handleMarkRead(item.id)} onClick={() => handleMarkRead(item.id)}
> >
@ -175,7 +175,7 @@ export default function NotificationsPage() {
<Show when={cursor() && !loading()}> <Show when={cursor() && !loading()}>
<div style="margin-top:12px"> <div style="margin-top:12px">
<button <button
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
onClick={() => load(cursor(), false)} onClick={() => load(cursor(), false)}
> >
Load More Load More

View file

@ -7,7 +7,7 @@ export default function OnboardingManagementAliasPage() {
onMount(() => navigate('/admin/onboarding-schemas', { replace: true })); onMount(() => navigate('/admin/onboarding-schemas', { replace: true }));
return ( return (
<AdminShell> <AdminShell>
<div class="card"><p class="notice">Redirecting to onboarding management...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to onboarding management...</p></div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -135,34 +135,34 @@ export default function OnboardingSchemaDetailPage() {
<AdminShell> <AdminShell>
<OnboardingManagementTabs /> <OnboardingManagementTabs />
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Onboarding Management</h1> <h1 class="text-2xl font-bold text-gray-900">Onboarding Management</h1>
<p class="page-subtitle">Open one onboarding form at a time, check if it is published, then update the role, questions, steps, and final success message.</p> <p class="mt-1 text-sm text-gray-500">Open one onboarding form at a time, check if it is published, then update the role, questions, steps, and final success message.</p>
</div> </div>
<div class="page-actions-right"> <div class="flex items-center gap-2">
<button class="btn" type="button" disabled={saving()} onClick={() => void persist(true)}> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" disabled={saving()} onClick={() => void persist(true)}>
Save Active Version Save Active Version
</button> </button>
<A class="btn" href="/admin/onboarding-schemas">Back to Onboarding Management</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/onboarding-schemas">Back to Onboarding Management</A>
</div> </div>
</div> </div>
<Show when={schema.loading}> <Show when={schema.loading}>
<div class="card"><p class="notice">Loading onboarding flow...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading onboarding flow...</p></div>
</Show> </Show>
<Show when={!schema.loading && !schema()}> <Show when={!schema.loading && !schema()}>
<div class="card"><p class="notice">Onboarding flow not found.</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Onboarding flow not found.</p></div>
</Show> </Show>
<Show when={schema() && loaded()}> <Show when={schema() && loaded()}>
<> <>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;margin-bottom:16px"> <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;margin-bottom:16px">
<div class="card"><p class="kv-label">Status</p><p class="kv-value">{schema()?.is_active ? 'PUBLISHED' : 'DRAFT'}</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="kv-label">Status</p><p class="kv-value">{schema()?.is_active ? 'PUBLISHED' : 'DRAFT'}</p></div>
<div class="card"><p class="kv-label">Version</p><p class="kv-value">{schema()?.schema_json?.version || 1}</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="kv-label">Version</p><p class="kv-value">{schema()?.schema_json?.version || 1}</p></div>
<div class="card"><p class="kv-label">Steps</p><p class="kv-value">{stepCount()}</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="kv-label">Steps</p><p class="kv-value">{stepCount()}</p></div>
<div class="card"><p class="kv-label">Questions</p><p class="kv-value">{selectedFields().length}</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="kv-label">Questions</p><p class="kv-value">{selectedFields().length}</p></div>
</div> </div>
<OnboardingFlowBuilder <OnboardingFlowBuilder

View file

@ -93,29 +93,29 @@ export default function OnboardingSchemasPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Onboarding Management</h1> <h1 class="text-2xl font-bold text-gray-900">Onboarding Management</h1>
<p class="page-subtitle">Manage onboarding flows, role assignments, and previewable step groups for external users.</p> <p class="mt-1 text-sm text-gray-500">Manage onboarding flows, role assignments, and previewable step groups for external users.</p>
</div> </div>
<A class="btn navy" href="/admin/onboarding-schemas/new">Create Onboarding Flow</A> <A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/onboarding-schemas/new">Create Onboarding Flow</A>
</div> </div>
<OnboardingManagementTabs /> <OnboardingManagementTabs />
<Show when={deleteError()}> <Show when={deleteError()}>
<div class="error-box">{deleteError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{deleteError()}</div>
</Show> </Show>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div style="display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #e2e8f0"> <div style="display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #e2e8f0">
<h2 style="margin:0;font-size:17px;font-weight:700">Onboarding Flows</h2> <h2 style="margin:0;font-size:17px;font-weight:700">Onboarding Flows</h2>
<Show when={!schemas.loading}> <Show when={!schemas.loading}>
<span style="font-size:13px;color:#64748b">{schemas()?.length || 0} flows</span> <span style="font-size:13px;color:#64748b">{schemas()?.length || 0} flows</span>
</Show> </Show>
</div> </div>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Flow</th> <th>Flow</th>
@ -123,7 +123,7 @@ export default function OnboardingSchemasPage() {
<th>Steps</th> <th>Steps</th>
<th>Version</th> <th>Version</th>
<th>Status</th> <th>Status</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -144,13 +144,13 @@ export default function OnboardingSchemasPage() {
<td style="color:#475569">{schema.stepCount}</td> <td style="color:#475569">{schema.stepCount}</td>
<td style="color:#475569">v{schema.version}</td> <td style="color:#475569">v{schema.version}</td>
<td> <td>
<span class={`status-chip ${schema.status === 'PUBLISHED' ? 'active' : ''}`}>{schema.status}</span> <span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${schema.status === 'PUBLISHED' ? 'active' : ''}`}>{schema.status}</span>
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="action-icon-btn" href={`/admin/onboarding-schemas/${schema.roleId || schema.id}`} title="Open Flow">👁</A> <A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/onboarding-schemas/${schema.roleId || schema.id}`} title="Open Flow">👁</A>
<button <button
class="action-icon-btn danger" class="rounded p-1.5 text-red-500 hover:bg-red-50 hover:text-red-700 text-sm"
disabled={deleting() === schema.id} disabled={deleting() === schema.id}
onClick={() => handleDelete(schema.id, schema.title)} onClick={() => handleDelete(schema.id, schema.title)}
title="Delete Flow" title="Delete Flow"

View file

@ -107,18 +107,18 @@ export default function NewOnboardingSchemaPage() {
<AdminShell> <AdminShell>
<OnboardingManagementTabs /> <OnboardingManagementTabs />
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Create Onboarding Flow</h1> <h1 class="text-2xl font-bold text-gray-900">Create Onboarding Flow</h1>
<p class="page-subtitle">Create one onboarding form at a time. Pick the role, choose the questions, set the steps, and write the final success message.</p> <p class="mt-1 text-sm text-gray-500">Create one onboarding form at a time. Pick the role, choose the questions, set the steps, and write the final success message.</p>
</div> </div>
<A class="btn" href="/admin/onboarding-schemas">Back to Onboarding Management</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/onboarding-schemas">Back to Onboarding Management</A>
</div> </div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;margin-bottom:16px"> <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;margin-bottom:16px">
<div class="card"><p class="kv-label">Role</p><p class="kv-value">{roleKey().replace(/_/g, ' ').toUpperCase()}</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="kv-label">Role</p><p class="kv-value">{roleKey().replace(/_/g, ' ').toUpperCase()}</p></div>
<div class="card"><p class="kv-label">Steps</p><p class="kv-value">{stepCount()}</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="kv-label">Steps</p><p class="kv-value">{stepCount()}</p></div>
<div class="card"><p class="kv-label">Questions</p><p class="kv-value">{selectedFields().length}</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="kv-label">Questions</p><p class="kv-value">{selectedFields().length}</p></div>
</div> </div>
<OnboardingFlowBuilder <OnboardingFlowBuilder

View file

@ -70,10 +70,10 @@ export default function OrderPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Order Management</h1> <h1 class="text-2xl font-bold text-gray-900">Order Management</h1>
<p class="page-subtitle">TraceCoin package purchase orders</p> <p class="mt-1 text-sm text-gray-500">TraceCoin package purchase orders</p>
</div> </div>
</div> </div>
@ -87,9 +87,9 @@ export default function OrderPage() {
/> />
</div> </div>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Order #</th> <th>Order #</th>
@ -124,7 +124,7 @@ export default function OrderPage() {
<td style="color:#475569">{formatAmount(item)}</td> <td style="color:#475569">{formatAmount(item)}</td>
<td> <td>
<span <span
class="status-chip" class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600"
style={`${statusStyle(item.status)};padding:2px 10px;border-radius:999px;font-size:12px;font-weight:600;border:1px solid`} style={`${statusStyle(item.status)};padding:2px 10px;border-radius:999px;font-size:12px;font-weight:600;border:1px solid`}
> >
{item.status || '—'} {item.status || '—'}

View file

@ -36,14 +36,14 @@ export default function PhotographerPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Photographer Management</h1> <h1 class="text-2xl font-bold text-gray-900">Photographer Management</h1>
<p class="page-subtitle">Manage all photographer accounts on the platform.</p> <p class="mt-1 text-sm text-gray-500">Manage all photographer accounts on the platform.</p>
</div> </div>
</div> </div>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;"> <div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
<input <input
type="text" type="text"
@ -64,15 +64,15 @@ export default function PhotographerPage() {
</select> </select>
</div> </div>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Email</th> <th>Email</th>
<th>Status</th> <th>Status</th>
<th>Registered</th> <th>Registered</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -93,22 +93,22 @@ export default function PhotographerPage() {
<td style="color:#475569">{item.email}</td> <td style="color:#475569">{item.email}</td>
<td> <td>
{item.status?.toUpperCase() === 'ACTIVE' && ( {item.status?.toUpperCase() === 'ACTIVE' && (
<span class="status-chip active">ACTIVE</span> <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'INACTIVE' && ( {item.status?.toUpperCase() === 'INACTIVE' && (
<span class="status-chip">INACTIVE</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'PENDING' && ( {item.status?.toUpperCase() === 'PENDING' && (
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
)} )}
{!item.status && <span class="status-chip"></span>} {!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600"></span>}
</td> </td>
<td style="color:#475569"> <td style="color:#475569">
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'} {item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/photographer/${item.id}`}>View</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/photographer/${item.id}`}>View</A>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -50,27 +50,27 @@ export default function PhotographerDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Photographer Detail</h1> <h1 class="text-2xl font-bold text-gray-900">Photographer Detail</h1>
<p class="page-subtitle">View profile snapshot and account metadata for one photographer.</p> <p class="mt-1 text-sm text-gray-500">View profile snapshot and account metadata for one photographer.</p>
</div> </div>
<div class="page-actions-right"> <div class="flex items-center gap-2">
<A class="btn" href="/admin/photographer">Back to Photographer List</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/photographer">Back to Photographer List</A>
<A class="btn" href={`/admin/users/details/${params.id}`}>Open User Detail</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/details/${params.id}`}>Open User Detail</A>
</div> </div>
</div> </div>
<Show when={profile.loading}> <Show when={profile.loading}>
<div class="card"><p class="notice">Loading photographer...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading photographer...</p></div>
</Show> </Show>
<Show when={!profile.loading && !profile()}> <Show when={!profile.loading && !profile()}>
<div class="card"><p class="notice">Photographer not found.</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Photographer not found.</p></div>
</Show> </Show>
<Show when={profile()}> <Show when={profile()}>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="kv-item"> <div class="kv-item">
<p class="kv-label">Name</p> <p class="kv-label">Name</p>
<p class="kv-value">{name()}</p> <p class="kv-value">{name()}</p>

View file

@ -150,10 +150,10 @@ export default function PricingPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Pricing Management</h1> <h1 class="text-2xl font-bold text-gray-900">Pricing Management</h1>
<p class="page-subtitle">Create and manage TraceCoin packages</p> <p class="mt-1 text-sm text-gray-500">Create and manage TraceCoin packages</p>
</div> </div>
</div> </div>
@ -177,9 +177,9 @@ export default function PricingPage() {
{/* Packages list tab */} {/* Packages list tab */}
<Show when={view() === 'packages'}> <Show when={view() === 'packages'}>
<section class="card" style="padding:0;overflow:hidden"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
@ -188,7 +188,7 @@ export default function PricingPage() {
<th>Price ()</th> <th>Price ()</th>
<th>Bonus (%)</th> <th>Bonus (%)</th>
<th>Status</th> <th>Status</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -212,15 +212,15 @@ export default function PricingPage() {
<td style="color:#475569">{(pkg.price_inr / 100).toFixed(2)}</td> <td style="color:#475569">{(pkg.price_inr / 100).toFixed(2)}</td>
<td style="color:#475569">{pkg.bonus_percentage != null ? `${pkg.bonus_percentage}%` : '—'}</td> <td style="color:#475569">{pkg.bonus_percentage != null ? `${pkg.bonus_percentage}%` : '—'}</td>
<td> <td>
<span class={`status-chip ${pkg.is_active ? 'active' : ''}`}> <span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${pkg.is_active ? 'active' : ''}`}>
{pkg.is_active ? 'Active' : 'Inactive'} {pkg.is_active ? 'Active' : 'Inactive'}
</span> </span>
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<button class="btn" onClick={() => startEdit(pkg)}>Edit</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => startEdit(pkg)}>Edit</button>
<button <button
class={pkg.is_active ? 'btn danger' : 'btn navy'} class={pkg.is_active ? 'inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors' : 'inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors'}
disabled={togglingId() === pkg.id} disabled={togglingId() === pkg.id}
onClick={() => toggleActive(pkg)} onClick={() => toggleActive(pkg)}
> >
@ -233,7 +233,7 @@ export default function PricingPage() {
<tr> <tr>
<td colspan="7" style="background:#f8fafc;padding:16px"> <td colspan="7" style="background:#f8fafc;padding:16px">
<Show when={editError()}> <Show when={editError()}>
<div class="error-box" style="margin-bottom:10px">{editError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:10px">{editError()}</div>
</Show> </Show>
<div style="display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end"> <div style="display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end">
<div class="field"> <div class="field">
@ -264,10 +264,10 @@ export default function PricingPage() {
/> />
</div> </div>
<div style="display:flex;gap:8px"> <div style="display:flex;gap:8px">
<button class="btn navy" disabled={editSaving()} onClick={() => saveEdit(pkg.id)}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={editSaving()} onClick={() => saveEdit(pkg.id)}>
{editSaving() ? 'Saving...' : 'Save'} {editSaving() ? 'Saving...' : 'Save'}
</button> </button>
<button class="btn" onClick={cancelEdit}>Cancel</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={cancelEdit}>Cancel</button>
</div> </div>
</div> </div>
</td> </td>
@ -285,10 +285,10 @@ export default function PricingPage() {
{/* Create Package tab */} {/* Create Package tab */}
<Show when={view() === 'create'}> <Show when={view() === 'create'}>
<section class="card" style="max-width:480px"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:480px">
<h2 style="margin:0 0 20px;font-size:16px;font-weight:700">New Package</h2> <h2 style="margin:0 0 20px;font-size:16px;font-weight:700">New Package</h2>
<Show when={cError()}> <Show when={cError()}>
<div class="error-box" style="margin-bottom:14px">{cError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:14px">{cError()}</div>
</Show> </Show>
<form onSubmit={handleCreate} style="display:flex;flex-direction:column;gap:14px"> <form onSubmit={handleCreate} style="display:flex;flex-direction:column;gap:14px">
<div class="field"> <div class="field">
@ -349,7 +349,7 @@ export default function PricingPage() {
/> />
</div> </div>
<div> <div>
<button class="btn navy" type="submit" disabled={cSaving()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={cSaving()}>
{cSaving() ? 'Creating...' : 'Create Package'} {cSaving() ? 'Creating...' : 'Create Package'}
</button> </button>
</div> </div>

View file

@ -6,5 +6,5 @@ export default function ProfileAliasPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const params = useParams(); const params = useParams();
onMount(() => navigate(`/admin/users/details/${params.id}`, { replace: true })); onMount(() => navigate(`/admin/users/details/${params.id}`, { replace: true }));
return <AdminShell><div class="card"><p class="notice">Redirecting to user profile detail...</p></div></AdminShell>; return <AdminShell><div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to user profile detail...</p></div></AdminShell>;
} }

View file

@ -46,14 +46,14 @@ export default function ReportPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Report Management</h1> <h1 class="text-2xl font-bold text-gray-900">Report Management</h1>
<p class="page-subtitle">View platform analytics and generate reports.</p> <p class="mt-1 text-sm text-gray-500">View platform analytics and generate reports.</p>
</div> </div>
</div> </div>
<section class="card" style="margin-bottom:16px"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px">
<h2 style="margin:0 0 16px;font-size:16px;font-weight:700">Date Range</h2> <h2 style="margin:0 0 16px;font-size:16px;font-weight:700">Date Range</h2>
<form onSubmit={handleLoad} style="display:flex;align-items:flex-end;gap:12px;flex-wrap:wrap"> <form onSubmit={handleLoad} style="display:flex;align-items:flex-end;gap:12px;flex-wrap:wrap">
<div> <div>
@ -76,40 +76,40 @@ export default function ReportPage() {
style="padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px" style="padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px"
/> />
</div> </div>
<button class="btn navy" type="submit" disabled={loading()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={loading()}>
{loading() ? 'Loading...' : 'Load Report'} {loading() ? 'Loading...' : 'Load Report'}
</button> </button>
</form> </form>
<Show when={error()}> <Show when={error()}>
<div class="error-box" style="margin-top:12px">{error()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-top:12px">{error()}</div>
</Show> </Show>
</section> </section>
<Show when={userReport() || revenueReport()}> <Show when={userReport() || revenueReport()}>
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;margin-bottom:16px"> <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;margin-bottom:16px">
<div class="card" style="text-align:center"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="text-align:center">
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">Total Users</p> <p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">Total Users</p>
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{userReport()?.total_users ?? '—'}</p> <p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{userReport()?.total_users ?? '—'}</p>
</div> </div>
<div class="card" style="text-align:center"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="text-align:center">
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">New Users</p> <p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">New Users</p>
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{userReport()?.new_users ?? '—'}</p> <p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{userReport()?.new_users ?? '—'}</p>
</div> </div>
<div class="card" style="text-align:center"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="text-align:center">
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">Active Users</p> <p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">Active Users</p>
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{userReport()?.active_users ?? '—'}</p> <p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{userReport()?.active_users ?? '—'}</p>
</div> </div>
<div class="card" style="text-align:center"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="text-align:center">
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">Total Revenue</p> <p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">Total Revenue</p>
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a"> <p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">
{revenueReport()?.total_revenue != null ? `${(revenueReport()!.total_revenue! / 100).toFixed(2)}` : ''} {revenueReport()?.total_revenue != null ? `${(revenueReport()!.total_revenue! / 100).toFixed(2)}` : ''}
</p> </p>
</div> </div>
<div class="card" style="text-align:center"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="text-align:center">
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">Total Orders</p> <p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">Total Orders</p>
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{revenueReport()?.total_orders ?? '—'}</p> <p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{revenueReport()?.total_orders ?? '—'}</p>
</div> </div>
<div class="card" style="text-align:center"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="text-align:center">
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">TraceCoins Sold</p> <p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">TraceCoins Sold</p>
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{revenueReport()?.total_tracecoins_sold ?? '—'}</p> <p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{revenueReport()?.total_tracecoins_sold ?? '—'}</p>
</div> </div>

View file

@ -40,25 +40,25 @@ export default function RequirementDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Requirement Request</h1> <h1 class="text-2xl font-bold text-gray-900">Requirement Request</h1>
<p class="page-subtitle">Review full requirement request details before approval action.</p> <p class="mt-1 text-sm text-gray-500">Review full requirement request details before approval action.</p>
</div> </div>
<A class="btn" href="/admin/approval">Back to Approval Management</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/approval">Back to Approval Management</A>
</div> </div>
<Show when={requirement.loading}> <Show when={requirement.loading}>
<div class="card"><p class="notice">Loading requirement...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading requirement...</p></div>
</Show> </Show>
<Show when={!requirement.loading && !requirement()}> <Show when={!requirement.loading && !requirement()}>
<div class="card"><p class="notice">Requirement not found.</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Requirement not found.</p></div>
</Show> </Show>
<Show when={requirement()}> <Show when={requirement()}>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div> <div>
<p class="hint">Title</p> <p class="hint">Title</p>
<p style="margin:6px 0 0;font-weight:700;color:#0f172a">{requirement()!.title || '—'}</p> <p style="margin:6px 0 0;font-weight:700;color:#0f172a">{requirement()!.title || '—'}</p>

View file

@ -77,18 +77,18 @@ export default function ResponsesPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card"> <div class="page-hero-card">
<h1 class="page-title">Responses</h1> <h1 class="text-2xl font-bold text-gray-900">Responses</h1>
<p class="page-subtitle">Track professional responses and move them through shortlist, accept, or reject states.</p> <p class="mt-1 text-sm text-gray-500">Track professional responses and move them through shortlist, accept, or reject states.</p>
</div> </div>
<Show when={payload.loading}> <Show when={payload.loading}>
<div class="card"><p class="notice">Loading responses...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading responses...</p></div>
</Show> </Show>
<Show when={error()}> <Show when={error()}>
<div class="card"><p class="error-note">{error()}</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="error-note">{error()}</p></div>
</Show> </Show>
<div class="card"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
<Show when={!payload.loading && responses().length === 0} fallback={ <Show when={!payload.loading && responses().length === 0} fallback={
<div class="list-grid" style="grid-template-columns:1fr;gap:12px"> <div class="list-grid" style="grid-template-columns:1fr;gap:12px">
<For each={responses()}> <For each={responses()}>
@ -106,11 +106,11 @@ export default function ResponsesPage() {
</div> </div>
<div class="actions"> <div class="actions">
<Show when={row.status === 'SUBMITTED'}> <Show when={row.status === 'SUBMITTED'}>
<button class="btn" disabled={busyId() === row.id} onClick={() => transition(row.id, 'SHORTLISTED')}>Shortlist</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" disabled={busyId() === row.id} onClick={() => transition(row.id, 'SHORTLISTED')}>Shortlist</button>
</Show> </Show>
<Show when={row.status === 'SUBMITTED' || row.status === 'SHORTLISTED'}> <Show when={row.status === 'SUBMITTED' || row.status === 'SHORTLISTED'}>
<button class="btn primary" disabled={busyId() === row.id} onClick={() => transition(row.id, 'ACCEPTED')}>Accept</button> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={busyId() === row.id} onClick={() => transition(row.id, 'ACCEPTED')}>Accept</button>
<button class="btn danger" disabled={busyId() === row.id} onClick={() => transition(row.id, 'REJECTED')}>Reject</button> <button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" disabled={busyId() === row.id} onClick={() => transition(row.id, 'REJECTED')}>Reject</button>
</Show> </Show>
</div> </div>
</div> </div>

View file

@ -120,10 +120,10 @@ export default function ReviewPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Review Management</h1> <h1 class="text-2xl font-bold text-gray-900">Review Management</h1>
<p class="page-subtitle">Moderate platform reviews</p> <p class="mt-1 text-sm text-gray-500">Moderate platform reviews</p>
</div> </div>
</div> </div>
@ -155,9 +155,9 @@ export default function ReviewPage() {
style="padding:8px 12px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;min-width:320px" style="padding:8px 12px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;min-width:320px"
/> />
</div> </div>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Reviewer</th> <th>Reviewer</th>
@ -165,7 +165,7 @@ export default function ReviewPage() {
<th>Rating</th> <th>Rating</th>
<th>Title</th> <th>Title</th>
<th>Status</th> <th>Status</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -196,17 +196,17 @@ export default function ReviewPage() {
</td> </td>
<td style="color:#475569">{item.title || '—'}</td> <td style="color:#475569">{item.title || '—'}</td>
<td> <td>
<span class={`status-chip ${isPublished ? 'active' : ''}`} <span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${isPublished ? 'active' : ''}`}
style={isPublished ? '' : 'background:#f1f5f9;color:#475569;border-color:#e2e8f0'} style={isPublished ? '' : 'background:#f1f5f9;color:#475569;border-color:#e2e8f0'}
> >
{isPublished ? 'Published' : 'Hidden'} {isPublished ? 'Published' : 'Hidden'}
</span> </span>
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<Show when={isPublished}> <Show when={isPublished}>
<button <button
class="btn" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
disabled={toggling() === item.id} disabled={toggling() === item.id}
onClick={() => handleUpdateStatus(item, 'HIDDEN')} onClick={() => handleUpdateStatus(item, 'HIDDEN')}
> >
@ -215,7 +215,7 @@ export default function ReviewPage() {
</Show> </Show>
<Show when={!isPublished}> <Show when={!isPublished}>
<button <button
class="btn navy" class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
disabled={toggling() === item.id} disabled={toggling() === item.id}
onClick={() => handleUpdateStatus(item, 'PUBLISHED')} onClick={() => handleUpdateStatus(item, 'PUBLISHED')}
> >
@ -236,10 +236,10 @@ export default function ReviewPage() {
</Show> </Show>
<Show when={activeTab() === 'create'}> <Show when={activeTab() === 'create'}>
<section class="card" style="max-width:520px"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:520px">
<h2 style="margin:0 0 20px;font-size:16px;font-weight:700">Create Review</h2> <h2 style="margin:0 0 20px;font-size:16px;font-weight:700">Create Review</h2>
<Show when={formError()}> <Show when={formError()}>
<div class="error-box" style="margin-bottom:12px">{formError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{formError()}</div>
</Show> </Show>
<form onSubmit={handleSave} style="display:flex;flex-direction:column;gap:14px"> <form onSubmit={handleSave} style="display:flex;flex-direction:column;gap:14px">
<div class="field"> <div class="field">
@ -306,7 +306,7 @@ export default function ReviewPage() {
/> />
</div> </div>
<div class="actions"> <div class="actions">
<button class="btn navy" type="submit" disabled={saving()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
{saving() ? 'Saving...' : 'Save Review'} {saving() ? 'Saving...' : 'Save Review'}
</button> </button>
</div> </div>

View file

@ -5,5 +5,5 @@ import AdminShell from '~/components/AdminShell';
export default function RoleModulesAliasPage() { export default function RoleModulesAliasPage() {
const navigate = useNavigate(); const navigate = useNavigate();
onMount(() => navigate('/admin/role-ui-configs', { replace: true })); onMount(() => navigate('/admin/role-ui-configs', { replace: true }));
return <AdminShell><div class="card"><p class="notice">Redirecting to role module configuration...</p></div></AdminShell>; return <AdminShell><div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to role module configuration...</p></div></AdminShell>;
} }

View file

@ -13,7 +13,7 @@ export default function EditRoleUiConfigRedirectPage() {
return ( return (
<AdminShell> <AdminShell>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<p class="notice">Redirecting to External Dashboard Management...</p> <p class="notice">Redirecting to External Dashboard Management...</p>
</section> </section>
</AdminShell> </AdminShell>

View file

@ -77,17 +77,17 @@ export default function RoleUiConfigsViewPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card"> <div class="page-hero-card">
<h1 class="page-title">External Dashboard Management</h1> <h1 class="text-2xl font-bold text-gray-900">External Dashboard Management</h1>
<p class="page-subtitle">Read-only view of the currently published external dashboard and runtime role configuration.</p> <p class="mt-1 text-sm text-gray-500">Read-only view of the currently published external dashboard and runtime role configuration.</p>
</div> </div>
<ExternalRoleTabs roleKey={roleKey()} /> <ExternalRoleTabs roleKey={roleKey()} />
<div style="display:grid;grid-template-columns:420px minmax(0,1fr);gap:16px"> <div style="display:grid;grid-template-columns:420px minmax(0,1fr);gap:16px">
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<div class="list-header"> <div class="list-header">
<h2 style="margin:0">External Dashboard Roles</h2> <h2 style="margin:0">External Dashboard Roles</h2>
<A class="btn" href="/admin/roles">Open Roles</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/roles">Open Roles</A>
</div> </div>
<p class="notice" style="margin-top:2px"> <p class="notice" style="margin-top:2px">
Select a role to inspect its published dashboard modules, onboarding assignment, and permissions. Select a role to inspect its published dashboard modules, onboarding assignment, and permissions.
@ -124,31 +124,31 @@ export default function RoleUiConfigsViewPage() {
</section> </section>
<section style="display:flex;flex-direction:column;gap:12px"> <section style="display:flex;flex-direction:column;gap:12px">
<div class="card"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
<div class="list-header"> <div class="list-header">
<div> <div>
<h2 style="margin:0">Dashboard Detail</h2> <h2 style="margin:0">Dashboard Detail</h2>
<p class="notice" style="margin-top:3px">This page is the safe inspector for published external dashboard and role configuration.</p> <p class="notice" style="margin-top:3px">This page is the safe inspector for published external dashboard and role configuration.</p>
</div> </div>
<Show when={selected()}> <Show when={selected()}>
<A class="btn" href={`/admin/runtime-roles/${encodeURIComponent(selected()!.roleKey)}`}>Open Role Detail</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/runtime-roles/${encodeURIComponent(selected()!.roleKey)}`}>Open Role Detail</A>
</Show> </Show>
</div> </div>
</div> </div>
<Show when={selected.loading}> <Show when={selected.loading}>
<div class="card"><p class="notice">Loading config detail...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading config detail...</p></div>
</Show> </Show>
<Show when={!selected.loading && !selected() && roleKey()}> <Show when={!selected.loading && !selected() && roleKey()}>
<div class="card"><p class="notice">No runtime role found for "{roleKey()}".</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">No runtime role found for "{roleKey()}".</p></div>
</Show> </Show>
<Show when={!selected.loading && !selected() && !roleKey()}> <Show when={!selected.loading && !selected() && !roleKey()}>
<div class="card"><p class="notice">Select an external role to view its current dashboard configuration.</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Select an external role to view its current dashboard configuration.</p></div>
</Show> </Show>
<Show when={selected()}> <Show when={selected()}>
<div style="display:flex;flex-direction:column;gap:12px"> <div style="display:flex;flex-direction:column;gap:12px">
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="kv-item"><p class="kv-label">Role Key</p><p class="kv-value">{selected()!.roleKey}</p></div> <div class="kv-item"><p class="kv-label">Role Key</p><p class="kv-value">{selected()!.roleKey}</p></div>
<div class="kv-item"><p class="kv-label">Display Name</p><p class="kv-value">{selected()!.displayName}</p></div> <div class="kv-item"><p class="kv-label">Display Name</p><p class="kv-value">{selected()!.displayName}</p></div>
<div class="kv-item"><p class="kv-label">Vertical</p><p class="kv-value">{selected()!.vertical || '—'}</p></div> <div class="kv-item"><p class="kv-label">Vertical</p><p class="kv-value">{selected()!.vertical || '—'}</p></div>
@ -174,7 +174,7 @@ export default function RoleUiConfigsViewPage() {
</div> </div>
</section> </section>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h2 style="margin:0 0 8px">Enabled Modules</h2> <h2 style="margin:0 0 8px">Enabled Modules</h2>
<Show when={selected()!.enabledModules.length > 0} fallback={<p class="notice">No modules configured.</p>}> <Show when={selected()!.enabledModules.length > 0} fallback={<p class="notice">No modules configured.</p>}>
<div style="display:flex;flex-wrap:wrap;gap:8px"> <div style="display:flex;flex-wrap:wrap;gap:8px">
@ -183,14 +183,14 @@ export default function RoleUiConfigsViewPage() {
</Show> </Show>
</section> </section>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h2 style="margin:0 0 8px">Permission Matrix</h2> <h2 style="margin:0 0 8px">Permission Matrix</h2>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Module</th> <th>Module</th>
{permissionActions.map((action) => <th class="align-right">{action}</th>)} {permissionActions.map((action) => <th class="text-right">{action}</th>)}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -198,7 +198,7 @@ export default function RoleUiConfigsViewPage() {
<tr> <tr>
<td>{moduleKey}</td> <td>{moduleKey}</td>
{permissionActions.map((action) => ( {permissionActions.map((action) => (
<td class="align-right"> <td class="text-right">
<input type="checkbox" checked={(selected()!.permissions[moduleKey] || []).includes(action)} disabled /> <input type="checkbox" checked={(selected()!.permissions[moduleKey] || []).includes(action)} disabled />
</td> </td>
))} ))}
@ -209,12 +209,12 @@ export default function RoleUiConfigsViewPage() {
</div> </div>
</section> </section>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h2 style="margin:0 0 8px">Feature Limits</h2> <h2 style="margin:0 0 8px">Feature Limits</h2>
<pre class="json">{JSON.stringify(selected()!.featureLimits || {}, null, 2)}</pre> <pre class="json">{JSON.stringify(selected()!.featureLimits || {}, null, 2)}</pre>
</section> </section>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h2 style="margin:0 0 8px">Published Runtime Config</h2> <h2 style="margin:0 0 8px">Published Runtime Config</h2>
<pre class="json">{JSON.stringify(selected(), null, 2)}</pre> <pre class="json">{JSON.stringify(selected(), null, 2)}</pre>
</section> </section>

View file

@ -11,7 +11,7 @@ export default function CreateRoleUiConfigRedirectPage() {
return ( return (
<AdminShell> <AdminShell>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<p class="notice">Redirecting to External Dashboard Management...</p> <p class="notice">Redirecting to External Dashboard Management...</p>
</section> </section>
</AdminShell> </AdminShell>

View file

@ -112,38 +112,38 @@ export default function EditInternalRolePage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Edit Internal Role</h1> <h1 class="text-2xl font-bold text-gray-900">Edit Internal Role</h1>
<p class="page-subtitle">Update role name, access areas, and permissions.</p> <p class="mt-1 text-sm text-gray-500">Update role name, access areas, and permissions.</p>
</div> </div>
<A class="btn" href={`/admin/roles/${params.id}`}>Back to Role</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/roles/${params.id}`}>Back to Role</A>
</div> </div>
<nav class="admin-link-tabs" aria-label="Role Management Navigation"> <nav class="hidden" aria-label="Role Management Navigation">
<A class="admin-link-tab active" href="/admin/roles">Internal Roles</A> <A class="hidden" href="/admin/roles">Internal Roles</A>
<A class="admin-link-tab" href="/admin/runtime-roles">External Runtime Roles</A> <A class="hidden" href="/admin/runtime-roles">External Runtime Roles</A>
<A class="admin-link-tab" href="/admin/onboarding-schemas">Onboarding Schemas</A> <A class="hidden" href="/admin/onboarding-schemas">Onboarding Schemas</A>
</nav> </nav>
<Show when={data.loading}> <Show when={data.loading}>
<div class="card"><p class="notice">Loading role details...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading role details...</p></div>
</Show> </Show>
<Show when={data.error}> <Show when={data.error}>
<div class="error-box">Failed to load role. Check that the backend is running.</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">Failed to load role. Check that the backend is running.</div>
</Show> </Show>
<Show when={error()}> <Show when={error()}>
<div class="error-box">{error()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
</Show> </Show>
<Show when={!data.loading && data()}> <Show when={!data.loading && data()}>
{/* Role Details */} {/* Role Details */}
<div class="role-form-section"> <div class="mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
<h3>Role Basics</h3> <h3>Role Basics</h3>
<p>Update the role name and description.</p> <p>Update the role name and description.</p>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label>Role Name <span style="color:#ef4444">*</span></label> <label>Role Name <span style="color:#ef4444">*</span></label>
<input <input
@ -164,14 +164,14 @@ export default function EditInternalRolePage() {
</div> </div>
{/* Module Access */} {/* Module Access */}
<div class="role-form-section"> <div class="mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
<h3>Area Access</h3> <h3>Area Access</h3>
<p>Select which areas this role can access.</p> <p>Select which areas this role can access.</p>
<div class="module-picker"> <div class="mt-3 flex flex-wrap gap-2">
{allModules().map((mod) => ( {allModules().map((mod) => (
<button <button
type="button" type="button"
class={`module-chip ${assignedModules().includes(mod) ? 'selected' : ''}`} class={`flex cursor-pointer items-center gap-1.5 rounded-full border border-gray-200 bg-white px-3 py-1.5 text-xs font-medium text-gray-600 hover:border-gray-300 ${assignedModules().includes(mod) ? 'selected' : ''}`}
onClick={() => toggleModule(mod)} onClick={() => toggleModule(mod)}
> >
<span style={`width:14px;height:14px;border-radius:3px;border:2px solid ${assignedModules().includes(mod) ? '#c2410c' : '#cbd5e1'};background:${assignedModules().includes(mod) ? '#c2410c' : '#fff'};flex-shrink:0;display:inline-block`} /> <span style={`width:14px;height:14px;border-radius:3px;border:2px solid ${assignedModules().includes(mod) ? '#c2410c' : '#cbd5e1'};background:${assignedModules().includes(mod) ? '#c2410c' : '#fff'};flex-shrink:0;display:inline-block`} />
@ -183,11 +183,11 @@ export default function EditInternalRolePage() {
{/* Permission Table */} {/* Permission Table */}
<Show when={assignedModules().length > 0}> <Show when={assignedModules().length > 0}>
<div class="role-form-section"> <div class="mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
<h3>Permissions</h3> <h3>Permissions</h3>
<p>Choose what this role can do in each selected area.</p> <p>Choose what this role can do in each selected area.</p>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="perm-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th style="width:45%">Area</th> <th style="width:45%">Area</th>
@ -228,7 +228,7 @@ export default function EditInternalRolePage() {
{/* Save */} {/* Save */}
<div style="display:flex;justify-content:flex-end;margin-top:8px"> <div style="display:flex;justify-content:flex-end;margin-top:8px">
<button <button
class="btn navy" class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
onClick={handleSave} onClick={handleSave}
disabled={saving() || !roleName().trim()} disabled={saving() || !roleName().trim()}
> >

View file

@ -43,39 +43,39 @@ export default function RoleDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Role Details</h1> <h1 class="text-2xl font-bold text-gray-900">Role Details</h1>
<p class="page-subtitle">View role information and assigned permissions.</p> <p class="mt-1 text-sm text-gray-500">View role information and assigned permissions.</p>
</div> </div>
<div class="page-actions-right"> <div class="flex items-center gap-2">
<A class="btn" href="/admin/roles">Back to List</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/roles">Back to List</A>
<Show when={data()?.role}> <Show when={data()?.role}>
<A class="btn navy" href={`/admin/roles/${params.id}/edit`}>Edit Role</A> <A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/roles/${params.id}/edit`}>Edit Role</A>
</Show> </Show>
</div> </div>
</div> </div>
<nav class="admin-link-tabs" aria-label="Role Management Navigation"> <nav class="hidden" aria-label="Role Management Navigation">
<A class="admin-link-tab active" href="/admin/roles">Internal Roles</A> <A class="hidden" href="/admin/roles">Internal Roles</A>
<A class="admin-link-tab" href="/admin/runtime-roles">External Runtime Roles</A> <A class="hidden" href="/admin/runtime-roles">External Runtime Roles</A>
<A class="admin-link-tab" href="/admin/onboarding-schemas">Onboarding Schemas</A> <A class="hidden" href="/admin/onboarding-schemas">Onboarding Schemas</A>
</nav> </nav>
<Show when={data.loading}> <Show when={data.loading}>
<div class="card"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
<p class="notice">Loading role details...</p> <p class="notice">Loading role details...</p>
</div> </div>
</Show> </Show>
<Show when={data.error}> <Show when={data.error}>
<div class="error-box">Failed to load role. Check that the backend is running.</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">Failed to load role. Check that the backend is running.</div>
</Show> </Show>
<Show when={data()?.role}> <Show when={data()?.role}>
{/* Role Info */} {/* Role Info */}
<div class="role-detail-card"> <div class="role-detail-card">
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label>Role Name</label> <label>Role Name</label>
<input class="role-field-readonly" value={data()!.role.name} readOnly disabled /> <input class="role-field-readonly" value={data()!.role.name} readOnly disabled />
@ -88,9 +88,9 @@ export default function RoleDetailPage() {
</div> </div>
{/* Permission Matrix */} {/* Permission Matrix */}
<div class="card" style="padding: 0; overflow: hidden;"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="perm-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th style="width:45%">Name of the module</th> <th style="width:45%">Name of the module</th>

View file

@ -93,29 +93,29 @@ export default function CreateInternalRolePage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Create Internal Role</h1> <h1 class="text-2xl font-bold text-gray-900">Create Internal Role</h1>
<p class="page-subtitle">Create a new internal role and choose what it can access.</p> <p class="mt-1 text-sm text-gray-500">Create a new internal role and choose what it can access.</p>
</div> </div>
<A class="btn" href="/admin/roles">Back to Roles</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/roles">Back to Roles</A>
</div> </div>
<nav class="admin-link-tabs" aria-label="Role Management Navigation"> <nav class="hidden" aria-label="Role Management Navigation">
<A class="admin-link-tab active" href="/admin/roles">Internal Roles</A> <A class="hidden" href="/admin/roles">Internal Roles</A>
<A class="admin-link-tab" href="/admin/runtime-roles">External Runtime Roles</A> <A class="hidden" href="/admin/runtime-roles">External Runtime Roles</A>
<A class="admin-link-tab" href="/admin/onboarding-schemas">Onboarding Schemas</A> <A class="hidden" href="/admin/onboarding-schemas">Onboarding Schemas</A>
</nav> </nav>
<Show when={error()}> <Show when={error()}>
<div class="error-box">{error()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
</Show> </Show>
{/* Role Details */} {/* Role Details */}
<div class="role-form-section"> <div class="mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
<h3>Role Basics</h3> <h3>Role Basics</h3>
<p>Start by giving this role a clear name.</p> <p>Start by giving this role a clear name.</p>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label>Role Name <span style="color:#ef4444">*</span></label> <label>Role Name <span style="color:#ef4444">*</span></label>
<input <input
@ -136,18 +136,18 @@ export default function CreateInternalRolePage() {
</div> </div>
{/* Module Access */} {/* Module Access */}
<div class="role-form-section"> <div class="mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
<h3>Area Access</h3> <h3>Area Access</h3>
<p>Select which areas this role can access. You can set permissions for selected areas below.</p> <p>Select which areas this role can access. You can set permissions for selected areas below.</p>
<Show when={permissions.loading}> <Show when={permissions.loading}>
<p class="notice">Loading available areas...</p> <p class="notice">Loading available areas...</p>
</Show> </Show>
<Show when={!permissions.loading && allModules().length > 0}> <Show when={!permissions.loading && allModules().length > 0}>
<div class="module-picker"> <div class="mt-3 flex flex-wrap gap-2">
{allModules().map((mod) => ( {allModules().map((mod) => (
<button <button
type="button" type="button"
class={`module-chip ${assignedModules().includes(mod) ? 'selected' : ''}`} class={`flex cursor-pointer items-center gap-1.5 rounded-full border border-gray-200 bg-white px-3 py-1.5 text-xs font-medium text-gray-600 hover:border-gray-300 ${assignedModules().includes(mod) ? 'selected' : ''}`}
onClick={() => toggleModule(mod)} onClick={() => toggleModule(mod)}
> >
<span style={`width:14px;height:14px;border-radius:3px;border:2px solid ${assignedModules().includes(mod) ? '#c2410c' : '#cbd5e1'};background:${assignedModules().includes(mod) ? '#c2410c' : '#fff'};flex-shrink:0;display:inline-block`} /> <span style={`width:14px;height:14px;border-radius:3px;border:2px solid ${assignedModules().includes(mod) ? '#c2410c' : '#cbd5e1'};background:${assignedModules().includes(mod) ? '#c2410c' : '#fff'};flex-shrink:0;display:inline-block`} />
@ -163,11 +163,11 @@ export default function CreateInternalRolePage() {
{/* Permission Table */} {/* Permission Table */}
<Show when={assignedModules().length > 0}> <Show when={assignedModules().length > 0}>
<div class="role-form-section"> <div class="mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
<h3>Permissions</h3> <h3>Permissions</h3>
<p>Choose what this role can do in each selected area.</p> <p>Choose what this role can do in each selected area.</p>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="perm-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th style="width:45%">Area</th> <th style="width:45%">Area</th>
@ -208,7 +208,7 @@ export default function CreateInternalRolePage() {
{/* Save */} {/* Save */}
<div style="display:flex;justify-content:flex-end;margin-top:8px"> <div style="display:flex;justify-content:flex-end;margin-top:8px">
<button <button
class="btn navy" class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
onClick={handleSave} onClick={handleSave}
disabled={saving() || !roleName().trim()} disabled={saving() || !roleName().trim()}
> >

View file

@ -50,32 +50,32 @@ export default function InternalRolesPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Internal Role Management</h1> <h1 class="text-2xl font-bold text-gray-900">Internal Role Management</h1>
<p class="page-subtitle">Manage internal employee roles and permissions from one clean list.</p> <p class="mt-1 text-sm text-gray-500">Manage internal employee roles and permissions from one clean list.</p>
</div> </div>
<A class="btn navy" href="/admin/roles/create">Create Internal Role</A> <A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/roles/create">Create Internal Role</A>
</div> </div>
<nav class="admin-link-tabs" aria-label="Role Management Navigation"> <nav class="hidden" aria-label="Role Management Navigation">
<A class="admin-link-tab active" href="/admin/roles">Internal Roles</A> <A class="hidden" href="/admin/roles">Internal Roles</A>
<A class="admin-link-tab" href="/admin/runtime-roles">External Runtime Roles</A> <A class="hidden" href="/admin/runtime-roles">External Runtime Roles</A>
<A class="admin-link-tab" href="/admin/onboarding-schemas">Onboarding Schemas</A> <A class="hidden" href="/admin/onboarding-schemas">Onboarding Schemas</A>
</nav> </nav>
<Show when={deleteError()}> <Show when={deleteError()}>
<div class="error-box">{deleteError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{deleteError()}</div>
</Show> </Show>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Description</th> <th>Description</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -105,11 +105,11 @@ export default function InternalRolesPage() {
</td> </td>
<td style="color:#475569;">{role.description || 'No description added yet.'}</td> <td style="color:#475569;">{role.description || 'No description added yet.'}</td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="action-icon-btn" href={`/admin/roles/${role.id}`} title="View Role">👁</A> <A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/roles/${role.id}`} title="View Role">👁</A>
<A class="action-icon-btn" href={`/admin/roles/${role.id}/edit`} title="Edit Role"></A> <A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/roles/${role.id}/edit`} title="Edit Role"></A>
<button <button
class="action-icon-btn danger" class="rounded p-1.5 text-red-500 hover:bg-red-50 hover:text-red-700 text-sm"
disabled={deleting() === role.id} disabled={deleting() === role.id}
onClick={() => handleDelete(role.id, role.name)} onClick={() => handleDelete(role.id, role.name)}
title="Delete Role" title="Delete Role"

View file

@ -36,27 +36,27 @@ export default function RoleTemplatesPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Role Templates</h1> <h1 class="text-2xl font-bold text-gray-900">Role Templates</h1>
<p class="page-subtitle">Starter role presets for faster internal role creation and cloning.</p> <p class="mt-1 text-sm text-gray-500">Starter role presets for faster internal role creation and cloning.</p>
</div> </div>
<A class="btn navy" href="/admin/roles/create">Create Internal Role</A> <A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/roles/create">Create Internal Role</A>
</div> </div>
<section class="card" style="padding:0;overflow:hidden"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid #e2e8f0"> <div style="display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid #e2e8f0">
<h2 style="margin:0;font-size:16px">Available Templates</h2> <h2 style="margin:0;font-size:16px">Available Templates</h2>
<span style="font-size:12px;color:#64748b">{count()} template{count() !== 1 ? 's' : ''}</span> <span style="font-size:12px;color:#64748b">{count()} template{count() !== 1 ? 's' : ''}</span>
</div> </div>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Description</th> <th>Description</th>
<th>Code</th> <th>Code</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -74,9 +74,9 @@ export default function RoleTemplatesPage() {
<td style="color:#475569">{item.description || '—'}</td> <td style="color:#475569">{item.description || '—'}</td>
<td style="color:#64748b;font-family:ui-monospace,SFMono-Regular,Menlo,monospace">{item.code || '—'}</td> <td style="color:#64748b;font-family:ui-monospace,SFMono-Regular,Menlo,monospace">{item.code || '—'}</td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/roles/${item.id}`}>View</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/roles/${item.id}`}>View</A>
<A class="btn" href="/admin/roles/create">Use As Base</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/roles/create">Use As Base</A>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -152,26 +152,26 @@ export default function EditExternalRolePage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">External Role Management</h1> <h1 class="text-2xl font-bold text-gray-900">External Role Management</h1>
<p class="page-subtitle"> <p class="mt-1 text-sm text-gray-500">
Update this external role with simple settings: pages, permissions, onboarding form, approvals, and limits. Update this external role with simple settings: pages, permissions, onboarding form, approvals, and limits.
</p> </p>
</div> </div>
<div class="page-actions-right"> <div class="flex items-center gap-2">
<A class="btn" href={`/admin/role-ui-configs?roleKey=${encodeURIComponent(roleKey())}`}>Open Inspector</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/role-ui-configs?roleKey=${encodeURIComponent(roleKey())}`}>Open Inspector</A>
<A class="btn" href="/admin/runtime-roles">Back to External Roles</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/runtime-roles">Back to External Roles</A>
</div> </div>
</div> </div>
<ExternalRoleTabs roleKey={roleKey()} /> <ExternalRoleTabs roleKey={roleKey()} />
<Show when={error()}> <Show when={error()}>
<div class="error-box">{error()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
</Show> </Show>
<div class="card"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h2 style="margin-bottom:6px">How Saving Works</h2> <h2 style="margin-bottom:6px">How Saving Works</h2>
<p class="notice" style="margin:0"> <p class="notice" style="margin:0">
Saving here updates the live role settings used by the app. Saving here updates the live role settings used by the app.
@ -179,11 +179,11 @@ export default function EditExternalRolePage() {
</div> </div>
<Show when={data.loading}> <Show when={data.loading}>
<div class="card"><p class="notice">Loading external role...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading external role...</p></div>
</Show> </Show>
<Show when={!data.loading && !data()}> <Show when={!data.loading && !data()}>
<div class="card"><p class="notice">Role "{roleKey()}" not found.</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Role "{roleKey()}" not found.</p></div>
</Show> </Show>
<Show when={!data.loading && data()}> <Show when={!data.loading && data()}>

View file

@ -77,20 +77,20 @@ export default function RuntimeRolesPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">External Role Management</h1> <h1 class="text-2xl font-bold text-gray-900">External Role Management</h1>
<p class="page-subtitle">Manage canonical external runtime roles, enabled modules, onboarding assignment, approval gates, and default runtime destinations from one place.</p> <p class="mt-1 text-sm text-gray-500">Manage canonical external runtime roles, enabled modules, onboarding assignment, approval gates, and default runtime destinations from one place.</p>
</div> </div>
<div class="page-actions-right"> <div class="flex items-center gap-2">
<A class="btn" href="/admin/role-ui-configs">Inspector</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/role-ui-configs">Inspector</A>
<A class="btn navy" href="/admin/runtime-roles/new">Create External Role</A> <A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/runtime-roles/new">Create External Role</A>
</div> </div>
</div> </div>
<ExternalRoleTabs /> <ExternalRoleTabs />
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div style="display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #e2e8f0"> <div style="display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #e2e8f0">
<div> <div>
<h2 style="margin:0;font-size:17px;font-weight:700">Published External Roles</h2> <h2 style="margin:0;font-size:17px;font-weight:700">Published External Roles</h2>
@ -100,8 +100,8 @@ export default function RuntimeRolesPage() {
<span style="font-size:13px;color:#64748b">{roles()?.length || 0} roles</span> <span style="font-size:13px;color:#64748b">{roles()?.length || 0} roles</span>
</Show> </Show>
</div> </div>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Role</th> <th>Role</th>
@ -109,7 +109,7 @@ export default function RuntimeRolesPage() {
<th>Modules</th> <th>Modules</th>
<th>Schema</th> <th>Schema</th>
<th>Status</th> <th>Status</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -124,7 +124,7 @@ export default function RuntimeRolesPage() {
</Show> </Show>
<Show when={!roles.loading && !roles.error && (roles()?.length ?? 0) > 0}> <Show when={!roles.loading && !roles.error && (roles()?.length ?? 0) > 0}>
{roles()!.map((role) => ( {roles()!.map((role) => (
<tr class={selectedRoleKey() === role.roleKey.toLowerCase() ? 'row-selected' : ''}> <tr class={selectedRoleKey() === role.roleKey.toLowerCase() ? 'bg-orange-50' : ''}>
<td> <td>
<div> <div>
<p style="margin:0;font-weight:600;color:#0f172a">{role.displayName}</p> <p style="margin:0;font-weight:600;color:#0f172a">{role.displayName}</p>
@ -135,14 +135,14 @@ export default function RuntimeRolesPage() {
<td style="color:#475569">{role.enabledModules.length}</td> <td style="color:#475569">{role.enabledModules.length}</td>
<td style="color:#475569;font-size:12px">{role.onboardingSchemaId || '—'}</td> <td style="color:#475569;font-size:12px">{role.onboardingSchemaId || '—'}</td>
<td> <td>
<span class={`status-chip ${role.isActive ? 'active' : ''}`}> <span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${role.isActive ? 'active' : ''}`}>
{role.isActive ? 'Active' : 'Inactive'} {role.isActive ? 'Active' : 'Inactive'}
</span> </span>
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="action-icon-btn" href={`/admin/runtime-roles/${encodeURIComponent(role.roleKey)}`} target="_blank" rel="noreferrer" title="View External Role">👁</A> <A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/runtime-roles/${encodeURIComponent(role.roleKey)}`} target="_blank" rel="noreferrer" title="View External Role">👁</A>
<A class="action-icon-btn" href={`/admin/runtime-roles/${encodeURIComponent(role.roleKey)}`} title="Edit External Role"></A> <A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/runtime-roles/${encodeURIComponent(role.roleKey)}`} title="Edit External Role"></A>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -84,20 +84,20 @@ export default function CreateExternalRolePage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Create External Role</h1> <h1 class="text-2xl font-bold text-gray-900">Create External Role</h1>
<p class="page-subtitle"> <p class="mt-1 text-sm text-gray-500">
Create a new external role and choose what it can access in the app. Create a new external role and choose what it can access in the app.
</p> </p>
</div> </div>
<A class="btn" href="/admin/runtime-roles">Back to External Roles</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/runtime-roles">Back to External Roles</A>
</div> </div>
<ExternalRoleTabs /> <ExternalRoleTabs />
<Show when={error()}> <Show when={error()}>
<div class="error-box">{error()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
</Show> </Show>
<ExternalRoleForm <ExternalRoleForm

View file

@ -5,5 +5,5 @@ import AdminShell from '~/components/AdminShell';
export default function SettingsAliasPage() { export default function SettingsAliasPage() {
const navigate = useNavigate(); const navigate = useNavigate();
onMount(() => navigate('/admin/internal-dashboard-management', { replace: true })); onMount(() => navigate('/admin/internal-dashboard-management', { replace: true }));
return <AdminShell><div class="card"><p class="notice">Redirecting to settings/configuration...</p></div></AdminShell>; return <AdminShell><div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to settings/configuration...</p></div></AdminShell>;
} }

View file

@ -36,14 +36,14 @@ export default function SocialMediaManagersPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Social Media Manager Management</h1> <h1 class="text-2xl font-bold text-gray-900">Social Media Manager Management</h1>
<p class="page-subtitle">Manage all social media manager accounts on the platform.</p> <p class="mt-1 text-sm text-gray-500">Manage all social media manager accounts on the platform.</p>
</div> </div>
</div> </div>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;"> <div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
<input <input
type="text" type="text"
@ -64,15 +64,15 @@ export default function SocialMediaManagersPage() {
</select> </select>
</div> </div>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Email</th> <th>Email</th>
<th>Status</th> <th>Status</th>
<th>Registered</th> <th>Registered</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -93,22 +93,22 @@ export default function SocialMediaManagersPage() {
<td style="color:#475569">{item.email}</td> <td style="color:#475569">{item.email}</td>
<td> <td>
{item.status?.toUpperCase() === 'ACTIVE' && ( {item.status?.toUpperCase() === 'ACTIVE' && (
<span class="status-chip active">ACTIVE</span> <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'INACTIVE' && ( {item.status?.toUpperCase() === 'INACTIVE' && (
<span class="status-chip">INACTIVE</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'PENDING' && ( {item.status?.toUpperCase() === 'PENDING' && (
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
)} )}
{!item.status && <span class="status-chip"></span>} {!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600"></span>}
</td> </td>
<td style="color:#475569"> <td style="color:#475569">
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'} {item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/users/${item.id}`}>View</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -158,10 +158,10 @@ export default function SupportPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Support Management</h1> <h1 class="text-2xl font-bold text-gray-900">Support Management</h1>
<p class="page-subtitle">Handle platform issues and customer queries</p> <p class="mt-1 text-sm text-gray-500">Handle platform issues and customer queries</p>
</div> </div>
</div> </div>
@ -211,9 +211,9 @@ export default function SupportPage() {
</select> </select>
</div> </div>
<section class="card" style="padding:0;overflow:hidden"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Issue</th> <th>Issue</th>
@ -222,7 +222,7 @@ export default function SupportPage() {
<th>Status</th> <th>Status</th>
<th>Requester</th> <th>Requester</th>
<th>Updated At</th> <th>Updated At</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -260,8 +260,8 @@ export default function SupportPage() {
{item.updatedAt ? new Date(item.updatedAt).toLocaleString() : '—'} {item.updatedAt ? new Date(item.updatedAt).toLocaleString() : '—'}
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/support/${item.id}`}>View</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/support/${item.id}`}>View</A>
</div> </div>
</td> </td>
</tr> </tr>
@ -277,7 +277,7 @@ export default function SupportPage() {
{/* Create Case Tab */} {/* Create Case Tab */}
<Show when={activeTab() === 'create'}> <Show when={activeTab() === 'create'}>
<section class="card" style="max-width:600px"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:600px">
<h2 style="margin:0 0 6px;font-size:16px;font-weight:700;color:#1e293b">Create Support Case</h2> <h2 style="margin:0 0 6px;font-size:16px;font-weight:700;color:#1e293b">Create Support Case</h2>
<p style="margin:0 0 20px;font-size:13px;color:#64748b"> <p style="margin:0 0 20px;font-size:13px;color:#64748b">
Create an internal support record for platform issues, customer concerns, or compensation-related reviews. Create an internal support record for platform issues, customer concerns, or compensation-related reviews.
@ -288,7 +288,7 @@ export default function SupportPage() {
</div> </div>
</Show> </Show>
<Show when={createError()}> <Show when={createError()}>
<div class="error-box" style="margin-bottom:14px">{createError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:14px">{createError()}</div>
</Show> </Show>
<form onSubmit={handleCreate} style="display:flex;flex-direction:column;gap:14px"> <form onSubmit={handleCreate} style="display:flex;flex-direction:column;gap:14px">
<div class="field"> <div class="field">
@ -311,7 +311,7 @@ export default function SupportPage() {
style="width:100%;padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;resize:vertical;box-sizing:border-box" style="width:100%;padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;resize:vertical;box-sizing:border-box"
/> />
</div> </div>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:4px">Type</label> <label style="display:block;font-size:13px;font-weight:600;margin-bottom:4px">Type</label>
<select <select
@ -337,7 +337,7 @@ export default function SupportPage() {
</select> </select>
</div> </div>
</div> </div>
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:4px">Requester Name</label> <label style="display:block;font-size:13px;font-weight:600;margin-bottom:4px">Requester Name</label>
<input <input
@ -358,7 +358,7 @@ export default function SupportPage() {
</div> </div>
</div> </div>
<div> <div>
<button class="btn navy" type="submit" disabled={createLoading()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={createLoading()}>
{createLoading() ? 'Creating...' : 'Create Support Case'} {createLoading() ? 'Creating...' : 'Create Support Case'}
</button> </button>
</div> </div>

View file

@ -64,21 +64,21 @@ export default function TaxPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Tax Management</h1> <h1 class="text-2xl font-bold text-gray-900">Tax Management</h1>
<p class="page-subtitle">Configure tax rates for platform transactions.</p> <p class="mt-1 text-sm text-gray-500">Configure tax rates for platform transactions.</p>
</div> </div>
<button class="btn navy" onClick={() => setShowForm(!showForm())}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={() => setShowForm(!showForm())}>
{showForm() ? 'Cancel' : 'Add Tax'} {showForm() ? 'Cancel' : 'Add Tax'}
</button> </button>
</div> </div>
<Show when={showForm()}> <Show when={showForm()}>
<section class="card" style="margin-bottom:16px"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px">
<h2 style="margin:0 0 16px;font-size:16px;font-weight:700">New Tax</h2> <h2 style="margin:0 0 16px;font-size:16px;font-weight:700">New Tax</h2>
<Show when={formError()}> <Show when={formError()}>
<div class="error-box" style="margin-bottom:12px">{formError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{formError()}</div>
</Show> </Show>
<form onSubmit={handleSave} style="display:flex;flex-direction:column;gap:12px;max-width:400px"> <form onSubmit={handleSave} style="display:flex;flex-direction:column;gap:12px;max-width:400px">
<div> <div>
@ -114,7 +114,7 @@ export default function TaxPage() {
/> />
</div> </div>
<div> <div>
<button class="btn navy" type="submit" disabled={saving()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
{saving() ? 'Saving...' : 'Save Tax'} {saving() ? 'Saving...' : 'Save Tax'}
</button> </button>
</div> </div>
@ -122,16 +122,16 @@ export default function TaxPage() {
</section> </section>
</Show> </Show>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Rate (%)</th> <th>Rate (%)</th>
<th>Description</th> <th>Description</th>
<th>Status</th> <th>Status</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -151,15 +151,15 @@ export default function TaxPage() {
<td style="color:#475569">{item.rate}%</td> <td style="color:#475569">{item.rate}%</td>
<td style="color:#475569">{item.description || '—'}</td> <td style="color:#475569">{item.description || '—'}</td>
<td> <td>
<span class={`status-chip ${item.is_active !== false ? 'active' : ''}`}> <span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${item.is_active !== false ? 'active' : ''}`}>
{item.is_active !== false ? 'Active' : 'Inactive'} {item.is_active !== false ? 'Active' : 'Inactive'}
</span> </span>
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<a class="btn" href={`/admin/tax/${item.id}/edit`}>Edit</a> <a class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/tax/${item.id}/edit`}>Edit</a>
<button <button
class="btn danger" class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
disabled={deleting() === item.id} disabled={deleting() === item.id}
onClick={() => handleDelete(item.id, item.name)} onClick={() => handleDelete(item.id, item.name)}
> >

View file

@ -36,14 +36,14 @@ export default function TutorsPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Tutors Management</h1> <h1 class="text-2xl font-bold text-gray-900">Tutors Management</h1>
<p class="page-subtitle">Manage all tutor accounts on the platform.</p> <p class="mt-1 text-sm text-gray-500">Manage all tutor accounts on the platform.</p>
</div> </div>
</div> </div>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;"> <div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
<input <input
type="text" type="text"
@ -64,15 +64,15 @@ export default function TutorsPage() {
</select> </select>
</div> </div>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Email</th> <th>Email</th>
<th>Status</th> <th>Status</th>
<th>Registered</th> <th>Registered</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -93,22 +93,22 @@ export default function TutorsPage() {
<td style="color:#475569">{item.email}</td> <td style="color:#475569">{item.email}</td>
<td> <td>
{item.status?.toUpperCase() === 'ACTIVE' && ( {item.status?.toUpperCase() === 'ACTIVE' && (
<span class="status-chip active">ACTIVE</span> <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'INACTIVE' && ( {item.status?.toUpperCase() === 'INACTIVE' && (
<span class="status-chip">INACTIVE</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'PENDING' && ( {item.status?.toUpperCase() === 'PENDING' && (
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
)} )}
{!item.status && <span class="status-chip"></span>} {!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600"></span>}
</td> </td>
<td style="color:#475569"> <td style="color:#475569">
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'} {item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/users/${item.id}`}>View</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -53,12 +53,12 @@ async function fetchUsers(): Promise<User[]> {
function StatusBadge(props: { status: string }) { function StatusBadge(props: { status: string }) {
if (props.status === 'ACTIVE') { if (props.status === 'ACTIVE') {
return <span class="status-chip active">ACTIVE</span>; return <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>;
} }
if (props.status === 'PENDING') { if (props.status === 'PENDING') {
return <span class="status-chip" style="background:#f59e0b;color:#fff">PENDING</span>; return <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#f59e0b;color:#fff">PENDING</span>;
} }
return <span class="status-chip">INACTIVE</span>; return <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>;
} }
export default function UsersPage() { export default function UsersPage() {
@ -107,10 +107,10 @@ export default function UsersPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">External User Management</h1> <h1 class="text-2xl font-bold text-gray-900">External User Management</h1>
<p class="page-subtitle">Manage all external platform users.</p> <p class="mt-1 text-sm text-gray-500">Manage all external platform users.</p>
</div> </div>
</div> </div>
@ -134,7 +134,7 @@ export default function UsersPage() {
{/* Filters */} {/* Filters */}
<Show when={activeTab() === 'list'}> <Show when={activeTab() === 'list'}>
<div class="card" style="margin-bottom:16px;display:flex;gap:12px;flex-wrap:wrap;align-items:center;"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px;display:flex;gap:12px;flex-wrap:wrap;align-items:center;">
<input <input
type="text" type="text"
placeholder="Search by name or email..." placeholder="Search by name or email..."
@ -180,9 +180,9 @@ export default function UsersPage() {
</Show> </Show>
<Show when={activeTab() === 'list'}> <Show when={activeTab() === 'list'}>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>User ID</th> <th>User ID</th>
@ -191,7 +191,7 @@ export default function UsersPage() {
<th>Registration Role</th> <th>Registration Role</th>
<th>Status</th> <th>Status</th>
<th>Created</th> <th>Created</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -221,11 +221,11 @@ export default function UsersPage() {
: '—'} : '—'}
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="action-icon-btn" href={`/admin/users/details/${item.id}`} title="Open Detail Page"></A> <A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/users/details/${item.id}`} title="Open Detail Page"></A>
<button class="action-icon-btn" type="button" onClick={() => onView(item)} title="Quick View">👁</button> <button class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" type="button" onClick={() => onView(item)} title="Quick View">👁</button>
<A class="action-icon-btn" href={`/admin/users/${item.id}/edit`} title="Edit User"></A> <A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/users/${item.id}/edit`} title="Edit User"></A>
<button class="action-icon-btn danger" type="button" title="Delete User">🗑</button> <button class="rounded p-1.5 text-red-500 hover:bg-red-50 hover:text-red-700 text-sm" type="button" title="Delete User">🗑</button>
</div> </div>
</td> </td>
</tr> </tr>
@ -236,11 +236,11 @@ export default function UsersPage() {
</table> </table>
</div> </div>
<div class="admin-pagination"> <div class="admin-pagination">
<button class="btn" type="button" disabled={currentPage() <= 1} onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" disabled={currentPage() <= 1} onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}>
Previous Previous
</button> </button>
<span>Page {currentPage()} of {totalPages()}</span> <span>Page {currentPage()} of {totalPages()}</span>
<button class="btn" type="button" disabled={currentPage() >= totalPages()} onClick={() => setCurrentPage((p) => Math.min(totalPages(), p + 1))}> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" disabled={currentPage() >= totalPages()} onClick={() => setCurrentPage((p) => Math.min(totalPages(), p + 1))}>
Next Next
</button> </button>
</div> </div>
@ -248,11 +248,11 @@ export default function UsersPage() {
</Show> </Show>
<Show when={activeTab() === 'view'}> <Show when={activeTab() === 'view'}>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<Show when={selectedUser()} fallback={<p class="notice">Select a user from list to view details.</p>}> <Show when={selectedUser()} fallback={<p class="notice">Select a user from list to view details.</p>}>
<div class="list-header"> <div class="list-header">
<h2>User Details</h2> <h2>User Details</h2>
<button class="btn" type="button" onClick={() => setActiveTab('list')}>Back To List</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" onClick={() => setActiveTab('list')}>Back To List</button>
</div> </div>
<div class="grid" style="margin-top:0"> <div class="grid" style="margin-top:0">
<div class="list-item"> <div class="list-item">
@ -268,8 +268,8 @@ export default function UsersPage() {
<p><strong>Created:</strong> {(selectedUser()!.created_at || selectedUser()!.createdAt) ? new Date((selectedUser()!.created_at || selectedUser()!.createdAt)!).toLocaleString() : '—'}</p> <p><strong>Created:</strong> {(selectedUser()!.created_at || selectedUser()!.createdAt) ? new Date((selectedUser()!.created_at || selectedUser()!.createdAt)!).toLocaleString() : '—'}</p>
<p><strong>Role ID:</strong> {selectedUser()!.roleId || '—'}</p> <p><strong>Role ID:</strong> {selectedUser()!.roleId || '—'}</p>
<div class="actions"> <div class="actions">
<A class="btn navy" href={`/admin/users/${selectedUser()!.id}/edit`}>Edit User</A> <A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/users/${selectedUser()!.id}/edit`}>Edit User</A>
<button class="btn danger" type="button">Deactivate</button> <button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" type="button">Deactivate</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -114,32 +114,32 @@ export default function EditUserPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Edit User</h1> <h1 class="text-2xl font-bold text-gray-900">Edit User</h1>
<p class="page-subtitle">Update user profile, role assignment, and account status.</p> <p class="mt-1 text-sm text-gray-500">Update user profile, role assignment, and account status.</p>
</div> </div>
<div class="page-actions-right"> <div class="flex items-center gap-2">
<A class="btn" href={`/admin/users/details/${params.id}`}>View Details</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/details/${params.id}`}>View Details</A>
<A class="btn" href="/admin/users">Back to Users</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/users">Back to Users</A>
</div> </div>
</div> </div>
<Show when={error()}> <Show when={error()}>
<div class="error-box">{error()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
</Show> </Show>
<Show when={user.loading}> <Show when={user.loading}>
<div class="card"><p class="notice">Loading user...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading user...</p></div>
</Show> </Show>
<Show when={!user.loading && !user()}> <Show when={!user.loading && !user()}>
<div class="card"><p class="notice">User not found.</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">User not found.</p></div>
</Show> </Show>
<Show when={user()}> <Show when={user()}>
<section class="card" style="max-width:900px"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:900px">
<div class="field-grid-2"> <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field"> <div class="field">
<label>Full Name</label> <label>Full Name</label>
<input value={name()} onInput={(e) => setName(e.currentTarget.value)} /> <input value={name()} onInput={(e) => setName(e.currentTarget.value)} />
@ -170,8 +170,8 @@ export default function EditUserPage() {
</div> </div>
<div class="actions" style="justify-content:flex-end"> <div class="actions" style="justify-content:flex-end">
<button class="btn" type="button" onClick={() => navigate('/admin/users')}>Cancel</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" onClick={() => navigate('/admin/users')}>Cancel</button>
<button class="btn primary" type="button" onClick={save} disabled={submitting()}> <button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="button" onClick={save} disabled={submitting()}>
{submitting() ? 'Saving...' : 'Save Changes'} {submitting() ? 'Saving...' : 'Save Changes'}
</button> </button>
</div> </div>

View file

@ -161,27 +161,27 @@ export default function UserDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">{roleTitleLabel() ? `${roleTitleLabel()} Profile` : 'User Details'}</h1> <h1 class="text-2xl font-bold text-gray-900">{roleTitleLabel() ? `${roleTitleLabel()} Profile` : 'User Details'}</h1>
<p class="page-subtitle">Review account profile and role registration data with approval status.</p> <p class="mt-1 text-sm text-gray-500">Review account profile and role registration data with approval status.</p>
</div> </div>
<div class="page-actions-right"> <div class="flex items-center gap-2">
<A class="btn" href="/admin/users">Back to Users</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/users">Back to Users</A>
<A class="btn navy" href={`/admin/users/${params.id}/edit`}>Edit User</A> <A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/users/${params.id}/edit`}>Edit User</A>
</div> </div>
</div> </div>
<Show when={bundle.loading}> <Show when={bundle.loading}>
<div class="card"><p class="notice">Loading user...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading user...</p></div>
</Show> </Show>
<Show when={!bundle.loading && !user()}> <Show when={!bundle.loading && !user()}>
<div class="card"><p class="notice">User not found.</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">User not found.</p></div>
</Show> </Show>
<Show when={user()}> <Show when={user()}>
<> <>
<section class="card" style="padding:22px"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:22px">
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap"> <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap">
<div style="display:flex;gap:14px;align-items:center;min-width:260px"> <div style="display:flex;gap:14px;align-items:center;min-width:260px">
<div class="identity-avatar"> <div class="identity-avatar">
@ -213,7 +213,7 @@ export default function UserDetailPage() {
</div> </div>
</section> </section>
<section class="card" style="margin-top:16px;padding:0;overflow:hidden"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-top:16px;padding:0;overflow:hidden">
<div style="padding:16px 18px;border-bottom:1px solid #e2e8f0;background:#f8fafc"> <div style="padding:16px 18px;border-bottom:1px solid #e2e8f0;background:#f8fafc">
<h2 style="margin:0;font-size:18px">{roleTitleLabel() ? `${roleTitleLabel()} Registration Data` : 'Registered Roles & Profiles'}</h2> <h2 style="margin:0;font-size:18px">{roleTitleLabel() ? `${roleTitleLabel()} Registration Data` : 'Registered Roles & Profiles'}</h2>
</div> </div>
@ -222,8 +222,8 @@ export default function UserDetailPage() {
<p class="notice">This user has no registered role profiles yet.</p> <p class="notice">This user has no registered role profiles yet.</p>
</div> </div>
}> }>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<Show when={!roleFilter()}> <Show when={!roleFilter()}>
@ -233,7 +233,7 @@ export default function UserDetailPage() {
<th>Verification</th> <th>Verification</th>
<th>Submitted</th> <th>Submitted</th>
<th>Last Updated</th> <th>Last Updated</th>
<th class="align-right">Action</th> <th class="text-right">Action</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -251,8 +251,8 @@ export default function UserDetailPage() {
</td> </td>
<td>{formatDate(item.submittedAt)}</td> <td>{formatDate(item.submittedAt)}</td>
<td>{formatDate(item.updatedAt)}</td> <td>{formatDate(item.updatedAt)}</td>
<td class="align-right"> <td class="text-right">
<A class={`btn ${item.isApproval ? '' : 'orange'}`} href={item.isApproval ? `/admin/approval/${item.id}` : `/admin/profile/${item.publicId || item.id}`}> <A class={`inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors ${item.isApproval ? '' : 'orange'}`} href={item.isApproval ? `/admin/approval/${item.id}` : `/admin/profile/${item.publicId || item.id}`}>
{item.isApproval ? 'View Request' : 'Open Profile'} {item.isApproval ? 'View Request' : 'Open Profile'}
</A> </A>
</td> </td>

View file

@ -42,17 +42,17 @@ export default function VerificationStatusPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Verification Status</h1> <h1 class="text-2xl font-bold text-gray-900">Verification Status</h1>
<p class="page-subtitle">Track request status states and open a specific record for follow-up.</p> <p class="mt-1 text-sm text-gray-500">Track request status states and open a specific record for follow-up.</p>
</div> </div>
<A class="btn" href="/admin/approval">Open Approval Center</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/approval">Open Approval Center</A>
</div> </div>
<section class="card" style="padding:0;overflow:hidden"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>ID</th> <th>ID</th>
@ -60,7 +60,7 @@ export default function VerificationStatusPage() {
<th>Requester</th> <th>Requester</th>
<th>Status</th> <th>Status</th>
<th>Submitted</th> <th>Submitted</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -80,12 +80,12 @@ export default function VerificationStatusPage() {
<div style="font-weight:600;color:#0f172a">{item.requesterName}</div> <div style="font-weight:600;color:#0f172a">{item.requesterName}</div>
<div style="font-size:12px;color:#64748b">{item.requesterEmail}</div> <div style="font-size:12px;color:#64748b">{item.requesterEmail}</div>
</td> </td>
<td><span class="status-chip">{item.status}</span></td> <td><span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">{item.status}</span></td>
<td style="color:#475569">{item.createdAt ? new Date(item.createdAt).toLocaleString() : '—'}</td> <td style="color:#475569">{item.createdAt ? new Date(item.createdAt).toLocaleString() : '—'}</td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="action-icon-btn" href={`/admin/verification-status/${item.id}`} title="Open Status Detail"></A> <A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/verification-status/${item.id}`} title="Open Status Detail"></A>
<A class="action-icon-btn" href={`/admin/approval/${item.id}`} title="Open Approval Detail">👁</A> <A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/approval/${item.id}`} title="Open Approval Detail">👁</A>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -40,41 +40,41 @@ export default function VerificationStatusDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Verification Status Detail</h1> <h1 class="text-2xl font-bold text-gray-900">Verification Status Detail</h1>
<p class="page-subtitle">Open one verification status request and jump into approval review when needed.</p> <p class="mt-1 text-sm text-gray-500">Open one verification status request and jump into approval review when needed.</p>
</div> </div>
<div class="page-actions-right"> <div class="flex items-center gap-2">
<A class="btn" href="/admin/verification-status">Back to Status List</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/verification-status">Back to Status List</A>
<A class="btn navy" href={`/admin/approval/${params.id}`}>Open Approval Detail</A> <A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/approval/${params.id}`}>Open Approval Detail</A>
</div> </div>
</div> </div>
<Show when={detail.loading}> <Show when={detail.loading}>
<div class="card"><p class="notice">Loading verification status...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading verification status...</p></div>
</Show> </Show>
<Show when={!detail.loading && !detail()}> <Show when={!detail.loading && !detail()}>
<div class="card"><p class="notice">Verification status record not found.</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Verification status record not found.</p></div>
</Show> </Show>
<Show when={detail()}> <Show when={detail()}>
<div class="grid" style="margin-top:0"> <div class="grid" style="margin-top:0">
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h2 style="margin-bottom:8px">Request</h2> <h2 style="margin-bottom:8px">Request</h2>
<p class="notice" style="margin:0"><strong>ID:</strong> {detail()!.id}</p> <p class="notice" style="margin:0"><strong>ID:</strong> {detail()!.id}</p>
<p class="notice" style="margin:8px 0 0"><strong>Type:</strong> {type()}</p> <p class="notice" style="margin:8px 0 0"><strong>Type:</strong> {type()}</p>
<p class="notice" style="margin:8px 0 0"><strong>Status:</strong> {status()}</p> <p class="notice" style="margin:8px 0 0"><strong>Status:</strong> {status()}</p>
<p class="notice" style="margin:8px 0 0"><strong>Submitted:</strong> {submitted() ? new Date(submitted()).toLocaleString() : '—'}</p> <p class="notice" style="margin:8px 0 0"><strong>Submitted:</strong> {submitted() ? new Date(submitted()).toLocaleString() : '—'}</p>
</section> </section>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h2 style="margin-bottom:8px">Requester</h2> <h2 style="margin-bottom:8px">Requester</h2>
<p class="notice" style="margin:0"><strong>Name:</strong> {requester()}</p> <p class="notice" style="margin:0"><strong>Name:</strong> {requester()}</p>
<p class="notice" style="margin:8px 0 0"><strong>Email:</strong> {email()}</p> <p class="notice" style="margin:8px 0 0"><strong>Email:</strong> {email()}</p>
</section> </section>
</div> </div>
<section class="card" style="margin-top:16px"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-top:16px">
<h2 style="margin-bottom:10px">Request Reason Payload</h2> <h2 style="margin-bottom:10px">Request Reason Payload</h2>
<pre class="json">{detail()!.requestReason || 'No requestReason payload found.'}</pre> <pre class="json">{detail()!.requestReason || 'No requestReason payload found.'}</pre>
</section> </section>

View file

@ -5,18 +5,18 @@ export default function VerificationManagementPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card"> <div class="page-hero-card">
<h1 class="page-title">Approval Management</h1> <h1 class="text-2xl font-bold text-gray-900">Approval Management</h1>
<p class="page-subtitle" style="margin-top:8px"> <p class="mt-1 text-sm text-gray-500" style="margin-top:8px">
Admin review now lives under Approval Management. Verification remains a user-facing status concept. Admin review now lives under Approval Management. Verification remains a user-facing status concept.
</p> </p>
</div> </div>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<p class="notice" style="margin:0"> <p class="notice" style="margin:0">
Use Approval Management to review companies, customers, candidates, and professional submissions. Use Approval Management to review companies, customers, candidates, and professional submissions.
</p> </p>
<div class="actions"> <div class="actions">
<A class="btn navy" href="/admin/approval">Open Approval Management</A> <A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/approval">Open Approval Management</A>
</div> </div>
</section> </section>
</AdminShell> </AdminShell>

View file

@ -37,26 +37,26 @@ export default function VerificationDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Verification Review</h1> <h1 class="text-2xl font-bold text-gray-900">Verification Review</h1>
<p class="page-subtitle">Review submission context, documents, and verification decision state.</p> <p class="mt-1 text-sm text-gray-500">Review submission context, documents, and verification decision state.</p>
</div> </div>
<div class="page-actions-right"> <div class="flex items-center gap-2">
<A class="btn" href="/admin/verification">Back to Verification</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/verification">Back to Verification</A>
<A class="btn navy" href={`/admin/approval/${params.id}`}>Open Approval Detail</A> <A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/approval/${params.id}`}>Open Approval Detail</A>
</div> </div>
</div> </div>
<Show when={approval.loading}> <Show when={approval.loading}>
<div class="card"><p class="notice">Loading verification detail...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading verification detail...</p></div>
</Show> </Show>
<Show when={!approval.loading && !approval()}> <Show when={!approval.loading && !approval()}>
<div class="card"><p class="notice">Verification request not found.</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Verification request not found.</p></div>
</Show> </Show>
<Show when={approval()}> <Show when={approval()}>
<div class="grid" style="margin-top:0"> <div class="grid" style="margin-top:0">
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h2 style="margin-bottom:8px">Summary</h2> <h2 style="margin-bottom:8px">Summary</h2>
<p class="notice" style="margin:0"><strong>Approval ID:</strong> {approval()!.id}</p> <p class="notice" style="margin:0"><strong>Approval ID:</strong> {approval()!.id}</p>
<p class="notice" style="margin:8px 0 0"><strong>Type:</strong> {type()}</p> <p class="notice" style="margin:8px 0 0"><strong>Type:</strong> {type()}</p>
@ -64,13 +64,13 @@ export default function VerificationDetailPage() {
<p class="notice" style="margin:8px 0 0"><strong>Requester:</strong> {requester()}</p> <p class="notice" style="margin:8px 0 0"><strong>Requester:</strong> {requester()}</p>
<p class="notice" style="margin:8px 0 0"><strong>Email:</strong> {email()}</p> <p class="notice" style="margin:8px 0 0"><strong>Email:</strong> {email()}</p>
</section> </section>
<section class="card"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<h2 style="margin-bottom:8px">Remark Snapshot</h2> <h2 style="margin-bottom:8px">Remark Snapshot</h2>
<p class="notice" style="margin:0"> <p class="notice" style="margin:0">
This route mirrors the Next.js verification detail entry point and delegates action workflow to Approval Management. This route mirrors the Next.js verification detail entry point and delegates action workflow to Approval Management.
</p> </p>
<div class="actions"> <div class="actions">
<A class="btn primary" href={`/admin/approval/${params.id}`}>Review & Take Action</A> <A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/approval/${params.id}`}>Review & Take Action</A>
</div> </div>
</section> </section>
</div> </div>

View file

@ -36,14 +36,14 @@ export default function VideoEditorsPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-actions"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="page-title">Video Editor Management</h1> <h1 class="text-2xl font-bold text-gray-900">Video Editor Management</h1>
<p class="page-subtitle">Manage all video editor accounts on the platform.</p> <p class="mt-1 text-sm text-gray-500">Manage all video editor accounts on the platform.</p>
</div> </div>
</div> </div>
<section class="card" style="padding: 0; overflow: hidden;"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;"> <div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
<input <input
type="text" type="text"
@ -64,15 +64,15 @@ export default function VideoEditorsPage() {
</select> </select>
</div> </div>
<div class="table-wrap"> <div class="overflow-x-auto">
<table class="list-table"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Email</th> <th>Email</th>
<th>Status</th> <th>Status</th>
<th>Registered</th> <th>Registered</th>
<th class="align-right">Actions</th> <th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -93,22 +93,22 @@ export default function VideoEditorsPage() {
<td style="color:#475569">{item.email}</td> <td style="color:#475569">{item.email}</td>
<td> <td>
{item.status?.toUpperCase() === 'ACTIVE' && ( {item.status?.toUpperCase() === 'ACTIVE' && (
<span class="status-chip active">ACTIVE</span> <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'INACTIVE' && ( {item.status?.toUpperCase() === 'INACTIVE' && (
<span class="status-chip">INACTIVE</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
)} )}
{item.status?.toUpperCase() === 'PENDING' && ( {item.status?.toUpperCase() === 'PENDING' && (
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span> <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
)} )}
{!item.status && <span class="status-chip"></span>} {!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600"></span>}
</td> </td>
<td style="color:#475569"> <td style="color:#475569">
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'} {item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
</td> </td>
<td> <td>
<div class="table-actions"> <div class="flex items-center justify-end gap-1">
<A class="btn" href={`/admin/users/${item.id}`}>View</A> <A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -5,5 +5,5 @@ import AdminShell from '~/components/AdminShell';
export default function WorkspaceAliasPage() { export default function WorkspaceAliasPage() {
const navigate = useNavigate(); const navigate = useNavigate();
onMount(() => navigate('/admin', { replace: true })); onMount(() => navigate('/admin', { replace: true }));
return <AdminShell><div class="card"><p class="notice">Redirecting to dashboard workspace...</p></div></AdminShell>; return <AdminShell><div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to dashboard workspace...</p></div></AdminShell>;
} }

View file

@ -8,7 +8,7 @@ export default function WorkspaceMenuAliasPage() {
onMount(() => navigate('/admin', { replace: true })); onMount(() => navigate('/admin', { replace: true }));
return ( return (
<AdminShell> <AdminShell>
<div class="card"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
<p class="notice">Redirecting workspace menu <strong>{params.menuId}</strong> to dashboard...</p> <p class="notice">Redirecting workspace menu <strong>{params.menuId}</strong> to dashboard...</p>
</div> </div>
</AdminShell> </AdminShell>

View file

@ -1,3 +1,9 @@
import { defineConfig } from '@solidjs/start/config'; import { defineConfig } from '@solidjs/start/config';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({}); export default defineConfig({
ssr: false,
vite: {
plugins: [tailwindcss()],
},
});