feat(login): custom CountryPicker — opens downward, searchable, ~150 countries
All checks were successful
Deploy to Production / deploy (push) Successful in 51s

Native <select> defers dropdown direction to the browser, which on mobile
routinely opens upward and hides countries behind the keyboard. Replaced
with a custom combobox that always opens DOWNWARD (absolute positioned
below the trigger) with a search input at top — at 150 countries a
scrollable list is unusable without search anyway.

COUNTRIES list expanded from 60 → 152 entries: every country with a
meaningful diaspora, including Russia, Pakistan, Bangladesh, Sri Lanka,
Cyprus, Malta, Albania, Bosnia, Kosovo, North Macedonia, Iran, Iraq,
Lebanon, Jordan, Kazakhstan, Morocco, Algeria, Tunisia, Ethiopia,
Tanzania, Uganda, Senegal, Ghana, Madagascar, Cameroon, Sri Lanka,
Belarus, Georgia, Armenia, Azerbaijan and the rest. Serbia was already in
the prior list — just unfindable without search.

Bonus: flag emojis computed from ISO-3166 alpha-2 codes (no asset files).
Search matches name + code + dial-prefix so "+41" or "CH" both find
Switzerland.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marco Sadjadi
2026-05-25 21:38:36 +02:00
parent 1cccdbdff1
commit e9827b1f77
2 changed files with 269 additions and 13 deletions

View File

