diff --git a/artifacts/api-server/src/routes/costs.ts b/artifacts/api-server/src/routes/costs.ts index 6eac8dd..56f1617 100644 --- a/artifacts/api-server/src/routes/costs.ts +++ b/artifacts/api-server/src/routes/costs.ts @@ -2,7 +2,7 @@ import { Router, type IRouter } from "express"; import { eq, and } from "drizzle-orm"; import { z } from "zod"; import { db, toolsTable, toolCostsTable } from "@workspace/db"; -import { requireAuth, requireAdmin } from "../middleware/auth"; +import { requireAuth } from "../middleware/auth"; import { requireFeature } from "../middleware/feature"; import { writeAuditLog } from "../lib/audit"; @@ -43,7 +43,7 @@ router.get("/tools/:id/costs", async (req, res): Promise => { res.json(costs); }); -router.post("/tools/:id/costs", requireAuth, requireFeature("costs"), requireAdmin, async (req, res): Promise => { +router.post("/tools/:id/costs", requireAuth, requireFeature("costs"), async (req, res): Promise => { const toolId = Number(req.params.id); if (isNaN(toolId)) { res.status(400).json({ error: "Invalid id" }); return; } @@ -71,7 +71,7 @@ router.post("/tools/:id/costs", requireAuth, requireFeature("costs"), requireAdm res.status(201).json(entry); }); -router.patch("/costs/:id", requireAuth, requireFeature("costs"), requireAdmin, async (req, res): Promise => { +router.patch("/costs/:id", requireAuth, requireFeature("costs"), async (req, res): Promise => { const id = Number(req.params.id); if (isNaN(id)) { res.status(400).json({ error: "Invalid id" }); return; } @@ -96,7 +96,7 @@ router.patch("/costs/:id", requireAuth, requireFeature("costs"), requireAdmin, a res.json(updated); }); -router.delete("/costs/:id", requireAuth, requireFeature("costs"), requireAdmin, async (req, res): Promise => { +router.delete("/costs/:id", requireAuth, requireFeature("costs"), async (req, res): Promise => { const id = Number(req.params.id); if (isNaN(id)) { res.status(400).json({ error: "Invalid id" }); return; } diff --git a/artifacts/toolrate/src/hooks/use-auth.ts b/artifacts/toolrate/src/hooks/use-auth.ts index 63cbb9d..b8a7f5c 100644 --- a/artifacts/toolrate/src/hooks/use-auth.ts +++ b/artifacts/toolrate/src/hooks/use-auth.ts @@ -22,6 +22,12 @@ export function useAuth() { const isLocalMode = authMode?.mode === "local"; const tier = isAuthenticated ? user?.tier ?? "free" : "free"; + function hasFeature(feature: string): boolean { + if (!isAuthenticated) return false; + if (user?.role === "admin") return true; + return !!user?.entitlements?.includes(feature); + } + function login(returnTo?: string) { if (isLocalMode) { const path = returnTo @@ -47,6 +53,7 @@ export function useAuth() { isAdmin, isLocalMode, tier, + hasFeature, login, logout, }; diff --git a/artifacts/toolrate/src/pages/tool-detail.tsx b/artifacts/toolrate/src/pages/tool-detail.tsx index b9efbc0..40653ba 100644 --- a/artifacts/toolrate/src/pages/tool-detail.tsx +++ b/artifacts/toolrate/src/pages/tool-detail.tsx @@ -74,7 +74,8 @@ export default function ToolDetail() { const [isReviewFormOpen, setIsReviewFormOpen] = useState(false); const [deleteOpen, setDeleteOpen] = useState(false); - const { user, isAdmin } = useAuth(); + const { user, isAdmin, hasFeature } = useAuth(); + const canManageCosts = hasFeature("costs"); const deleteTool = useDeleteTool(); const [similarData, setSimilarData] = useState<{ manual: any[]; auto: any[] } | null>(null); @@ -535,7 +536,7 @@ export default function ToolDetail() {

Costs

- {isAdmin && ( + {canManageCosts && ( @@ -560,16 +561,16 @@ export default function ToolDetail() {
{c.notes &&

{c.notes}

}
- {isAdmin && ( -
- - -
- )} + {canManageCosts && ( +
+ + +
+ )}