From 30a916549787eba86070d7ec98b9c36e695d21d3 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Mon, 4 May 2026 22:41:15 +0200 Subject: [PATCH] =?UTF-8?q?feat(pitch):=20showcase=20mode=20=E2=80=94=20pe?= =?UTF-8?q?r-investor=20toggle=20hides=20financial/investor=20slides=20for?= =?UTF-8?q?=20customer=20demos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds is_showcase boolean to pitch_investors; when set, filters out financials, the ask, cap table, assumptions, finanzplan, risks, and intro-presenter slides. Slide navigation is fully dynamic — progress bar and counts update accordingly. Co-Authored-By: Claude Sonnet 4.6 --- .../app/api/admin/investors/[id]/route.ts | 17 ++++++---- pitch-deck/app/api/auth/me/route.ts | 2 +- .../(authed)/investors/[id]/page.tsx | 32 +++++++++++++++++-- pitch-deck/components/PitchDeck.tsx | 12 +++++-- pitch-deck/lib/hooks/useAuth.ts | 1 + pitch-deck/lib/hooks/useSlideNavigation.ts | 20 ++++++------ pitch-deck/lib/slide-order.ts | 12 +++++++ 7 files changed, 74 insertions(+), 22 deletions(-) diff --git a/pitch-deck/app/api/admin/investors/[id]/route.ts b/pitch-deck/app/api/admin/investors/[id]/route.ts index 15ef3cf..343b489 100644 --- a/pitch-deck/app/api/admin/investors/[id]/route.ts +++ b/pitch-deck/app/api/admin/investors/[id]/route.ts @@ -19,7 +19,7 @@ export async function GET(request: NextRequest, ctx: RouteContext) { pool.query( `SELECT i.id, i.email, i.name, i.company, i.status, i.last_login_at, i.login_count, i.created_at, i.updated_at, i.first_activity_at, i.data_masked_at, - i.assigned_version_id, + i.assigned_version_id, i.is_showcase, v.name AS version_name, v.status AS version_status FROM pitch_investors i LEFT JOIN pitch_versions v ON v.id = i.assigned_version_id @@ -68,14 +68,14 @@ export async function PATCH(request: NextRequest, ctx: RouteContext) { const { id } = await ctx.params const body = await request.json().catch(() => ({})) - const { name, company, assigned_version_id } = body + const { name, company, assigned_version_id, is_showcase } = body - if (name === undefined && company === undefined && assigned_version_id === undefined) { - return NextResponse.json({ error: 'name, company, or assigned_version_id required' }, { status: 400 }) + if (name === undefined && company === undefined && assigned_version_id === undefined && is_showcase === undefined) { + return NextResponse.json({ error: 'name, company, assigned_version_id, or is_showcase required' }, { status: 400 }) } const before = await pool.query( - `SELECT name, company, assigned_version_id FROM pitch_investors WHERE id = $1`, + `SELECT name, company, assigned_version_id, is_showcase FROM pitch_investors WHERE id = $1`, [id], ) if (before.rows.length === 0) { @@ -99,15 +99,18 @@ export async function PATCH(request: NextRequest, ctx: RouteContext) { // Use null to clear version assignment, undefined to leave unchanged const versionValue = assigned_version_id === undefined ? before.rows[0].assigned_version_id : (assigned_version_id || null) + const showcaseValue = is_showcase !== undefined ? Boolean(is_showcase) : before.rows[0].is_showcase + const { rows } = await pool.query( `UPDATE pitch_investors SET name = COALESCE($1, name), company = COALESCE($2, company), assigned_version_id = $4, + is_showcase = $5, updated_at = NOW() WHERE id = $3 - RETURNING id, email, name, company, status, assigned_version_id`, - [name ?? null, company ?? null, id, versionValue], + RETURNING id, email, name, company, status, assigned_version_id, is_showcase`, + [name ?? null, company ?? null, id, versionValue, showcaseValue], ) const action = assigned_version_id !== undefined && assigned_version_id !== before.rows[0].assigned_version_id diff --git a/pitch-deck/app/api/auth/me/route.ts b/pitch-deck/app/api/auth/me/route.ts index 5d053d8..55dd2ec 100644 --- a/pitch-deck/app/api/auth/me/route.ts +++ b/pitch-deck/app/api/auth/me/route.ts @@ -14,7 +14,7 @@ export async function GET() { } const { rows } = await pool.query( - `SELECT id, email, name, company, status, last_login_at, login_count, created_at + `SELECT id, email, name, company, status, last_login_at, login_count, created_at, is_showcase FROM pitch_investors WHERE id = $1`, [session.sub] ) diff --git a/pitch-deck/app/pitch-admin/(authed)/investors/[id]/page.tsx b/pitch-deck/app/pitch-admin/(authed)/investors/[id]/page.tsx index 385c33f..640bfb8 100644 --- a/pitch-deck/app/pitch-admin/(authed)/investors/[id]/page.tsx +++ b/pitch-deck/app/pitch-admin/(authed)/investors/[id]/page.tsx @@ -21,6 +21,7 @@ interface InvestorDetail { assigned_version_id: string | null version_name: string | null version_status: string | null + is_showcase: boolean } sessions: Array<{ id: string @@ -293,7 +294,7 @@ export default function InvestorDetailPage() { {/* Version assignment */}

Pitch Version

-
+
{inv.assigned_version_id - ? `Investor sees version "${inv.version_name || ''}"` - : 'Investor sees default pitch data'} + ? `Sees version "${inv.version_name || ''}"` + : 'Sees default pitch data'}
+ + {/* Showcase toggle */} +
+
+
Showcase mode
+
Hides financials, The Ask, and investor-only slides — for customer demos
+
+ +
{/* Audit log for this investor */} diff --git a/pitch-deck/components/PitchDeck.tsx b/pitch-deck/components/PitchDeck.tsx index ff13c67..c59fc57 100644 --- a/pitch-deck/components/PitchDeck.tsx +++ b/pitch-deck/components/PitchDeck.tsx @@ -2,7 +2,8 @@ import { useCallback, useEffect, useState } from 'react' import { AnimatePresence } from 'framer-motion' -import { useSlideNavigation } from '@/lib/hooks/useSlideNavigation' +import { useSlideNavigation, SLIDE_ORDER } from '@/lib/hooks/useSlideNavigation' +import { SHOWCASE_HIDDEN_SLIDES } from '@/lib/slide-order' import { useKeyboard } from '@/lib/hooks/useKeyboard' import { usePitchData } from '@/lib/hooks/usePitchData' import { usePresenterMode } from '@/lib/hooks/usePresenterMode' @@ -68,15 +69,22 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout, const data = previewData || fetched.data const loading = previewData ? false : fetched.loading const error = previewData ? null : fetched.error - const nav = useSlideNavigation() const [fabOpen, setFabOpen] = useState(false) const isWandeldarlehen = (data?.funding?.instrument || '').toLowerCase() === 'wandeldarlehen' + const isShowcase = investor?.is_showcase === true // Derive fp_scenario IDs from version snapshot (fm_scenarios stores fp_scenario IDs directly) const fpScenarios = data?.fp_scenarios || [] const fpBaseScenarioId = fpScenarios.find(s => s.is_default)?.id ?? fpScenarios[0]?.id ?? null const preferredScenarioId = fpBaseScenarioId + // Showcase mode: filter out investor/financial slides + const activeSlideOrder = isShowcase + ? SLIDE_ORDER.filter(s => !SHOWCASE_HIDDEN_SLIDES.has(s)) + : SLIDE_ORDER + + const nav = useSlideNavigation(activeSlideOrder) + // Skip cap-table slide for Wandeldarlehen versions useEffect(() => { if (nav.currentSlide === 'cap-table' && isWandeldarlehen) { diff --git a/pitch-deck/lib/hooks/useAuth.ts b/pitch-deck/lib/hooks/useAuth.ts index fa52f5d..f56eeaa 100644 --- a/pitch-deck/lib/hooks/useAuth.ts +++ b/pitch-deck/lib/hooks/useAuth.ts @@ -11,6 +11,7 @@ export interface Investor { last_login_at: string | null login_count: number created_at: string + is_showcase: boolean } export function useAuth() { diff --git a/pitch-deck/lib/hooks/useSlideNavigation.ts b/pitch-deck/lib/hooks/useSlideNavigation.ts index 3c7c517..a236091 100644 --- a/pitch-deck/lib/hooks/useSlideNavigation.ts +++ b/pitch-deck/lib/hooks/useSlideNavigation.ts @@ -1,21 +1,23 @@ 'use client' import { useState, useCallback } from 'react' +import { SlideId } from '../types' import { SLIDE_ORDER, TOTAL_SLIDES } from '../slide-order' // Re-export for backwards compatibility export { SLIDE_ORDER, TOTAL_SLIDES } -export function useSlideNavigation() { +export function useSlideNavigation(slideOrder: SlideId[] = SLIDE_ORDER) { + const total = slideOrder.length const [currentIndex, setCurrentIndex] = useState(0) const [direction, setDirection] = useState(0) const [visitedSlides, setVisitedSlides] = useState>(new Set([0])) const [showOverview, setShowOverview] = useState(false) - const currentSlide = SLIDE_ORDER[currentIndex] + const currentSlide = slideOrder[currentIndex] const goToSlide = useCallback((index: number) => { - if (index < 0 || index >= TOTAL_SLIDES) return + if (index < 0 || index >= total) return setDirection(index > currentIndex ? 1 : -1) setCurrentIndex(index) setVisitedSlides(prev => new Set([...prev, index])) @@ -23,10 +25,10 @@ export function useSlideNavigation() { }, [currentIndex]) const nextSlide = useCallback(() => { - if (currentIndex < TOTAL_SLIDES - 1) { + if (currentIndex < total - 1) { goToSlide(currentIndex + 1) } - }, [currentIndex, goToSlide]) + }, [currentIndex, goToSlide, total]) const prevSlide = useCallback(() => { if (currentIndex > 0) { @@ -35,7 +37,7 @@ export function useSlideNavigation() { }, [currentIndex, goToSlide]) const goToFirst = useCallback(() => goToSlide(0), [goToSlide]) - const goToLast = useCallback(() => goToSlide(TOTAL_SLIDES - 1), [goToSlide]) + const goToLast = useCallback(() => goToSlide(total - 1), [goToSlide, total]) const toggleOverview = useCallback(() => { setShowOverview(prev => !prev) @@ -47,8 +49,8 @@ export function useSlideNavigation() { direction, visitedSlides, showOverview, - totalSlides: TOTAL_SLIDES, - slideOrder: SLIDE_ORDER, + totalSlides: total, + slideOrder, goToSlide, nextSlide, prevSlide, @@ -57,6 +59,6 @@ export function useSlideNavigation() { toggleOverview, setShowOverview, isFirst: currentIndex === 0, - isLast: currentIndex === TOTAL_SLIDES - 1, + isLast: currentIndex === total - 1, } } diff --git a/pitch-deck/lib/slide-order.ts b/pitch-deck/lib/slide-order.ts index 1fe3ac2..1b0eca6 100644 --- a/pitch-deck/lib/slide-order.ts +++ b/pitch-deck/lib/slide-order.ts @@ -1,5 +1,17 @@ import { SlideId } from './types' +// Slides hidden in showcase (customer) mode — financial and investor-specific content +export const SHOWCASE_HIDDEN_SLIDES = new Set([ + 'intro-presenter', + 'executive-summary', + 'financials', + 'the-ask', + 'cap-table', + 'annex-assumptions', + 'annex-finanzplan', + 'risks', +]) + export const SLIDE_ORDER: SlideId[] = [ 'intro-presenter', 'executive-summary',