feat(routes): unify won leads and projects on a single /projects/:id page

- add role-prefix-free /projects/:id route (OWNER/ADMIN/FIELD_AGENT; ABAC guard lands in Phase 8)
- relax detail lookup so admin/field-agent open any project, owners only their own
- kanban drawer + project list route won leads (with projectId) to /projects/:id; pre-sale leads stay on the lead page
This commit is contained in:
Satyam Rastogi
2026-05-29 15:51:09 +05:30
parent d5ea0935b4
commit ec1d9dfb25
4 changed files with 15 additions and 6 deletions
+6
View File
@@ -188,6 +188,12 @@ function App() {
<OwnerProjectDetail />
</ProtectedRoute>
} />
{/* Unified project detail — won leads and projects share this page (interim coarse guard; ABAC policy guard lands in Phase 8) */}
<Route path="/projects/:projectId" element={
<ProtectedRoute allowedRoles={['OWNER', 'ADMIN', 'FIELD_AGENT']}>
<OwnerProjectDetail />
</ProtectedRoute>
} />
<Route path="/owner/maps" element={
<ProtectedRoute allowedRoles={['OWNER']}>
<Maps />
+3 -1
View File
@@ -299,7 +299,9 @@ export default function LeadInfoDrawer({ lead, columns, isOpen, onClose }) {
<button
onClick={() => {
onClose();
navigate(`${basePath}/leads/${lead.id}`);
// Won leads are projects now — open the unified project page; pre-sale leads stay on the lead page.
if (lead.projectId) navigate(`/projects/${lead.projectId}`);
else navigate(`${basePath}/leads/${lead.id}`);
}}
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl bg-blue-600 hover:bg-blue-700 text-white text-sm font-semibold transition-colors shadow-sm"
>
+2 -1
View File
@@ -294,7 +294,8 @@ const OwnerProjectDetail = () => {
const totalReceived = payments.reduce((sum, p) => sum + p.amount, 0);
const project = useMemo(() =>
projects.find(p => p.id === projectId && p.ownerId === user?.id)
// Owners only see their own projects; admin/field-agent can open any project.
projects.find(p => p.id === projectId && (user?.role !== 'OWNER' || p.ownerId === user?.id))
, [projects, projectId, user]);
useEffect(() => {
+4 -4
View File
@@ -277,7 +277,7 @@ const OwnerProjectList = () => {
return (
<div
key={project.id}
onClick={() => navigate(`/owner/projects/${project.id}`)}
onClick={() => navigate(`/projects/${project.id}`)}
className="p-4 active:bg-zinc-50 dark:active:bg-white/5 transition-colors cursor-pointer"
>
<div className="flex justify-between items-start mb-2">
@@ -363,7 +363,7 @@ const OwnerProjectList = () => {
return (
<tr
key={project.id}
onClick={() => navigate(`/owner/projects/${project.id}`)}
onClick={() => navigate(`/projects/${project.id}`)}
className="hover:bg-zinc-50 dark:hover:bg-white/5 transition-colors cursor-pointer group"
>
{/* Project Name */}
@@ -496,7 +496,7 @@ const OwnerProjectList = () => {
return (
<div
key={lead.id}
onClick={() => navigate(`/owner/leads/${lead.id}`)}
onClick={() => navigate(lead.projectId ? `/projects/${lead.projectId}` : `/owner/leads/${lead.id}`)}
className="p-4 active:bg-zinc-50 dark:active:bg-white/5 transition-colors cursor-pointer"
>
<div className="flex justify-between items-start mb-2">
@@ -562,7 +562,7 @@ const OwnerProjectList = () => {
return (
<tr
key={lead.id}
onClick={() => navigate(`/owner/leads/${lead.id}`)}
onClick={() => navigate(lead.projectId ? `/projects/${lead.projectId}` : `/owner/leads/${lead.id}`)}
className="hover:bg-zinc-50 dark:hover:bg-white/5 transition-colors cursor-pointer group"
>
<td className="px-5 py-4">