feat(website): show partner location instead of served regions in marketplace (#22224)

## Context

On the partners marketplace, the eyebrow shown below each partner's name
(on both the list card and the profile header) displayed the served
regions (e.g. `APAC`, `EUROPE`). The partner's actual location was
buried in the "Where & how" facts list. The location is more useful at a
glance, so this swaps the two.

## Changes

**List card (`PartnerCard`)**
- The eyebrow below the name now shows the partner's city and country
instead of the first served region.

**Profile page**
- `PartnerProfileHeader`: the eyebrow near the title now shows the real
location (city, country).
- `PartnerFactsList`: the served regions move into the "Where & how"
section as a `Regions` row, taking the place of the now-redundant "Based
in" row.

The `Regions` chip rows on the card are unchanged.

https://claude.ai/code/session_01FQRRfvPpmNsjAAnZeDc5sy

---
_Generated by [Claude
Code](https://claude.ai/code/session_01FQRRfvPpmNsjAAnZeDc5sy)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22224?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Félix Malfait
2026-06-26 16:55:49 +02:00
committed by GitHub
parent 1b4bddba6f
commit 7caf2a14c5
4 changed files with 23 additions and 29 deletions
@@ -29,6 +29,7 @@ import { PartnerMoneyRow } from './PartnerMoneyRow';
import { PARTNER_SCOPE_LABELS } from './partner-scope-labels';
import { SERVED_GEO_LABELS } from './served-geo-labels';
import { SPOKEN_LANGUAGE_LABELS } from './spoken-language-labels';
import { titleCaseFallback } from './title-case-fallback';
type PartnerCardStyle = CSSProperties & {
'--partner-card-index': number;
@@ -146,7 +147,7 @@ const LinkedinIconLink = styled(ExternalLink)`
}
`;
const CountryEyebrow = styled.span`
const LocationEyebrow = styled.span`
color: ${semanticColor.inkMuted};
font-family: ${fontFamily('mono')};
font-size: ${fontSize(3)};
@@ -210,10 +211,12 @@ export function PartnerCard({ partner, index }: PartnerCardProps) {
// Unprefixed; LocalizedLink (the name link) and the Button add the locale.
const profileHref = `/partners/profile/${partner.slug}`;
const firstGeo = partner.region[0];
const countryLine = firstGeo
? i18n._(SERVED_GEO_LABELS[firstGeo]).toUpperCase()
: '';
const locationLine = [
partner.city,
partner.country ? titleCaseFallback(partner.country) : '',
]
.filter(Boolean)
.join(', ');
const linkedinSafe = isSafeHttpUrl(partner.linkedinUrl);
@@ -239,7 +242,7 @@ export function PartnerCard({ partner, index }: PartnerCardProps) {
</LinkedinIconLink>
)}
</NameRow>
<CountryEyebrow>{countryLine}</CountryEyebrow>
<LocationEyebrow>{locationLine}</LocationEyebrow>
</HeaderText>
</CardHeader>
@@ -9,6 +9,7 @@ import { color, fontFamily, fontSize, semanticColor, spacing } from '@/tokens';
import { type MarketplacePartner } from './marketplace-partner';
import { PARTNER_SCOPE_LABELS } from './partner-scope-labels';
import { SERVED_GEO_LABELS } from './served-geo-labels';
import { SPOKEN_LANGUAGE_LABELS } from './spoken-language-labels';
import { titleCaseFallback } from './title-case-fallback';
@@ -57,23 +58,18 @@ function resolveLabels<TValue extends string>(
}
export function PartnerFactsList({
city,
country,
region,
languagesSpoken,
partnerScope,
}: {
city: MarketplacePartner['city'];
country: MarketplacePartner['country'];
region: MarketplacePartner['region'];
languagesSpoken: MarketplacePartner['languagesSpoken'];
partnerScope: MarketplacePartner['partnerScope'];
}) {
const { i18n } = useLingui();
const translate = (descriptor: MessageDescriptor) => i18n._(descriptor);
// Country is a CRM SELECT enum (e.g. "UNITED_STATES"); title-case it.
const locationText = [city, country ? titleCaseFallback(country) : '']
.filter(Boolean)
.join(', ');
const regionText = resolveLabels(region, SERVED_GEO_LABELS, translate);
const languageText = resolveLabels(
languagesSpoken,
SPOKEN_LANGUAGE_LABELS,
@@ -87,10 +83,10 @@ export function PartnerFactsList({
return (
<FactsDl>
{locationText && (
{regionText && (
<FactRow>
<FactLabel>{i18n._(msg`Based in`)}</FactLabel>
<FactValue>{locationText}</FactValue>
<FactLabel>{i18n._(msg`Regions`)}</FactLabel>
<FactValue>{regionText}</FactValue>
</FactRow>
)}
{languageText && (
@@ -173,8 +173,7 @@ export function PartnerProfile({ partner }: { partner: MarketplacePartner }) {
<Block>
<ProfileEyebrow>{i18n._(msg`Where & how`)}</ProfileEyebrow>
<PartnerFactsList
city={partner.city}
country={partner.country}
region={partner.region}
languagesSpoken={partner.languagesSpoken}
partnerScope={partner.partnerScope}
/>
@@ -1,6 +1,3 @@
'use client';
import { useLingui } from '@lingui/react';
import { styled } from '@linaria/react';
import {
@@ -15,7 +12,7 @@ import {
import { type MarketplacePartner } from './marketplace-partner';
import { ProfileEyebrow } from './ProfileEyebrow';
import { SERVED_GEO_LABELS } from './served-geo-labels';
import { titleCaseFallback } from './title-case-fallback';
const Wrapper = styled.div`
display: flex;
@@ -56,13 +53,12 @@ export function PartnerProfileHeader({
}: {
partner: MarketplacePartner;
}) {
const { i18n } = useLingui();
// Served regions are the partner's market coverage, not their address.
const eyebrow = partner.region
.map((geo) => i18n._(SERVED_GEO_LABELS[geo]))
const eyebrow = [
partner.city,
partner.country ? titleCaseFallback(partner.country) : '',
]
.filter(Boolean)
.join(' · ');
.join(', ');
return (
<Wrapper>