1+ /* eslint-disable @typescript-eslint/no-explicit-any */
2+ 'use client' ;
3+
4+ import { useState } from 'react' ;
5+ import { Control , Controller , FieldError } from 'react-hook-form' ;
6+ import {
7+ Popover ,
8+ PopoverContent ,
9+ PopoverTrigger ,
10+ } from '@/components/ui/popover' ;
11+ import {
12+ Command ,
13+ CommandEmpty ,
14+ CommandGroup ,
15+ CommandInput ,
16+ CommandItem ,
17+ CommandList ,
18+ } from '@/components/ui/command' ;
19+ import { Button } from '@/components/ui/button' ;
20+ import { Label } from '@/components/ui/label' ;
21+ import { Check , ChevronsUpDown } from 'lucide-react' ;
22+ import { cn } from '@/lib/utils' ;
23+ import countryList from 'react-select-country-list' ;
24+
25+ type CountrySelectProps = {
26+ name : string ;
27+ label : string ;
28+ control : Control < any > ;
29+ error ?: FieldError ;
30+ required ?: boolean ;
31+ } ;
32+
33+ const CountrySelect = ( {
34+ value,
35+ onChange,
36+ } : {
37+ value : string ;
38+ onChange : ( value : string ) => void ;
39+ } ) => {
40+ const [ open , setOpen ] = useState ( false ) ;
41+
42+ // Get country options with flags
43+ const countries = countryList ( ) . getData ( ) ;
44+
45+ // Helper function to get flag emoji
46+ const getFlagEmoji = ( countryCode : string ) => {
47+ const codePoints = countryCode
48+ . toUpperCase ( )
49+ . split ( '' )
50+ . map ( ( char ) => 127397 + char . charCodeAt ( 0 ) ) ;
51+ return String . fromCodePoint ( ...codePoints ) ;
52+ } ;
53+
54+ return (
55+ < Popover open = { open } onOpenChange = { setOpen } >
56+ < PopoverTrigger asChild >
57+ < Button
58+ variant = 'outline'
59+ role = 'combobox'
60+ aria-expanded = { open }
61+ className = 'country-select-trigger'
62+ >
63+ { value ? (
64+ < span className = 'flex items-center gap-2' >
65+ < span > { getFlagEmoji ( value ) } </ span >
66+ < span > { countries . find ( ( c ) => c . value === value ) ?. label } </ span >
67+ </ span >
68+ ) : (
69+ 'Select your country...'
70+ ) }
71+ < ChevronsUpDown className = 'ml-2 h-4 w-4 shrink-0 opacity-50' />
72+ </ Button >
73+ </ PopoverTrigger >
74+ < PopoverContent
75+ className = 'w-full p-0 bg-gray-800 border-gray-600'
76+ align = 'start'
77+ >
78+ < Command className = 'bg-gray-800 border-gray-600' >
79+ < CommandInput
80+ placeholder = 'Search countries...'
81+ className = 'country-select-input'
82+ />
83+ < CommandEmpty className = 'country-select-empty' >
84+ No country found.
85+ </ CommandEmpty >
86+ < CommandList className = 'max-h-60 bg-gray-800 scrollbar-hide-default' >
87+ < CommandGroup className = 'bg-gray-800' >
88+ { countries . map ( ( country ) => (
89+ < CommandItem
90+ key = { country . value }
91+ value = { `${ country . label } ${ country . value } ` }
92+ onSelect = { ( ) => {
93+ onChange ( country . value ) ;
94+ setOpen ( false ) ;
95+ } }
96+ className = 'country-select-item'
97+ >
98+ < Check
99+ className = { cn (
100+ 'mr-2 h-4 w-4 text-yellow-500' ,
101+ value === country . value ? 'opacity-100' : 'opacity-0'
102+ ) }
103+ />
104+ < span className = 'flex items-center gap-2' >
105+ < span > { getFlagEmoji ( country . value ) } </ span >
106+ < span > { country . label } </ span >
107+ </ span >
108+ </ CommandItem >
109+ ) ) }
110+ </ CommandGroup >
111+ </ CommandList >
112+ </ Command >
113+ </ PopoverContent >
114+ </ Popover >
115+ ) ;
116+ } ;
117+
118+ export const CountrySelectField = ( {
119+ name,
120+ label,
121+ control,
122+ error,
123+ required = false ,
124+ } : CountrySelectProps ) => {
125+ return (
126+ < div className = 'space-y-2' >
127+ < Label htmlFor = { name } className = 'form-label' >
128+ { label }
129+ </ Label >
130+ < Controller
131+ name = { name }
132+ control = { control }
133+ rules = { {
134+ required : required ? `Please select ${ label . toLowerCase ( ) } ` : false ,
135+ } }
136+ render = { ( { field } ) => (
137+ < CountrySelect value = { field . value } onChange = { field . onChange } />
138+ ) }
139+ />
140+ { error && < p className = 'text-sm text-red-500' > { error . message } </ p > }
141+ < p className = 'text-xs text-gray-500' >
142+ Helps us show market data and news relevant to you.
143+ </ p >
144+ </ div >
145+ ) ;
146+ } ;
0 commit comments