diff --git a/packages/twenty-website-new/package.json b/packages/twenty-website-new/package.json index 48a47ca65a..82526dead1 100644 --- a/packages/twenty-website-new/package.json +++ b/packages/twenty-website-new/package.json @@ -10,6 +10,7 @@ "dependencies": { "@base-ui/react": "^1.3.0", "@base-ui/utils": "^0.2.6", + "@calcom/embed-react": "^1.5.3", "@gsap/react": "2.1.2", "@linaria/core": "^7.0.0", "@linaria/react": "^7.0.1", diff --git a/packages/twenty-website-new/public/lottie/stepper/stepper.lottie b/packages/twenty-website-new/public/lottie/stepper/stepper.lottie index 6a752de079..9b3e0008fa 100644 Binary files a/packages/twenty-website-new/public/lottie/stepper/stepper.lottie and b/packages/twenty-website-new/public/lottie/stepper/stepper.lottie differ diff --git a/packages/twenty-website-new/src/app/(home)/_constants/helped.ts b/packages/twenty-website-new/src/app/(home)/_constants/helped.ts index 5b57577c90..5fd86d42ca 100644 --- a/packages/twenty-website-new/src/app/(home)/_constants/helped.ts +++ b/packages/twenty-website-new/src/app/(home)/_constants/helped.ts @@ -19,29 +19,31 @@ export const HELPED_DATA: HelpedDataType = { ], cards: [ { - icon: 'realytics', - heading: { text: 'Increase lead qualification', fontFamily: 'sans' }, + icon: 'w3villa', + heading: { text: 'Ship a product on Twenty', fontFamily: 'sans' }, body: { - text: 'Realytics built lead scoring into their CRM and increased qualified outbound by 40%.', + text: 'W3villa built W3Grads — AI mock interviews at scale — on Twenty as the operational backbone.', }, illustration: 'target', + href: '/case-studies/w3villa', }, { - icon: 'beagle', - heading: { text: 'Build performing onboarding', fontFamily: 'sans' }, + icon: 'act-education', + heading: { text: 'Own your CRM end to end', fontFamily: 'sans' }, body: { - text: 'Beagle launched a custom onboarding pipeline and shortened their sales cycle by 30%.', + text: 'AC&T replaced a shuttered vendor CRM with self-hosted Twenty and cut CRM costs by more than 90%.', }, illustration: 'spaceship', + href: '/case-studies/act-education', }, - { - icon: 'evergreen', - heading: { text: 'Unify workflow', fontFamily: 'sans' }, + icon: 'netzero', + heading: { text: 'Grow with a flexible foundation', fontFamily: 'sans' }, body: { - text: 'Evergreen unified sales and CS workflows, boosting expansion revenue by 25%.', + text: 'NetZero runs a modular Twenty setup across carbon credits, ag products, and industrial systems.', }, illustration: 'money', + href: '/case-studies/netzero', }, ], }; diff --git a/packages/twenty-website-new/src/app/(home)/_constants/testimonials.ts b/packages/twenty-website-new/src/app/(home)/_constants/testimonials.ts index ad9b9c9df8..627cbac2d7 100644 --- a/packages/twenty-website-new/src/app/(home)/_constants/testimonials.ts +++ b/packages/twenty-website-new/src/app/(home)/_constants/testimonials.ts @@ -7,47 +7,34 @@ export const TESTIMONIALS_DATA: TestimonialsDataType = { testimonials: [ { heading: { - text: "YES @twentycrm has json object fields. Such a simple feature and yet so many CRMs don't. It allows me to add data without caring too much about the data model, and eventually make it cleaner if the model gets stable.", + text: 'The flexibility is really what made the difference. Our needs evolve very fast. I discover a new need and in two clicks I can address it. That is a real advantage when you are moving quickly.', fontFamily: 'sans', }, author: { - avatar: { - src: '/images/home/testimonials/avatar-one.png', - alt: '', - }, - name: { text: 'Wayne Hamadi' }, - handle: { text: '@wayne_hamadi' }, - date: new Date('2024-05-23'), + name: { text: 'Olivier Reinaud' }, + designation: { text: 'Co-founder at NetZero' }, }, }, { heading: { - text: 'We switched from a rigid CRM where every custom field felt like a ticket. Twenty lets our team model how we actually sell without waiting on consultants.', + text: "We didn't want to patch over the problem. We wanted to build something institutions could rely on at scale, and that meant starting from a foundation solid enough to support the full complexity of what we had in mind.", fontFamily: 'sans', }, author: { - avatar: { - src: '/images/home/testimonials/avatar-two.png', - alt: '', - }, - name: { text: 'Priya Nandakumar' }, - handle: { text: '@priya_builds' }, - date: new Date('2024-07-11'), + name: { text: 'Amrendra Pratap Singh' }, + designation: { text: 'VP of Engineering at W3villa Technologies' }, }, }, { heading: { - text: 'Open source was the hook; the product is what kept us. Pipelines, people, and reporting finally live in one place our engineers are happy to extend.', + text: 'It is just such a nicer experience than dealing with a Salesforce or a HubSpot. My mission has been to get every tool API-accessible, so everything talks to each other. Twenty made that possible in a way older CRM platforms simply do not.', fontFamily: 'sans', }, author: { - avatar: { - src: '/images/home/testimonials/avatar-three.png', - alt: '', + name: { text: 'Justin Beadle' }, + designation: { + text: 'Director of Digital and Information, Elevate Consulting', }, - name: { text: 'Marcus Chen' }, - handle: { text: '@marcus_ops' }, - date: new Date('2024-09-02'), }, }, ], diff --git a/packages/twenty-website-new/src/app/(home)/_constants/three-cards-illustration.ts b/packages/twenty-website-new/src/app/(home)/_constants/three-cards-illustration.ts index 4dbd3b4427..1784fd95e5 100644 --- a/packages/twenty-website-new/src/app/(home)/_constants/three-cards-illustration.ts +++ b/packages/twenty-website-new/src/app/(home)/_constants/three-cards-illustration.ts @@ -9,7 +9,7 @@ export const THREE_CARDS_ILLUSTRATION_DATA: ThreeCardsIllustrationDataType = { { text: " that's quick to flex", fontFamily: 'sans' }, ], body: { - text: 'Compose your CRM and interal apps with a single exesibility toolkit.', + text: 'Compose your CRM and internal apps with a single extensibility toolkit.', }, illustrationCards: [ { @@ -23,6 +23,7 @@ export const THREE_CARDS_ILLUSTRATION_DATA: ThreeCardsIllustrationDataType = { company: { text: 'Mid-Market Fintech' }, }, illustration: 'diamond', + caseStudySlug: '9dots', }, { heading: { text: 'AI for rapid iterations', fontFamily: 'sans' }, @@ -35,6 +36,7 @@ export const THREE_CARDS_ILLUSTRATION_DATA: ThreeCardsIllustrationDataType = { company: { text: 'Mid-Market Fintech' }, }, illustration: 'flash', + caseStudySlug: 'alternative-partners', }, { heading: { text: 'Control without drag', fontFamily: 'sans' }, @@ -47,6 +49,7 @@ export const THREE_CARDS_ILLUSTRATION_DATA: ThreeCardsIllustrationDataType = { company: { text: 'Mid-Market Fintech' }, }, illustration: 'lock', + caseStudySlug: 'elevate-consulting', }, ], }; diff --git a/packages/twenty-website-new/src/app/(home)/page.tsx b/packages/twenty-website-new/src/app/(home)/page.tsx index b7d4ffbba4..f6c4ae5a43 100644 --- a/packages/twenty-website-new/src/app/(home)/page.tsx +++ b/packages/twenty-website-new/src/app/(home)/page.tsx @@ -7,6 +7,7 @@ import { THREE_CARDS_FEATURE_DATA, THREE_CARDS_ILLUSTRATION_DATA, } from '@/app/(home)/_constants'; +import { TalkToUsButton } from '@/app/components/ContactCalModal'; import { FAQ_DATA, MENU_DATA, TRUSTED_BY_DATA } from '@/app/_constants'; import { Body, Eyebrow, Heading, LinkButton } from '@/design-system/components'; import { Pages } from '@/enums/pages'; @@ -94,6 +95,11 @@ export default async function HomePage() { type="anchor" variant="contained" /> + @@ -158,6 +164,13 @@ export default async function HomePage() { size="lg" weight="light" /> + - diff --git a/packages/twenty-website-new/src/app/_constants/contact-cal.ts b/packages/twenty-website-new/src/app/_constants/contact-cal.ts new file mode 100644 index 0000000000..5c01f2005e --- /dev/null +++ b/packages/twenty-website-new/src/app/_constants/contact-cal.ts @@ -0,0 +1,2 @@ +export const CONTACT_CAL_FORM_CAL_LINK = + 'forms/f7841033-0a20-4958-8c92-4e34ec128a81'; diff --git a/packages/twenty-website-new/src/app/_constants/footer.ts b/packages/twenty-website-new/src/app/_constants/footer.ts index 6526690720..e50c7797a6 100644 --- a/packages/twenty-website-new/src/app/_constants/footer.ts +++ b/packages/twenty-website-new/src/app/_constants/footer.ts @@ -23,9 +23,16 @@ export const FOOTER_DATA: FooterDataType = { title: 'Help', ctas: [], links: [ - { label: 'Developers', href: '#', external: false }, - { label: 'FAQ', href: '#', external: false }, - { label: 'Support', href: '#', external: false }, + { + label: 'Developers', + href: 'https://docs.twenty.com/developers/introduction', + external: true, + }, + { + label: 'User Guide', + href: 'https://docs.twenty.com/user-guide/introduction', + external: true, + }, { label: 'Release Notes', href: '/release-notes', external: false }, { label: 'Halftone generator', @@ -39,7 +46,6 @@ export const FOOTER_DATA: FooterDataType = { title: 'Legal', ctas: [], links: [ - { label: 'Cookie Policy', href: '#', external: false }, { label: 'Privacy Policy', href: '/privacy-policy', external: false }, { label: 'Terms and Conditions', href: '/terms', external: false }, ], @@ -50,16 +56,15 @@ export const FOOTER_DATA: FooterDataType = { ctas: [ { color: 'secondary', - href: 'https://app.twenty.com/welcome', + kind: 'contactModal', label: 'Talk to us', - type: 'anchor', variant: 'contained', }, { color: 'secondary', href: 'https://app.twenty.com/welcome', + kind: 'link', label: 'Get started', - type: 'anchor', variant: 'outlined', }, ], diff --git a/packages/twenty-website-new/src/app/case-studies/9dots/page.tsx b/packages/twenty-website-new/src/app/case-studies/9dots/page.tsx new file mode 100644 index 0000000000..3b2ce6967e --- /dev/null +++ b/packages/twenty-website-new/src/app/case-studies/9dots/page.tsx @@ -0,0 +1,157 @@ +import { FAQ_DATA, MENU_DATA } from '@/app/_constants'; +import { TalkToUsButton } from '@/app/components/ContactCalModal'; +import type { CaseStudyData } from '@/app/case-studies/_constants/types'; +import { Eyebrow, LinkButton } from '@/design-system/components'; +import { fetchCommunityStats } from '@/lib/community/fetch-community-stats'; +import { mergeSocialLinkLabels } from '@/lib/community/merge-social-link-labels'; +import { CaseStudy } from '@/sections/CaseStudy/components'; +import { Faq } from '@/sections/Faq/components'; +import { Menu } from '@/sections/Menu/components'; +import { theme } from '@/theme'; +import type { Metadata } from 'next'; + +const PLACEHOLDER_HERO = '/images/home/hero/avatars/katherine-adams.jpg'; + +const CASE_STUDY: CaseStudyData = { + meta: { + title: + 'Homeseller, WhatsApp, and a CRM built around the business — Nine Dots & Twenty', + description: + 'How Nine Dots Ventures rebuilt a Singapore real estate agency on Twenty with APIs, n8n, Grafana, and AI on top of 2,000+ WhatsApp messages a day.', + }, + hero: { + readingTime: '9 min', + title: [ + { text: 'A real estate agency on WhatsApp ', fontFamily: 'serif' }, + { text: 'built a CRM around it', fontFamily: 'sans' }, + ], + author: 'Mike Babiy & Azmat Parveen', + clientIcon: 'nine-dots', + heroImageSrc: PLACEHOLDER_HERO, + }, + sections: [ + { + type: 'text', + eyebrow: 'Homeseller', + heading: [ + { text: 'When the channel ', fontFamily: 'serif' }, + { text: 'is the business', fontFamily: 'sans' }, + ], + paragraphs: [ + 'Homeseller is a high-volume real estate agency in Singapore, founded by one of the country\'s top-performing property agents. The whole operation runs on WhatsApp: no email, no calendars — group chats, thousands of them, with clients, agents, and leads together.', + 'That works until you need to understand the business underneath. Which deals are stuck? Where are leads coming from? What is the close rate? With spreadsheets and a legacy custom CRM that could not keep up, those questions were nearly impossible to answer.', + 'Mike and Azmat from Nine Dots stepped in to fix that — not by changing how Homeseller works, but by building a system that finally fit around it.', + ], + callout: + '"Twenty lets us build a CRM around the business and not the business around the CRM." — Mike Babiy, Founder, Nine Dots Ventures', + }, + { + type: 'text', + eyebrow: 'Architecture', + heading: [ + { text: 'The CRM ', fontFamily: 'serif' }, + { text: 'as a control hub', fontFamily: 'sans' }, + ], + paragraphs: [ + 'Nine Dots rebuilt Homeseller\'s operations on Twenty, with a custom data model shaped around their sales flow. Because Twenty is open and everything is accessible via API, they connected it to what the business actually needed: n8n for automated workflows (in-app workflows were not available at that time), Grafana for live dashboards fed from Twenty, and a custom AI layer to parse and extract structured insights from more than 2,000 WhatsApp messages a day.', + 'Homeseller kept their habits. WhatsApp stayed WhatsApp. What changed is that everything flowing through those conversations now lands in a structured system — tracked, classified, and visible in real time.', + ], + callout: + '"Twenty is the heart of the system. Everything branches from it." — Azmat Parveen, Nine Dots Ventures', + }, + { + type: 'text', + eyebrow: 'The result', + heading: [ + { text: '150 hours ', fontFamily: 'serif' }, + { text: 'saved every month', fontFamily: 'sans' }, + ], + paragraphs: [ + 'About 150 hours per month saved in manual operations. Real-time metrics for the business owner. Growth readiness without adding operational headcount. A team that can answer questions that used to take days to piece together.', + 'The full rollout landed in July 2025. Since then, Nine Dots built a Smart Assistant on top of the system — nudging agents with tasks, reminders, and on-demand market analysis. Some agents never open Twenty directly, yet they are powered by it, outperforming peers on manual processes alone. By Q1 2026, Homeseller had recorded its best sales quarter ever.', + ], + }, + ], + testimonial: { + eyebrow: 'What they say', + quote: + 'Twenty lets us build a CRM around the business and not the business around the CRM.', + author: { + name: 'Mike Babiy', + handle: 'Founder, Nine Dots Ventures', + date: 'July 2025', + avatarSrc: PLACEHOLDER_HERO, + }, + }, + tableOfContents: [ + 'When the channel is the business', + 'The CRM as a control hub', + 'The result', + ], + catalogCard: { + summary: + 'Nine Dots put Twenty at the center of Homeseller\'s stack with APIs, automation, and AI on top of WhatsApp-heavy operations.', + date: 'Jul 2025', + }, +}; + +export const metadata: Metadata = { + title: CASE_STUDY.meta.title, + description: CASE_STUDY.meta.description, +}; + +export default async function NineDotsCaseStudyPage() { + const stats = await fetchCommunityStats(); + const menuSocialLinks = mergeSocialLinkLabels(MENU_DATA.socialLinks, stats); + + return ( + <> + + + + + + + + + + {CASE_STUDY.sections.map((block, index) => { + if (block.type === 'text') { + return ; + } + return ; + })} + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/twenty-website-new/src/app/case-studies/_constants/index.ts b/packages/twenty-website-new/src/app/case-studies/_constants/index.ts new file mode 100644 index 0000000000..d2ca65b417 --- /dev/null +++ b/packages/twenty-website-new/src/app/case-studies/_constants/index.ts @@ -0,0 +1,7 @@ +export type { + CaseStudyCatalogEntry, + CaseStudyContentBlock, + CaseStudyData, + CaseStudyTextBlock, + CaseStudyVisualBlock, +} from './types'; diff --git a/packages/twenty-website-new/src/app/case-studies/_constants/types.ts b/packages/twenty-website-new/src/app/case-studies/_constants/types.ts new file mode 100644 index 0000000000..d61d76269e --- /dev/null +++ b/packages/twenty-website-new/src/app/case-studies/_constants/types.ts @@ -0,0 +1,52 @@ +import type { HeadingType } from '@/design-system/components/Heading/types/Heading'; + +export type CaseStudyTextBlock = { + type: 'text'; + eyebrow?: string; + heading: HeadingType[]; + paragraphs: string[]; + callout?: string; +}; + +export type CaseStudyVisualBlock = { + type: 'visual'; + src: string; + alt: string; +}; + +export type CaseStudyContentBlock = CaseStudyTextBlock | CaseStudyVisualBlock; + +export type CaseStudyTestimonialAuthor = { + name: string; + handle: string; + date: string; + avatarSrc: string; +}; + +export type CaseStudyData = { + meta: { title: string; description: string }; + hero: { + readingTime: string; + title: HeadingType[]; + author: string; + clientIcon: string; + heroImageSrc: string; + }; + sections: CaseStudyContentBlock[]; + testimonial: { + eyebrow: string; + quote: string; + author: CaseStudyTestimonialAuthor; + }; + tableOfContents: string[]; + catalogCard: { + summary: string; + date: string; + }; +}; + +export type CaseStudyCatalogEntry = { + href: string; + hero: CaseStudyData['hero']; + catalogCard: CaseStudyData['catalogCard']; +}; diff --git a/packages/twenty-website-new/src/app/case-studies/act-education/page.tsx b/packages/twenty-website-new/src/app/case-studies/act-education/page.tsx new file mode 100644 index 0000000000..31d0f29756 --- /dev/null +++ b/packages/twenty-website-new/src/app/case-studies/act-education/page.tsx @@ -0,0 +1,168 @@ +import { FAQ_DATA, MENU_DATA } from '@/app/_constants'; +import { TalkToUsButton } from '@/app/components/ContactCalModal'; +import type { CaseStudyData } from '@/app/case-studies/_constants/types'; +import { Eyebrow, LinkButton } from '@/design-system/components'; +import { fetchCommunityStats } from '@/lib/community/fetch-community-stats'; +import { mergeSocialLinkLabels } from '@/lib/community/merge-social-link-labels'; +import { CaseStudy } from '@/sections/CaseStudy/components'; +import { Faq } from '@/sections/Faq/components'; +import { Menu } from '@/sections/Menu/components'; +import { theme } from '@/theme'; +import type { Metadata } from 'next'; + +const PLACEHOLDER_HERO = '/images/home/hero/avatars/katherine-adams.jpg'; + +const CASE_STUDY: CaseStudyData = { + meta: { + title: + 'Burned by vendor lock-in, AC&T built a CRM they actually own — Twenty', + description: + 'How AC&T Education Migration and Flycoder replaced a shuttered vendor CRM with self-hosted Twenty — 90%+ lower cost and full ownership.', + }, + hero: { + readingTime: '7 min', + title: [ + { text: 'A CRM they ', fontFamily: 'serif' }, + { text: 'actually own', fontFamily: 'sans' }, + ], + author: 'Joseph Chiang', + clientIcon: 'act-education', + heroImageSrc: PLACEHOLDER_HERO, + }, + sections: [ + { + type: 'text', + eyebrow: 'AC&T Education Migration', + heading: [ + { text: 'When the vendor ', fontFamily: 'serif' }, + { text: 'pulled the plug', fontFamily: 'sans' }, + ], + paragraphs: [ + 'AC&T Education Migration (actimmi.com) is an education agency in Australia. They help international students with applications to education providers and visas. They had been on a previous CRM until the vendor shut the system down — leaving nothing but a CSV export.', + 'Whatever came next had to be something they could own.', + ], + callout: + '"They did not want to learn someone else\'s system. They wanted to keep working the way they already did and make it smoother." — Joseph Chiang, CRM Engineer, AC&T Education Migration', + }, + { + type: 'text', + eyebrow: 'Implementation', + heading: [ + { text: 'No more renting ', fontFamily: 'serif' }, + { text: 'someone else\'s structure', fontFamily: 'sans' }, + ], + paragraphs: [ + 'They evaluated Salesforce, Zoho, Pipedrive, and SuiteCRM. Each came with the same tradeoffs: too expensive, too rigid, or too generic — and none fixed the underlying problem. They were still renting a structure they did not control.', + 'Flycoder, a full-stack development partner, helped them set up Twenty as a self-hosted instance shaped around how AC&T actually operates. The data model centers on students, not a generic contact-and-deal pipeline. Statuses update automatically: a workflow runs nightly to keep enrollment records current. Automated email reminders cover important dates. Adding a new record takes under a minute.', + 'The result is a system that fits how AC&T already worked, instead of the other way around.', + ], + }, + { + type: 'text', + eyebrow: 'Control', + heading: [ + { text: 'Control without ', fontFamily: 'serif' }, + { text: 'the overhead', fontFamily: 'sans' }, + ], + paragraphs: [ + 'Self-hosted means AC&T carries no vendor risk: no pricing model that can change, no platform that can disappear, no forced migration. The system is theirs.', + 'Because everything is built on Twenty\'s open foundation, Flycoder could wire the exact logic AC&T needed without fighting the platform.', + ], + }, + { + type: 'text', + eyebrow: 'The result', + heading: [ + { text: 'Costs down ', fontFamily: 'serif' }, + { text: 'more than 90%', fontFamily: 'sans' }, + ], + paragraphs: [ + 'CRM costs dropped by more than 90%. Manual overhead tied to the old system is gone. For the first time, AC&T has a CRM they will not lose again.', + 'They did not just replace a tool — they took back ownership of how their business runs.', + ], + }, + ], + testimonial: { + eyebrow: 'What they say', + quote: + 'They did not want to learn someone else\'s system. They wanted to keep working the way they already did and make it smoother.', + author: { + name: 'Joseph Chiang', + handle: 'CRM Engineer, AC&T Education Migration', + date: '2025', + avatarSrc: PLACEHOLDER_HERO, + }, + }, + tableOfContents: [ + 'When the vendor pulled the plug', + 'No more renting someone else\'s structure', + 'Control without the overhead', + 'The result', + ], + catalogCard: { + summary: + 'AC&T and Flycoder moved from a dead vendor export to self-hosted Twenty — over 90% lower CRM cost and full control.', + date: '2025', + }, +}; + +export const metadata: Metadata = { + title: CASE_STUDY.meta.title, + description: CASE_STUDY.meta.description, +}; + +export default async function ActEducationCaseStudyPage() { + const stats = await fetchCommunityStats(); + const menuSocialLinks = mergeSocialLinkLabels(MENU_DATA.socialLinks, stats); + + return ( + <> + + + + + + + + + + {CASE_STUDY.sections.map((block, index) => { + if (block.type === 'text') { + return ; + } + return ; + })} + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/twenty-website-new/src/app/case-studies/alternative-partners/page.tsx b/packages/twenty-website-new/src/app/case-studies/alternative-partners/page.tsx new file mode 100644 index 0000000000..ffdb3e5104 --- /dev/null +++ b/packages/twenty-website-new/src/app/case-studies/alternative-partners/page.tsx @@ -0,0 +1,136 @@ +import { FAQ_DATA, MENU_DATA } from '@/app/_constants'; +import { TalkToUsButton } from '@/app/components/ContactCalModal'; +import type { CaseStudyData } from '@/app/case-studies/_constants/types'; +import { Eyebrow, LinkButton } from '@/design-system/components'; +import { fetchCommunityStats } from '@/lib/community/fetch-community-stats'; +import { mergeSocialLinkLabels } from '@/lib/community/merge-social-link-labels'; +import { CaseStudy } from '@/sections/CaseStudy/components'; +import { Faq } from '@/sections/Faq/components'; +import { Menu } from '@/sections/Menu/components'; +import { theme } from '@/theme'; +import type { Metadata } from 'next'; + +const PLACEHOLDER_HERO = '/images/home/hero/avatars/katherine-adams.jpg'; + +const CASE_STUDY: CaseStudyData = { + meta: { + title: + 'From Salesforce to self-hosted Twenty, powered by AI — Alternative Partners', + description: + 'How Alternative Partners migrated from Salesforce to self-hosted Twenty using agentic AI in the implementation loop — fast migration, durable ownership.', + }, + hero: { + readingTime: '7 min', + title: [ + { text: 'From Salesforce to ', fontFamily: 'serif' }, + { text: 'self-hosted Twenty', fontFamily: 'sans' }, + ], + author: 'Benjamin Reynolds', + clientIcon: 'alternative-partners', + heroImageSrc: PLACEHOLDER_HERO, + }, + sections: [ + { + type: 'text', + eyebrow: 'Alternative Partners', + heading: [ + { text: 'AI in the ', fontFamily: 'serif' }, + { text: 'migration workflow', fontFamily: 'sans' }, + ], + paragraphs: [ + 'Alternative Partners is a consulting firm that moved from Salesforce to a self-hosted Twenty instance. Benjamin Reynolds led the migration — he had already become a Twenty expert implementing Twenty for one of Twenty\'s first cloud customers.', + 'His approach was unconventional. Instead of mapping fields manually, scripting transforms, and validating data step by step, he handed the job to agentic AI tools with a brief: where the data lives, the GitHub repo for the target platform, the Railway deployment — start, and only return if something breaks beyond a 70% confidence fix.', + 'It worked. This is AI-assisted iteration in practice: not AI as a product feature, but as part of implementation work, compressing what would typically be weeks into something one person can oversee without being the bottleneck.', + ], + }, + { + type: 'text', + eyebrow: 'Ownership', + heading: [ + { text: 'Self-hosted ', fontFamily: 'serif' }, + { text: 'means control', fontFamily: 'sans' }, + ], + paragraphs: [ + 'The self-hosted setup means Alternative Partners owns the full stack: no vendor access to their data, no dependency on a SaaS pricing model, full control over how the system evolves. The migration was fast because of AI; the result is durable because the stack is open source.', + ], + }, + ], + testimonial: { + eyebrow: 'What they say', + quote: + 'Agentic AI let us migrate faster than a traditional playbook, and self-hosted Twenty means we own where our CRM data lives.', + author: { + name: 'Benjamin Reynolds', + handle: 'Alternative Partners', + date: '2025', + avatarSrc: PLACEHOLDER_HERO, + }, + }, + tableOfContents: ['AI in the migration workflow', 'Self-hosted means control'], + catalogCard: { + summary: + 'Alternative Partners replaced Salesforce with self-hosted Twenty, using agentic AI to compress migration work.', + date: '2025', + }, +}; + +export const metadata: Metadata = { + title: CASE_STUDY.meta.title, + description: CASE_STUDY.meta.description, +}; + +export default async function AlternativePartnersCaseStudyPage() { + const stats = await fetchCommunityStats(); + const menuSocialLinks = mergeSocialLinkLabels(MENU_DATA.socialLinks, stats); + + return ( + <> + + + + + + + + + + {CASE_STUDY.sections.map((block, index) => { + if (block.type === 'text') { + return ; + } + return ; + })} + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/twenty-website-new/src/app/case-studies/elevate-consulting/page.tsx b/packages/twenty-website-new/src/app/case-studies/elevate-consulting/page.tsx new file mode 100644 index 0000000000..58f51ac564 --- /dev/null +++ b/packages/twenty-website-new/src/app/case-studies/elevate-consulting/page.tsx @@ -0,0 +1,172 @@ +import { FAQ_DATA, MENU_DATA } from '@/app/_constants'; +import { TalkToUsButton } from '@/app/components/ContactCalModal'; +import type { CaseStudyData } from '@/app/case-studies/_constants/types'; +import { Eyebrow, LinkButton } from '@/design-system/components'; +import { fetchCommunityStats } from '@/lib/community/fetch-community-stats'; +import { mergeSocialLinkLabels } from '@/lib/community/merge-social-link-labels'; +import { CaseStudy } from '@/sections/CaseStudy/components'; +import { Faq } from '@/sections/Faq/components'; +import { Menu } from '@/sections/Menu/components'; +import { theme } from '@/theme'; +import type { Metadata } from 'next'; + +const PLACEHOLDER_HERO = '/images/home/hero/avatars/katherine-adams.jpg'; + +const CASE_STUDY: CaseStudyData = { + meta: { + title: + 'Twenty as the API backbone of a go-to-market stack — Elevate Consulting', + description: + 'How Elevate Consulting moved off documents and spreadsheets to Twenty as the API-connected CRM at the center of their stack.', + }, + hero: { + readingTime: '8 min', + title: [ + { text: 'Twenty as the API backbone ', fontFamily: 'serif' }, + { text: 'of a go-to-market stack', fontFamily: 'sans' }, + ], + author: 'Justin Beadle', + clientIcon: 'elevate-consulting', + heroImageSrc: PLACEHOLDER_HERO, + }, + sections: [ + { + type: 'text', + eyebrow: 'The situation', + heading: [ + { text: 'From documents ', fontFamily: 'serif' }, + { text: 'to open APIs', fontFamily: 'sans' }, + ], + paragraphs: [ + 'Elevate Consulting is a management consultancy based in Canada. When Justin Beadle, Director of Digital and Information, joined, the company ran entirely on Word documents, Excel spreadsheets, sticky notes, emails, and reliance on people. There was no CRM, no API-accessible tools — only a patchwork trying to stand in for a single source of truth.', + 'The CEO had resisted bringing in a CRM for years. The business development team had no experience using one, and the licensing costs of well-known CRMs like Salesforce or HubSpot were hard to justify without any guarantee of adoption: CRMs are only as good as the maintenance of the data inside them.', + 'In June 2025, Justin learned Twenty v1 had shipped. Within two or three days, the CEO asked him to look into setting up a CRM. The shift came from the potential of what could be built on top of fully open APIs. The timing was perfect.', + ], + callout: + '"It is just such a nicer experience than dealing with a Salesforce or a HubSpot. My mission has been to get every tool API-accessible, so everything talks to each other. Twenty made that possible in a way older CRM platforms simply do not." — Justin Beadle, Director of Digital and Information, Elevate Consulting', + }, + { + type: 'text', + eyebrow: 'Integration', + heading: [ + { text: 'One API ', fontFamily: 'serif' }, + { text: 'to rule them all', fontFamily: 'sans' }, + ], + paragraphs: [ + 'Justin\'s broader mission at Elevate has been to move the company off static documents and onto tools with API access. By the end of 2025, that was in place: time billing, resource planning, Microsoft Teams, and project management were all accessible via API, with Twenty at the center holding client and opportunity data. Team members could use that information strategically instead of re-keying it.', + 'That opened the door to something more powerful. Justin built a custom front end that pulls live data from those systems into a single view, tailored to each role. When a proposal is won, what used to require four separate people manually setting up instances across four different tools now happens in a single click, drawing on data collected in Twenty across the full opportunity lifecycle — another shift toward higher-value work for clients.', + 'Twenty is not only where CRM data lives. It is the API backbone that makes the rest of the stack possible.', + ], + callout: + '"Because Twenty\'s API is fully open, I could connect it to every other tool in our stack. When a proposal is won, what used to take four people manually setting things up across four different tools now happens in a single click. That is the kind of time saving that only becomes possible when everything is connected." — Justin Beadle, Director of Digital and Information, Elevate Consulting', + }, + { + type: 'text', + eyebrow: 'Adoption', + heading: [ + { text: 'Workflows that ', fontFamily: 'serif' }, + { text: 'actually get used', fontFamily: 'sans' }, + ], + paragraphs: [ + 'The business development team finally had the CRM they had been asking for. Adoption came naturally: their data was already there when they logged in.', + 'Justin built workflows for notifications across the team, alerting the right people in Teams when a prospect becomes a lead or when project milestones are reached. Forms in Twenty let the business development team log activity without leaving the tool. The impact is real for the organization — the tool has been adaptable from opportunity-level work at a client to executive-level decisions.', + 'The flexibility to wire this together, without outside help and without fighting the platform, is what made it possible for a single person to stand up and maintain a connected stack across an entire consultancy.', + ], + }, + { + type: 'text', + eyebrow: 'What is next', + heading: [ + { text: 'Beyond ', fontFamily: 'serif' }, + { text: 'internal rollout', fontFamily: 'sans' }, + ], + paragraphs: [ + 'Elevate\'s CEO was so impressed with Twenty he started recommending it to clients before the internal setup was even complete. The team is exploring bringing Twenty to client projects as part of their consulting practice, including as the backend for custom-built products tailored to specific operational needs.', + 'For a firm that once ran on sticky notes, this is more than an upgrade — it is a complete transformation.', + ], + }, + ], + testimonial: { + eyebrow: 'What they say', + quote: + 'Because Twenty\'s API is fully open, I could connect it to every other tool in our stack. When a proposal is won, what used to take four people across four tools now happens in a single click.', + author: { + name: 'Justin Beadle', + handle: 'Director of Digital and Information, Elevate Consulting', + date: 'June 2025', + avatarSrc: PLACEHOLDER_HERO, + }, + }, + tableOfContents: [ + 'From documents to open APIs', + 'One API to rule them all', + 'Workflows that actually get used', + 'What is next', + ], + catalogCard: { + summary: + 'Elevate Consulting uses Twenty as the API backbone connecting billing, Teams, resourcing, and a custom front end around client and opportunity data.', + date: 'Jun 2025', + }, +}; + +export const metadata: Metadata = { + title: CASE_STUDY.meta.title, + description: CASE_STUDY.meta.description, +}; + +export default async function ElevateConsultingCaseStudyPage() { + const stats = await fetchCommunityStats(); + const menuSocialLinks = mergeSocialLinkLabels(MENU_DATA.socialLinks, stats); + + return ( + <> + + + + + + + + + + {CASE_STUDY.sections.map((block, index) => { + if (block.type === 'text') { + return ; + } + return ; + })} + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/twenty-website-new/src/app/case-studies/netzero/page.tsx b/packages/twenty-website-new/src/app/case-studies/netzero/page.tsx new file mode 100644 index 0000000000..752f25266d --- /dev/null +++ b/packages/twenty-website-new/src/app/case-studies/netzero/page.tsx @@ -0,0 +1,168 @@ +import { FAQ_DATA, MENU_DATA } from '@/app/_constants'; +import { TalkToUsButton } from '@/app/components/ContactCalModal'; +import type { CaseStudyData } from '@/app/case-studies/_constants/types'; +import { Eyebrow, LinkButton } from '@/design-system/components'; +import { fetchCommunityStats } from '@/lib/community/fetch-community-stats'; +import { mergeSocialLinkLabels } from '@/lib/community/merge-social-link-labels'; +import { CaseStudy } from '@/sections/CaseStudy/components'; +import { Faq } from '@/sections/Faq/components'; +import { Menu } from '@/sections/Menu/components'; +import { theme } from '@/theme'; +import type { Metadata } from 'next'; + +const PLACEHOLDER_HERO = '/images/home/hero/avatars/katherine-adams.jpg'; + +const CASE_STUDY: CaseStudyData = { + meta: { + title: 'A CRM that grows with you — NetZero & Twenty', + description: + 'How NetZero uses Twenty across carbon credits, agricultural products, and franchised industrial systems with a modular CRM and a roadmap toward AI-assisted workflows.', + }, + hero: { + readingTime: '8 min', + title: [ + { text: 'A CRM that ', fontFamily: 'serif' }, + { text: 'grows with you', fontFamily: 'sans' }, + ], + author: 'Olivier Reinaud', + clientIcon: 'netzero', + heroImageSrc: PLACEHOLDER_HERO, + }, + sections: [ + { + type: 'text', + eyebrow: 'NetZero', + heading: [ + { text: 'The right ', fontFamily: 'serif' }, + { text: 'foundation', fontFamily: 'sans' }, + ], + paragraphs: [ + 'NetZero works with the agro-industry, serving clients from multinationals to smallholder farmers. They sell carbon credits, agricultural products, and franchised industrial systems — three different product lines across multiple countries and company sizes. When Olivier Reinaud, co-founder of NetZero, started looking at CRMs in late 2024, he was not chasing the most feature-rich platform. He wanted the right foundation.', + ], + callout: + '"Twenty delivers on what CRMs should have always been: fairly priced software with a fully modular and customizable model, a clean and modern UI, granular permissions, automations, enterprise features. A compelling solution with high potential to rightfully disrupt the CRM market." — Olivier Reinaud, co-founder of NetZero', + }, + { + type: 'text', + eyebrow: 'Flexibility', + heading: [ + { text: 'A business that ', fontFamily: 'serif' }, + { text: 'does not fit a template', fontFamily: 'sans' }, + ], + paragraphs: [ + 'What convinced Olivier was the flexibility of the platform and where it was headed. Even when initial needs were basic record-keeping, he still needed a custom data model with granular permissions to manage the wide range of NetZero activities. He also needed a system that could adapt quickly to a fast-iteration company.', + 'With Twenty, when a new need appears, he can address it himself — no developer required, no support ticket.', + ], + callout: + '"The flexibility is really what made the difference. Our needs evolve very fast. I discover a new need and in two clicks I can address it. That is a real advantage when you are moving quickly." — Olivier Reinaud, co-founder of NetZero', + }, + { + type: 'text', + eyebrow: 'Roadmap', + heading: [ + { text: 'From simple ', fontFamily: 'serif' }, + { text: 'to advanced', fontFamily: 'sans' }, + ], + paragraphs: [ + 'Olivier recognizes that NetZero\'s current use of Twenty is still relatively simple: workflows and integrations are not yet as deep as he eventually wants, because he prioritized getting foundations right first.', + 'What is planned is significant. NetZero has a data lake, online forms, and multiple internal systems that he wants to connect to Twenty. The pipes are there; the next step is automations that tie them together.', + 'What is coming in April 2026 is what he has been waiting for: AI-assisted workflow creation — describing what he needs and iterating from there instead of building complex logic from scratch. For a founder who runs the CRM himself, that changes what is realistically possible.', + ], + }, + { + type: 'text', + eyebrow: 'Results', + heading: [ + { text: 'The bet ', fontFamily: 'serif' }, + { text: 'is paying off', fontFamily: 'sans' }, + ], + paragraphs: [ + 'While NetZero still runs a second CRM in parallel for WhatsApp-heavy operations with farmers in Brazil, they expect to migrate all of it to Twenty as features and the ecosystem grow. Already, their structured, multinational pipeline is powered by Twenty.', + 'The early bet on the architecture is holding, and upcoming AI features are expected to make it even more relevant.', + ], + }, + ], + testimonial: { + eyebrow: 'What they say', + quote: + 'The flexibility is really what made the difference. Our needs evolve very fast. I discover a new need and in two clicks I can address it.', + author: { + name: 'Olivier Reinaud', + handle: 'Co-founder, NetZero', + date: '2025', + avatarSrc: PLACEHOLDER_HERO, + }, + }, + tableOfContents: [ + 'The right foundation', + 'A business that does not fit a template', + 'From simple to advanced', + 'The bet is paying off', + ], + catalogCard: { + summary: + 'NetZero uses Twenty as a modular CRM across product lines and countries, with a roadmap into AI-assisted workflows.', + date: '2025', + }, +}; + +export const metadata: Metadata = { + title: CASE_STUDY.meta.title, + description: CASE_STUDY.meta.description, +}; + +export default async function NetZeroCaseStudyPage() { + const stats = await fetchCommunityStats(); + const menuSocialLinks = mergeSocialLinkLabels(MENU_DATA.socialLinks, stats); + + return ( + <> + + + + + + + + + + {CASE_STUDY.sections.map((block, index) => { + if (block.type === 'text') { + return ; + } + return ; + })} + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/twenty-website-new/src/app/case-studies/page.tsx b/packages/twenty-website-new/src/app/case-studies/page.tsx new file mode 100644 index 0000000000..3ed8e5321e --- /dev/null +++ b/packages/twenty-website-new/src/app/case-studies/page.tsx @@ -0,0 +1,223 @@ +import { FAQ_DATA, MENU_DATA } from '@/app/_constants'; +import { TalkToUsButton } from '@/app/components/ContactCalModal'; +import type { CaseStudyCatalogEntry } from '@/app/case-studies/_constants/types'; +import { Eyebrow, LinkButton } from '@/design-system/components'; +import { Pages } from '@/enums/pages'; +import { fetchCommunityStats } from '@/lib/community/fetch-community-stats'; +import { mergeSocialLinkLabels } from '@/lib/community/merge-social-link-labels'; +import { CaseStudyCatalog } from '@/sections/CaseStudyCatalog/components'; +import { Faq } from '@/sections/Faq/components'; +import { Hero } from '@/sections/Hero/components'; +import { Menu } from '@/sections/Menu/components'; +import { Signoff } from '@/sections/Signoff/components'; +import { theme } from '@/theme'; +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Case Studies — Twenty', + description: + 'See how teams use Twenty to build custom CRM workflows and drive real business results.', +}; + +const HERO_HEADING = [ + { text: 'See how teams build ', fontFamily: 'serif' as const }, + { text: 'with Twenty', fontFamily: 'sans' as const }, +]; + +const HERO_BODY = { + text: 'Real stories from real teams — how they shaped Twenty to fit their workflow and accelerated their growth.', +}; + +const SIGNOFF_HEADING = [ + { text: 'Ready to build ', fontFamily: 'serif' as const }, + { text: 'your own story?', fontFamily: 'sans' as const }, +]; + +const SIGNOFF_BODY = { + text: 'Join the teams that chose to own their CRM. Start building with Twenty today.', +}; + +const PLACEHOLDER_HERO = '/images/home/hero/avatars/katherine-adams.jpg'; + +const CASE_STUDY_CATALOG_ENTRIES: CaseStudyCatalogEntry[] = [ + { + href: '/case-studies/elevate-consulting', + hero: { + readingTime: '8 min', + title: [ + { text: 'Twenty as the API backbone ', fontFamily: 'serif' }, + { text: 'of a go-to-market stack', fontFamily: 'sans' }, + ], + author: 'Justin Beadle', + clientIcon: 'elevate-consulting', + heroImageSrc: PLACEHOLDER_HERO, + }, + catalogCard: { + summary: + 'Elevate Consulting uses Twenty as the API backbone connecting billing, Teams, resourcing, and a custom front end around client and opportunity data.', + date: 'Jun 2025', + }, + }, + { + href: '/case-studies/9dots', + hero: { + readingTime: '9 min', + title: [ + { text: 'A real estate agency on WhatsApp ', fontFamily: 'serif' }, + { text: 'built a CRM around it', fontFamily: 'sans' }, + ], + author: 'Mike Babiy & Azmat Parveen', + clientIcon: 'nine-dots', + heroImageSrc: PLACEHOLDER_HERO, + }, + catalogCard: { + summary: + 'Nine Dots put Twenty at the center of Homeseller\'s stack with APIs, automation, and AI on top of WhatsApp-heavy operations.', + date: 'Jul 2025', + }, + }, + { + href: '/case-studies/alternative-partners', + hero: { + readingTime: '7 min', + title: [ + { text: 'From Salesforce to ', fontFamily: 'serif' }, + { text: 'self-hosted Twenty', fontFamily: 'sans' }, + ], + author: 'Benjamin Reynolds', + clientIcon: 'alternative-partners', + heroImageSrc: PLACEHOLDER_HERO, + }, + catalogCard: { + summary: + 'Alternative Partners replaced Salesforce with self-hosted Twenty, using agentic AI to compress migration work.', + date: '2025', + }, + }, + { + href: '/case-studies/netzero', + hero: { + readingTime: '8 min', + title: [ + { text: 'A CRM that ', fontFamily: 'serif' }, + { text: 'grows with you', fontFamily: 'sans' }, + ], + author: 'Olivier Reinaud', + clientIcon: 'netzero', + heroImageSrc: PLACEHOLDER_HERO, + }, + catalogCard: { + summary: + 'NetZero uses Twenty as a modular CRM across product lines and countries, with a roadmap into AI-assisted workflows.', + date: '2025', + }, + }, + { + href: '/case-studies/act-education', + hero: { + readingTime: '7 min', + title: [ + { text: 'A CRM they ', fontFamily: 'serif' }, + { text: 'actually own', fontFamily: 'sans' }, + ], + author: 'Joseph Chiang', + clientIcon: 'act-education', + heroImageSrc: PLACEHOLDER_HERO, + }, + catalogCard: { + summary: + 'AC&T and Flycoder moved from a dead vendor export to self-hosted Twenty — over 90% lower CRM cost and full control.', + date: '2025', + }, + }, + { + href: '/case-studies/w3villa', + hero: { + readingTime: '8 min', + title: [ + { text: 'When your CRM ', fontFamily: 'serif' }, + { text: 'is the product', fontFamily: 'sans' }, + ], + author: 'Amrendra Pratap Singh', + clientIcon: 'w3villa', + heroImageSrc: PLACEHOLDER_HERO, + }, + catalogCard: { + summary: + 'W3villa shipped W3Grads on Twenty — AI interviews, scoring, and institution-scale workflows without rebuilding CRM plumbing.', + date: '2025', + }, + }, +]; + +export default async function CaseStudiesCatalogPage() { + const stats = await fetchCommunityStats(); + const menuSocialLinks = mergeSocialLinkLabels(MENU_DATA.socialLinks, stats); + + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/twenty-website-new/src/app/case-studies/w3villa/page.tsx b/packages/twenty-website-new/src/app/case-studies/w3villa/page.tsx new file mode 100644 index 0000000000..38db0420b3 --- /dev/null +++ b/packages/twenty-website-new/src/app/case-studies/w3villa/page.tsx @@ -0,0 +1,169 @@ +import { FAQ_DATA, MENU_DATA } from '@/app/_constants'; +import { TalkToUsButton } from '@/app/components/ContactCalModal'; +import type { CaseStudyData } from '@/app/case-studies/_constants/types'; +import { Eyebrow, LinkButton } from '@/design-system/components'; +import { fetchCommunityStats } from '@/lib/community/fetch-community-stats'; +import { mergeSocialLinkLabels } from '@/lib/community/merge-social-link-labels'; +import { CaseStudy } from '@/sections/CaseStudy/components'; +import { Faq } from '@/sections/Faq/components'; +import { Menu } from '@/sections/Menu/components'; +import { theme } from '@/theme'; +import type { Metadata } from 'next'; + +const PLACEHOLDER_HERO = '/images/home/hero/avatars/katherine-adams.jpg'; + +const CASE_STUDY: CaseStudyData = { + meta: { + title: + 'When your CRM is the product: W3Grads on Twenty — W3villa Technologies', + description: + 'How W3villa Technologies shipped W3Grads, an AI mock interview platform for institutions, on Twenty as the operational backbone.', + }, + hero: { + readingTime: '8 min', + title: [ + { text: 'When your CRM ', fontFamily: 'serif' }, + { text: 'is the product', fontFamily: 'sans' }, + ], + author: 'Amrendra Pratap Singh', + clientIcon: 'w3villa', + heroImageSrc: PLACEHOLDER_HERO, + }, + sections: [ + { + type: 'text', + eyebrow: 'W3Grads', + heading: [ + { text: 'Scale without ', fontFamily: 'serif' }, + { text: 'breaking operations', fontFamily: 'sans' }, + ], + paragraphs: [ + 'Running mock interview programs for hundreds of students sounds straightforward. In practice, universities and training institutes hit the same wall: registrations entered by hand, interview links sent one by one, faculty reviewing every session without scoring or classification. At real scale, it breaks.', + 'W3villa Technologies set out to solve it properly — not with a workaround, but with a product.', + ], + callout: + '"We did not want to patch over the problem. We wanted to build something institutions could rely on at scale, and that meant starting from a foundation solid enough to support the full complexity of what we had in mind." — Amrendra Pratap Singh, VP of Engineering, W3villa Technologies', + }, + { + type: 'text', + eyebrow: 'Architecture', + heading: [ + { text: 'Focus on the use case, ', fontFamily: 'serif' }, + { text: 'not the plumbing', fontFamily: 'sans' }, + ], + paragraphs: [ + 'W3villa built W3Grads (w3grads.com), an AI-powered mock interview platform for universities and training institutes, using Twenty as its operational backbone.', + 'The key decision was not to build everything from scratch. Twenty covers the data model, permissions, authentication, and workflow engine — the parts that would have taken months to rebuild — so the team could focus on product-specific logic.', + 'When a student registers via QR at a campus event, the system assigns a plan, generates an interview session, and sends a link. The AI conducts the interview, scores the candidate, and classifies the result. Faculty see where each student stands without manually reviewing every session. Building and iterating on these workflows was faster with AI in the loop.', + ], + }, + { + type: 'text', + eyebrow: 'Scale', + heading: [ + { text: 'A platform ', fontFamily: 'serif' }, + { text: 'ready to grow', fontFamily: 'sans' }, + ], + paragraphs: [ + 'Because the foundation is solid, W3Grads is architected for what comes next — including a payment layer for future paid interview plans and nationwide scale without structural rewrites.', + ], + callout: + '"Twenty gave us the flexibility to model the entire interview lifecycle as custom objects and workflows. We could build something genuinely complex without fighting the platform to do it." — Piyush Khandelwal, Director, W3villa Technologies, Partner', + }, + { + type: 'text', + eyebrow: 'The result', + heading: [ + { text: 'Zero manual work ', fontFamily: 'serif' }, + { text: 'at the core', fontFamily: 'sans' }, + ], + paragraphs: [ + 'Programs that previously needed heavy manual coordination now run end-to-end with automation. Institutions get a scalable, intelligent system; students get faster preparation for interviews that matter; W3villa shipped a product institutions can build revenue around.', + 'Zero manual work at the core. Full automation. Built on Twenty.', + ], + }, + ], + testimonial: { + eyebrow: 'What they say', + quote: + 'Twenty gave us the flexibility to model the entire interview lifecycle as custom objects and workflows.', + author: { + name: 'Piyush Khandelwal', + handle: 'Director, W3villa Technologies', + date: '2025', + avatarSrc: PLACEHOLDER_HERO, + }, + }, + tableOfContents: [ + 'Scale without breaking operations', + 'Focus on the use case, not the plumbing', + 'A platform ready to grow', + 'The result', + ], + catalogCard: { + summary: + 'W3villa shipped W3Grads on Twenty — AI interviews, scoring, and institution-scale workflows without rebuilding CRM plumbing.', + date: '2025', + }, +}; + +export const metadata: Metadata = { + title: CASE_STUDY.meta.title, + description: CASE_STUDY.meta.description, +}; + +export default async function W3villaCaseStudyPage() { + const stats = await fetchCommunityStats(); + const menuSocialLinks = mergeSocialLinkLabels(MENU_DATA.socialLinks, stats); + + return ( + <> + + + + + + + + + + {CASE_STUDY.sections.map((block, index) => { + if (block.type === 'text') { + return ; + } + return ; + })} + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/twenty-website-new/src/app/components/ContactCalModal/CalFormEmbed.tsx b/packages/twenty-website-new/src/app/components/ContactCalModal/CalFormEmbed.tsx new file mode 100644 index 0000000000..4cd02dbfba --- /dev/null +++ b/packages/twenty-website-new/src/app/components/ContactCalModal/CalFormEmbed.tsx @@ -0,0 +1,14 @@ +'use client'; + +import { CONTACT_CAL_FORM_CAL_LINK } from '@/app/_constants/contact-cal'; +import Cal from '@calcom/embed-react'; + +export function CalFormEmbed() { + return ( + + ); +} diff --git a/packages/twenty-website-new/src/app/components/ContactCalModal/ContactCalModal.tsx b/packages/twenty-website-new/src/app/components/ContactCalModal/ContactCalModal.tsx new file mode 100644 index 0000000000..e2e4eb3848 --- /dev/null +++ b/packages/twenty-website-new/src/app/components/ContactCalModal/ContactCalModal.tsx @@ -0,0 +1,145 @@ +'use client'; + +import { theme } from '@/theme'; +import { styled } from '@linaria/react'; +import dynamic from 'next/dynamic'; +import { useCallback, useEffect } from 'react'; +import { createPortal } from 'react-dom'; + +const EmbedFallback = styled.p` + color: ${theme.colors.secondary.text[60]}; + font-family: ${theme.font.family.sans}; + font-size: ${theme.font.size(4)}; + margin: 0; + padding-bottom: ${theme.spacing(8)}; + padding-top: ${theme.spacing(8)}; + text-align: center; +`; + +const CalFormEmbed = dynamic( + () => + import('./CalFormEmbed').then((mod) => ({ + default: mod.CalFormEmbed, + })), + { + loading: () => Loading form…, + ssr: false, + }, +); + +const Overlay = styled.div` + align-items: center; + backdrop-filter: blur(4px); + background: rgba(28, 28, 28, 0.8); + box-sizing: border-box; + display: flex; + inset: 0; + justify-content: center; + padding-bottom: ${theme.spacing(4)}; + padding-left: ${theme.spacing(4)}; + padding-right: ${theme.spacing(4)}; + padding-top: ${theme.spacing(4)}; + position: fixed; + z-index: 300; +`; + +const Panel = styled.div` + background: #0c0c0c; + border-radius: ${theme.radius(2)}; + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: ${theme.spacing(4)}; + max-height: 100%; + max-width: 100%; + overflow-y: auto; + padding-bottom: ${theme.spacing(6)}; + padding-left: ${theme.spacing(4)}; + padding-right: ${theme.spacing(4)}; + padding-top: ${theme.spacing(5)}; + position: relative; + width: min(100%, 720px); + + @media (min-width: ${theme.breakpoints.md}px) { + padding-bottom: ${theme.spacing(8)}; + padding-left: ${theme.spacing(6)}; + padding-right: ${theme.spacing(6)}; + padding-top: ${theme.spacing(6)}; + } +`; + +const Title = styled.h2` + color: ${theme.colors.secondary.text[100]}; + font-family: ${theme.font.family.serif}; + font-size: ${theme.font.size(10)}; + font-weight: ${theme.font.weight.light}; + line-height: ${theme.lineHeight(11.5)}; + margin: 0; + + @media (min-width: ${theme.breakpoints.md}px) { + font-size: ${theme.font.size(12)}; + line-height: ${theme.lineHeight(14)}; + } +`; + +const EmbedShell = styled.div` + min-height: 400px; + width: 100%; +`; + +type ContactCalModalProps = { + open: boolean; + onClose: () => void; +}; + +export function ContactCalModal({ open, onClose }: ContactCalModalProps) { + const handleOverlayPointerDown = useCallback( + (event: React.PointerEvent) => { + if (event.target === event.currentTarget) { + onClose(); + } + }, + [onClose], + ); + + useEffect(() => { + if (!open) { + return; + } + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + onClose(); + } + }; + + const previousOverflow = document.body.style.overflow; + document.body.style.overflow = 'hidden'; + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.body.style.overflow = previousOverflow; + document.removeEventListener('keydown', handleKeyDown); + }; + }, [open, onClose]); + + if (!open) { + return null; + } + + return createPortal( + + + Talk to us + + + + + , + document.body, + ); +} diff --git a/packages/twenty-website-new/src/app/components/ContactCalModal/ContactCalModalRoot.tsx b/packages/twenty-website-new/src/app/components/ContactCalModal/ContactCalModalRoot.tsx new file mode 100644 index 0000000000..6d663968cd --- /dev/null +++ b/packages/twenty-website-new/src/app/components/ContactCalModal/ContactCalModalRoot.tsx @@ -0,0 +1,53 @@ +'use client'; + +import { + createContext, + useCallback, + useContext, + useMemo, + useState, + type ReactNode, +} from 'react'; + +import { ContactCalModal } from './ContactCalModal'; + +type ContactCalModalContextValue = { + openContactCalModal: () => void; +}; + +const ContactCalModalContext = + createContext(null); + +export function useContactCalModal() { + const value = useContext(ContactCalModalContext); + if (!value) { + throw new Error( + 'useContactCalModal must be used within ContactCalModalRoot', + ); + } + return value; +} + +export function ContactCalModalRoot({ children }: { children: ReactNode }) { + const [open, setOpen] = useState(false); + + const openContactCalModal = useCallback(() => { + setOpen(true); + }, []); + + const closeContactCalModal = useCallback(() => { + setOpen(false); + }, []); + + const contextValue = useMemo( + () => ({ openContactCalModal }), + [openContactCalModal], + ); + + return ( + + {children} + + + ); +} diff --git a/packages/twenty-website-new/src/app/components/ContactCalModal/TalkToUsButton.tsx b/packages/twenty-website-new/src/app/components/ContactCalModal/TalkToUsButton.tsx new file mode 100644 index 0000000000..0bc6dc1586 --- /dev/null +++ b/packages/twenty-website-new/src/app/components/ContactCalModal/TalkToUsButton.tsx @@ -0,0 +1,29 @@ +'use client'; + +import { + BaseButton, + buttonBaseStyles, +} from '@/design-system/components/Button/BaseButton'; +import { styled } from '@linaria/react'; + +import { useContactCalModal } from './ContactCalModalRoot'; + +const StyledTrigger = styled.button` + ${buttonBaseStyles} +`; + +type TalkToUsButtonProps = { + color: 'primary' | 'secondary'; + label: string; + variant: 'contained' | 'outlined'; +}; + +export function TalkToUsButton({ color, label, variant }: TalkToUsButtonProps) { + const { openContactCalModal } = useContactCalModal(); + + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/app/components/ContactCalModal/index.ts b/packages/twenty-website-new/src/app/components/ContactCalModal/index.ts new file mode 100644 index 0000000000..e5a3d2811d --- /dev/null +++ b/packages/twenty-website-new/src/app/components/ContactCalModal/index.ts @@ -0,0 +1,2 @@ +export { ContactCalModalRoot, useContactCalModal } from './ContactCalModalRoot'; +export { TalkToUsButton } from './TalkToUsButton'; diff --git a/packages/twenty-website-new/src/app/layout.tsx b/packages/twenty-website-new/src/app/layout.tsx index 96b10b9897..41d1243edd 100644 --- a/packages/twenty-website-new/src/app/layout.tsx +++ b/packages/twenty-website-new/src/app/layout.tsx @@ -1,5 +1,6 @@ -import { FOOTER_DATA } from '@/app/_constants/footer'; import { FooterVisibilityGate } from '@/app/_components/FooterVisibilityGate'; +import { FOOTER_DATA } from '@/app/_constants/footer'; +import { ContactCalModalRoot } from '@/app/components/ContactCalModal'; import { fetchCommunityStats } from '@/lib/community/fetch-community-stats'; import { mergeSocialLinkLabels } from '@/lib/community/merge-social-link-labels'; import { Footer } from '@/sections/Footer/components'; @@ -85,17 +86,19 @@ export default async function RootLayout({ - {children} - - - - - - - + + {children} + + + + + + + + ); diff --git a/packages/twenty-website-new/src/app/partner/_constants/engagement-band.ts b/packages/twenty-website-new/src/app/partner/_constants/engagement-band.ts index bd683d2b08..35a3e599d5 100644 --- a/packages/twenty-website-new/src/app/partner/_constants/engagement-band.ts +++ b/packages/twenty-website-new/src/app/partner/_constants/engagement-band.ts @@ -2,7 +2,7 @@ import type { EngagementBandDataType } from '@/sections/EngagementBand/types'; export const ENGAGEMENT_BAND_DATA: EngagementBandDataType = { heading: { - text: 'Looking for a partners', + text: 'Looking for a partner?', fontFamily: 'serif', }, body: { diff --git a/packages/twenty-website-new/src/app/partner/_constants/partner-application-modal.ts b/packages/twenty-website-new/src/app/partner/_constants/partner-application-modal.ts new file mode 100644 index 0000000000..bbc5257037 --- /dev/null +++ b/packages/twenty-website-new/src/app/partner/_constants/partner-application-modal.ts @@ -0,0 +1,32 @@ +export type PartnerProgramId = 'technology' | 'content' | 'solutions'; + +export const PARTNER_PROGRAM_OPTIONS: ReadonlyArray<{ + id: PartnerProgramId; + label: string; +}> = [ + { id: 'technology', label: 'Technology Partner' }, + { id: 'content', label: 'Content & Community Partner' }, + { id: 'solutions', label: 'Solutions Partner' }, +]; + +export const PARTNER_APPLICATION_MODAL_COPY = { + titleSerif: 'Apply to build', + titleSans: 'the future of CRM', + subtitleLine1: + 'Join our ecosystem and help businesses take control of their customer data with', + subtitleLine2: 'open-source primitives.', + selectLabel: 'Select your team', + fields: { + name: 'Your name *', + email: 'Work email *', + company: 'Company or brand *', + website: 'Website or github link *', + opportunities: 'Estimated monthly opportunities (optional)', + messageLabel: 'How do you want to partner with Twenty? *', + messageHint: + 'Tell us about the custom solutions or integrations you plan to build.', + }, + footnote: + "Our partner team typically reviews applications within 48 hours. Once approved, you'll get access to our partner portal and developer resources.", + submit: 'Submit application', +} as const; diff --git a/packages/twenty-website-new/src/app/partner/_constants/testimonials.ts b/packages/twenty-website-new/src/app/partner/_constants/testimonials.ts index 984cbb4f11..41b16aab26 100644 --- a/packages/twenty-website-new/src/app/partner/_constants/testimonials.ts +++ b/packages/twenty-website-new/src/app/partner/_constants/testimonials.ts @@ -7,47 +7,32 @@ export const TESTIMONIALS_DATA: TestimonialsDataType = { testimonials: [ { heading: { - text: 'Our clients ask for a CRM they can trust and shape. Twenty gives us a credible stack to implement fast, without hiding the data model behind opaque APIs.', + text: "Twenty gives you the kind of flexibility that actually changes what you can offer your clients. The dev experience is clean, the APIs are open, and when something needs to be customized, you can just do it. There's no fighting the platform.", fontFamily: 'sans', }, author: { - avatar: { - src: '/images/partner/testimonials/avatar-four.png', - alt: '', - }, - name: { text: 'Elena Vasquez' }, - handle: { text: '@elena_integrations' }, - date: new Date('2024-04-18'), + name: { text: 'Benjamin Reynolds' }, + designation: { text: 'Principal and Founder, Alternative Partners' }, }, }, { heading: { - text: 'Partnering around an open codebase changed how we scope projects. We ship integrations with clear ownership and fewer surprises at go-live.', + text: "The flexibility is just amazing. Literally, there's nothing you cannot do. You can create objects, access everything through the API, pull notes and send them to the portal. Try doing that in HubSpot. No way. It's the true ability to build exactly what's actually needed.", fontFamily: 'sans', }, author: { - avatar: { - src: '/images/partner/testimonials/avatar-five.png', - alt: '', - }, - name: { text: 'Jordan Okonkwo' }, - handle: { text: '@jordan_solutions' }, - date: new Date('2024-06-27'), + name: { text: 'Bertrams' }, + designation: { text: 'Founder, Wintactix' }, }, }, { heading: { - text: 'We needed something modern for mid-market teams that outgrow spreadsheets but dread enterprise lock-in. Twenty hits that gap for the customers we serve.', + text: "Twenty Apps opens the door to building products, not just implementations. For example, we're developing a WhatsApp Business integration that any Twenty’s client could get. That's a recurring revenue stream we wouldn't have if we were just configuring someone else's platform.", fontFamily: 'sans', }, author: { - avatar: { - src: '/images/partner/testimonials/avatar-two.png', - alt: '', - }, - name: { text: 'Samira Haddad' }, - handle: { text: '@samira_revenue' }, - date: new Date('2024-08-14'), + name: { text: 'Mike Babiy' }, + designation: { text: 'Founder, Nine Dots Ventures' }, }, }, ], diff --git a/packages/twenty-website-new/src/app/partner/components/PartnerApplication/BecomePartnerButton.tsx b/packages/twenty-website-new/src/app/partner/components/PartnerApplication/BecomePartnerButton.tsx new file mode 100644 index 0000000000..a0c88c4153 --- /dev/null +++ b/packages/twenty-website-new/src/app/partner/components/PartnerApplication/BecomePartnerButton.tsx @@ -0,0 +1,35 @@ +'use client'; + +import { + BaseButton, + buttonBaseStyles, +} from '@/design-system/components/Button/BaseButton'; +import { styled } from '@linaria/react'; + +import { usePartnerApplicationModal } from './PartnerApplicationModalRoot'; + +const StyledTrigger = styled.button` + ${buttonBaseStyles} +`; + +type BecomePartnerButtonProps = { + color?: 'primary' | 'secondary'; + variant?: 'contained' | 'outlined'; +}; + +export function BecomePartnerButton({ + color = 'secondary', + variant = 'contained', +}: BecomePartnerButtonProps) { + const { openPartnerApplicationModal } = usePartnerApplicationModal(); + + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/app/partner/components/PartnerApplication/PartnerApplicationModal.tsx b/packages/twenty-website-new/src/app/partner/components/PartnerApplication/PartnerApplicationModal.tsx new file mode 100644 index 0000000000..d400c1cd5a --- /dev/null +++ b/packages/twenty-website-new/src/app/partner/components/PartnerApplication/PartnerApplicationModal.tsx @@ -0,0 +1,586 @@ +'use client'; + +import { + PARTNER_APPLICATION_MODAL_COPY, + PARTNER_PROGRAM_OPTIONS, + type PartnerProgramId, +} from '@/app/partner/_constants/partner-application-modal'; +import { buttonBaseStyles } from '@/design-system/components/Button/BaseButton'; +import { ButtonShape } from '@/design-system/components/Button/ButtonShape'; +import { theme } from '@/theme'; +import { styled } from '@linaria/react'; +import { IconChevronDown } from '@tabler/icons-react'; +import { useCallback, useEffect, useId, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; + +const PANEL_BG = '#0c0c0c'; + +const Overlay = styled.div` + align-items: center; + backdrop-filter: blur(4px); + background: rgba(28, 28, 28, 0.8); + box-sizing: border-box; + display: flex; + inset: 0; + justify-content: center; + padding-bottom: ${theme.spacing(4)}; + padding-left: ${theme.spacing(4)}; + padding-right: ${theme.spacing(4)}; + padding-top: ${theme.spacing(4)}; + position: fixed; + z-index: 300; +`; + +const Panel = styled.div` + background: ${PANEL_BG}; + border-radius: ${theme.radius(2)}; + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: clamp(8px, 1.5vh, 16px); + max-height: 100%; + max-width: 100%; + overflow-y: auto; + padding-bottom: clamp(16px, 4vh, 40px); + padding-left: ${theme.spacing(4)}; + padding-right: ${theme.spacing(4)}; + padding-top: clamp(12px, 2vh, 16px); + position: relative; + width: min(360px, 100%); + + @media (min-width: ${theme.breakpoints.md}px) { + padding-bottom: clamp(16px, 4vh, 40px); + padding-left: ${theme.spacing(6)}; + padding-right: ${theme.spacing(6)}; + padding-top: clamp(16px, 4vh, 40px); + width: min(902px, 100%); + } +`; + +const TitleBlock = styled.div` + display: flex; + flex-direction: column; + gap: clamp(8px, 2vh, 24px); +`; + +const Title = styled.h2` + color: ${theme.colors.secondary.text[100]}; + font-family: ${theme.font.family.serif}; + font-size: clamp(${theme.font.size(12)}, 8vw, ${theme.font.size(15)}); + font-weight: ${theme.font.weight.light}; + line-height: 1.12; + margin: 0; + + @media (min-width: ${theme.breakpoints.md}px) { + font-size: clamp(${theme.font.size(15)}, 4vw, ${theme.font.size(20)}); + line-height: 1.05; + } +`; + +const TitleAccent = styled.span` + display: block; + font-family: ${theme.font.family.sans}; + font-weight: ${theme.font.weight.light}; + letter-spacing: -0.04em; +`; + +const Subtitle = styled.div` + color: ${theme.colors.secondary.text[60]}; + font-family: ${theme.font.family.sans}; + font-size: ${theme.font.size(4)}; + font-weight: ${theme.font.weight.regular}; + line-height: ${theme.lineHeight(5.5)}; + + & p { + margin: 0; + } +`; + +const Segments = styled.div` + display: none; + gap: ${theme.spacing(4)}; + width: 100%; + + @media (min-width: ${theme.breakpoints.md}px) { + display: flex; + } +`; + +const SegmentButton = styled.button` + background: rgba(255, 255, 255, 0.1); + border: 1px solid ${theme.colors.secondary.border[10]}; + border-radius: ${theme.radius(2)}; + box-sizing: border-box; + color: ${theme.colors.secondary.text[100]}; + cursor: pointer; + flex: 1 1 0; + font-family: ${theme.font.family.sans}; + font-size: ${theme.font.size(3.5)}; + font-weight: ${theme.font.weight.regular}; + line-height: ${theme.lineHeight(3.5)}; + min-width: 0; + padding-bottom: ${theme.spacing(1.5)}; + padding-left: ${theme.spacing(2)}; + padding-right: ${theme.spacing(2)}; + padding-top: ${theme.spacing(1.5)}; + + &[data-active='true'] { + background: ${theme.colors.primary.background[100]}; + color: ${theme.colors.primary.text[100]}; + } + + &:focus-visible { + outline: 2px solid ${theme.colors.highlight[100]}; + outline-offset: 2px; + } +`; + +const MobileProgramField = styled.div` + display: flex; + flex-direction: column; + gap: ${theme.spacing(2)}; + width: 100%; + + @media (min-width: ${theme.breakpoints.md}px) { + display: none; + } +`; + +const DropdownRoot = styled.div` + position: relative; + width: 100%; +`; + +const DropdownTrigger = styled.button` + align-items: center; + background: ${theme.colors.secondary.background[100]}; + border: 1px solid ${theme.colors.highlight[100]}; + border-radius: ${theme.radius(2)}; + box-sizing: border-box; + color: ${theme.colors.secondary.text[100]}; + cursor: pointer; + display: flex; + font-family: ${theme.font.family.sans}; + height: clamp(40px, 5.5vh, 56px); + justify-content: space-between; + padding-left: ${theme.spacing(3)}; + padding-right: ${theme.spacing(1)}; + width: 100%; + + &:focus-visible { + outline: 2px solid ${theme.colors.highlight[100]}; + outline-offset: 2px; + } +`; + +const DropdownTriggerContent = styled.div` + display: flex; + flex-direction: column; + gap: 2px; +`; + +const DropdownLabel = styled.span` + color: ${theme.colors.secondary.text[40]}; + font-size: ${theme.font.size(2.5)}; + font-weight: ${theme.font.weight.regular}; + line-height: ${theme.lineHeight(4)}; + text-align: left; +`; + +const DropdownValue = styled.span` + font-size: ${theme.font.size(4)}; + font-weight: ${theme.font.weight.regular}; + line-height: ${theme.lineHeight(5.5)}; + text-align: left; +`; + +const DropdownIconContainer = styled.span` + align-items: center; + display: flex; + flex-shrink: 0; + height: 48px; + justify-content: center; + width: 48px; +`; + +const DropdownPanel = styled.div` + background: ${theme.colors.secondary.background[100]}; + border: 1px solid ${theme.colors.highlight[100]}; + border-radius: ${theme.radius(2)}; + box-shadow: 0 0 16px 0 rgba(15, 15, 15, 0.25); + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: ${theme.spacing(1)}; + left: 0; + margin-top: ${theme.spacing(2)}; + overflow: hidden; + padding: ${theme.spacing(1)}; + position: absolute; + right: 0; + z-index: 1; +`; + +const DropdownOption = styled.button` + align-items: center; + background: transparent; + border: none; + border-radius: ${theme.radius(1)}; + box-sizing: border-box; + color: ${theme.colors.secondary.text[100]}; + cursor: pointer; + display: flex; + font-family: ${theme.font.family.sans}; + font-size: ${theme.font.size(4)}; + font-weight: ${theme.font.weight.regular}; + line-height: ${theme.lineHeight(5.5)}; + padding-bottom: ${theme.spacing(3)}; + padding-left: ${theme.spacing(2)}; + padding-right: ${theme.spacing(4)}; + padding-top: ${theme.spacing(3)}; + text-align: left; + width: 100%; + + &[data-selected='true'] { + background: rgba(74, 56, 245, 0.3); + } + + &:hover { + background: rgba(74, 56, 245, 0.15); + } + + &[data-selected='true']:hover { + background: rgba(74, 56, 245, 0.3); + } + + &:focus-visible { + outline: 2px solid ${theme.colors.highlight[100]}; + outline-offset: -2px; + } +`; + +const FieldRow = styled.div` + display: flex; + flex-direction: column; + gap: clamp(8px, 1.5vh, 16px); + width: 100%; + + @media (min-width: ${theme.breakpoints.md}px) { + flex-direction: row; + gap: ${theme.spacing(6)}; + } +`; + +const TextInput = styled.input` + background: transparent; + border: 1px solid ${theme.colors.secondary.border[20]}; + border-radius: ${theme.radius(2)}; + box-sizing: border-box; + color: ${theme.colors.secondary.text[100]}; + font-family: ${theme.font.family.sans}; + font-size: ${theme.font.size(4)}; + font-weight: ${theme.font.weight.regular}; + height: clamp(40px, 5.5vh, 56px); + line-height: ${theme.lineHeight(5.5)}; + padding-bottom: ${theme.spacing(1)}; + padding-left: ${theme.spacing(3)}; + padding-right: ${theme.spacing(3)}; + padding-top: ${theme.spacing(1)}; + width: 100%; + + &::placeholder { + color: ${theme.colors.secondary.text[40]}; + } + + &:focus-visible { + border-color: ${theme.colors.highlight[100]}; + outline: none; + } +`; + +const TextArea = styled.textarea` + background: transparent; + border: 1px solid ${theme.colors.secondary.border[20]}; + border-radius: ${theme.radius(2)}; + box-sizing: border-box; + color: ${theme.colors.secondary.text[100]}; + font-family: ${theme.font.family.sans}; + font-size: ${theme.font.size(4)}; + font-weight: ${theme.font.weight.regular}; + line-height: ${theme.lineHeight(5.5)}; + min-height: clamp(80px, 18vh, 185px); + padding-bottom: ${theme.spacing(1)}; + padding-left: ${theme.spacing(3)}; + padding-right: ${theme.spacing(3)}; + padding-top: ${theme.spacing(1)}; + resize: vertical; + width: 100%; + + &::placeholder { + color: ${theme.colors.secondary.text[40]}; + } + + &:focus-visible { + border-color: ${theme.colors.highlight[100]}; + outline: none; + } +`; + +const Footnote = styled.p` + color: ${theme.colors.secondary.text[40]}; + font-family: ${theme.font.family.sans}; + font-size: ${theme.font.size(3)}; + font-weight: ${theme.font.weight.regular}; + line-height: ${theme.lineHeight(3.5)}; + margin: 0; +`; + +const SubmitButton = styled.button` + ${buttonBaseStyles} + position: relative; + width: 100%; +`; + +const SubmitLabel = styled.span` + color: ${theme.colors.primary.text[100]}; + font-family: ${theme.font.family.mono}; + font-size: ${theme.font.size(3)}; + font-weight: ${theme.font.weight.medium}; + position: relative; + text-transform: uppercase; + z-index: 1; +`; + +const FooterBlock = styled.div` + display: flex; + flex-direction: column; + gap: clamp(8px, 1.5vh, 16px); +`; + +const FormFields = styled.div` + display: flex; + flex-direction: column; + gap: clamp(8px, 1.5vh, 16px); +`; + +type PartnerApplicationModalProps = { + open: boolean; + onClose: () => void; +}; + +export function PartnerApplicationModal({ + open, + onClose, +}: PartnerApplicationModalProps) { + const titleId = useId(); + const panelRef = useRef(null); + const dropdownRef = useRef(null); + const [programId, setProgramId] = useState('technology'); + const [dropdownOpen, setDropdownOpen] = useState(false); + + const handleOverlayPointerDown = useCallback( + (event: React.PointerEvent) => { + if (event.target === event.currentTarget) { + onClose(); + } + }, + [onClose], + ); + + useEffect(() => { + if (!open) { + return; + } + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + onClose(); + } + }; + + const previousOverflow = document.body.style.overflow; + document.body.style.overflow = 'hidden'; + document.addEventListener('keydown', handleKeyDown); + + const frame = window.requestAnimationFrame(() => { + panelRef.current + ?.querySelector( + 'button, [href], input, textarea, [tabindex]:not([tabindex="-1"])', + ) + ?.focus(); + }); + + return () => { + document.body.style.overflow = previousOverflow; + document.removeEventListener('keydown', handleKeyDown); + window.cancelAnimationFrame(frame); + }; + }, [open, onClose]); + + const handleDropdownBlur = useCallback( + (event: React.FocusEvent) => { + if (!dropdownRef.current?.contains(event.relatedTarget as Node)) { + setDropdownOpen(false); + } + }, + [], + ); + + const handleSubmit = useCallback( + (event: React.FormEvent) => { + event.preventDefault(); + }, + [], + ); + + if (!open) { + return null; + } + + const copy = PARTNER_APPLICATION_MODAL_COPY; + + return createPortal( + + + + + {copy.titleSerif} + <TitleAccent>{copy.titleSans}</TitleAccent> + + +

{copy.subtitleLine1}

+

{copy.subtitleLine2}

+
+
+ +
+ + + {PARTNER_PROGRAM_OPTIONS.map((option) => ( + { + setProgramId(option.id); + }} + > + {option.label} + + ))} + + + + + { + setDropdownOpen((previous) => !previous); + }} + > + + {copy.selectLabel} + + {PARTNER_PROGRAM_OPTIONS.find( + (option) => option.id === programId, + )?.label} + + + + + + + + {dropdownOpen && ( + + {PARTNER_PROGRAM_OPTIONS.map((option) => ( + { + setProgramId(option.id); + setDropdownOpen(false); + }} + > + {option.label} + + ))} + + )} + + + + + + + + + + + + + + +