feat(pitch): showcase mode — per-investor toggle hides financial/investor slides for customer demos
Build pitch-deck / build-push-deploy (push) Successful in 1m35s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 39s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 30s
Build pitch-deck / build-push-deploy (push) Successful in 1m35s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 39s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 30s
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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]
|
||||
)
|
||||
|
||||
@@ -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 */}
|
||||
<section className="bg-white/[0.04] border border-white/[0.06] rounded-2xl p-5">
|
||||
<h2 className="text-sm font-semibold text-white mb-3">Pitch Version</h2>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<select
|
||||
value={inv.assigned_version_id || ''}
|
||||
onChange={async (e) => {
|
||||
@@ -318,10 +319,35 @@ export default function InvestorDetailPage() {
|
||||
</select>
|
||||
<span className="text-xs text-white/40">
|
||||
{inv.assigned_version_id
|
||||
? `Investor sees version "${inv.version_name || ''}"`
|
||||
: 'Investor sees default pitch data'}
|
||||
? `Sees version "${inv.version_name || ''}"`
|
||||
: 'Sees default pitch data'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Showcase toggle */}
|
||||
<div className="flex items-center justify-between mt-4 pt-4 border-t border-white/[0.06]">
|
||||
<div>
|
||||
<div className="text-sm text-white font-medium">Showcase mode</div>
|
||||
<div className="text-xs text-white/40 mt-0.5">Hides financials, The Ask, and investor-only slides — for customer demos</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={async () => {
|
||||
setBusy(true)
|
||||
const res = await fetch(`/api/admin/investors/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ is_showcase: !inv.is_showcase }),
|
||||
})
|
||||
setBusy(false)
|
||||
if (res.ok) { flashToast(inv.is_showcase ? 'Switched to investor mode' : 'Switched to showcase mode'); load() }
|
||||
else { flashToast('Update failed') }
|
||||
}}
|
||||
disabled={busy}
|
||||
className={`relative w-11 h-6 rounded-full transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-black focus:ring-indigo-500 ${inv.is_showcase ? 'bg-indigo-500' : 'bg-white/10'}`}
|
||||
>
|
||||
<span className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform duration-200 ${inv.is_showcase ? 'translate-x-5' : 'translate-x-0'}`} />
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Audit log for this investor */}
|
||||
|
||||
Reference in New Issue
Block a user