Commit Graph

16 Commits

Author SHA1 Message Date
Abdullah. c05c34aaec [Website] Convert remaining images to WebP and compress some current ones. (#21404)
As title. Also deleted some unused assets that are no longer required.
2026-06-11 08:47:44 +00:00
Abdullah. 4db049db27 [Website] Product hero rework into a single, scroll-driven story. (#21105)
This reworks the /product hero into a single scroll-driven story. It
opens on a collaborative CRM intro — an autoplay cursor ("Alice") tours
real app-preview surfaces (Companies → an Anthropic record, clicking
through its tabs → People → Notes) — then, as you scroll, the light
intro morphs into a dark AI section via one continuous color wipe that
flows up through the (transparent) navigation menu. The AI section
presents four "build" pillars (pipeline board, follow-up tasks, sales
dashboard, email-sequence workflow), each playing an agentic preamble
(thinking + tool steps) and skeleton-loading state before revealing the
finished artifact as a finale. All preview surfaces — table, kanban,
record, dashboard charts, and workflow — are rendered for real and
matched to twenty-front's design (step-8 chart palette, default
opportunity stages, etc.) rather than static images.

Beyond the core morph, this PR layers in the coherence and polish work: 
- the intro halftone reuses the AI section's pattern tinted to the brand
blue and scoped to the visual;
- the workflow finale draws its edges/labels in as a staged build; 
- AI response copy is kept consistent with the visuals (matching stage
names and task counts);
- the ARR line chart fades in cleanly instead of flashing a partial
draw;
- the experience resets sensibly on scroll — the intro cursor tour
restarts when you scroll back to it, and the AI section returns to its
first tab once you're back at the top.

Here is the video showcasing the experience:


https://github.com/user-attachments/assets/c20708ed-4435-4f1a-a1c6-308b99305a97


Awaiting confirmation about the experience from @Bonapara before
merging.
2026-06-08 12:03:30 +00:00
Abdullah. b60a91a075 fix(website): render releases from local notes, drop GitHub gate (#21238)
The releases page gated visible notes on fetchLatestGithubReleaseTag();
when that unauthenticated GitHub call was rate-limited at build (common
on shared Cloudflare build IPs, no GITHUB_TOKEN), it returned null and
getVisibleReleaseNotes returned [], rendering 'No releases are visible
yet for the current published version' — the exact prod symptom.
force-static made each deploy a coin flip.

Committed MDX (+images) is the single source of truth, so gate
visibility on nothing: render all local notes deterministically with no
network call. Removes the dev/prod divergence and deletes the now-dead
fetch-latest-release-tag.ts and get-visible-releases.ts.
2026-06-05 08:59:40 +00:00
Abdullah. d3a1781a59 Use serialize-javascript for JSON-LD serialization on twenty-website (#21223)
Our scanner flags the `dangerouslySetInnerHTML` in `JsonLd.tsx` as a
potential XSS sink. Since JSON-LD must be emitted as raw `<script
type="application/ld+json">` text (rendering it as a React child
HTML-entity-escapes it and corrupts the JSON, and the site is statically
generated so it must be in the SSG HTML for crawlers),
`dangerouslySetInnerHTML` is the correct, Next.js-documented approach
(the real fix is sanitizing the payload). This PR swaps our hand-rolled
`JSON.stringify().replace(/</g, ...)` for
[`serialize-javascript`](https://www.npmjs.com/package/serialize-javascript)
in `isJSON` mode, the maintained library [Next.js explicitly
recommends](https://nextjs.org/docs/app/guides/json-ld) for this, so the
script-unsafe characters are escaped by a vetted serializer rather than
custom code.
2026-06-04 14:38:18 +00:00
Rashad Karanouh 5a55021e26 feat(website): add /partners/apply standalone page (#21219)
## Summary

- Adds a standalone `/partners/apply` page — a shareable URL that opens
the partner application wizard full-page (no modal, no nav, no footer),
on a plain black background
- Adds a `slots` prop to `PartnerApplicationWizard` so it can render
outside a `Dialog.Root` context (Base UI), keeping the existing modal on
`/partners` completely untouched
- Surfaces logic function errors to the user: the API route now checks
the webhook response body for `ok: true`, so a silent backend failure no
longer shows a false success state

## Test plan

- [ ] `yarn jest --no-coverage` — 365/365 passing
- [ ] `npx oxlint -c .oxlintrc.json .` — 0 errors (1 pre-existing
warning unrelated to this PR)
- [ ] `npx oxfmt --check .` — clean
- [ ] `npx tsc --noEmit` — clean
- [ ] Visit `/partners/apply` — wizard loads full-page on black
background, no menu, no footer
- [ ] Complete the wizard and submit — redirects to `/partners/list`
- [ ] Visit `/partners` — "Become a partner" modal still opens normally
2026-06-04 12:30:26 +00:00
Rashad Karanouh 7e034f711f feat(website): surface partner Categories (partnerScope) in marketplace, drop deploymentExpertise facet (#21127)
## What

Rebinds the marketplace's expertise facet from `deploymentExpertise`
(Cloud / Self-host) to **`partnerScope`** — the five partner Categories:
Advisory & Discovery · Solutioning · Custom Development · Hosting &
Infrastructure · Training & Adoption.

Moves the card chip, the profile facts row, the dropdown filter, the
`?categories=` URL param, and the API-boundary normalization onto
`partnerScope`. The standalone Cloud/Self-host facet is **dropped**
(hosting is now the `HOSTING` category), per the harmonization decision.

## Depends on

- The app exposing `partnerScope` — companion app PR #21126.
- The new `partnerScope` options + data migration — signup app PR
#21040.

## Tests

TDD red→green on: `filter-partners`, both API normalizers,
`filter-url-helpers`, `PartnerCard`, `use-filter-state`. 53/53 pass;
typecheck + lint + format clean.

## Merge order (we'll decide)

Independent diff. Suggested last of the four, after the signup PRs
(#21039 / #21040) and the app PR (#21126). Run `lingui:extract` once
after #21039 merges so the `.po` files don't conflict twice. Deploy the
app + migrate before the website ships.
2026-06-02 10:32:54 +00:00
Abdullah. b027e4bdb1 [Website] i18n module, page-local sections, translatable copy (#21082)
**i18n** — collapsed the ~22 scattered i18n files into a single module
and turned on Spanish alongside French.

**Sections** — dropped the old compound pattern (`Section.Root`,
`Section.Heading`, …). Reusable layout shells moved to `src/templates/`,
atomic bits stay in `design-system/`, and each page now owns its copy in
local `_components` blocks instead of pulling it out of shared sections.
Data files hold arrays only, no prose.

**Copy → `<Trans>`** — A lot of headings were split across several
`<HeadingPart>`s just for font styling, which meant each piece was a
separate translation string. A translator got "Build your Enterprise
CRM" and "at AI Speed" as two unrelated strings and had no way to
reorder them for their language. Those are now single `<Trans>` units
with placeholders. Same idea for the old `\n` + `white-space: pre-line`
line-break trick: replaced with a small `ResponsiveLineBreak` element so
the break is doesn't quietly rot, and did a dead-code pass.

The de-fragmentation changes the message IDs, so around 60 strings will
fall back to English in fr/es until Crowdin re-syncs.
2026-05-31 12:39:35 +00:00
Félix Malfait dde6df7a26 refactor(website): replace axios with native fetch (#20967)
## Summary

Follow-up to #20966 (Stripe fetch client). axios's default Node http
adapter on workerd has the same TLS hang the Stripe SDK had — it just
doesn't surface today because all three call sites are wrapped in
\`unstable_cache(revalidate: 3600)\` and the cache is populated at build
time, so misses are rare and the failure mode is a silent \`null\` to
the layout.

This swaps the three remaining axios calls in \`twenty-website\` for
native \`fetch\` and removes axios from
\`packages/twenty-website/package.json\`. The package is still used by
other workspaces, so yarn.lock keeps the other resolution.

Touched call sites:
- \`src/lib/releases/fetch-latest-release-tag.ts\` (GitHub releases —
runs at build time, cosmetic)
- \`src/lib/community/fetch-github-star-count.ts\` (GitHub star count in
menu)
- \`src/lib/community/fetch-discord-member-count.ts\` (Discord member
count in menu)

## Test plan

- [ ] After merge + deploy: confirm GitHub star + Discord member counts
render in the site menu (non-zero, formatted)
- [ ] Confirm \`/releases\` shows the latest tag-gated visible release
notes
- [ ] No \`axios\` in worker bundle (\`grep axios .open-next/worker.js\`
should be empty)
2026-05-27 16:31:39 +02:00
Félix Malfait e79f43813b fix(website): use Stripe fetch client on Cloudflare Workers (#20966)
## Summary

Every `/api/enterprise/*` route on `twenty-website-prod` is currently
timing out at exactly 80s with `Request aborted due to timeout being
reached (80000ms)` — that's `Stripe.DEFAULT_TIMEOUT` aborting itself,
not Cloudflare.

The Stripe SDK's default transport uses Node's `http`/`https` module.
Under workerd, even with `nodejs_compat`, the outbound TLS connection to
`api.stripe.com` hangs and the SDK eventually times out at its built-in
80s ceiling. Worked on EKS (real Node), breaks on Cloudflare Workers.

Fix is one line: pass `httpClient: Stripe.createFetchHttpClient()` so
the SDK uses workerd's native `fetch` instead of the polyfilled Node
transport.

## Blast radius

`getStripeClient()` is shared across every enterprise route. All of
these are silently broken on prod right now:

- `POST /api/enterprise/checkout` (confirmed in logs)
- `POST /api/enterprise/portal`
- `POST /api/enterprise/seats`
- `GET  /api/enterprise/status`
- `POST /api/enterprise/activate`
- `POST /api/enterprise/validate`

Single change in `stripe-client.ts` unblocks all of them.

## Test plan

- [ ] After merge + deploy to prod: `wrangler tail twenty-website-prod`
while hitting the enterprise checkout flow; same call should complete in
<2s instead of 80s
- [ ] Verify a real Stripe checkout session is created (Stripe dashboard
→ Payments → Checkout sessions)
- [ ] Spot-check `/api/enterprise/status` and `/api/enterprise/portal`
are no longer timing out
2026-05-27 16:03:48 +02:00
Etienne fc3004b2f6 fix(website) - fix getPartners (#20947)
**Context**
Fix empty marketplace at /partners/list: page was being statically
prerendered with getPartners() returning [] (build-time fetch failure),
then served from OpenNext's R2 cache forever — so the partners API was
never actually called in production.

**Change**
Wrap getPartners in unstable_cache with revalidate: 300, matching the
existing fetchGithubStarCount / fetchDiscordMemberCount ISR pattern.
After deploy, the cached empty result expires within 5 minutes and the
worker refetches from the partners API at runtime (where the env vars
actually exist), populating the page. Also drops the now-redundant
cache: 'no-store' from partnersApiFetch.
2026-05-27 10:53:17 +00:00
Abdullah. 53d4e92dda [Website] Generate release notes manifest at build time (#20913)
Removed the releases page’s runtime dependency on `fs` and
`process.cwd()` by introducing a build-time manifest generator: release
notes still live as markdown under `src/content/releases`, but a new
script now parses their frontmatter/content, validates that each note
has a release, title, and preview image (and that the image actually
exists), sorts the notes, and emits a typed `generated-release-notes.ts`
file that the app imports at runtime.

Updated the releases loader to return that generated data, changed the
menu releases preview and release JSON-LD to use explicit typed fields
(`title`, `previewImage`) instead of scraping markdown with regex at
runtime, wired the generator into Nx so it runs automatically before
`dev`, `build`, and `typecheck`, and fixed two stale image references in
the release MDX files that the new validation exposed.

---------

Co-authored-by: prastoin <paul@twenty.com>
2026-05-26 16:46:11 +02:00
Rashad Karanouh 07a20cba5e [Website] Partners directory (#20632)
## What this PR does

Adds the **Partners Marketplace** page to the Twenty marketing website
(`/partners-marketplace`), built with Next.js App Router. The page
fetches live partner data from the Twenty API and presents it in a
responsive grid with an interactive filter bar.

## Partners grid

- Fetches partners from the `/s/partners` endpoint via a typed
`getPartners()` server-side fetcher
- Responsive 1 → 2 → 3 column grid (mobile / tablet / desktop)
- Each card shows name, region eyebrow, intro text, chip rows (Regions /
Languages / Deploys), and a Calendly CTA
- Stagger entrance animation (700ms cascade, respects
`prefers-reduced-motion`)

## Filter bar

- Three facets: **Region**, **Language**, **Deployment** — multi-select
chips
- **Selection model:** OR within a facet, AND across facets (e.g.
`Europe OR US` AND `French`)
- Filter state lives in URL search params
(`?regions=EUROPE,US&languages=FRENCH`) — filtered views are shareable
and browser-back works correctly
- Client-side filtering — no server round-trip per interaction
- Result count ("Showing 3 of 8 partners") updates live with
`aria-live="polite"`
- "Clear filters" button resets all facets in one URL update, only shown
when filters are active
- Empty state ("No partners match your filters") replaces the grid when
nothing matches
- 200ms opacity fade-out on card removal; initial stagger animation
preserved on first load
- `prefers-reduced-motion: reduce` disables all transitions

## Architecture

- `page.tsx` stays a **Server Component** — fetches partners
server-side, all partner HTML is in the initial response for SEO
- `<MarketplaceClient>` is the client boundary — owns filter state via
`useFilterState()` (backed by `useSearchParams`)
- Canonical URL set in page metadata so `?regions=...` deep-links don't
get indexed as duplicates
- `<Suspense>` wrapper around `MarketplaceClient` for Next.js 15
`useSearchParams` compliance
- No new npm dependencies

## Test coverage

31 tests across three suites:
- `filter-partners.test.ts` — pure filter logic (OR / AND semantics,
empty results)
- `filter-url-helpers.test.ts` — URL param encode / decode / toggle /
round-trip
- `use-filter-state.test.tsx` — hook behaviour with mocked
`next/navigation`

## Screenshot

<img width="1783" height="1196" alt="Screenshot 2026-05-17 at 15 01 54"
src="https://github.com/user-attachments/assets/9dddf827-f440-4cad-8ec3-81ede6d46434"
/>


## Test plan

- [ ] Navigate to `/partners-marketplace` — all live partners render
- [ ] Click a Region chip — URL updates with `?regions=...`, cards
filter, count updates
- [ ] Click the same chip again — selection removed, all cards return
- [ ] Select chips from two different facets — AND behaviour narrows
results correctly
- [ ] Trigger empty state (e.g. filter to a region with no partners) —
empty state shown with "Clear filters" button
- [ ] Click "Clear filters" — all cards return, URL cleared
- [ ] Deep-link to `?regions=EUROPE&languages=FRENCH` — page loads with
filters applied
- [ ] Browser back button restores previous filter state

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-21 19:13:14 +00:00
Abdullah. 8826d12a18 [Website] Host customer story hero images locally and fix multi-segment redirects (#20790)
Customer story page shows the following error, which I believe leads to
an internal server on the individual customer story pages.

<img width="636" height="75" alt="image"
src="https://github.com/user-attachments/assets/fc9ede75-fd3b-4538-8211-8182f4a99b9b"
/>

This PR replaces remote URLs of those images with local copies to avoid
a 404 issue. Will test once deployed on dev to confirm if the error is
resolved, but locally, I do not see console errors any longer after this
change.

There is some duplicated copy that I found upon audit which can be made
DRY, but I will resolve it in a separate PR to keep this PR
single-responsibility.
2026-05-21 14:10:24 +00:00
Félix Malfait 658bdf3e57 chore(website): rename twenty-website-new → twenty-website (#20745)
## Summary
Follow-up to the Cloudflare/OpenNext migration (#20741). Now that the
legacy `twenty-website` package was already removed in #20270, the
`-new` suffix on the marketing site package is no longer meaningful.

## What changes
- **Directory rename**: `git mv packages/twenty-website-new
packages/twenty-website` (1213 files moved, no content change)
- **Package + nx config**: `package.json` and `project.json` name fields
updated, `sourceRoot` repointed
- **Source refs**: `load-local-articles.ts` and
`load-local-release-notes.ts` had a hardcoded `'twenty-website-new'`
segment in their monorepo-root fallback path;
`app/[locale]/releases/page.tsx` had display strings showing where to
add content
- **External refs**: root `package.json` workspaces, root `CLAUDE.md` /
`README.md`, `twenty-sdk` + `create-twenty-app` READMEs,
`.vscode/twenty.code-workspace`, `.cursor/rules/changelog-process.mdc`,
Crowdin config + the three `website-i18n-*` CI workflows +
`ci-website.yaml`
- **Docker cleanup**:
`packages/twenty-docker/twenty-website-new/Dockerfile` deleted; the two
Makefile targets (`prod-website-new-build` / `prod-website-new-run`)
that referenced it removed — EKS deploy was retired in the Cloudflare
migration
- **`yarn.lock`** regenerated against the new workspace path

## What's deliberately not in this PR
The dev hostname `website-new.twenty-main.com` in `wrangler.jsonc` stays
for now. Migrating it to `website.twenty-main.com` needs coordinated DNS
deletion (current CNAME points at the legacy Docusaurus NLB and serves
503s) and removal of the matching legacy `website` Helm chart in
`twenty-infra`. Flagged as a separate cleanup.

Companion infra PR: https://github.com/twentyhq/twenty-infra/pull/682
(workflow paths + Terraform ECR + docs)

## Test plan
- [x] `yarn install --immutable` resolves clean against the new path
- [x] `npx nx typecheck twenty-website` passes
- [x] `npx nx lint twenty-website` passes
- [ ] CI on this PR confirms the same on a fresh checkout
- [ ] After merge: trigger `Deploy Website` workflow against
`environment=dev` to confirm the renamed working-directory deploys
correctly
2026-05-19 23:42:09 +02:00
Félix Malfait 5de1c2c31d New folder structure for website (#4159)
New folder structure
2024-02-23 17:42:13 +01:00
Deepak Kumar 8dda4b0b8f GH 3365 Add contributors page on twenty-website (#3745)
* add transition in mobile navbar

* add contributors listing page

* Add breadcrumb component

* Add profilecard component

* Make profile info dynamic

* Style activity log component

* Make title a re-usable component

* Make card container re-usable

* add rank and active days logic

* complete single contributor page

* add styles for mobile

* Add github link

* Reset header desktop

* update calendar height

* remove conditional header

* add GH PR link

* display 10 prs

* Remove employees and fix rank

* Unrelated CSS adjustment

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2024-02-05 13:56:12 +01:00