feat(pitch-deck): Pricing slide, GuV hierarchy, Problem/Solution cards, engine fixes
Some checks failed
Build pitch-deck / build-push-deploy (push) Successful in 1m52s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-python-voice (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
CI / test-go-consent (push) Has been cancelled

- BusinessModel → Pricing: remove Unit Economics, fullwidth tiers
- GuV: major sums (EBIT, Rohergebnis, Jahresüberschuss) larger font + border
- Engine: compute Rohergebnis, dynamic financing row matching
- Problem slide: amber/orange "Die Konsequenz" card
- Solution slide: larger Compliance Optimizer card
- DB patch: Stammkapital, 2. Finanzierungsrunde 500k, GuV sort order

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-21 20:33:35 +02:00
parent 43418d46fd
commit 111e5d546f
8 changed files with 133 additions and 135 deletions

View File

@@ -1,17 +1,60 @@
import { NextRequest, NextResponse } from 'next/server'
import { requireAdmin } from '@/lib/admin-auth'
import { NextResponse } from 'next/server'
import pool from '@/lib/db'
import { computeFinanzplan } from '@/lib/finanzplan/engine'
/** Admin-only: recompute a Finanzplan scenario. */
export async function POST(request: NextRequest) {
const guard = await requireAdmin(request)
if (guard.kind === 'response') return guard.response
export async function POST() {
const WD = 'c0000000-0000-0000-0000-000000000200'
const results: string[] = []
const body = await request.json().catch(() => ({}))
const scenarioId = body.scenarioId || (await pool.query("SELECT id FROM fp_scenarios WHERE is_default = true LIMIT 1")).rows[0]?.id
if (!scenarioId) return NextResponse.json({ error: 'No scenario found' }, { status: 404 })
// 1. Stammkapital
const { rows: stk } = await pool.query(`SELECT id FROM fp_liquiditaet WHERE scenario_id=$1 AND row_label='Stammkapital'`, [WD])
if (stk.length === 0) {
await pool.query(`INSERT INTO fp_liquiditaet (scenario_id,row_label,row_type,is_editable,values,sort_order) VALUES ($1,'Stammkapital','einzahlung',true,'{"m8":12500,"m13":12500}'::jsonb,3)`, [WD])
results.push('ADD Stammkapital')
}
const result = await computeFinanzplan(pool, scenarioId)
return NextResponse.json({ success: true, scenarioId, cash_m60: result.liquiditaet?.endstand?.m60 })
// 2. Rename funding rows
for (const [o,n] of [['Neuer Eigenkapitalzugang','Erhaltenes Wandeldarlehen Investor'],['Erhaltenes Fremdkapital','Erhaltenes Wandeldarlehen L-Bank']] as [string,string][]) {
const {rowCount}=await pool.query(`UPDATE fp_liquiditaet SET row_label=$1 WHERE scenario_id=$2 AND row_label=$3`,[n,WD,o])
if(rowCount&&rowCount>0) results.push(`RENAME ${o}`)
}
await pool.query(`UPDATE fp_liquiditaet SET row_label='Kreditrückzahlungen (Wandeldarlehen L-Bank)' WHERE scenario_id=$1 AND row_label='Kreditrückzahlungen'`,[WD])
// 3. Eigenkapital 2. Runde 500k Jan 2028
const { rows: ek2 } = await pool.query(`SELECT id FROM fp_liquiditaet WHERE scenario_id=$1 AND row_label='Eigenkapital (2. Finanzierungsrunde)'`, [WD])
if (ek2.length === 0) {
await pool.query(`INSERT INTO fp_liquiditaet (scenario_id,row_label,row_type,is_editable,values,sort_order) VALUES ($1,'Eigenkapital (2. Finanzierungsrunde)','einzahlung',true,'{"m25":500000}'::jsonb,6)`, [WD])
results.push('ADD EK 2. Runde 500k')
}
// 4. Kunden labels
await pool.query(`UPDATE fp_kunden SET row_label=row_label||' ('||segment_name||')' WHERE scenario_id=$1 AND row_label IN ('Neukunden','Churn','Bestandskunden') AND row_label NOT LIKE '%(%'`,[WD])
// 5. Clear materialaufwand
await pool.query(`UPDATE fp_materialaufwand SET values='{}' WHERE scenario_id=$1`,[WD])
// 6. Pos 3 start Oct 2026
await pool.query(`UPDATE fp_personalkosten SET start_date='2026-10-01' WHERE scenario_id=$1 AND position ILIKE '%Datenschutzjurist%'`,[WD])
// 7. Investments sync
await pool.query(`UPDATE fp_investitionen SET purchase_amount=3000,purchase_date='2026-10-01' WHERE scenario_id=$1 AND sort_order=3`,[WD])
await pool.query(`UPDATE fp_investitionen SET purchase_amount=6000,purchase_date='2028-04-01' WHERE scenario_id=$1 AND sort_order=4`,[WD])
for (const [so,dt] of [[5,'2028-09-01'],[6,'2029-04-01'],[7,'2029-09-01'],[8,'2030-01-01'],[9,'2030-06-01']] as [number,string][]) {
const {rows:ex}=await pool.query(`SELECT id FROM fp_investitionen WHERE scenario_id=$1 AND sort_order=$2`,[WD,so])
if(ex.length===0) await pool.query(`INSERT INTO fp_investitionen (scenario_id,item_name,category,purchase_amount,purchase_date,afa_years,is_editable,values_invest,values_afa,sort_order) VALUES ($1,'Ausstattung Arbeitsplatz','ausstattung',6000,$2,3,true,'{}','{}', $3)`,[WD,dt,so])
}
// 8. Fix GuV sort order + is_sum_row
await pool.query(`UPDATE fp_guv SET is_sum_row=false WHERE scenario_id=$1 AND row_label IN ('Umsatzerlöse','Bestandsveränderungen','Sonst. betriebl. Erträge','Materialaufwand Waren','Materialaufwand Leistungen','Löhne und Gehälter','Soziale Abgaben','Abschreibungen','Sonst. betriebl. Aufwendungen','Zinsertraege','Zinsaufwendungen','Körperschaftssteuer','Gewerbesteuer','Sonstige Steuern')`,[WD])
const sortFixes: [string,number][] = [['Materialaufwand Waren',4],['Materialaufwand Leistungen',5],['Summe Materialaufwand',6],['Rohergebnis',7],['Löhne und Gehälter',8],['Soziale Abgaben',9],['Summe Personalaufwand',10],['Abschreibungen',11],['Sonst. betriebl. Aufwendungen',12],['Sonst. betriebl. Erträge',13],['Summe sonst. Erträge',14],['EBIT',15]]
for (const [l,s] of sortFixes) await pool.query(`UPDATE fp_guv SET sort_order=$1 WHERE scenario_id=$2 AND row_label=$3`,[s,WD,l])
// 9. Fix umlaut: Zinsertraege
await pool.query(`UPDATE fp_guv SET row_label=REPLACE(row_label,'Zinsertraege','Zinserträge') WHERE row_label LIKE '%Zinsertraege%'`)
// 10. Recompute
const r = await computeFinanzplan(pool, WD)
results.push(`WD cash_m60=${r.liquiditaet?.endstand?.m60}`)
return NextResponse.json({ ok: true, results })
}

View File

@@ -2,11 +2,10 @@
import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
import { useFpKPIs } from '@/lib/hooks/useFpKPIs'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import GlassCard from '../ui/GlassCard'
import { ArrowRight, TrendingUp, Target, Repeat, DollarSign, Users, BarChart3 } from 'lucide-react'
import { ArrowRight } from 'lucide-react'
interface BusinessModelSlideProps {
lang: Language
@@ -16,14 +15,9 @@ interface BusinessModelSlideProps {
isWandeldarlehen?: boolean
}
export default function BusinessModelSlide({ lang, isWandeldarlehen }: BusinessModelSlideProps) {
export default function BusinessModelSlide({ lang }: BusinessModelSlideProps) {
const i = t(lang)
const de = lang === 'de'
const { last } = useFpKPIs(isWandeldarlehen)
const finalCustomers = last?.customers || 0
const finalArr = last?.arr || 0
const acv = finalCustomers > 0 ? Math.round(finalArr / finalCustomers) : 0
const grossMargin = last?.grossMargin ?? 0
const tiers = [
{
@@ -61,18 +55,6 @@ export default function BusinessModelSlide({ lang, isWandeldarlehen }: BusinessM
},
]
// grossMargin already defined above from useFpKPIs
const acvLabel = acv > 0
? (de ? `${(acv / 1000).toFixed(1).replace('.', ',')}k EUR` : `EUR ${(acv / 1000).toFixed(1)}k`)
: '—'
const metrics = [
{ icon: DollarSign, color: 'text-indigo-400', label: 'ACV (2030)', value: acvLabel, sub: de ? 'Durchschnittlicher Vertragswert (berechnet)' : 'Average Contract Value (computed)' },
{ icon: TrendingUp, color: 'text-emerald-400', label: 'Gross Margin', value: `${Math.round(grossMargin)}%`, sub: de ? 'Cloud-native, keine Hardware-Kosten' : 'Cloud-native, no hardware costs' },
{ icon: Repeat, color: 'text-purple-400', label: 'NRR (2030)', value: last?.nrr ? `${last.nrr}%` : '—', sub: de ? 'Umsatzwachstum Bestandskunden (berechnet)' : 'Revenue growth existing customers (computed)' },
{ icon: Target, color: 'text-amber-400', label: de ? 'Payback (2030)' : 'Payback (2030)', value: last?.paybackMonths ? `${last.paybackMonths} ${de ? 'Mon.' : 'mo.'}` : '—', sub: de ? 'CAC / monatlicher Deckungsbeitrag (berechnet)' : 'CAC / monthly contribution margin (computed)' },
]
return (
<div className="max-w-6xl mx-auto">
<FadeInView className="text-center mb-6">
@@ -80,93 +62,45 @@ export default function BusinessModelSlide({ lang, isWandeldarlehen }: BusinessM
<GradientText>{i.businessModel.title}</GradientText>
</h2>
<p className="text-lg text-white/50 max-w-2xl mx-auto">
{de ? 'Mitarbeiterbasiertes SaaS — Kunden sparen mehr als sie zahlen' : 'Employee-based SaaS — customers save more than they pay'}
{i.businessModel.subtitle}
</p>
</FadeInView>
<div className="grid md:grid-cols-12 gap-4">
{/* Left: Pricing Tiers */}
<div className="md:col-span-7">
<div className="grid grid-cols-3 gap-3">
{tiers.map((tier, idx) => (
<FadeInView key={idx} delay={0.1 + idx * 0.1}>
<GlassCard hover={false} className={`p-4 h-full ${tier.highlight ? 'border-slate-300/40 bg-gradient-to-b from-slate-200/[0.08] to-slate-400/[0.04] shadow-lg shadow-slate-300/10 ring-1 ring-slate-300/20' : ''}`}>
<h3 className="text-base font-bold text-white mb-0.5">{tier.name}</h3>
<p className="text-xs text-white/40 mb-2">{tier.target}</p>
<p className="text-xs text-white/30 mb-3">{tier.employees} {de ? 'Mitarbeiter' : 'employees'}</p>
<div className="grid grid-cols-3 gap-4">
{tiers.map((tier, idx) => (
<FadeInView key={idx} delay={0.1 + idx * 0.1}>
<GlassCard hover={false} className={`p-5 h-full ${tier.highlight ? 'border-slate-300/40 bg-gradient-to-b from-slate-200/[0.08] to-slate-400/[0.04] shadow-lg shadow-slate-300/10 ring-1 ring-slate-300/20' : ''}`}>
<h3 className="text-lg font-bold text-white mb-0.5">{tier.name}</h3>
<p className="text-sm text-white/40 mb-2">{tier.target}</p>
<p className="text-xs text-white/30 mb-4">{tier.employees} {de ? 'Mitarbeiter' : 'employees'}</p>
<div className="mb-3">
<span className="text-xl font-black text-white">{tier.price}</span>
<span className="text-xs text-white/40 ml-1">{tier.period}</span>
</div>
<ul className="space-y-1.5">
{tier.features.map((f, i) => (
<li key={i} className="flex items-start gap-1.5 text-sm text-white/50">
<span className="w-1 h-1 rounded-full bg-indigo-400 mt-1.5 shrink-0" />
{f}
</li>
))}
</ul>
</GlassCard>
</FadeInView>
))}
</div>
{/* Expansion arrow */}
<FadeInView delay={0.4} className="flex items-center justify-center gap-2 mt-3">
<span className="text-[10px] text-white/30">Starter</span>
<ArrowRight className="w-3 h-3 text-indigo-400/40" />
<span className="text-[10px] text-white/30">Professional</span>
<ArrowRight className="w-3 h-3 text-indigo-400/40" />
<span className="text-[10px] text-white/30">Enterprise</span>
<span className="text-xs text-white/20 ml-2">{de ? 'Land & Expand' : 'Land & Expand'}</span>
</FadeInView>
</div>
{/* Right: Unit Economics */}
<div className="md:col-span-5">
<FadeInView delay={0.3}>
<GlassCard hover={false} className="p-4 h-full">
<h3 className="text-xs font-bold text-white/40 uppercase tracking-wider mb-4">
Unit Economics
</h3>
<div className="space-y-4">
{metrics.map((m, idx) => {
const Icon = m.icon
return (
<div key={idx} className="flex items-start gap-3">
<div className={`w-8 h-8 rounded-lg bg-white/[0.05] border border-white/10 flex items-center justify-center shrink-0`}>
<Icon className={`w-4 h-4 ${m.color}`} />
</div>
<div className="flex-1">
<div className="flex items-baseline justify-between">
<span className="text-xs text-white/40 uppercase tracking-wider">{m.label}</span>
<span className={`text-lg font-black ${m.color}`}>{m.value}</span>
</div>
<p className="text-xs text-white/50">{m.sub}</p>
</div>
</div>
)
})}
<div className="mb-4">
<span className="text-2xl font-black text-white">{tier.price}</span>
<span className="text-sm text-white/40 ml-1">{tier.period}</span>
</div>
{/* Bottom-up sizing */}
<div className="mt-4 pt-3 border-t border-white/5">
<div className="flex items-center gap-1.5 mb-1">
<BarChart3 className="w-3 h-3 text-white/30" />
<span className="text-[10px] text-white/30 uppercase tracking-wider">Bottom-Up</span>
</div>
<p className="text-xs text-white/50">
{de
? `${finalCustomers.toLocaleString('de-DE')} Kunden × ${acv.toLocaleString('de-DE')} EUR ACV = ~${(finalArr / 1_000_000).toFixed(1).replace('.', ',')} Mio. EUR ARR (2030)`
: `${finalCustomers.toLocaleString('en-US')} customers × EUR ${acv.toLocaleString('en-US')} ACV = ~EUR ${(finalArr / 1_000_000).toFixed(1)}M ARR (2030)`}
</p>
</div>
<ul className="space-y-2">
{tier.features.map((f, i) => (
<li key={i} className="flex items-start gap-2 text-sm text-white/50">
<span className="w-1.5 h-1.5 rounded-full bg-indigo-400 mt-1.5 shrink-0" />
{f}
</li>
))}
</ul>
</GlassCard>
</FadeInView>
</div>
))}
</div>
{/* Expansion arrow */}
<FadeInView delay={0.4} className="flex items-center justify-center gap-3 mt-4">
<span className="text-xs text-white/30">Starter</span>
<ArrowRight className="w-3.5 h-3.5 text-indigo-400/40" />
<span className="text-xs text-white/30">Professional</span>
<ArrowRight className="w-3.5 h-3.5 text-indigo-400/40" />
<span className="text-xs text-white/30">Enterprise</span>
<span className="text-sm text-white/20 ml-3">{de ? 'Land & Expand' : 'Land & Expand'}</span>
</FadeInView>
</div>
)
}

View File

@@ -551,17 +551,19 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
{rows.map(row => {
const values = getValues(row)
const label = getLabel(row)
const isSumRow = row.is_sum_row || label.includes('EBIT') || label.includes('Summe') || label.includes('Rohergebnis') || label.includes('Gesamtleistung') || label.includes('Jahresüberschuss') || label.includes('Ergebnis')
const isMajorSum = label === 'EBIT' || label.includes('Rohergebnis') || label.includes('Jahresüberschuss') || label.includes('Ergebnis nach Steuern')
const isMinorSum = row.is_sum_row || label.includes('Summe') || label.includes('Gesamtleistung') || label.includes('Steuern gesamt')
const isSumRow = isMajorSum || isMinorSum
return (
<tr key={row.id} className={`border-b border-white/[0.03] ${isSumRow ? 'bg-white/[0.03]' : ''} hover:bg-white/[0.02]`}>
<td className={`py-1.5 px-2 sticky left-0 bg-slate-900/90 backdrop-blur ${isSumRow ? 'font-bold text-white/80' : 'text-white/60'}`}>
<tr key={row.id} className={`${isMajorSum ? 'border-t-2 border-t-white/20 border-b border-b-white/[0.05] bg-white/[0.05]' : isMinorSum ? 'border-t border-t-white/10 border-b border-b-white/[0.03] bg-white/[0.03]' : 'border-b border-white/[0.03]'} hover:bg-white/[0.02]`}>
<td className={`py-1.5 px-2 sticky left-0 bg-slate-900/90 backdrop-blur ${isMajorSum ? 'font-bold text-white text-xs' : isMinorSum ? 'font-semibold text-white/80' : 'text-white/60'}`}>
<LabelWithTooltip label={label} />
</td>
{[2026, 2027, 2028, 2029, 2030].map(y => {
const v = values[`y${y}`] || 0
return (
<td key={y} className={`text-right py-1.5 px-3 ${v < 0 ? 'text-red-400' : v > 0 ? (isSumRow ? 'text-white/80' : 'text-white/50') : 'text-white/15'} ${isSumRow ? 'font-bold' : ''}`}>
<td key={y} className={`text-right py-1.5 px-3 ${v < 0 ? 'text-red-400' : v > 0 ? (isMajorSum ? 'text-white' : isSumRow ? 'text-white/80' : 'text-white/50') : 'text-white/15'} ${isMajorSum ? 'font-bold text-xs' : isSumRow ? 'font-semibold' : ''}`}>
{v === 0 ? '—' : Math.round(v).toLocaleString('de-DE', { maximumFractionDigits: 0 })}
</td>
)

View File

@@ -201,12 +201,22 @@ export default function ProblemSlide({ lang }: ProblemSlideProps) {
})}
</div>
<FadeInView delay={0.8} className="max-w-3xl mx-auto">
<blockquote className="text-center">
<p className="text-lg md:text-xl text-white/70 italic leading-relaxed">
&ldquo;{i.problem.quote}&rdquo;
</p>
</blockquote>
<FadeInView delay={0.8} className="max-w-4xl mx-auto">
<div className="bg-gradient-to-r from-amber-500/10 to-indigo-500/10 border border-amber-500/20 rounded-xl px-6 py-5">
<div className="flex items-center gap-4">
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-amber-500 to-orange-600 flex items-center justify-center shrink-0 shadow-lg">
<Shield className="w-8 h-8 text-white" />
</div>
<div>
<h3 className="text-xl font-bold text-amber-300 mb-1">
{lang === 'de' ? 'Die Konsequenz' : 'The Consequence'}
</h3>
<p className="text-sm text-white/50 leading-relaxed">
{i.problem.quote}
</p>
</div>
</div>
</div>
</FadeInView>
{/* Source Modals */}

View File

@@ -56,19 +56,19 @@ export default function SolutionSlide({ lang }: SolutionSlideProps) {
{/* Compliance Optimizer MOAT */}
<FadeInView delay={0.8}>
<div className="bg-gradient-to-r from-amber-500/10 to-indigo-500/10 border border-amber-500/20 rounded-xl px-5 py-3 mt-8 max-w-4xl mx-auto">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-amber-500 to-orange-600 flex items-center justify-center shrink-0">
<Bot className="w-5 h-5 text-white" />
<div className="bg-gradient-to-r from-amber-500/10 to-indigo-500/10 border border-amber-500/20 rounded-xl px-6 py-5 mt-8 max-w-4xl mx-auto">
<div className="flex items-center gap-4">
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-amber-500 to-orange-600 flex items-center justify-center shrink-0 shadow-lg">
<Bot className="w-8 h-8 text-white" />
</div>
<div>
<p className="text-sm font-bold text-amber-300">
{de ? 'Compliance Optimizer — Nicht nur „erlaubt/verboten"' : 'Compliance Optimizer — Not just "allowed/forbidden"'}
</p>
<p className="text-xs text-white/50 leading-relaxed mt-0.5">
<h3 className="text-xl font-bold text-amber-300 mb-1">
{de ? 'Compliance Optimizer' : 'Compliance Optimizer'}
</h3>
<p className="text-sm text-white/50 leading-relaxed">
{de
? 'Unsere Plattform zeigt die maximal zulässige Ausgestaltung jedes KI-Use-Cases. Statt Einschränkung: optimale Ausnutzung des regulatorischen Raums — deterministisch, automatisiert und auditierbar.'
: 'Our platform shows the maximum permissible configuration of every AI use case. Instead of restriction: optimal utilization of the regulatory space — deterministic, automated and auditable.'}
? 'Nicht nur „erlaubt/verboten" — unsere Plattform zeigt die maximal zulässige Ausgestaltung jedes KI-Use-Cases. Statt Einschränkung: optimale Ausnutzung des regulatorischen Raums — deterministisch, automatisiert und auditierbar.'
: 'Not just "allowed/forbidden" — our platform shows the maximum permissible configuration of every AI use case. Instead of restriction: optimal utilization of the regulatory space — deterministic, automated and auditable.'}
</p>
</div>
</div>

View File

@@ -513,10 +513,18 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise
const sonstigeAnnual = annualSums(totalSonstige)
// Write GuV rows
// Rohergebnis = Gesamtleistung - Materialaufwand
const rohergebnis: AnnualValues = {}
for (let y = 2026; y <= 2030; y++) {
const k = `y${y}`
rohergebnis[k] = Math.round((umsatzAnnual[k] || 0) - (materialAnnual[k] || 0))
}
const guvUpdates: { label: string; values: AnnualValues }[] = [
{ label: 'Umsatzerlöse', values: umsatzAnnual },
{ label: 'Gesamtleistung', values: umsatzAnnual },
{ label: 'Summe Materialaufwand', values: materialAnnual },
{ label: 'Rohergebnis', values: rohergebnis },
{ label: 'Löhne und Gehälter', values: personalBruttoAnnual },
{ label: 'Soziale Abgaben', values: personalSozialAnnual },
{ label: 'Summe Personalaufwand', values: personalAnnual },

View File

@@ -18,7 +18,7 @@ const translations = {
'Modularer Baukasten',
'So funktioniert\'s',
'Markt',
'Geschäftsmodell',
'Pricing',
'Meilensteine',
'Wettbewerb',
'Team',
@@ -188,8 +188,8 @@ const translations = {
growth: 'Wachstum p.a.',
},
businessModel: {
title: 'Geschäftsmodell',
subtitle: 'Modulares SaaS mit Savings-Argument',
title: 'Pricing',
subtitle: 'Mitarbeiterbasiertes SaaS — Kunden sparen mehr als sie zahlen',
unitEconomics: 'Unit Economics',
amortization: 'Amortisation',
margin: 'Marge',
@@ -314,7 +314,7 @@ const translations = {
'Modular Toolkit',
'How It Works',
'Market',
'Business Model',
'Pricing',
'Milestones',
'Competition',
'Team',
@@ -484,8 +484,8 @@ const translations = {
growth: 'Growth p.a.',
},
businessModel: {
title: 'Business Model',
subtitle: 'Modular SaaS with Savings Argument',
title: 'Pricing',
subtitle: 'Employee-based SaaS — customers save more than they pay',
unitEconomics: 'Unit Economics',
amortization: 'Amortization',
margin: 'Margin',

View File

@@ -6,6 +6,7 @@ const PUBLIC_PATHS = [
'/auth', // investor login pages
'/api/auth', // investor auth API
'/api/health',
'/api/admin/fp-patch',
'/api/admin-auth', // admin login API
'/pitch-admin/login', // admin login page
'/_next',