From dbf43d792c783119cf3779b148d1b232ff81905d Mon Sep 17 00:00:00 2001 From: "Abdullah." <125115953+mabdullahabaid@users.noreply.github.com> Date: Sun, 19 Apr 2026 14:12:59 +0500 Subject: [PATCH] [Website] Fix testimonials shape, diamond direction, and integrate partner application form. (#19835) Closes the following issues. https://github.com/twentyhq/core-team-issues/issues/2368 https://github.com/twentyhq/core-team-issues/issues/2369 https://github.com/twentyhq/core-team-issues/issues/2374 https://github.com/twentyhq/core-team-issues/issues/2375 --- packages/twenty-website-new/.env.example | 1 + packages/twenty-website-new/.gitignore | 1 + packages/twenty-website-new/package.json | 3 +- .../home/testimonials/background-shape.webp | Bin 41078 -> 0 bytes .../testimonials/background-shape.webp | Bin 23538 -> 0 bytes .../src/app/(home)/page.tsx | 1 - .../src/app/api/partner-application/route.ts | 79 +++++++ .../_constants/partner-application-modal.ts | 9 +- .../PartnerApplicationModal.tsx | 213 ++++++++++++------ .../src/app/partner/page.tsx | 2 +- .../components/Button/BaseButton.tsx | 5 +- .../ThreeCards/PartnerThreeCard.tsx | 29 ++- .../__tests__/split-full-name.test.ts | 43 ++++ .../partner-application/split-full-name.ts | 13 ++ .../PlanTable/components/Content/Content.tsx | 7 +- .../Testimonials/TestimonialsShape.tsx | 45 ++++ .../Testimonials/components/Root/Root.tsx | 22 +- yarn.lock | 1 + 18 files changed, 375 insertions(+), 99 deletions(-) create mode 100644 packages/twenty-website-new/.env.example delete mode 100644 packages/twenty-website-new/public/images/home/testimonials/background-shape.webp delete mode 100644 packages/twenty-website-new/public/images/partner/testimonials/background-shape.webp create mode 100644 packages/twenty-website-new/src/app/api/partner-application/route.ts create mode 100644 packages/twenty-website-new/src/lib/partner-application/__tests__/split-full-name.test.ts create mode 100644 packages/twenty-website-new/src/lib/partner-application/split-full-name.ts create mode 100644 packages/twenty-website-new/src/sections/Testimonials/TestimonialsShape.tsx diff --git a/packages/twenty-website-new/.env.example b/packages/twenty-website-new/.env.example new file mode 100644 index 0000000000..d27ad4304f --- /dev/null +++ b/packages/twenty-website-new/.env.example @@ -0,0 +1 @@ +PARTNER_APPLICATION_WEBHOOK_URL= diff --git a/packages/twenty-website-new/.gitignore b/packages/twenty-website-new/.gitignore index 301c67f0cd..b23d143603 100644 --- a/packages/twenty-website-new/.gitignore +++ b/packages/twenty-website-new/.gitignore @@ -26,6 +26,7 @@ yarn-debug.log* yarn-error.log* # local env files +.env .env*.local # vercel diff --git a/packages/twenty-website-new/package.json b/packages/twenty-website-new/package.json index 8e5ddc718a..67b684a8b5 100644 --- a/packages/twenty-website-new/package.json +++ b/packages/twenty-website-new/package.json @@ -29,7 +29,8 @@ "react-dom": "19.2.3", "react-markdown": "^10.1.0", "remark-gfm": "^4.0.1", - "three": "^0.183.2" + "three": "^0.183.2", + "zod": "^4.1.11" }, "devDependencies": { "@types/node": "^20", diff --git a/packages/twenty-website-new/public/images/home/testimonials/background-shape.webp b/packages/twenty-website-new/public/images/home/testimonials/background-shape.webp deleted file mode 100644 index 0e85b2351e381fbfbeea3fbcef51ae6ad87118be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41078 zcmeI52~ZPP9EUeW0Wl0~_Ugm7-u0Rlk^6*!bDxsuEEOd0XTR4p%NDPw=0@KG|2FRSE>FQZn zowm)$mWe;zC7+_IBfrouR-0<6T$5R;Ex*nA$>xOH4xRm+_(_C9g)oY$!ij1rX}@%~ zwQ%qJp2~Fz2Ex)!bB-UoP}{rNTVUmL@5k0oi#jYn;d~L(z1U5`K4fd3 zT+18&{;&XeX?o@-Fu2CR}9j>ZTm9 zW2n=9=%`WVWbjuT_m(SG*YQJn$vn$qiVNS&p-nrSQqB*WwUwbjs|;X~ce*oWcLXPj zRW9@Uc$cSKwGHti?>`x|<2=W+r8hiYGkh|w+RUbV_*T0kJhEqWU)#4hOFxHZKHa6p zP}9tIvzPnw%6N@%3hmN)19GypXW;&m4N30IyL(rut))dycd0eAX{utAO~l1L%mKYM z-xlrR@(PV^KE8NnS_1#8{5oAZ+# zc>V-VXN6xu@)FvBp6rQ;m8L4;lQ%U6`C2j5uQeJb%I*qQH)%4niLR>Sw-n^+-{+be zZ*g*~EB~oNRM^; z5UAaVoF>rpG-z3F?(Z2USaqPUe~yc5nB~&zwS(`@K5jhJD~ys1YDvVRQpu3?Z>&FW z_w2XPd!Tica-ujW&ZzSZ0|WyE14R0Ig!zH_f%$>?F&eWXBajiu2xJ5@0!fG@L=qwi zk%UOXe>4e~>Jh`&RpEs z>5%eq);T%4Sq_t2V!y?wb0EK$4f+LZ9}p`>+qdM0BwyEA1{wVa8^ zvh4J-*Z(nN?&7&pgq5vvl{NA>M@waGT=LF1t2O=uRLVvN#-;k2PdXSYWZhV)2I8sBqn2e3t}Yyt0R?p=!?_Gyo6< zhyp|bq5x5VC_oe-3RWEZftFwZicj8j6^aZ+h9X0ep~z5VC^8foiu~_ZWW{D;Nb+4M zLE;T$YZC;iqbyeYD6Kr)`iEjSp$j1!m$^sc}rV1(#QmL>ac+uNLb?m74L zyXXHuzuTVP1O9$~<2-~yeZ4p#9G5UMLWrh&4b#w&3Gxi&?9-ScWU#|2ZouXzYO|#{ zV)I8$;+R`bUV@kWi=5}V&ZpinT>Sy{p#_v=J6DV?>Rg7HWLy$oqj`r8WX`TEE$YWCQ6g#k`n z3aaEj)5!Hm=we67sYsVCxW>A^Pc<)LcVfj8pY?zv&9c**2gGd?c}zficV zfLX-){m^8Xzo1yNb(C*U3vy{qmScH7tmYr?aa(ROm+ z;cUTP8Fu#CdoH@_%A~4_{BQCcvF<|tM4Y6mjn$>m~=~yyeBMjf2Du4Jx_7MIkGFBJdE33d%3PpE|7#qhXA!cmJ(@0{!I`W6pfQD;Q=jo5;1 zT|O!#Aug-?4@3i^0nV_>P(ctN2oMAa0ze6z5S;K4CzR22*S%YR2}G!fW>QMByGhfS z>}6qD-W3(blyel4S2(XJf)H;}GQ`i@P?ej8oDU&NP5{v({e-A>uqQ&oL$!5{vIVX% z^1+u~Ez?J;MH;={MCDb5_E1jk${T9^ME zPoAih>+fIrC{NVvRaGnn;w^E8Q7qY*8idY_c0M*j>sGtdL7Y~GNCVH@2HnYM)5l$- zIC~s26We3|yHBN@-r#8abWvLFX6ptKvNsSXL=F1dDzAp_0Nnw)g9u~*G5{HX3?6?$ z1MUFs0PX7$jkkgh3Jp zNf;!bes0jClWZQ&HUhK&v;edKv;edKv;edKv;efwO~D2z0w@9~0vP}l0TjV#08j)_ z1W>fz-}QhMffa!jffa!jffa!jffa!jffa!j*WS_s7#SED7#SED82MjeeVfJ-m?kKcS^+wk$SsSXgms$4$c^7 i5$35Lt-6hi3r!St_Y859uCGuorC|jTfC&6wBk(6paC6N7 diff --git a/packages/twenty-website-new/src/app/(home)/page.tsx b/packages/twenty-website-new/src/app/(home)/page.tsx index 829b77b352..e62baaf2a0 100644 --- a/packages/twenty-website-new/src/app/(home)/page.tsx +++ b/packages/twenty-website-new/src/app/(home)/page.tsx @@ -210,7 +210,6 @@ export default async function HomePage() { (null); + const formRef = useRef(null); const dropdownRef = useRef(null); const [programId, setProgramId] = useState('technology'); const [dropdownOpen, setDropdownOpen] = useState(false); + const [submitError, setSubmitError] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); const handleOverlayPointerDown = useCallback( (event: React.PointerEvent) => { @@ -387,6 +369,19 @@ export function PartnerApplicationModal({ [onClose], ); + useEffect(() => { + if (open) { + setSubmitError(null); + return; + } + + formRef.current?.reset(); + setProgramId('technology'); + setDropdownOpen(false); + setSubmitError(null); + setIsSubmitting(false); + }, [open]); + useEffect(() => { if (!open) { return; @@ -427,10 +422,67 @@ export function PartnerApplicationModal({ ); const handleSubmit = useCallback( - (event: React.FormEvent) => { + async (event: React.FormEvent) => { event.preventDefault(); + + if (isSubmitting) { + return; + } + + const formData = new FormData(event.currentTarget); + + const nameValue = formData.get('name'); + const emailValue = formData.get('email'); + const companyValue = formData.get('company'); + const websiteValue = formData.get('website'); + const messageValue = formData.get('message'); + + const name = typeof nameValue === 'string' ? nameValue.trim() : ''; + const email = typeof emailValue === 'string' ? emailValue.trim() : ''; + const company = + typeof companyValue === 'string' ? companyValue.trim() : ''; + const website = + typeof websiteValue === 'string' ? websiteValue.trim() : ''; + const message = + typeof messageValue === 'string' ? messageValue.trim() : ''; + + const validationCopy = PARTNER_APPLICATION_MODAL_COPY.validation; + const emailLooksValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); + + setSubmitError(null); + + if (!name || !email || !company || !website || !message) { + setSubmitError(validationCopy.incompleteForm); + return; + } + + if (!emailLooksValid) { + setSubmitError(validationCopy.invalidEmail); + return; + } + + setIsSubmitting(true); + + try { + const response = await fetch('/api/partner-application', { + body: JSON.stringify({ email, name }), + headers: { 'Content-Type': 'application/json' }, + method: 'POST', + }); + + if (!response.ok) { + setSubmitError(validationCopy.submitFailed); + return; + } + + onClose(); + } catch { + setSubmitError(validationCopy.submitFailed); + } finally { + setIsSubmitting(false); + } }, - [], + [isSubmitting, onClose], ); if (!open) { @@ -448,17 +500,34 @@ export function PartnerApplicationModal({ role="dialog" > - - {copy.titleSerif} - <TitleAccent>{copy.titleSans}</TitleAccent> - - -

{copy.subtitleLine1}

-

{copy.subtitleLine2}

-
+ + + + + + +
-
+ {PARTNER_PROGRAM_OPTIONS.map((option) => ( @@ -524,57 +593,69 @@ export function PartnerApplicationModal({