@@ -1,5 +1,6 @@
'use client';
import { CountryPicker } from '@/components/country-picker';
import { Input, Label } from '@/components/input';
import { Logo } from '@/components/logo';
import { Button } from '@/components/ui/button';
@@ -21,69 +22,163 @@ const ERROR_COPY: Record<string, string> = {
sms_verify_failed: 'Could not verify the code. Try again.',
};
// Country dial codes for the phone-login picker. Sorted by name; Switzerland
// is the default (Swiss-built product, Swiss Twilio sender number).
// Country dial codes for the phone-login picker. ~150 entries — every country
// with a non-trivial diaspora. Sorted alphabetically by name. Switzerland is
// the default (Swiss-built product, Swiss Twilio sender number).
const COUNTRIES: { code: string; name: string; dial: string }[] = [
{ code: 'AL', name: 'Albania', dial: '+355' },
{ code: 'DZ', name: 'Algeria', dial: '+213' },
{ code: 'AD', name: 'Andorra', dial: '+376' },
{ code: 'AO', name: 'Angola', dial: '+244' },
{ code: 'AR', name: 'Argentina', dial: '+54' },
{ code: 'AM', name: 'Armenia', dial: '+374' },
{ code: 'AW', name: 'Aruba', dial: '+297' },
{ code: 'AU', name: 'Australia', dial: '+61' },
{ code: 'AT', name: 'Austria', dial: '+43' },
{ code: 'AZ', name: 'Azerbaijan', dial: '+994' },
{ code: 'BS', name: 'Bahamas', dial: '+1' },
{ code: 'BH', name: 'Bahrain', dial: '+973' },
{ code: 'BD', name: 'Bangladesh', dial: '+880' },
{ code: 'BB', name: 'Barbados', dial: '+1' },
{ code: 'BY', name: 'Belarus', dial: '+375' },
{ code: 'BE', name: 'Belgium', dial: '+32' },
{ code: 'BZ', name: 'Belize', dial: '+501' },
{ code: 'BJ', name: 'Benin', dial: '+229' },
{ code: 'BM', name: 'Bermuda', dial: '+1' },
{ code: 'BT', name: 'Bhutan', dial: '+975' },
{ code: 'BO', name: 'Bolivia', dial: '+591' },
{ code: 'BA', name: 'Bosnia & Herzegovina', dial: '+387' },
{ code: 'BW', name: 'Botswana', dial: '+267' },
{ code: 'BR', name: 'Brazil', dial: '+55' },
{ code: 'BN', name: 'Brunei', dial: '+673' },
{ code: 'BG', name: 'Bulgaria', dial: '+359' },
{ code: 'BF', name: 'Burkina Faso', dial: '+226' },
{ code: 'KH', name: 'Cambodia', dial: '+855' },
{ code: 'CM', name: 'Cameroon', dial: '+237' },
{ code: 'CA', name: 'Canada', dial: '+1' },
{ code: 'CV', name: 'Cape Verde', dial: '+238' },
{ code: 'KY', name: 'Cayman Islands', dial: '+1' },
{ code: 'CL', name: 'Chile', dial: '+56' },
{ code: 'CN', name: 'China', dial: '+86' },
{ code: 'CO', name: 'Colombia', dial: '+57' },
{ code: 'CR', name: 'Costa Rica', dial: '+506' },
{ code: 'HR', name: 'Croatia', dial: '+385' },
{ code: 'CY', name: 'Cyprus', dial: '+357' },
{ code: 'CZ', name: 'Czechia', dial: '+420' },
{ code: 'DK', name: 'Denmark', dial: '+45' },
{ code: 'DO', name: 'Dominican Republic', dial: '+1' },
{ code: 'EC', name: 'Ecuador', dial: '+593' },
{ code: 'EG', name: 'Egypt', dial: '+20' },
{ code: 'SV', name: 'El Salvador', dial: '+503' },
{ code: 'EE', name: 'Estonia', dial: '+372' },
{ code: 'ET', name: 'Ethiopia', dial: '+251' },
{ code: 'FJ', name: 'Fiji', dial: '+679' },
{ code: 'FI', name: 'Finland', dial: '+358' },
{ code: 'FR', name: 'France', dial: '+33' },
{ code: 'GE', name: 'Georgia', dial: '+995' },
{ code: 'DE', name: 'Germany', dial: '+49' },
{ code: 'GH', name: 'Ghana', dial: '+233' },
{ code: 'GR', name: 'Greece', dial: '+30' },
{ code: 'GT', name: 'Guatemala', dial: '+502' },
{ code: 'HN', name: 'Honduras', dial: '+504' },
{ code: 'HK', name: 'Hong Kong', dial: '+852' },
{ code: 'HU', name: 'Hungary', dial: '+36' },
{ code: 'IS', name: 'Iceland', dial: '+354' },
{ code: 'IN', name: 'India', dial: '+91' },
{ code: 'ID', name: 'Indonesia', dial: '+62' },
{ code: 'IR', name: 'Iran', dial: '+98' },
{ code: 'IQ', name: 'Iraq', dial: '+964' },
{ code: 'IE', name: 'Ireland', dial: '+353' },
{ code: 'IL', name: 'Israel', dial: '+972' },
{ code: 'IT', name: 'Italy', dial: '+39' },
{ code: 'CI', name: 'Ivory Coast', dial: '+225' },
{ code: 'JM', name: 'Jamaica', dial: '+1' },
{ code: 'JP', name: 'Japan', dial: '+81' },
{ code: 'JO', name: 'Jordan', dial: '+962' },
{ code: 'KZ', name: 'Kazakhstan', dial: '+7' },
{ code: 'KE', name: 'Kenya', dial: '+254' },
{ code: 'XK', name: 'Kosovo', dial: '+383' },
{ code: 'KW', name: 'Kuwait', dial: '+965' },
{ code: 'KG', name: 'Kyrgyzstan', dial: '+996' },
{ code: 'LA', name: 'Laos', dial: '+856' },
{ code: 'LV', name: 'Latvia', dial: '+371' },
{ code: 'LB', name: 'Lebanon', dial: '+961' },
{ code: 'LY', name: 'Libya', dial: '+218' },
{ code: 'LI', name: 'Liechtenstein', dial: '+423' },
{ code: 'LT', name: 'Lithuania', dial: '+370' },
{ code: 'LU', name: 'Luxembourg', dial: '+352' },
{ code: 'MO', name: 'Macau', dial: '+853' },
{ code: 'MK', name: 'North Macedonia', dial: '+389' },
{ code: 'MG', name: 'Madagascar', dial: '+261' },
{ code: 'MY', name: 'Malaysia', dial: '+60' },
{ code: 'MV', name: 'Maldives', dial: '+960' },
{ code: 'MT', name: 'Malta', dial: '+356' },
{ code: 'MX', name: 'Mexico', dial: '+52' },
{ code: 'MD', name: 'Moldova', dial: '+373' },
{ code: 'MC', name: 'Monaco', dial: '+377' },
{ code: 'MN', name: 'Mongolia', dial: '+976' },
{ code: 'ME', name: 'Montenegro', dial: '+382' },
{ code: 'MA', name: 'Morocco', dial: '+212' },
{ code: 'MZ', name: 'Mozambique', dial: '+258' },
{ code: 'MM', name: 'Myanmar', dial: '+95' },
{ code: 'NA', name: 'Namibia', dial: '+264' },
{ code: 'NP', name: 'Nepal', dial: '+977' },
{ code: 'NL', name: 'Netherlands', dial: '+31' },
{ code: 'NZ', name: 'New Zealand', dial: '+64' },
{ code: 'NI', name: 'Nicaragua', dial: '+505' },
{ code: 'NG', name: 'Nigeria', dial: '+234' },
{ code: 'NO', name: 'Norway', dial: '+47' },
{ code: 'OM', name: 'Oman', dial: '+968' },
{ code: 'PK', name: 'Pakistan', dial: '+92' },
{ code: 'PS', name: 'Palestine', dial: '+970' },
{ code: 'PA', name: 'Panama', dial: '+507' },
{ code: 'PG', name: 'Papua New Guinea', dial: '+675' },
{ code: 'PY', name: 'Paraguay', dial: '+595' },
{ code: 'PE', name: 'Peru', dial: '+51' },
{ code: 'PH', name: 'Philippines', dial: '+63' },
{ code: 'PL', name: 'Poland', dial: '+48' },
{ code: 'PT', name: 'Portugal', dial: '+351' },
{ code: 'PR', name: 'Puerto Rico', dial: '+1' },
{ code: 'QA', name: 'Qatar', dial: '+974' },
{ code: 'RO', name: 'Romania', dial: '+40' },
{ code: 'RU', name: 'Russia', dial: '+7' },
{ code: 'RW', name: 'Rwanda', dial: '+250' },
{ code: 'SM', name: 'San Marino', dial: '+378' },
{ code: 'SA', name: 'Saudi Arabia', dial: '+966' },
{ code: 'SN', name: 'Senegal', dial: '+221' },
{ code: 'RS', name: 'Serbia', dial: '+381' },
{ code: 'SG', name: 'Singapore', dial: '+65' },
{ code: 'SK', name: 'Slovakia', dial: '+421' },
{ code: 'SI', name: 'Slovenia', dial: '+386' },
{ code: 'SO', name: 'Somalia', dial: '+252' },
{ code: 'ZA', name: 'South Africa', dial: '+27' },
{ code: 'KR', name: 'South Korea', dial: '+82' },
{ code: 'ES', name: 'Spain', dial: '+34' },
{ code: 'LK', name: 'Sri Lanka', dial: '+94' },
{ code: 'SD', name: 'Sudan', dial: '+249' },
{ code: 'SE', name: 'Sweden', dial: '+46' },
{ code: 'CH', name: 'Switzerland', dial: '+41' },
{ code: 'SY', name: 'Syria', dial: '+963' },
{ code: 'TW', name: 'Taiwan', dial: '+886' },
{ code: 'TJ', name: 'Tajikistan', dial: '+992' },
{ code: 'TZ', name: 'Tanzania', dial: '+255' },
{ code: 'TH', name: 'Thailand', dial: '+66' },
{ code: 'TT', name: 'Trinidad & Tobago', dial: '+1' },
{ code: 'TN', name: 'Tunisia', dial: '+216' },
{ code: 'TR', name: 'Turkey', dial: '+90' },
{ code: 'TM', name: 'Turkmenistan', dial: '+993' },
{ code: 'UG', name: 'Uganda', dial: '+256' },
{ code: 'UA', name: 'Ukraine', dial: '+380' },
{ code: 'AE', name: 'United Arab Emirates', dial: '+971' },
{ code: 'GB', name: 'United Kingdom', dial: '+44' },
{ code: 'US', name: 'United States', dial: '+1' },
{ code: 'UY', name: 'Uruguay', dial: '+598' },
{ code: 'UZ', name: 'Uzbekistan', dial: '+998' },
{ code: 'VE', name: 'Venezuela', dial: '+58' },
{ code: 'VN', name: 'Vietnam', dial: '+84' },
{ code: 'YE', name: 'Yemen', dial: '+967' },
{ code: 'ZM', name: 'Zambia', dial: '+260' },
{ code: 'ZW', name: 'Zimbabwe', dial: '+263' },
];
function dialFor(code: string): string {
@@ -301,18 +396,11 @@ export default function LoginPage() {
<form onSubmit={requestSmsCode} className="space-y-3">
<div className="space-y-1.5">
<Label htmlFor="country">Country</Label>
<select
id="country"
<CountryPicker
countries={COUNTRIES}
value={country}
onChange={(e) => setCountry(e.target.value)}
className="h-8 w-full rounded-md border border-[--color-border] bg-[--color-bg-subtle] px-2 text-[13px] text-[--color-fg] transition-colors duration-200 focus:border-[--color-accent] focus:outline-none focus:ring-1 focus:ring-[--color-accent]"
>
{COUNTRIES.map((c) => (
<option key={c.code} value={c.code}>
{c.name} ({c.dial})
</option>
))}
</select>
onChange={setCountry}
/>
</div>
<div className="space-y-1.5">
<Label htmlFor="phone" hint={dialFor(country)}>