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:
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.3 KiB |
@@ -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 && (
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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('')
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user