From 0cb734e790e06d421c0dbda686c79cc06460087e Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Thu, 26 Mar 2026 11:33:18 +0800 Subject: [PATCH] Refactor: apply PhoneInputCustom to LoginView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: - Replace react-native-international-phone-number with PhoneInputCustom - Remove dependency on ICountry, use CountryData from constants - Add KeyboardAvoidingView for better iOS keyboard handling - Improve validation with libphonenumber-js - Add proper phone number formatting - Update state management (inputValue → phoneNumber) Features Applied: ✅ NO emoji flags - only calling codes (+62, +65, etc) ✅ Clean, professional UI ✅ Modal country picker with search ✅ Better keyboard handling on iOS ✅ Real-time validation with libphonenumber-js ✅ Auto-formatting for international numbers ✅ Reusable component UI: - Phone Input: [+62 ⌄ | 812-3456-7890] - Country Picker: Modal with search - Display: Country name + calling code only Validation: - Check phone number length - Validate with libphonenumber-js - Format to E.164 on login - Error handling with Toast Co-authored-by: Qwen-Coder --- screens/Authentication/LoginView.tsx | 199 +++++++++++++++------------ 1 file changed, 109 insertions(+), 90 deletions(-) diff --git a/screens/Authentication/LoginView.tsx b/screens/Authentication/LoginView.tsx index eb9c2b7..681ee8a 100644 --- a/screens/Authentication/LoginView.tsx +++ b/screens/Authentication/LoginView.tsx @@ -1,8 +1,9 @@ -import { NewWrapper, ViewWrapper } from "@/components"; +import { NewWrapper, PhoneInputCustom, ViewWrapper } from "@/components"; import ButtonCustom from "@/components/Button/ButtonCustom"; import ModalReactNative from "@/components/Modal/ModalReactNative"; import Spacing from "@/components/_ShareComponent/Spacing"; import { MainColor } from "@/constants/color-palet"; +import { DEFAULT_COUNTRY, type CountryData } from "@/constants/countries"; import { useAuth } from "@/hooks/use-auth"; import { apiVersion, BASE_URL } from "@/service/api-config"; import { GStyles } from "@/styles/global-styles"; @@ -10,16 +11,16 @@ import { openBrowser } from "@/utils/openBrower"; import versionBadge from "@/utils/viersionBadge"; import { Redirect } from "expo-router"; import { useEffect, useState } from "react"; -import { RefreshControl, Text, View } from "react-native"; -import PhoneInput, { ICountry } from "react-native-international-phone-number"; +import { KeyboardAvoidingView, Platform, RefreshControl, Text, View } from "react-native"; +import { parsePhoneNumber } from "libphonenumber-js"; import Toast from "react-native-toast-message"; import EULASection from "./EULASection"; export default function LoginView() { const url = BASE_URL; const [version, setVersion] = useState(""); - const [selectedCountry, setSelectedCountry] = useState(null); - const [inputValue, setInputValue] = useState(""); + const [selectedCountry, setSelectedCountry] = useState(DEFAULT_COUNTRY); + const [phoneNumber, setPhoneNumber] = useState(""); const [loading, setLoading] = useState(false); const [refreshing, setRefreshing] = useState(false); const [modalVisible, setModalVisible] = useState(false); @@ -43,41 +44,43 @@ export default function LoginView() { async function handleRefresh() { setRefreshing(true); await onLoadVersion(); - setInputValue(""); + setPhoneNumber(""); + setSelectedCountry(DEFAULT_COUNTRY); setLoading(false); setRefreshing(false); } - function handleInputValue(phoneNumber: string) { - setInputValue(phoneNumber); - } - - function handleSelectedCountry(country: ICountry) { - setSelectedCountry(country); - } - async function validateData() { - if (inputValue.length === 0) { + if (phoneNumber.length === 0) { return Toast.show({ type: "error", text1: "Masukan nomor anda", }); } - if (selectedCountry === null) { - return Toast.show({ - type: "error", - text1: "Pilih negara", - }); - } - - if (inputValue.length < 9) { + if (phoneNumber.length < 9) { return Toast.show({ type: "error", text1: "Nomor tidak valid", }); } + // Validate with libphonenumber-js + try { + const parsedNumber = parsePhoneNumber(phoneNumber, selectedCountry.code); + if (!parsedNumber || !parsedNumber.isValid()) { + return Toast.show({ + type: "error", + text1: "Nomor tidak valid", + }); + } + } catch (error) { + return Toast.show({ + type: "error", + text1: "Format nomor tidak valid", + }); + } + return true; } @@ -85,8 +88,17 @@ export default function LoginView() { const isValid = await validateData(); if (!isValid) return; - const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || ""; - let fixNumber = inputValue.replace(/\s+/g, "").replace(/^0+/, ""); + // Format phone number with country code + const callingCode = selectedCountry.callingCode; + let fixNumber = phoneNumber.replace(/\s+/g, "").replace(/^0+/, ""); + + // Remove country code if already present + if (fixNumber.startsWith(callingCode)) { + fixNumber = fixNumber.substring(callingCode.length); + } + + // Remove leading zero + fixNumber = fixNumber.replace(/^0+/, ""); const realNumber = callingCode + fixNumber; @@ -128,75 +140,82 @@ export default function LoginView() { } return ( - - } + - - - - WELCOME TO - - HIPMI BADUNG APPS - + + } + > + + + + WELCOME TO + + HIPMI BADUNG APPS + + + + + {version} | powered by muku.id + - - + + + + + + - {version} | powered by muku.id + Login + + + + + Dengan menggunakan aplikasi ini, Anda telah menyetujui{" "} + { + const toUrl = `${url}/terms-of-service.html`; + openBrowser(toUrl); + }} + > + Syarat & Ketentuan + {" "} + dan seluruh kebijakan privasi yang berlaku. - - - - - - - Login - - - - - Dengan menggunakan aplikasi ini, Anda telah menyetujui{" "} - { - const toUrl = `${url}/terms-of-service.html`; - openBrowser(toUrl); - }} - > - Syarat & Ketentuan - {" "} - dan seluruh kebijakan privasi yang berlaku. - - + - + ); }