Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/playwright-scheduled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ on:
- webkit
- all

permissions:
contents: read

jobs:
playwright:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -118,6 +121,9 @@ jobs:
- name: Run Playwright tests
env:
PLAYWRIGHT_BASE_URL: ${{ steps.url.outputs.base }}
# Staging is behind HTTP Basic Auth; production is public.
PLAYWRIGHT_HTTP_USERNAME: ${{ (inputs.environment || 'staging') != 'production' && secrets.PLAYWRIGHT_STAGING_USERNAME || '' }}
PLAYWRIGHT_HTTP_PASSWORD: ${{ (inputs.environment || 'staging') != 'production' && secrets.PLAYWRIGHT_STAGING_PASSWORD || '' }}
PLAYWRIGHT_NO_SERVER: '1'
CI: 'true'
run: yarn playwright test ${{ steps.scope.outputs.path }} ${{ steps.browser.outputs.flag }}
Expand Down
10 changes: 10 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ const baseURL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3005';
// CI sets PLAYWRIGHT_NO_SERVER=1 to skip the local webServer and target a deployed URL.
const skipWebServer = process.env.PLAYWRIGHT_NO_SERVER === '1';

// Staging sits behind HTTP Basic Auth; production and local dev don't.
const httpCredentials =
process.env.PLAYWRIGHT_HTTP_USERNAME && process.env.PLAYWRIGHT_HTTP_PASSWORD
? {
username: process.env.PLAYWRIGHT_HTTP_USERNAME,
password: process.env.PLAYWRIGHT_HTTP_PASSWORD,
}
: undefined;

