From 7e034f711f88c5a3ca2558b10a15b6b5bd6ce5b7 Mon Sep 17 00:00:00 2001 From: Rashad Karanouh <11599358+rashad@users.noreply.github.com> Date: Tue, 2 Jun 2026 14:32:54 +0400 Subject: [PATCH] feat(website): surface partner Categories (partnerScope) in marketplace, drop deploymentExpertise facet (#21127) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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. --- DESIGN.md | 159 ++++++++ PRODUCT.md | 80 ++++ packages/twenty-website/package.json | 1 + .../partners/list/MarketplaceClient.tsx | 12 +- .../list/__tests__/PartnerCard.test.tsx | 24 +- .../list/__tests__/filter-partners.test.ts | 48 ++- .../list/__tests__/filter-url-helpers.test.ts | 26 +- .../list/__tests__/use-filter-state.test.tsx | 2 +- .../list/components/ActiveFilterPills.tsx | 86 ++++ .../partners/list/components/FilterBar.tsx | 82 ++-- .../list/components/FilterChipRow.tsx | 98 ----- .../list/components/FilterDropdown.tsx | 160 ++++++++ .../list/components/MarketplaceGrid.tsx | 10 +- .../partners/list/components/PartnerCard.tsx | 88 ++++- .../list/components/PartnerChipRow.tsx | 26 +- .../list/components/PartnerMoneyRow.tsx | 64 +++ .../partners/list/components/chip-labels.ts | 46 ++- .../partners/list/components/index.ts | 5 +- .../[locale]/partners/list/filter-partners.ts | 10 +- .../partners/list/filter-url-helpers.ts | 8 +- .../src/app/[locale]/partners/list/page.tsx | 5 +- .../partners/list/use-filter-state.ts | 14 +- .../components/BackToMarketplaceLink.tsx | 32 ++ .../[slug]/components/PartnerFactsList.tsx | 125 ++++++ .../[slug]/components/PartnerProfileCtas.tsx | 79 ++++ .../components/PartnerProfileHeader.tsx | 71 ++++ .../[slug]/components/PartnerProfileIntro.tsx | 26 ++ .../[slug]/components/PartnerProfilePhoto.tsx | 67 ++++ .../[slug]/components/PartnerRatesPanel.tsx | 89 +++++ .../profile/[slug]/components/index.ts | 7 + .../[locale]/partners/profile/[slug]/page.tsx | 289 ++++++++++++++ .../lib/format/__tests__/format-usd.test.ts | 34 ++ .../src/lib/format/format-usd.ts | 23 ++ .../__tests__/get-partner-by-slug.test.ts | 75 ++++ .../__tests__/get-partners.test.ts | 106 +++++ .../lib/partners-api/get-partner-by-slug.ts | 85 ++++ .../src/lib/partners-api/get-partners.ts | 44 ++- .../src/lib/partners-api/index.ts | 6 +- .../src/lib/partners-api/partner-facets.ts | 50 ++- .../src/lib/partners-api/partner-types.ts | 12 +- yarn.lock | 371 +++++++++++++++++- 41 files changed, 2429 insertions(+), 216 deletions(-) create mode 100644 DESIGN.md create mode 100644 PRODUCT.md create mode 100644 packages/twenty-website/src/app/[locale]/partners/list/components/ActiveFilterPills.tsx delete mode 100644 packages/twenty-website/src/app/[locale]/partners/list/components/FilterChipRow.tsx create mode 100644 packages/twenty-website/src/app/[locale]/partners/list/components/FilterDropdown.tsx create mode 100644 packages/twenty-website/src/app/[locale]/partners/list/components/PartnerMoneyRow.tsx create mode 100644 packages/twenty-website/src/app/[locale]/partners/profile/[slug]/components/BackToMarketplaceLink.tsx create mode 100644 packages/twenty-website/src/app/[locale]/partners/profile/[slug]/components/PartnerFactsList.tsx create mode 100644 packages/twenty-website/src/app/[locale]/partners/profile/[slug]/components/PartnerProfileCtas.tsx create mode 100644 packages/twenty-website/src/app/[locale]/partners/profile/[slug]/components/PartnerProfileHeader.tsx create mode 100644 packages/twenty-website/src/app/[locale]/partners/profile/[slug]/components/PartnerProfileIntro.tsx create mode 100644 packages/twenty-website/src/app/[locale]/partners/profile/[slug]/components/PartnerProfilePhoto.tsx create mode 100644 packages/twenty-website/src/app/[locale]/partners/profile/[slug]/components/PartnerRatesPanel.tsx create mode 100644 packages/twenty-website/src/app/[locale]/partners/profile/[slug]/components/index.ts create mode 100644 packages/twenty-website/src/app/[locale]/partners/profile/[slug]/page.tsx create mode 100644 packages/twenty-website/src/lib/format/__tests__/format-usd.test.ts create mode 100644 packages/twenty-website/src/lib/format/format-usd.ts create mode 100644 packages/twenty-website/src/lib/partners-api/__tests__/get-partner-by-slug.test.ts create mode 100644 packages/twenty-website/src/lib/partners-api/__tests__/get-partners.test.ts create mode 100644 packages/twenty-website/src/lib/partners-api/get-partner-by-slug.ts diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 00000000000..9d3db7f5a37 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,159 @@ +# Twenty Website — DESIGN.md + +> Visual system for the Twenty marketing site. Distilled from `packages/twenty-website/src/theme/`. Loaded by every `impeccable` invocation alongside PRODUCT.md. + +## Theme + +**Light by default.** A founder browsing a partner profile in daylight on a 14–27 inch monitor is the default scene. The site does ship a `data-scheme="dark"` override (see `css-variables.ts`), but no current public page opts into it. Treat dark as a deferred surface. + +## Color + +Palette is OKLCH-equivalent neutrals at the surface level. The brand accents (blue, pink, yellow, green) are present in the token system but used sparingly — none of them appear on the partner pages. + +### Strategy: Restrained + +Tinted neutrals + one accent ≤10%. The accent for partner pages is the deep ink black (`var(--color-black-100)`) used in CTAs and hover states. Anything beyond a hairline border, an icon glyph, or a primary CTA should question whether it needs color at all. + +### Tokens (from `src/theme/colors.ts` + `css-variables.ts`) + +Neutrals (the workhorses): + +| Token | Hex (computed) | Role | +| --- | --- | --- | +| `colors.primary.background[100]` | `#ffffff` | Page + card surface | +| `colors.primary.text[100]` | `#1c1c1c` | Headlines, primary text | +| `colors.primary.text[80]` | `#1c1c1ccc` | Body text | +| `colors.primary.text[60]` | `#1c1c1c99` | Eyebrows, meta, captions | +| `colors.primary.text[40]` | `#1c1c1c66` | Disabled / placeholder | +| `colors.primary.text[20]` | `#1c1c1c33` | Subtle separators | +| `colors.primary.text[10]` | `#1c1c1c1a` | Hairline borders | +| `colors.primary.text[5]` | `#1c1c1c0d` | Subtle fills (rates panel, skill chips) | +| `colors.primary.border[10]` | `#1c1c1c1a` | Default border | +| `colors.primary.border[20]` | `#1c1c1c33` | Hover border | + +Reverse palette (for dark CTAs): + +| Token | Role | +| --- | --- | +| `colors.secondary.background[100]` | Filled CTA background (deep ink) | +| `colors.secondary.text[100]` | Filled CTA text (white) | + +Brand accents (currently absent from partner pages; available if needed): + +- `colors.accent.blue` — `#4a38f5` / `#8174f8` +- `colors.accent.pink` — `#ed87fc` / `#f3abfd` +- `colors.accent.yellow` — `#feffb7` / `#feffd9` +- `colors.accent.green` — `#89fc9a` / `#b0fdbe` +- `colors.highlight` — same hue as blue accent + +**Do not introduce gradients, glass blurs, or saturated fills on partner pages.** Color is conviction here, not decoration. + +## Typography + +Three families, each load-balanced via CSS variables: + +| Family | Var | Use | +| --- | --- | --- | +| `theme.font.family.serif` | `--font-serif` | Headlines, partner names, headline values | +| `theme.font.family.sans` | `--font-sans` | Body, prose, interactive labels | +| `theme.font.family.mono` | `--font-mono` | Eyebrows, meta, currency labels, tabular numerics | +| `theme.font.family.retro` | `--font-retro` | Reserved (not used on partner pages) | + +### Weight + Size Contrast + +Weights: `light: 300`, `regular: 400`, `medium: 500`. No bold. Hierarchy is driven by scale and family contrast, never by weight alone. + +Scale (`theme.font.size(n)` → `calc(var(--font-base) * n)`, where `--font-base: 0.25rem` ≈ 4px): + +- Display / h1: size 9–12 (36–48px) +- h2 / section heads: size 7–8 (28–32px) +- h3 / card heads: size 5–6 (20–24px) +- Body / prose: size 4–5 (16–20px) +- Eyebrow / meta: size 3 (12px) with `letter-spacing: 0.06–0.08em` and `text-transform: uppercase` + +Body line length: cap at 65–75ch (the existing `PartnerProfileIntro` uses `max-width: 62ch` — keep that order of magnitude). + +### Hierarchy contract + +- A serif `