Add Kosovo and Western Sahara, surface Tibet in country pickers

Add Kosovo (XK) and Western Sahara (EH) to the country list. Kosovo
has no Unicode emoji flag, so it follows the Tibet pattern with a
bundled SVG asset that CountryFlag swaps in.

Surface Tibet (CN-XZ) as a search-list entry so it can be picked from
country autocompletes and pickers. The on-wire identifier stays
iso3166:CN-XZ; only the picker pretends.

Route every remaining raw country.flag span through CountryFlag so
bundled SVGs render in autocomplete dropdowns, organizer selects, the
ComposeBox destination switcher, and the world stats dialog.
This commit is contained in:
Chad Curtis
2026-05-26 19:53:42 -05:00
parent 00f936fcd8
commit f173b975b7
13 changed files with 252 additions and 64 deletions
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.3 KiB

+22 -6
View File
@@ -38,6 +38,7 @@ import { EmojiShortcodeAutocomplete } from '@/components/EmojiShortcodeAutocompl
import { StickerPicker } from '@/components/StickerPicker';
import { NoteContent } from '@/components/NoteContent';
import { CountryFlag } from '@/components/CountryFlag';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useNostrPublish } from '@/hooks/useNostrPublish';
@@ -1552,9 +1553,15 @@ export function ComposeBox({
{/* Show just the flag in the trigger to keep the row
compact on mobile. The list items below carry the
country name so users can still tell them apart. */}
<span aria-hidden="true">
{selectedCountryInfo?.flag ?? '🌍'}
</span>
{selectedCountryCode && selectedCountryInfo ? (
<CountryFlag
code={selectedCountryCode}
emoji={selectedCountryInfo.flag}
label={`Flag of ${selectedCountryInfo.subdivisionName ?? selectedCountryInfo.name}`}
/>
) : (
<span aria-hidden="true">🌍</span>
)}
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-[240px]">
<DropdownMenuItem
@@ -1595,8 +1602,12 @@ export function ComposeBox({
className="cursor-pointer"
>
<span className="inline-flex items-center gap-2 flex-1">
<span aria-hidden="true">{info.flag}</span>
<span>{info.name}</span>
<CountryFlag
code={code}
emoji={info.flag}
label={`Flag of ${info.subdivisionName ?? info.name}`}
/>
<span>{info.subdivisionName ?? info.name}</span>
</span>
{destination === code && (
<Check className="size-4 text-primary" aria-hidden />
@@ -1683,7 +1694,12 @@ export function ComposeBox({
setCountryPickerOpen(false);
}}
>
<span aria-hidden="true" className="mr-2">{country.flag}</span>
<CountryFlag
code={country.code}
emoji={country.flag}
label={`Flag of ${country.name}`}
className="mr-2"
/>
<span>{country.name}</span>
<span className="ml-2 text-xs text-muted-foreground">{country.code}</span>
{destination === country.code && (
+17 -2
View File
@@ -12,6 +12,7 @@ import {
CommandList,
} from '@/components/ui/command';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { CountryFlag } from '@/components/CountryFlag';
import { countryCodeToFlag, getAllCountries } from '@/lib/countries';
import { cn } from '@/lib/utils';
@@ -71,7 +72,12 @@ export function CountryPickerButton({ value, onChange, className }: CountryPicke
aria-label={t('common.countryFilterAriaLabel')}
>
{value ? (
<span className="text-2xl leading-none">{countryCodeToFlag(value)}</span>
<CountryFlag
code={value}
emoji={countryCodeToFlag(value)}
label={t('common.countryFilterAriaLabel')}
className="text-2xl"
/>
) : (
<Globe className="h-5 w-5 text-primary" />
)}
@@ -93,7 +99,16 @@ export function CountryPickerButton({ value, onChange, className }: CountryPicke
}}
className="gap-2"
>
<span>{option.flag}</span>
{option.value === 'global' ? (
<span className="text-base leading-none">{option.flag}</span>
) : (
<CountryFlag
code={option.value}
emoji={option.flag}
label={`Flag of ${option.label}`}
className="text-base"
/>
)}
<span className="flex-1">{option.label}</span>
<Check
className={cn(
+10 -4
View File
@@ -3,7 +3,8 @@ import { MapPin, X } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Input } from '@/components/ui/input';
import { COUNTRIES, searchCountries, type CountryEntry } from '@/lib/countries';
import { CountryFlag } from '@/components/CountryFlag';
import { getCountryInfo, searchCountries, type CountryEntry } from '@/lib/countries';
import { cn } from '@/lib/utils';
interface CountrySelectProps {
@@ -28,7 +29,7 @@ export function CountrySelect({
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
const selectedCountry = selectedCode ? COUNTRIES[selectedCode] : undefined;
const selectedCountry = selectedCode ? getCountryInfo(selectedCode) : undefined;
const results = useMemo(() => searchCountries(query), [query]);
const showResults = open && results.length > 0;
const resultsId = `${id}-results`;
@@ -105,8 +106,13 @@ export function CountrySelect({
index === selectedIndex && 'bg-secondary/60',
)}
>
<span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-secondary text-lg leading-none" role="img" aria-label={`Flag of ${country.name}`}>
{country.flag}
<span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-secondary leading-none">
<CountryFlag
code={country.code}
emoji={country.flag}
label={`Flag of ${country.name}`}
className="text-lg"
/>
</span>
<span className="min-w-0 flex-1">
<span className="block truncate text-sm font-semibold">{country.name}</span>
+9 -1
View File
@@ -23,6 +23,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { CountryFlag } from '@/components/CountryFlag';
import { useToast } from '@/hooks/useToast';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
@@ -254,7 +255,14 @@ export function OrganizersManager() {
<SelectContent>
{countries.map((country) => (
<SelectItem key={country.code} value={country.code}>
{country.flag} {country.name}
<span className="inline-flex items-center gap-2">
<CountryFlag
code={country.code}
emoji={country.flag}
label={`Flag of ${country.name}`}
/>
{country.name}
</span>
</SelectItem>
))}
</SelectContent>
+7 -3
View File
@@ -5,6 +5,7 @@ import { nip19 } from 'nostr-tools';
import { Input } from '@/components/ui/input';
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
import { EmojifiedText } from '@/components/CustomEmoji';
import { CountryFlag } from '@/components/CountryFlag';
import { useSearchProfiles, type SearchProfile } from '@/hooks/useSearchProfiles';
import { genUserName } from '@/lib/genUserName';
import { useNip05Verify } from '@/hooks/useNip05Verify';
@@ -818,9 +819,12 @@ function CountryItem({
onMouseDown={(e) => e.preventDefault()}
>
<div className="size-10 shrink-0 rounded-full bg-secondary flex items-center justify-center">
<span className="text-lg leading-none" role="img" aria-label={`Flag of ${country.name}`}>
{country.flag}
</span>
<CountryFlag
code={country.code}
emoji={country.flag}
label={`Flag of ${country.name}`}
className="text-lg"
/>
</div>
<div className="flex-1 min-w-0">
<span className="font-semibold text-sm truncate">{country.name}</span>
+10 -10
View File
@@ -7,7 +7,8 @@ import {
DialogTitle,
} from '@/components/ui/dialog';
import { CommunityStatsPanel } from '@/components/CommunityStatsPanel';
import { COUNTRIES } from '@/lib/countries';
import { CountryFlag } from '@/components/CountryFlag';
import { getCountryInfo } from '@/lib/countries';
interface CountryStatsDialogProps {
/** ISO 3166-1 alpha-2 country code (e.g. `VE`). */
@@ -31,8 +32,8 @@ interface CountryStatsDialogProps {
* trigger inside a dropdown menu without rendering two visible affordances.
*/
export function CountryStatsDialog({ countryCode, open, onOpenChange }: CountryStatsDialogProps) {
const country = COUNTRIES[countryCode.toUpperCase()];
const countryName = country?.name ?? countryCode;
const country = getCountryInfo(countryCode);
const countryName = country?.subdivisionName ?? country?.name ?? countryCode;
const flag = country?.flag ?? '';
return (
@@ -43,13 +44,12 @@ export function CountryStatsDialog({ countryCode, open, onOpenChange }: CountryS
<DialogHeader className="px-4 pt-5 pb-3 border-b border-border/40">
<DialogTitle className="flex items-center gap-2 text-base">
{flag ? (
<span
className="text-lg leading-none"
role="img"
aria-label={`Flag of ${countryName}`}
>
{flag}
</span>
<CountryFlag
code={countryCode}
emoji={flag}
label={`Flag of ${countryName}`}
className="text-lg"
/>
) : (
<Trophy className="size-4 text-primary shrink-0" />
)}
+31 -10
View File
@@ -95,6 +95,7 @@ export const COUNTRIES: Record<string, { name: string; flag: string }> = {
KZ: { name: 'Kazakhstan', flag: '🇰🇿' },
KE: { name: 'Kenya', flag: '🇰🇪' },
KI: { name: 'Kiribati', flag: '🇰🇮' },
XK: { name: 'Kosovo', flag: '🌍' },
KP: { name: 'North Korea', flag: '🇰🇵' },
KR: { name: 'South Korea', flag: '🇰🇷' },
KW: { name: 'Kuwait', flag: '🇰🇼' },
@@ -199,15 +200,30 @@ export const COUNTRIES: Record<string, { name: string; flag: string }> = {
VA: { name: 'Vatican City', flag: '🇻🇦' },
VE: { name: 'Venezuela', flag: '🇻🇪' },
VN: { name: 'Vietnam', flag: '🇻🇳' },
EH: { name: 'Western Sahara', flag: '🇪🇭' },
YE: { name: 'Yemen', flag: '🇾🇪' },
ZM: { name: 'Zambia', flag: '🇿🇲' },
ZW: { name: 'Zimbabwe', flag: '🇿🇼' },
};
/** Pre-sorted array of country entries for searching. */
export const COUNTRY_LIST = Object.entries(COUNTRIES)
.map(([code, { name, flag }]) => ({ code, name, flag }))
.sort((a, b) => a.name.localeCompare(b.name));
export const COUNTRY_LIST = (() => {
const base = Object.entries(COUNTRIES).map(([code, { name, flag }]) => ({ code, name, flag }));
// Promote a handful of ISO 3166-2 subdivisions to country-level entries
// in the search list. These are editorial choices to surface places that
// are commonly thought of as countries but lack their own ISO 3166-1
// code. The on-wire identifier stays `iso3166:CC-XX` so we don't fork
// a parallel addressing scheme — only the picker pretends.
const promoted: { code: string; name: string; flag: string }[] = [
// Tibet (CN-XZ) — bundled Snow Lion SVG renders via CountryFlag; the
// `flag` field here is the text fallback for raw-text consumers, so
// we use the parent-country emoji rather than nothing.
{ code: 'CN-XZ', name: 'Tibet', flag: '🇨🇳' },
];
return [...base, ...promoted].sort((a, b) => a.name.localeCompare(b.name));
})();
export type CountryEntry = typeof COUNTRY_LIST[number];
@@ -390,15 +406,15 @@ export function isValidGeoCode(code: string): boolean {
// ---------------------------------------------------------------------------
/**
* Return the list of ISO 3166-1 countries Agora knows about, sorted
* alphabetically by English name. Pathos exposes a localized variant — Agora
* is currently English-only so the `lang` argument is ignored. Kept for
* call-site compatibility with ports.
* Return the list of countries Agora surfaces for picker UIs, sorted
* alphabetically by English name. Mirrors {@link COUNTRY_LIST} (which
* also includes editorially promoted ISO 3166-2 entries like Tibet).
* Pathos exposes a localized variant — Agora is currently English-only
* so the `lang` argument is ignored. Kept for call-site compatibility
* with ports.
*/
export function getAllCountries(_lang?: string): { code: string; name: string; flag: string }[] {
return Object.entries(COUNTRIES)
.map(([code, info]) => ({ code, name: info.name, flag: info.flag }))
.sort((a, b) => a.name.localeCompare(b.name));
return COUNTRY_LIST.map(({ code, name, flag }) => ({ code, name, flag }));
}
/**
@@ -428,6 +444,11 @@ export function countryCodeToFlag(code: string): string {
const upper = code.toUpperCase();
const parentCode = upper.includes('-') ? upper.split('-')[0] : upper;
if (!/^[A-Z]{2}$/.test(parentCode)) return '';
// Honour explicit overrides first — covers user-assigned codes like
// Kosovo (`XK`) whose regional-indicator sequence has no associated
// Unicode flag glyph and would otherwise render as raw letters.
const explicit = COUNTRIES[parentCode]?.flag;
if (explicit) return explicit;
// Regional indicator symbols start at U+1F1E6 (🇦); A=0x41.
return parentCode
.split('')
+9 -4
View File
@@ -4,13 +4,18 @@
* these as `<img>` elements; callers that need the raw URL (e.g. a
* card backdrop, a CSS background) use {@link customFlagAsset}.
*
* Editorial choice: Tibet (ISO 3166-2 `CN-XZ`) is surfaced as a
* country-level entity with the Snow Lion flag, matching the older
* Agora codebase. Add additional entries here as new bundled assets
* land in `/public`.
* Editorial choices:
* - Tibet (ISO 3166-2 `CN-XZ`) is surfaced as a country-level entity
* with the Snow Lion flag, matching the older Agora codebase.
* - Kosovo (`XK`) is a user-assigned ISO 3166-1 code with no Unicode
* emoji flag, so we bundle its SVG to render alongside the rest of
* the country list.
*
* Add additional entries here as new bundled assets land in `/public`.
*/
const CUSTOM_FLAG_ASSETS: Record<string, string> = {
'CN-XZ': '/flag-tibet.svg',
'XK': '/flag-kosovo.svg',
};
/**
+14 -7
View File
@@ -15,6 +15,7 @@ import {
} from 'lucide-react';
import { CoverImageField } from '@/components/CoverImageField';
import { CountryFlag } from '@/components/CountryFlag';
import { FormSection } from '@/components/FormSection';
import { OrganizationContextChip } from '@/components/OrganizationContextChip';
import { TimezoneSwitcher } from '@/components/TimezoneSwitcher';
@@ -31,7 +32,7 @@ import { useManageableOrganizations } from '@/hooks/useManageableOrganizations';
import { useNostrPublish } from '@/hooks/useNostrPublish';
import { useToast } from '@/hooks/useToast';
import { usdToSats } from '@/lib/bitcoin';
import { COUNTRIES, searchCountries, type CountryEntry } from '@/lib/countries';
import { getCountryInfo, searchCountries, type CountryEntry } from '@/lib/countries';
import { parseContentTagInput } from '@/lib/contentTags';
import { createCountryIdentifier } from '@/lib/countryIdentifiers';
import { getTodayDateInput } from '@/lib/dateInput';
@@ -87,7 +88,7 @@ export function CreateActionPage() {
const [coverImage, setCoverImage] = useState<string>('');
const [coverUploading, setCoverUploading] = useState(false);
const [countryCode, setCountryCode] = useState(pageCountryCode);
const [countryQuery, setCountryQuery] = useState(pageCountryCode ? (COUNTRIES[pageCountryCode]?.name ?? pageCountryCode) : '');
const [countryQuery, setCountryQuery] = useState(pageCountryCode ? (getCountryInfo(pageCountryCode)?.subdivisionName ?? getCountryInfo(pageCountryCode)?.name ?? pageCountryCode) : '');
// Effective org coordinate to attach on publish. Sourced only from the
// URL — never editable inside the form. Drops to '' when the user
// isn't authorized for the param's org.
@@ -293,8 +294,9 @@ export function CreateActionPage() {
selectedCode={countryCode}
onQueryChange={(value) => {
setCountryQuery(value);
const selectedCountry = countryCode ? COUNTRIES[countryCode] : undefined;
if (selectedCountry && value !== selectedCountry.name && value.toUpperCase() !== countryCode) {
const selectedCountry = countryCode ? getCountryInfo(countryCode) : undefined;
const selectedName = selectedCountry?.subdivisionName ?? selectedCountry?.name;
if (selectedCountry && value !== selectedName && value.toUpperCase() !== countryCode) {
setCountryCode('');
}
}}
@@ -447,7 +449,7 @@ function CountrySelect({
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
const selectedCountry = selectedCode ? COUNTRIES[selectedCode] : undefined;
const selectedCountry = selectedCode ? getCountryInfo(selectedCode) : undefined;
const results = useMemo(() => searchCountries(query), [query]);
const showResults = open && results.length > 0;
@@ -523,8 +525,13 @@ function CountrySelect({
index === selectedIndex && 'bg-secondary/60',
)}
>
<span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-secondary text-lg leading-none" role="img" aria-label={t('pledges.create.flagOfAria', { name: country.name })}>
{country.flag}
<span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-secondary leading-none">
<CountryFlag
code={country.code}
emoji={country.flag}
label={t('pledges.create.flagOfAria', { name: country.name })}
className="text-lg"
/>
</span>
<span className="min-w-0 flex-1">
<span className="block truncate text-sm font-semibold">{country.name}</span>
+14 -7
View File
@@ -17,6 +17,7 @@ import {
} from 'lucide-react';
import { CoverImageField } from '@/components/CoverImageField';
import { CountryFlag } from '@/components/CountryFlag';
import { FormSection } from '@/components/FormSection';
import { OrganizationContextChip } from '@/components/OrganizationContextChip';
import { LoginArea } from '@/components/auth/LoginArea';
@@ -54,7 +55,7 @@ import { genUserName } from '@/lib/genUserName';
import { createOrganizationAssociationTags, decodeOrganizationParam } from '@/lib/organizationContext';
import { sanitizeUrl } from '@/lib/sanitizeUrl';
import { withAgoraTag } from '@/lib/agoraNoteTags';
import { COUNTRIES, searchCountries, type CountryEntry } from '@/lib/countries';
import { getCountryInfo, searchCountries, type CountryEntry } from '@/lib/countries';
import { getEditableContentTags, parseContentTagInput } from '@/lib/contentTags';
import { createCountryIdentifier } from '@/lib/countryIdentifiers';
import { cn } from '@/lib/utils';
@@ -281,7 +282,7 @@ export function CreateCampaignPage() {
setDeadline(formatDateInput(editCampaign.deadline));
const editCountryCode = editCampaign.countryCode ?? '';
setCountryCode(editCountryCode);
setCountryQuery(editCountryCode ? COUNTRIES[editCountryCode]?.name ?? editCountryCode : '');
setCountryQuery(editCountryCode ? (getCountryInfo(editCountryCode)?.subdivisionName ?? getCountryInfo(editCountryCode)?.name ?? editCountryCode) : '');
setTagInput(getEditableContentTags(editCampaign.event.tags).join(', '));
const existingOrgATag = editCampaign.event.tags.find(
([n, v]) => n === 'A' && typeof v === 'string' && v.startsWith('34550:'),
@@ -692,8 +693,9 @@ export function CreateCampaignPage() {
selectedCode={countryCode}
onQueryChange={(value) => {
setCountryQuery(value);
const selectedCountry = countryCode ? COUNTRIES[countryCode] : undefined;
if (selectedCountry && value !== selectedCountry.name && value.toUpperCase() !== countryCode) {
const selectedCountry = countryCode ? getCountryInfo(countryCode) : undefined;
const selectedName = selectedCountry?.subdivisionName ?? selectedCountry?.name;
if (selectedCountry && value !== selectedName && value.toUpperCase() !== countryCode) {
setCountryCode('');
}
}}
@@ -1058,7 +1060,7 @@ function CountrySelect({
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
const selectedCountry = selectedCode ? COUNTRIES[selectedCode] : undefined;
const selectedCountry = selectedCode ? getCountryInfo(selectedCode) : undefined;
const results = useMemo(() => searchCountries(query), [query]);
const showResults = open && results.length > 0;
@@ -1134,8 +1136,13 @@ function CountrySelect({
index === selectedIndex && 'bg-secondary/60',
)}
>
<span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-secondary text-lg leading-none" role="img" aria-label={t('campaignsCreate.flagOfAria', { name: country.name })}>
{country.flag}
<span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-secondary leading-none">
<CountryFlag
code={country.code}
emoji={country.flag}
label={t('campaignsCreate.flagOfAria', { name: country.name })}
className="text-lg"
/>
</span>
<span className="min-w-0 flex-1">
<span className="block truncate text-sm font-semibold">{country.name}</span>
+14 -7
View File
@@ -17,6 +17,7 @@ import {
import { PersonSearch } from '@/components/PersonSearch';
import { CoverImageField } from '@/components/CoverImageField';
import { CountryFlag } from '@/components/CountryFlag';
import { FormSection } from '@/components/FormSection';
import { LoginArea } from '@/components/auth/LoginArea';
import { Alert, AlertDescription } from '@/components/ui/alert';
@@ -38,7 +39,7 @@ import {
type ParsedCommunity,
} from '@/lib/communityUtils';
import { fetchFreshEvent } from '@/lib/fetchFreshEvent';
import { COUNTRIES, searchCountries, type CountryEntry } from '@/lib/countries';
import { getCountryInfo, searchCountries, type CountryEntry } from '@/lib/countries';
import { parseContentTagInput } from '@/lib/contentTags';
import { createCountryIdentifier } from '@/lib/countryIdentifiers';
import { genUserName } from '@/lib/genUserName';
@@ -257,7 +258,7 @@ export function CreateCommunityPage() {
setImageUrl(editCommunity.community.image ?? '');
const editCountryCode = editCommunity.community.countryCode ?? '';
setCountryCode(editCountryCode);
setCountryQuery(editCountryCode ? COUNTRIES[editCountryCode]?.name ?? editCountryCode : '');
setCountryQuery(editCountryCode ? (getCountryInfo(editCountryCode)?.subdivisionName ?? getCountryInfo(editCountryCode)?.name ?? editCountryCode) : '');
setTagInput(editCommunity.community.topicTags.join(', '));
setModerators(editCommunity.community.moderatorPubkeys.map(makeProfileFromPubkey));
setPrepopulatedEventId(editCommunity.event.id);
@@ -608,8 +609,9 @@ export function CreateCommunityPage() {
selectedCode={countryCode}
onQueryChange={(value) => {
setCountryQuery(value);
const selectedCountry = countryCode ? COUNTRIES[countryCode] : undefined;
if (selectedCountry && value !== selectedCountry.name && value.toUpperCase() !== countryCode) {
const selectedCountry = countryCode ? getCountryInfo(countryCode) : undefined;
const selectedName = selectedCountry?.subdivisionName ?? selectedCountry?.name;
if (selectedCountry && value !== selectedName && value.toUpperCase() !== countryCode) {
setCountryCode('');
}
}}
@@ -729,7 +731,7 @@ function CountrySelect({
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
const selectedCountry = selectedCode ? COUNTRIES[selectedCode] : undefined;
const selectedCountry = selectedCode ? getCountryInfo(selectedCode) : undefined;
const results = useMemo(() => searchCountries(query), [query]);
const showResults = open && results.length > 0;
@@ -805,8 +807,13 @@ function CountrySelect({
index === selectedIndex && 'bg-secondary/60',
)}
>
<span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-secondary text-lg leading-none" role="img" aria-label={t('groups.create.flagOfAria', { name: country.name })}>
{country.flag}
<span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-secondary leading-none">
<CountryFlag
code={country.code}
emoji={country.flag}
label={t('groups.create.flagOfAria', { name: country.name })}
className="text-lg"
/>
</span>
<span className="min-w-0 flex-1">
<span className="block truncate text-sm font-semibold">{country.name}</span>
+3 -3
View File
@@ -41,7 +41,7 @@ import { useNotificationPreview } from '@/hooks/useNotificationPreview';
import { useUserOrganizations, type UserOrganization } from '@/hooks/useUserOrganizations';
import { satsToUSD, formatBTC } from '@/lib/bitcoin';
import { COUNTRIES } from '@/lib/countries';
import { getCountryInfo } from '@/lib/countries';
import { getDisplayName } from '@/lib/genUserName';
import { sanitizeUrl } from '@/lib/sanitizeUrl';
import { cn } from '@/lib/utils';
@@ -533,8 +533,8 @@ function CountriesSection({
) : followedCountries.length > 0 ? (
<div className="flex gap-2 flex-wrap pb-1">
{followedCountries.map((code) => {
const info = COUNTRIES[code];
const name = info?.name ?? code;
const info = getCountryInfo(code);
const name = info?.subdivisionName ?? info?.name ?? code;
const flag = info?.flag ?? '';
return (