From 7887c2c24f39029285909ea45eb542fe1607d362 Mon Sep 17 00:00:00 2001 From: janpeter visser Date: Mon, 27 Apr 2026 19:36:25 +0200 Subject: [PATCH] feat: active PB indicator, Maak actief button and new product link in settings --- app/(app)/products/[id]/page.tsx | 2 +- app/(app)/settings/page.tsx | 104 ++++++++++++------ components/shared/activate-product-button.tsx | 10 +- 3 files changed, 79 insertions(+), 37 deletions(-) diff --git a/app/(app)/products/[id]/page.tsx b/app/(app)/products/[id]/page.tsx index c0bc24e..b6e110b 100644 --- a/app/(app)/products/[id]/page.tsx +++ b/app/(app)/products/[id]/page.tsx @@ -69,7 +69,7 @@ export default async function ProductBacklogPage({ params }: Props) {
{user?.active_product_id !== id && ( - + )} {activeSprint ? ( diff --git a/app/(app)/settings/page.tsx b/app/(app)/settings/page.tsx index 70e0466..07b05a8 100644 --- a/app/(app)/settings/page.tsx +++ b/app/(app)/settings/page.tsx @@ -5,6 +5,7 @@ import { prisma } from '@/lib/prisma' import { RoleManager } from '@/components/settings/role-manager' import { LeaveProductButton } from '@/components/settings/leave-product-button' import { ProfileEditor } from '@/components/settings/profile-editor' +import { ActivateProductButton } from '@/components/shared/activate-product-button' import Link from 'next/link' export default async function SettingsPage() { @@ -13,7 +14,7 @@ export default async function SettingsPage() { const [user, userRoles, ownedProducts, memberships] = await Promise.all([ prisma.user.findUnique({ where: { id: session.userId }, - select: { username: true, email: true, bio: true, bio_detail: true, avatar_data: true, updated_at: true }, + select: { username: true, email: true, bio: true, bio_detail: true, avatar_data: true, updated_at: true, active_product_id: true }, }), prisma.userRole.findMany({ where: { user_id: session.userId } }), prisma.product.findMany({ @@ -33,7 +34,9 @@ export default async function SettingsPage() { | { kind: 'owner'; id: string; name: string } | { kind: 'member'; id: string; name: string; ownerUsername: string } - const productBacklogs: PbEntry[] = [ + const activeProductId = user?.active_product_id ?? null + + const allBacklogs: PbEntry[] = [ ...ownedProducts.map(p => ({ kind: 'owner' as const, id: p.id, name: p.name })), ...memberships.map(m => ({ kind: 'member' as const, @@ -43,6 +46,12 @@ export default async function SettingsPage() { })), ] + // Active product floats to the top + const productBacklogs = [ + ...allBacklogs.filter(pb => pb.id === activeProductId), + ...allBacklogs.filter(pb => pb.id !== activeProductId), + ] + return (

Instellingen

@@ -78,11 +87,21 @@ export default async function SettingsPage() {
-
-

Product Backlogs

-

- Alle product backlogs waarbij je betrokken bent. -

+
+
+

Product Backlogs

+

+ Alle product backlogs waarbij je betrokken bent. +

+
+ {!session.isDemo && ( + + + Nieuw product + + )}
{productBacklogs.length === 0 ? (

@@ -91,33 +110,52 @@ export default async function SettingsPage() {

) : (
    - {productBacklogs.map(pb => ( -
  • -
    -
    - - {pb.name} - - - {pb.kind === 'owner' ? 'Eigenaar' : 'Developer'} - + {productBacklogs.map(pb => { + const isActive = pb.id === activeProductId + return ( +
  • +
    +
    + + {pb.name} + + + {pb.kind === 'owner' ? 'Eigenaar' : 'Developer'} + + {isActive && ( + + Actief + + )} +
    + {pb.kind === 'member' && ( +

    Eigenaar: {pb.ownerUsername}

    + )}
    - {pb.kind === 'member' && ( -

    Eigenaar: {pb.ownerUsername}

    - )} -
- {pb.kind === 'member' && !session.isDemo && ( - - )} - - ))} +
+ {!isActive && ( + + )} + {pb.kind === 'member' && !session.isDemo && ( + + )} +
+ + ) + })} )}
diff --git a/components/shared/activate-product-button.tsx b/components/shared/activate-product-button.tsx index 21c4edf..90cf54b 100644 --- a/components/shared/activate-product-button.tsx +++ b/components/shared/activate-product-button.tsx @@ -8,9 +8,12 @@ import { setActiveProductAction } from '@/actions/active-product' interface Props { productId: string isDemo: boolean + /** Navigate here after activation. Omit to refresh the current page in place. */ + redirectTo?: string + label?: string } -export function ActivateProductButton({ productId, isDemo }: Props) { +export function ActivateProductButton({ productId, isDemo, redirectTo, label = 'Activeer' }: Props) { const router = useRouter() const [isPending, startTransition] = useTransition() @@ -19,7 +22,8 @@ export function ActivateProductButton({ productId, isDemo }: Props) { startTransition(async () => { const result = await setActiveProductAction(productId) if (result?.error) toast.error(typeof result.error === 'string' ? result.error : 'Activeren mislukt') - else router.push(`/products/${productId}`) + else if (redirectTo) router.push(redirectTo) + else router.refresh() }) } @@ -29,7 +33,7 @@ export function ActivateProductButton({ productId, isDemo }: Props) { disabled={isPending} className="text-xs text-primary hover:underline font-medium disabled:opacity-50" > - Activeer + {label} ) }