diff --git a/.qwen/settings.json b/.qwen/settings.json
new file mode 100644
index 0000000..322d785
--- /dev/null
+++ b/.qwen/settings.json
@@ -0,0 +1,7 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(git add *)"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/components/PhoneInput/PhoneInputCustom.tsx b/components/PhoneInput/PhoneInputCustom.tsx
new file mode 100644
index 0000000..f385646
--- /dev/null
+++ b/components/PhoneInput/PhoneInputCustom.tsx
@@ -0,0 +1,259 @@
+import { MainColor } from "@/constants/color-palet";
+import {
+ COUNTRIES,
+ DEFAULT_COUNTRY,
+ searchCountries,
+ type CountryData,
+} from "@/constants/countries";
+import { useState } from "react";
+import {
+ Modal,
+ ScrollView,
+ StyleSheet,
+ Text,
+ TextInput,
+ TouchableOpacity,
+ View,
+} from "react-native";
+
+interface PhoneInputProps {
+ value: string;
+ onChangePhoneNumber: (phone: string) => void;
+ selectedCountry?: CountryData;
+ onChangeCountry: (country: CountryData) => void;
+ placeholder?: string;
+ disabled?: boolean;
+}
+
+export default function PhoneInputCustom({
+ value,
+ onChangePhoneNumber,
+ selectedCountry = DEFAULT_COUNTRY,
+ onChangeCountry,
+ placeholder = "Masukkan nomor",
+ disabled = false,
+}: PhoneInputProps) {
+ const [countryPickerVisible, setCountryPickerVisible] = useState(false);
+ const [searchQuery, setSearchQuery] = useState("");
+
+ const filteredCountries = searchCountries(searchQuery);
+
+ const handleSelectCountry = (country: CountryData) => {
+ onChangeCountry(country);
+ setCountryPickerVisible(false);
+ setSearchQuery("");
+ };
+
+ const handlePhoneChange = (text: string) => {
+ // Only allow numbers and spaces
+ const cleaned = text.replace(/[^\d\s]/g, "");
+ onChangePhoneNumber(cleaned);
+ };
+
+ return (
+ <>
+ {/* Phone Input Field */}
+
+ setCountryPickerVisible(true)}
+ disabled={disabled}
+ activeOpacity={0.7}
+ >
+ +{selectedCountry.callingCode}
+ ⌄
+
+
+
+
+
+
+
+ {/* Country Picker Modal */}
+ setCountryPickerVisible(false)}
+ >
+
+
+
+ Pilih Negara
+ setCountryPickerVisible(false)}>
+ ✕
+
+
+
+
+
+
+ {filteredCountries.map((country) => (
+ handleSelectCountry(country)}
+ activeOpacity={0.7}
+ >
+
+ {country.name}
+ +{country.callingCode}
+
+ {selectedCountry.code === country.code && (
+ ✓
+ )}
+
+ ))}
+
+
+
+
+ >
+ );
+}
+
+const styles = StyleSheet.create({
+ // Container
+ container: {
+ flexDirection: "row",
+ backgroundColor: MainColor.white,
+ borderRadius: 8,
+ borderWidth: 1,
+ borderColor: MainColor.white_gray,
+ marginBottom: 16,
+ overflow: "hidden",
+ },
+ // Country Picker Button
+ countryPickerButton: {
+ flexDirection: "row",
+ alignItems: "center",
+ paddingHorizontal: 16,
+ paddingVertical: 14,
+ backgroundColor: MainColor.text_input,
+ borderRightWidth: 1,
+ borderRightColor: MainColor.white_gray,
+ },
+ countryCodeText: {
+ fontSize: 16,
+ color: MainColor.black,
+ fontWeight: "600",
+ },
+ dropdownIcon: {
+ fontSize: 18,
+ color: MainColor.placeholder,
+ marginLeft: 4,
+ },
+ // Divider
+ divider: {
+ width: 1,
+ backgroundColor: MainColor.white_gray,
+ },
+ // Phone Input
+ phoneInput: {
+ flex: 1,
+ paddingVertical: 14,
+ paddingHorizontal: 12,
+ fontSize: 16,
+ color: MainColor.black,
+ },
+ disabledInput: {
+ backgroundColor: MainColor.text_input,
+ color: MainColor.placeholder,
+ },
+ // Modal
+ modalOverlay: {
+ flex: 1,
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
+ justifyContent: "flex-end",
+ },
+ modalContent: {
+ backgroundColor: MainColor.white,
+ borderTopLeftRadius: 20,
+ borderTopRightRadius: 20,
+ maxHeight: "80%",
+ paddingBottom: 34,
+ },
+ modalHeader: {
+ flexDirection: "row",
+ justifyContent: "space-between",
+ alignItems: "center",
+ padding: 20,
+ borderBottomWidth: 1,
+ borderBottomColor: MainColor.white_gray,
+ },
+ modalTitle: {
+ fontSize: 18,
+ fontWeight: "bold",
+ color: MainColor.black,
+ },
+ modalClose: {
+ fontSize: 24,
+ color: MainColor.placeholder,
+ padding: 5,
+ },
+ // Search Input
+ searchInput: {
+ backgroundColor: MainColor.text_input,
+ margin: 16,
+ padding: 12,
+ borderRadius: 8,
+ fontSize: 16,
+ color: MainColor.black,
+ },
+ // Country List
+ countryList: {
+ paddingHorizontal: 16,
+ },
+ countryItem: {
+ flexDirection: "row",
+ justifyContent: "space-between",
+ alignItems: "center",
+ paddingVertical: 12,
+ paddingHorizontal: 12,
+ borderBottomWidth: 1,
+ borderBottomColor: MainColor.white_gray,
+ },
+ countryItemSelected: {
+ backgroundColor: MainColor.soft_darkblue + "15",
+ },
+ countryInfo: {
+ flex: 1,
+ },
+ countryName: {
+ fontSize: 16,
+ color: MainColor.black,
+ fontWeight: "500",
+ },
+ countryCode: {
+ fontSize: 14,
+ color: MainColor.placeholder,
+ marginTop: 2,
+ },
+ checkmark: {
+ fontSize: 20,
+ color: MainColor.green,
+ fontWeight: "bold",
+ },
+});
diff --git a/components/index.ts b/components/index.ts
index 27e7c1f..d94d80e 100644
--- a/components/index.ts
+++ b/components/index.ts
@@ -49,6 +49,8 @@ import MapCustom from "./Map/MapCustom";
import CenterCustom from "./Center/CenterCustom";
// Clickable
import ClickableCustom from "./Clickable/ClickableCustom";
+// PhoneInput
+import PhoneInputCustom from "./PhoneInput/PhoneInputCustom";
// Scroll
import ScrollableCustom from "./Scroll/ScrollCustom";
// ShareComponent
@@ -95,6 +97,8 @@ export {
CheckboxGroup,
// Clickable
ClickableCustom,
+ // PhoneInput
+ PhoneInputCustom,
// Container
CircleContainer,
// Divider
diff --git a/constants/countries.ts b/constants/countries.ts
new file mode 100644
index 0000000..f0b3d30
--- /dev/null
+++ b/constants/countries.ts
@@ -0,0 +1,89 @@
+import { type CountryCode } from "libphonenumber-js";
+
+/**
+ * Country data for phone number input
+ * Contains only country name and calling code (NO flags for maximum compatibility)
+ */
+export interface CountryData {
+ code: CountryCode;
+ name: string;
+ callingCode: string;
+}
+
+/**
+ * List of supported countries for phone number input
+ *
+ * @description
+ * This list includes major countries across different regions.
+ * Countries are ordered by likelihood of use (Indonesia first as default).
+ *
+ * @note
+ * NO emoji flags used - only text-based country name and calling code
+ * This ensures maximum compatibility across all platforms and iOS versions
+ */
+export const COUNTRIES: CountryData[] = [
+ // Asia Pacific (Primary markets)
+ { code: "ID", name: "Indonesia", callingCode: "62" },
+ { code: "SG", name: "Singapore", callingCode: "65" },
+ { code: "MY", name: "Malaysia", callingCode: "60" },
+ { code: "AU", name: "Australia", callingCode: "61" },
+
+ // Asia (Other)
+ { code: "CN", name: "China", callingCode: "86" },
+ { code: "JP", name: "Japan", callingCode: "81" },
+ { code: "KR", name: "South Korea", callingCode: "82" },
+ { code: "IN", name: "India", callingCode: "91" },
+
+ // Middle East
+ { code: "AE", name: "United Arab Emirates", callingCode: "971" },
+ { code: "SA", name: "Saudi Arabia", callingCode: "966" },
+
+ // Europe
+ { code: "GB", name: "United Kingdom", callingCode: "44" },
+ { code: "DE", name: "Germany", callingCode: "49" },
+ { code: "FR", name: "France", callingCode: "33" },
+ { code: "NL", name: "Netherlands", callingCode: "31" },
+
+ // Americas
+ { code: "US", name: "United States", callingCode: "1" },
+];
+
+/**
+ * Default country for phone number input
+ * Used when no country is selected (Indonesia by default)
+ */
+export const DEFAULT_COUNTRY: CountryData = COUNTRIES[0];
+
+/**
+ * Get country by calling code
+ * @param callingCode - The calling code to search for (e.g., "62", "1")
+ * @returns The matching country data or undefined if not found
+ */
+export function getCountryByCallingCode(callingCode: string): CountryData | undefined {
+ return COUNTRIES.find((country) => country.callingCode === callingCode);
+}
+
+/**
+ * Get country by country code (ISO 3166-1 alpha-2)
+ * @param code - The country code to search for (e.g., "ID", "US")
+ * @returns The matching country data or undefined if not found
+ */
+export function getCountryByCode(code: CountryCode): CountryData | undefined {
+ return COUNTRIES.find((country) => country.code === code);
+}
+
+/**
+ * Search countries by name or calling code
+ * @param query - The search query (case-insensitive)
+ * @returns Array of matching countries
+ */
+export function searchCountries(query: string): CountryData[] {
+ const normalizedQuery = query.toLowerCase().trim();
+
+ return COUNTRIES.filter(
+ (country) =>
+ country.name.toLowerCase().includes(normalizedQuery) ||
+ country.code.toLowerCase().includes(normalizedQuery) ||
+ country.callingCode.includes(normalizedQuery)
+ );
+}