diff --git a/apps/api/src/controllers/Users.ts b/apps/api/src/controllers/Users.ts index 1fe8314..160a263 100644 --- a/apps/api/src/controllers/Users.ts +++ b/apps/api/src/controllers/Users.ts @@ -230,6 +230,14 @@ export class Users { client_reference_id: project.id, // Store project ID for webhook line_items: lineItems, ...(checkoutCurrency && {currency: checkoutCurrency}), + custom_fields: [ + { + key: 'promo_code', + label: {type: 'custom', custom: 'Promo code'}, + type: 'text', + optional: true, + }, + ], subscription_data: { billing_cycle_anchor: billingCycleAnchor, }, diff --git a/apps/api/src/controllers/Webhooks.ts b/apps/api/src/controllers/Webhooks.ts index a36b965..9ac0c3c 100644 --- a/apps/api/src/controllers/Webhooks.ts +++ b/apps/api/src/controllers/Webhooks.ts @@ -530,10 +530,24 @@ export class Webhooks { }, }); + // Base onboarding credit: refund the 1-unit card-verification charge + let creditBalance = -100; + + // Switching-offer promo: 2 extra units of credit if the customer typed SWITCH + // into the Promo code custom field on Stripe Checkout. + const promoField = session.custom_fields?.find((f) => f.key === 'promo_code'); + const promoCode = promoField?.text?.value?.trim().toUpperCase(); + if (promoCode === 'SWITCH') { + creditBalance -= 200; + signale.success(`[WEBHOOK] SWITCH promo applied for project ${projectId}`); + } else if (promoCode) { + signale.info(`[WEBHOOK] Unknown promo code "${promoCode}" entered for project ${projectId}`); + } + // Update Stripe customer name to match project name and add credit for onboarding fee await stripe.customers.update(customerId, { name: updatedProject.name, - balance: -100, + balance: creditBalance, }); signale.success(`[WEBHOOK] Checkout completed for project ${projectId}`); diff --git a/apps/landing/src/components/SwitchOffer.tsx b/apps/landing/src/components/SwitchOffer.tsx new file mode 100644 index 0000000..685218e --- /dev/null +++ b/apps/landing/src/components/SwitchOffer.tsx @@ -0,0 +1,114 @@ +import {AnimatePresence, motion} from 'framer-motion'; +import React, {useEffect, useState} from 'react'; +import {ArrowRight, Gift, X} from 'lucide-react'; +import {DASHBOARD_URI} from '../lib/constants'; + +interface SwitchOfferProps { + competitorName: string; +} + +export function SwitchOffer({competitorName}: SwitchOfferProps) { + const storageKey = `switch-offer-dismissed:${competitorName}`; + const [visible, setVisible] = useState(false); + + useEffect(() => { + if (typeof window === 'undefined') return; + if (window.localStorage.getItem(storageKey) === '1') return; + const t = window.setTimeout(() => setVisible(true), 800); + return () => window.clearTimeout(t); + }, [storageKey]); + + const dismiss = () => { + setVisible(false); + if (typeof window !== 'undefined') { + window.localStorage.setItem(storageKey, '1'); + } + }; + + return ( + + {visible && ( + +
+ + +
+
+ + + Switching offer + +
+ +

+ Switching from {competitorName}? +
+ Get 2,000 free emails. +

+ +

+ Sign up and enter this code at checkout to redeem 2,000 email credits. +

+ +
+
+ + SWITCH + +
+
+ + + Claim your credits + + +
+
+
+ )} +
+ ); +} diff --git a/apps/landing/src/components/index.ts b/apps/landing/src/components/index.ts index 0913e3e..6c5f2c3 100644 --- a/apps/landing/src/components/index.ts +++ b/apps/landing/src/components/index.ts @@ -4,3 +4,4 @@ export * from './ComparisonTable'; export * from './FAQSection'; export * from './CodeBlock'; export * from './SectionHeader'; +export * from './SwitchOffer'; diff --git a/apps/landing/src/pages/vs/activecampaign.tsx b/apps/landing/src/pages/vs/activecampaign.tsx index 7c23167..6e822f5 100644 --- a/apps/landing/src/pages/vs/activecampaign.tsx +++ b/apps/landing/src/pages/vs/activecampaign.tsx @@ -1,4 +1,4 @@ -import {ComparisonTable, FAQSection, Footer, Navbar} from '../../components'; +import {ComparisonTable, FAQSection, Footer, Navbar, SwitchOffer} from '../../components'; import {motion} from 'framer-motion'; import {DASHBOARD_URI, WIKI_URI} from '../../lib/constants'; import React from 'react'; @@ -170,6 +170,8 @@ export default function ActiveCampaignComparison() { {/* FAQ */} + + {/* CTA */}
diff --git a/apps/landing/src/pages/vs/amazon-ses.tsx b/apps/landing/src/pages/vs/amazon-ses.tsx index ad4b08e..2c06dc6 100644 --- a/apps/landing/src/pages/vs/amazon-ses.tsx +++ b/apps/landing/src/pages/vs/amazon-ses.tsx @@ -1,4 +1,4 @@ -import {ComparisonTable, FAQSection, Footer, Navbar} from '../../components'; +import {ComparisonTable, FAQSection, Footer, Navbar, SwitchOffer} from '../../components'; import {motion} from 'framer-motion'; import {DASHBOARD_URI, WIKI_URI} from '../../lib/constants'; import React from 'react'; @@ -186,6 +186,8 @@ export default function AmazonSesComparison() { {/* FAQ */} + + {/* CTA */}
diff --git a/apps/landing/src/pages/vs/bento.tsx b/apps/landing/src/pages/vs/bento.tsx index 907c32b..80fae73 100644 --- a/apps/landing/src/pages/vs/bento.tsx +++ b/apps/landing/src/pages/vs/bento.tsx @@ -1,4 +1,4 @@ -import {ComparisonTable, FAQSection, Footer, Navbar} from '../../components'; +import {ComparisonTable, FAQSection, Footer, Navbar, SwitchOffer} from '../../components'; import {motion} from 'framer-motion'; import {DASHBOARD_URI, WIKI_URI} from '../../lib/constants'; import React from 'react'; @@ -169,6 +169,8 @@ export default function BentoComparison() { {/* FAQ */} + + {/* CTA */}
diff --git a/apps/landing/src/pages/vs/brevo.tsx b/apps/landing/src/pages/vs/brevo.tsx index bb0c30e..2cdc096 100644 --- a/apps/landing/src/pages/vs/brevo.tsx +++ b/apps/landing/src/pages/vs/brevo.tsx @@ -1,4 +1,4 @@ -import {ComparisonTable, FAQSection, Footer, Navbar} from '../../components'; +import {ComparisonTable, FAQSection, Footer, Navbar, SwitchOffer} from '../../components'; import {motion} from 'framer-motion'; import {DASHBOARD_URI, WIKI_URI} from '../../lib/constants'; import React from 'react'; @@ -193,6 +193,8 @@ export default function BrevoComparison() { {/* FAQ */} + + {/* CTA */}
diff --git a/apps/landing/src/pages/vs/buttondown.tsx b/apps/landing/src/pages/vs/buttondown.tsx index e5b8830..0e03d71 100644 --- a/apps/landing/src/pages/vs/buttondown.tsx +++ b/apps/landing/src/pages/vs/buttondown.tsx @@ -1,4 +1,4 @@ -import {ComparisonTable, FAQSection, Footer, Navbar} from '../../components'; +import {ComparisonTable, FAQSection, Footer, Navbar, SwitchOffer} from '../../components'; import {motion} from 'framer-motion'; import {DASHBOARD_URI, WIKI_URI} from '../../lib/constants'; import React from 'react'; @@ -186,6 +186,8 @@ export default function ButtondownComparison() { {/* FAQ */} + + {/* CTA */}
diff --git a/apps/landing/src/pages/vs/convertkit.tsx b/apps/landing/src/pages/vs/convertkit.tsx index 5437ce5..eecc8c9 100644 --- a/apps/landing/src/pages/vs/convertkit.tsx +++ b/apps/landing/src/pages/vs/convertkit.tsx @@ -1,4 +1,4 @@ -import {ComparisonTable, FAQSection, Footer, Navbar} from '../../components'; +import {ComparisonTable, FAQSection, Footer, Navbar, SwitchOffer} from '../../components'; import {motion} from 'framer-motion'; import {DASHBOARD_URI, WIKI_URI} from '../../lib/constants'; import React from 'react'; @@ -193,6 +193,8 @@ export default function ConvertkitComparison() { {/* FAQ */} + + {/* CTA */}
diff --git a/apps/landing/src/pages/vs/customerio.tsx b/apps/landing/src/pages/vs/customerio.tsx index f611a4e..98e5348 100644 --- a/apps/landing/src/pages/vs/customerio.tsx +++ b/apps/landing/src/pages/vs/customerio.tsx @@ -1,4 +1,4 @@ -import {ComparisonTable, FAQSection, Footer, Navbar} from '../../components'; +import {ComparisonTable, FAQSection, Footer, Navbar, SwitchOffer} from '../../components'; import {motion} from 'framer-motion'; import {DASHBOARD_URI, WIKI_URI} from '../../lib/constants'; import React from 'react'; @@ -189,6 +189,8 @@ export default function CustomerioComparison() { {/* FAQ */} + + {/* CTA */}
diff --git a/apps/landing/src/pages/vs/klaviyo.tsx b/apps/landing/src/pages/vs/klaviyo.tsx index b4d1383..a815f11 100644 --- a/apps/landing/src/pages/vs/klaviyo.tsx +++ b/apps/landing/src/pages/vs/klaviyo.tsx @@ -1,4 +1,4 @@ -import {ComparisonTable, FAQSection, Footer, Navbar} from '../../components'; +import {ComparisonTable, FAQSection, Footer, Navbar, SwitchOffer} from '../../components'; import {motion} from 'framer-motion'; import {DASHBOARD_URI, WIKI_URI} from '../../lib/constants'; import React from 'react'; @@ -170,6 +170,8 @@ export default function KlaviyoComparison() { {/* FAQ */} + + {/* CTA */}
diff --git a/apps/landing/src/pages/vs/loops.tsx b/apps/landing/src/pages/vs/loops.tsx index 778efc6..48a7e73 100644 --- a/apps/landing/src/pages/vs/loops.tsx +++ b/apps/landing/src/pages/vs/loops.tsx @@ -1,4 +1,4 @@ -import {ComparisonTable, FAQSection, Footer, Navbar} from '../../components'; +import {ComparisonTable, FAQSection, Footer, Navbar, SwitchOffer} from '../../components'; import {motion} from 'framer-motion'; import {DASHBOARD_URI, WIKI_URI} from '../../lib/constants'; import React from 'react'; @@ -193,6 +193,8 @@ export default function LoopsComparison() { {/* FAQ */} + + {/* CTA */}
diff --git a/apps/landing/src/pages/vs/mailchimp.tsx b/apps/landing/src/pages/vs/mailchimp.tsx index 9f79934..e2edc6b 100644 --- a/apps/landing/src/pages/vs/mailchimp.tsx +++ b/apps/landing/src/pages/vs/mailchimp.tsx @@ -1,4 +1,4 @@ -import {ComparisonTable, FAQSection, Footer, Navbar} from '../../components'; +import {ComparisonTable, FAQSection, Footer, Navbar, SwitchOffer} from '../../components'; import {motion} from 'framer-motion'; import {DASHBOARD_URI, WIKI_URI} from '../../lib/constants'; import React from 'react'; @@ -195,6 +195,8 @@ export default function MailchimpComparison() { {/* FAQ */} + + {/* CTA */}
diff --git a/apps/landing/src/pages/vs/mailerlite.tsx b/apps/landing/src/pages/vs/mailerlite.tsx index 1bce5f3..2bbb9c5 100644 --- a/apps/landing/src/pages/vs/mailerlite.tsx +++ b/apps/landing/src/pages/vs/mailerlite.tsx @@ -1,4 +1,4 @@ -import {ComparisonTable, FAQSection, Footer, Navbar} from '../../components'; +import {ComparisonTable, FAQSection, Footer, Navbar, SwitchOffer} from '../../components'; import {motion} from 'framer-motion'; import {DASHBOARD_URI, WIKI_URI} from '../../lib/constants'; import React from 'react'; @@ -170,6 +170,8 @@ export default function MailerliteComparison() { {/* FAQ */} + + {/* CTA */}
diff --git a/apps/landing/src/pages/vs/mailgun.tsx b/apps/landing/src/pages/vs/mailgun.tsx index a06f56e..a35372b 100644 --- a/apps/landing/src/pages/vs/mailgun.tsx +++ b/apps/landing/src/pages/vs/mailgun.tsx @@ -1,4 +1,4 @@ -import {ComparisonTable, FAQSection, Footer, Navbar} from '../../components'; +import {ComparisonTable, FAQSection, Footer, Navbar, SwitchOffer} from '../../components'; import {motion} from 'framer-motion'; import {DASHBOARD_URI, WIKI_URI} from '../../lib/constants'; import React from 'react'; @@ -189,6 +189,8 @@ export default function MailgunComparison() { {/* FAQ */} + + {/* CTA */}
diff --git a/apps/landing/src/pages/vs/mailjet.tsx b/apps/landing/src/pages/vs/mailjet.tsx index b58e7f8..008830f 100644 --- a/apps/landing/src/pages/vs/mailjet.tsx +++ b/apps/landing/src/pages/vs/mailjet.tsx @@ -1,4 +1,4 @@ -import {ComparisonTable, FAQSection, Footer, Navbar} from '../../components'; +import {ComparisonTable, FAQSection, Footer, Navbar, SwitchOffer} from '../../components'; import {motion} from 'framer-motion'; import {DASHBOARD_URI, WIKI_URI} from '../../lib/constants'; import React from 'react'; @@ -186,6 +186,8 @@ export default function MailjetComparison() { {/* FAQ */} + + {/* CTA */}
diff --git a/apps/landing/src/pages/vs/postmark.tsx b/apps/landing/src/pages/vs/postmark.tsx index bcd5ea0..003dbec 100644 --- a/apps/landing/src/pages/vs/postmark.tsx +++ b/apps/landing/src/pages/vs/postmark.tsx @@ -1,4 +1,4 @@ -import {ComparisonTable, FAQSection, Footer, Navbar} from '../../components'; +import {ComparisonTable, FAQSection, Footer, Navbar, SwitchOffer} from '../../components'; import {motion} from 'framer-motion'; import {DASHBOARD_URI, WIKI_URI} from '../../lib/constants'; import React from 'react'; @@ -190,6 +190,8 @@ export default function PostmarkComparison() { {/* FAQ */} + + {/* CTA */}
diff --git a/apps/landing/src/pages/vs/resend.tsx b/apps/landing/src/pages/vs/resend.tsx index b7a4daa..a914dae 100644 --- a/apps/landing/src/pages/vs/resend.tsx +++ b/apps/landing/src/pages/vs/resend.tsx @@ -1,4 +1,4 @@ -import {ComparisonTable, FAQSection, Footer, Navbar} from '../../components'; +import {ComparisonTable, FAQSection, Footer, Navbar, SwitchOffer} from '../../components'; import {motion} from 'framer-motion'; import {DASHBOARD_URI, WIKI_URI} from '../../lib/constants'; import React from 'react'; @@ -189,6 +189,8 @@ export default function ResendComparison() { {/* FAQ */} + + {/* CTA */}
diff --git a/apps/landing/src/pages/vs/sendgrid.tsx b/apps/landing/src/pages/vs/sendgrid.tsx index 7a0b6d6..d64060f 100644 --- a/apps/landing/src/pages/vs/sendgrid.tsx +++ b/apps/landing/src/pages/vs/sendgrid.tsx @@ -1,4 +1,4 @@ -import {ComparisonTable, FAQSection, Footer, Navbar} from '../../components'; +import {ComparisonTable, FAQSection, Footer, Navbar, SwitchOffer} from '../../components'; import {motion} from 'framer-motion'; import {DASHBOARD_URI, WIKI_URI} from '../../lib/constants'; import React from 'react'; @@ -193,6 +193,8 @@ export default function SendGridComparison() { {/* FAQ */} + + {/* CTA */}