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
+
+ )}
+
+ )}
+
+ );
+};