diff --git a/app/(app)/products/[id]/layout.tsx b/app/(app)/products/[id]/layout.tsx new file mode 100644 index 0000000..decfdcb --- /dev/null +++ b/app/(app)/products/[id]/layout.tsx @@ -0,0 +1,25 @@ +import { redirect, notFound } from 'next/navigation' +import { getSession } from '@/lib/auth' +import { getAccessibleProduct } from '@/lib/product-access' +import { SetCurrentProduct } from '@/components/shared/set-current-product' + +interface Props { + children: React.ReactNode + params: Promise<{ id: string }> +} + +export default async function ProductLayout({ children, params }: Props) { + const { id } = await params + const session = await getSession() + if (!session.userId) redirect('/login') + + const product = await getAccessibleProduct(id, session.userId) + if (!product) notFound() + + return ( + <> + + {children} + + ) +} diff --git a/components/shared/nav-bar.tsx b/components/shared/nav-bar.tsx index adeca44..b2c9888 100644 --- a/components/shared/nav-bar.tsx +++ b/components/shared/nav-bar.tsx @@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { AppIcon } from '@/components/shared/app-icon' import { cn } from '@/lib/utils' +import { useProductStore } from '@/stores/product-store' const ROLE_LABELS: Record = { PRODUCT_OWNER: 'PO', @@ -21,6 +22,7 @@ interface NavBarProps { export function NavBar({ isDemo, roles }: NavBarProps) { const pathname = usePathname() + const currentProduct = useProductStore(s => s.currentProduct) const productMatch = pathname.match(/^\/products\/([^/]+)/) const productId = productMatch ? productMatch[1] : null @@ -81,6 +83,17 @@ export function NavBar({ isDemo, roles }: NavBarProps) { {roles.map(r => ROLE_LABELS[r]).filter(Boolean).join(' · ')} )} + {currentProduct && ( + + {currentProduct.name.length > 20 + ? currentProduct.name.slice(0, 20) + '…' + : currentProduct.name} + + )} { + setCurrentProduct(id, name) + return () => clearCurrentProduct() + }, [id, name, setCurrentProduct, clearCurrentProduct]) + + return null +} diff --git a/lib/product-access.ts b/lib/product-access.ts index b315fee..3b80a21 100644 --- a/lib/product-access.ts +++ b/lib/product-access.ts @@ -1,3 +1,4 @@ +import { cache } from 'react' import { prisma } from '@/lib/prisma' const accessFilter = (userId: string) => ({ @@ -7,11 +8,11 @@ const accessFilter = (userId: string) => ({ ], }) -export async function getAccessibleProduct(productId: string, userId: string) { +export const getAccessibleProduct = cache(async (productId: string, userId: string) => { return prisma.product.findFirst({ where: { id: productId, ...accessFilter(userId) }, }) -} +}) export function productAccessFilter(userId: string) { return accessFilter(userId) diff --git a/stores/product-store.ts b/stores/product-store.ts new file mode 100644 index 0000000..ad48ea4 --- /dev/null +++ b/stores/product-store.ts @@ -0,0 +1,13 @@ +import { create } from 'zustand' + +interface ProductStore { + currentProduct: { id: string; name: string } | null + setCurrentProduct: (id: string, name: string) => void + clearCurrentProduct: () => void +} + +export const useProductStore = create((set) => ({ + currentProduct: null, + setCurrentProduct: (id, name) => set({ currentProduct: { id, name } }), + clearCurrentProduct: () => set({ currentProduct: null }), +}))