Files
twenty/packages/twenty-website/.env.example
T
Rashad KaranouhandGitHub 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

43 lines
1.8 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
PARTNER_APPLICATION_WEBHOOK_URL=
# Optional GitHub personal-access token (no scopes required) used by the
# community-stats fetcher (`src/lib/community/fetch-github-star-count.ts`).
# When set, requests use the 5000/hr authenticated limit instead of the
# 60/hr unauthenticated per-IP limit. Both responses are also cached for
# one hour via `unstable_cache`, so dev without a token still works.
# GITHUB_TOKEN=
# Public site URL — canonical origin used by `metadataBase`, sitemap, robots,
# OG/Twitter card URLs, Stripe checkout success URL, and billing portal return.
# No trailing slash. Defaults to https://twenty.com when unset.
NEXT_PUBLIC_WEBSITE_URL=
# --- Visual runtime kill switches ---------------------------------------
# Hard kill switch for every WebGL/Three/R3F decorative visual on the site.
# Set to "1" / "true" to ship a build with all heavy visuals statically
# replaced by their fallbacks (e.g. during a GPU-driver-related incident).
# NEXT_PUBLIC_DISABLE_HEAVY_VISUALS=
# Soft cap on the number of concurrent WebGL contexts the page is allowed
# to spin up. The browser's own hard cap is typically 816; we default to 8
# so a single page that mounts every illustration cannot exhaust the GPU
# context pool on integrated graphics.
# NEXT_PUBLIC_MAX_WEBGL_CONTEXTS=8
# Stripe — self-hosted enterprise checkout & subscription APIs
STRIPE_SECRET_KEY=
STRIPE_ENTERPRISE_MONTHLY_PRICE_ID=
STRIPE_ENTERPRISE_YEARLY_PRICE_ID=
# RS256 key pair used to sign enterprise license JWTs (use literal \n in PEM for env)
ENTERPRISE_JWT_PRIVATE_KEY=
ENTERPRISE_JWT_PUBLIC_KEY=
# Optional: short-lived validity token length in days (default 30)
# ENTERPRISE_VALIDITY_TOKEN_DURATION_DAYS=
# Twenty workspace the partners marketplace reads partner data from
# (server-side only) via the /s/partners REST endpoint.
TWENTY_PARTNERS_API_URL=
TWENTY_PARTNERS_API_KEY=