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 }),
+}))