diff --git a/apps/hackathon/src/app/onboarding/user/page.tsx b/apps/hackathon/src/app/onboarding/user/page.tsx index 0b4e653..f349283 100644 --- a/apps/hackathon/src/app/onboarding/user/page.tsx +++ b/apps/hackathon/src/app/onboarding/user/page.tsx @@ -13,7 +13,7 @@ import { } from '@imphnen-frontend-service/service'; import { zodResolver } from '@hookform/resolvers/zod'; -import INDONESIAN_CITIES from '../../../constants/cities'; +import { CitySelect } from '../../../components/city-select'; const ROLE_OPTIONS = [ 'Frontend Developer', @@ -172,24 +172,12 @@ const UserOnboardingPage: FC = (): ReactElement => { control={form.control} name="location" render={({ field, fieldState }) => ( -
- - {fieldState.error && ( -

- {fieldState.error.message} -

- )} -
+ )} /> diff --git a/apps/hackathon/src/components/city-select.tsx b/apps/hackathon/src/components/city-select.tsx new file mode 100644 index 0000000..5af998a --- /dev/null +++ b/apps/hackathon/src/components/city-select.tsx @@ -0,0 +1,115 @@ +import { FC, useState, useRef, useEffect } from 'react'; +import INDONESIAN_CITIES from '../constants/cities'; + +interface CitySelectProps { + value: string; + onChange: (value: string) => void; + error?: string; + placeholder?: string; +} + +export const CitySelect: FC = ({ + value, + onChange, + error, + placeholder = 'Search your city...', +}) => { + const [isOpen, setIsOpen] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); + const dropdownRef = useRef(null); + const inputRef = useRef(null); + + // Filter cities based on search query + const filteredCities = INDONESIAN_CITIES.filter((city) => + city.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) + ) { + setIsOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + const handleSelectCity = (city: string) => { + onChange(city); + setSearchQuery(''); + setIsOpen(false); + }; + + const handleInputClick = () => { + setIsOpen(true); + setSearchQuery(''); + }; + + const handleClearSelection = () => { + onChange(''); + setSearchQuery(''); + setIsOpen(true); + inputRef.current?.focus(); + }; + + return ( +
+
+ { + setSearchQuery(e.target.value); + setIsOpen(true); + }} + onClick={handleInputClick} + placeholder={placeholder} + className={`w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent ${ + error ? 'border-red-500' : 'border-gray-300' + }`} + /> + {value && !isOpen && ( + + )} +
+ + {error &&

{error}

} + + {isOpen && ( +
+ {filteredCities.length > 0 ? ( +
    + {filteredCities.map((city) => ( +
  • handleSelectCity(city)} + className={`px-3 py-2 cursor-pointer hover:bg-blue-50 ${ + value === city ? 'bg-blue-100 text-blue-700' : '' + }`} + > + {city} +
  • + ))} +
+ ) : ( +
+ No cities found +
+ )} +
+ )} +
+ ); +};