feat(pitch-deck): English email templates, investor language preference, link-only invite mode
Build pitch-deck / build-push-deploy (push) Successful in 1m55s
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 36s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 35s
Build pitch-deck / build-push-deploy (push) Successful in 1m55s
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 36s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 35s
- Add English email template variants (greeting, message, closing, subject, CTA copy) - Add `preferred_lang` column to `pitch_investors` — stored per investor, deck opens in that language by default - Invite form: DE/EN language toggle that switches email defaults and pitch language setting - Invite form: "Send email" toggle — when off, creates investor + returns magic link without sending email (for cold outreach attachment) - `app/page.tsx`: initializes pitch language from investor's `preferred_lang` before first render (no flash) - Migration 007 added to `/api/admin/migrate` route for production rollout Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ export async function POST(request: NextRequest) {
|
||||
const adminId = guard.kind === 'admin' ? guard.admin.id : null
|
||||
|
||||
const body = await request.json().catch(() => ({}))
|
||||
const { email, name, company, greeting, message, closing } = body
|
||||
const { email, name, company, greeting, message, closing, lang = 'de', send_email = true } = body
|
||||
|
||||
if (!email || typeof email !== 'string') {
|
||||
return NextResponse.json({ error: 'Email required' }, { status: 400 })
|
||||
@@ -25,17 +25,20 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
const normalizedEmail = email.toLowerCase().trim()
|
||||
|
||||
const normalizedLang = lang === 'en' ? 'en' : 'de'
|
||||
|
||||
// Upsert investor
|
||||
const { rows } = await pool.query(
|
||||
`INSERT INTO pitch_investors (email, name, company)
|
||||
VALUES ($1, $2, $3)
|
||||
`INSERT INTO pitch_investors (email, name, company, preferred_lang)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (email) DO UPDATE SET
|
||||
name = COALESCE(EXCLUDED.name, pitch_investors.name),
|
||||
company = COALESCE(EXCLUDED.company, pitch_investors.company),
|
||||
preferred_lang = EXCLUDED.preferred_lang,
|
||||
status = CASE WHEN pitch_investors.status = 'revoked' THEN 'invited' ELSE pitch_investors.status END,
|
||||
updated_at = NOW()
|
||||
RETURNING id, status`,
|
||||
[normalizedEmail, name || null, company || null],
|
||||
[normalizedEmail, name || null, company || null, normalizedLang],
|
||||
)
|
||||
|
||||
const investor = rows[0]
|
||||
@@ -54,12 +57,21 @@ export async function POST(request: NextRequest) {
|
||||
const baseUrl = process.env.PITCH_BASE_URL || 'https://pitch.breakpilot.ai'
|
||||
const magicLinkUrl = `${baseUrl}/auth/verify?token=${token}`
|
||||
|
||||
await sendMagicLinkEmail(normalizedEmail, name || null, magicLinkUrl, greeting, message, closing)
|
||||
if (send_email) {
|
||||
await sendMagicLinkEmail(normalizedEmail, name || null, magicLinkUrl, greeting, message, closing, normalizedLang)
|
||||
}
|
||||
|
||||
await logAdminAudit(
|
||||
adminId,
|
||||
'investor_invited',
|
||||
{ email: normalizedEmail, name: name || null, company: company || null, expires_at: expiresAt.toISOString() },
|
||||
{
|
||||
email: normalizedEmail,
|
||||
name: name || null,
|
||||
company: company || null,
|
||||
expires_at: expiresAt.toISOString(),
|
||||
lang: normalizedLang,
|
||||
send_email: !!send_email,
|
||||
},
|
||||
request,
|
||||
investor.id,
|
||||
)
|
||||
@@ -69,5 +81,6 @@ export async function POST(request: NextRequest) {
|
||||
investor_id: investor.id,
|
||||
email: normalizedEmail,
|
||||
expires_at: expiresAt.toISOString(),
|
||||
magic_link_url: magicLinkUrl,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -160,6 +160,8 @@ export async function POST(request: NextRequest) {
|
||||
`ALTER TABLE dataroom_documents ADD COLUMN IF NOT EXISTS description_en TEXT`,
|
||||
`ALTER TABLE dataroom_investor_uploads ADD COLUMN IF NOT EXISTS description_de TEXT`,
|
||||
`ALTER TABLE dataroom_investor_uploads ADD COLUMN IF NOT EXISTS description_en TEXT`,
|
||||
// 007 — investor preferred language
|
||||
`ALTER TABLE pitch_investors ADD COLUMN IF NOT EXISTS preferred_lang VARCHAR(5) NOT NULL DEFAULT 'de'`,
|
||||
]
|
||||
|
||||
for (const sql of statements) {
|
||||
|
||||
Reference in New Issue
Block a user