export default defineConfig({
testDir: './tests',
globalSetup: './tests/global-setup.ts',
Expand All @@ -22,6 +31,7 @@ export default defineConfig({

use: {
baseURL,
httpCredentials,
testIdAttribute: 'data-testid',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
border-radius: 9999px;
padding: 2px 10px;
font-family: var(--font-geist-mono), ui-monospace, monospace;
font-size: 11px;
font-size: 14px;
font-weight: 600;
transition: opacity 0.2s;

Expand Down Expand Up @@ -153,7 +153,7 @@
.Rationale {
margin: 4px 0 0;
max-width: 500px;
font-size: 13px;
font-size: 16px;
line-height: 1.6;
color: #64748b;
}
Expand All @@ -163,7 +163,7 @@
border-radius: 9999px;
padding: 4px 14px;
font-family: var(--font-geist-mono), ui-monospace, monospace;
font-size: 12px;
font-size: 14px;
font-weight: 500;
letter-spacing: 0.025em;
}
Expand Down Expand Up @@ -204,21 +204,23 @@

.BiasNumber {
font-family: var(--font-geist-mono), ui-monospace, monospace;
font-size: 11px;
font-size: 14px;
letter-spacing: 0.025em;
color: #337ab7;
}

.BiasName {
font-size: 14px;
font-size: 16px;
font-weight: 600;
color: #1a1a2e;
}

.BiasShort {
font-size: 12px;
font-size: 14px;
line-height: 1.6;
color: #64748b;
max-height: calc(1.6em * 8);
overflow-y: auto;

p {
margin: 0;
Expand Down Expand Up @@ -246,7 +248,7 @@

.FooterMeta {
font-family: var(--font-geist-mono), ui-monospace, monospace;
font-size: 12px;
font-size: 14px;
color: #94a3b8;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ const BiasPanel = forwardRef<HTMLDivElement, BiasPanelProps>(
onClick={handleUse}
className={styles.UseButton}
>
{ui.useInPersonaBuilder}
{ui.useInPersonaBuilder.replace('{country}', country.name)}
</button>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,6 @@
transition: all 0.3s;
}

.Tagline {
max-width: 600px;
margin: 0 auto;
font-size: 16px;
line-height: 1.6;
color: #64748b;
}

.SubTagline {
max-width: 560px;
margin: 16px auto 0;
Expand Down
188 changes: 186 additions & 2 deletions src/components/_uxcp/CountryBiasMap/CountryBiasMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { type FC, useEffect, useRef, useState } from 'react';
import type { StrapiBiasType } from '@local-types/data';
import type { TRouter } from '@local-types/global';

import { countryBiasByLocale } from '@data/countryBias';
import { type Country,countryBiasByLocale } from '@data/countryBias';

import BiasPanel from './BiasPanel';
import CountryList from './CountryList';
Expand All @@ -14,6 +14,186 @@ import styles from './CountryBiasMap.module.scss';

const TITLE_INTERVAL = 3000;

const TZ_TO_COUNTRY: Record<string, string> = {
// Europe
'Europe/Vienna': 'at',
'Europe/Brussels': 'be',
'Europe/Prague': 'cz',
'Europe/Copenhagen': 'dk',
'Europe/Helsinki': 'fi',
'Europe/Paris': 'fr',
'Europe/Berlin': 'de',
'Europe/Athens': 'gr',
'Europe/Dublin': 'ie',
'Europe/Rome': 'it',
'Europe/Amsterdam': 'nl',
'Europe/Oslo': 'no',
'Europe/Warsaw': 'pl',
'Europe/Lisbon': 'pt',
'Atlantic/Azores': 'pt',
'Atlantic/Madeira': 'pt',
'Europe/Bucharest': 'ro',
'Europe/Madrid': 'es',
'Atlantic/Canary': 'es',
'Africa/Ceuta': 'es',
'Europe/Stockholm': 'se',
'Europe/Zurich': 'ch',
'Europe/Istanbul': 'tr',
'Europe/Kyiv': 'ua',
'Europe/Kiev': 'ua',
'Europe/Zaporozhye': 'ua',
'Europe/Simferopol': 'ua',
'Europe/Uzhgorod': 'ua',
'Europe/London': 'gb',
// Asia / Middle East
'Asia/Yerevan': 'am',
'Asia/Shanghai': 'cn',
'Asia/Urumqi': 'cn',
'Asia/Chongqing': 'cn',
'Asia/Harbin': 'cn',
'Asia/Kashgar': 'cn',
'Asia/Kolkata': 'in',
'Asia/Calcutta': 'in',
'Asia/Jakarta': 'id',
'Asia/Makassar': 'id',
'Asia/Jayapura': 'id',
'Asia/Pontianak': 'id',
'Asia/Tehran': 'ir',
'Asia/Jerusalem': 'il',
'Asia/Tel_Aviv': 'il',
'Asia/Tokyo': 'jp',
'Asia/Kuala_Lumpur': 'my',
'Asia/Kuching': 'my',
'Asia/Karachi': 'pk',
'Asia/Manila': 'ph',
'Asia/Riyadh': 'sa',
'Asia/Singapore': 'sg',
'Asia/Seoul': 'kr',
'Asia/Bangkok': 'th',
'Asia/Dubai': 'ae',
'Asia/Ho_Chi_Minh': 'vn',
'Asia/Saigon': 'vn',
// Africa
'Africa/Cairo': 'eg',
'Africa/Addis_Ababa': 'et',
'Africa/Nairobi': 'ke',
'Africa/Casablanca': 'ma',
'Africa/Lagos': 'ng',
'Africa/Johannesburg': 'za',
// Americas — Colombia, Peru, Chile
'America/Bogota': 'co',
'America/Lima': 'pe',
'America/Santiago': 'cl',
'America/Punta_Arenas': 'cl',
'Pacific/Easter': 'cl',
// Mexico
'America/Mexico_City': 'mx',
'America/Cancun': 'mx',
'America/Tijuana': 'mx',
'America/Hermosillo': 'mx',
'America/Mazatlan': 'mx',
'America/Chihuahua': 'mx',
'America/Monterrey': 'mx',
'America/Bahia_Banderas': 'mx',
'America/Merida': 'mx',
'America/Matamoros': 'mx',
'America/Ojinaga': 'mx',
// Brazil
'America/Sao_Paulo': 'br',
'America/Manaus': 'br',
'America/Recife': 'br',
'America/Fortaleza': 'br',
'America/Bahia': 'br',
'America/Belem': 'br',
'America/Cuiaba': 'br',
'America/Campo_Grande': 'br',
'America/Boa_Vista': 'br',
'America/Porto_Velho': 'br',
'America/Rio_Branco': 'br',
'America/Maceio': 'br',
'America/Santarem': 'br',
'America/Eirunepe': 'br',
'America/Araguaina': 'br',
'America/Noronha': 'br',
// United States
'America/New_York': 'us',
'America/Los_Angeles': 'us',
'America/Chicago': 'us',
'America/Denver': 'us',
'America/Phoenix': 'us',
'America/Anchorage': 'us',
'America/Honolulu': 'us',
'America/Detroit': 'us',
'America/Indianapolis': 'us',
'America/Boise': 'us',
'America/Juneau': 'us',
'America/Nome': 'us',
'America/Sitka': 'us',
'America/Adak': 'us',
'America/Menominee': 'us',
'America/Louisville': 'us',
// Canada
'America/Toronto': 'ca',
'America/Vancouver': 'ca',
'America/Edmonton': 'ca',
'America/Halifax': 'ca',
'America/Winnipeg': 'ca',
'America/Regina': 'ca',
'America/St_Johns': 'ca',
'America/Whitehorse': 'ca',
'America/Yellowknife': 'ca',
'America/Iqaluit': 'ca',
'America/Cambridge_Bay': 'ca',
'America/Inuvik': 'ca',
'America/Rankin_Inlet': 'ca',
'America/Resolute': 'ca',
'America/Atikokan': 'ca',
'America/Blanc-Sablon': 'ca',
'America/Creston': 'ca',
'America/Dawson': 'ca',
'America/Dawson_Creek': 'ca',
'America/Fort_Nelson': 'ca',
'America/Glace_Bay': 'ca',
'America/Goose_Bay': 'ca',
'America/Moncton': 'ca',
'America/Montreal': 'ca',
// Oceania
'Pacific/Auckland': 'nz',
'Pacific/Chatham': 'nz',
};

const TZ_PREFIX_TO_COUNTRY: Array<[string, string]> = [
['Australia/', 'au'],
['America/Argentina/', 'ar'],
];

const detectUserCountry = (countries: Country[]): string | null => {
if (typeof window === 'undefined') return null;
const has = (id: string) => countries.some(c => c.id === id);

try {
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
const exact = TZ_TO_COUNTRY[tz];
if (exact && has(exact)) return exact;
for (const [prefix, code] of TZ_PREFIX_TO_COUNTRY) {
if (tz.startsWith(prefix) && has(code)) return code;
}
} catch {
// Intl unavailable — fall through to language detection
}

const langs = [navigator.language, ...(navigator.languages ?? [])];
for (const lang of langs) {
const match = lang.match(/[-_]([A-Za-z]{2})$/);
if (match) {
const code = match[1].toLowerCase();
if (has(code)) return code;
}
}

return null;
};

interface CyclingSubtitleWordProps {
words: string[];
interval: number;
Expand Down Expand Up @@ -84,6 +264,11 @@ const CountryBiasMap: FC<CountryBiasMapProps> = ({ biases, onUseBiases }) => {
const [filterRegion, setFilterRegion] = useState('All');
const biasPanelRef = useRef<HTMLDivElement>(null);

useEffect(() => {
setSelected(prev => prev ?? detectUserCountry(countries));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const handleSelect = (id: string) => {
setSelected(prev => (prev === id ? null : id));
};
Expand All @@ -103,7 +288,6 @@ const CountryBiasMap: FC<CountryBiasMapProps> = ({ biases, onUseBiases }) => {
boldWord={ui.cyclingBoldWord}
/>
</div>
<p className={styles.Tagline}>{ui.tagline}</p>
<p className={styles.SubTagline}>
<span className={styles.SubTaglineLead}>{ui.subTaglineLead}</span>
{ui.subTagline}
Expand Down
Loading
Loading