From c1247f97f431cfa3079642749b25358a6e16e514 Mon Sep 17 00:00:00 2001 From: leoAshu Date: Wed, 27 Aug 2025 22:01:33 -0400 Subject: [PATCH 1/3] updates bg color for input fields and dropdown --- apps/frontend/app/globals.css | 16 +++--- apps/frontend/components/filters/Dropdown.tsx | 52 ++++++++++++++----- .../components/form/SecondaryInputField.tsx | 11 ++-- .../components/sections/VerifyIdentity.tsx | 1 + apps/frontend/hooks/appData/useCountry.tsx | 4 +- apps/frontend/types/props.d.ts | 5 +- 6 files changed, 60 insertions(+), 29 deletions(-) diff --git a/apps/frontend/app/globals.css b/apps/frontend/app/globals.css index 8a06b71..5e9c932 100644 --- a/apps/frontend/app/globals.css +++ b/apps/frontend/app/globals.css @@ -52,9 +52,9 @@ @apply gap-y-4; } - .input-wrapper { + /* .input-wrapper { @apply w-full; - } + } */ .input-label { @apply pl-1 font-clashDisplay; @@ -497,7 +497,7 @@ } .deposit-form-value-wrapper { - @apply flex-1 rounded-l-lg bg-card px-4 py-3 dark:bg-card-dark; + @apply flex-1 rounded-l-lg bg-base-white px-4 py-3 dark:bg-base-dark; } .deposit-form-value { @@ -505,7 +505,7 @@ } .deposit-form-icon-wrapper { - @apply w-14 items-center justify-center rounded-r-lg bg-base-white dark:bg-base-dark; + @apply w-14 items-center justify-center rounded-r-lg bg-card dark:bg-card-dark; } .deposit-form-footer { @@ -545,7 +545,7 @@ } .dropdown-wrapper { - @apply flex-row rounded-lg border border-stroke dark:border-stroke-dark; + @apply w-full flex-row rounded-lg border border-stroke dark:border-stroke-dark; } .dropdown-label { @@ -553,7 +553,7 @@ } .dropdown-value-wrapper { - @apply flex-1 rounded-l-lg bg-card px-4 py-3 dark:bg-card-dark; + @apply flex-1 rounded-l-lg bg-base-white px-4 py-3 dark:bg-base-dark; } .dropdown-value { @@ -565,7 +565,7 @@ } .dropdown-icon-wrapper { - @apply w-14 items-center justify-center rounded-r-lg bg-base-white dark:bg-base-dark; + @apply w-14 items-center justify-center rounded-r-lg bg-card dark:bg-card-dark; } .dropdown-error { @@ -573,7 +573,7 @@ } .dropdown-options-wrapper { - @apply rounded-lg border-[0.5px] border-stroke bg-card shadow-lg dark:border-stroke-dark dark:bg-card-dark; + @apply rounded-lg border-[0.5px] border-stroke bg-card shadow-lg dark:border-stroke-dark dark:bg-card-info-dark; } .dropdown-option { diff --git a/apps/frontend/components/filters/Dropdown.tsx b/apps/frontend/components/filters/Dropdown.tsx index dd77e1e..16de832 100644 --- a/apps/frontend/components/filters/Dropdown.tsx +++ b/apps/frontend/components/filters/Dropdown.tsx @@ -12,8 +12,10 @@ const Dropdown = (props: DropdownProps) => { const animationProgress = useSharedValue(0); + const showIcon = props.showIcon ?? 'true'; const value = props.items.find((item) => item.id === props.value?.id); const displayValue = value?.label || props.placeholder || 'Select an option'; + const disabled = props.disabled ?? props.items.length === 1; const handleSelect = (item: FilterItem) => { handleCloseDropdown(); @@ -23,7 +25,7 @@ const Dropdown = (props: DropdownProps) => { }; const handleOpenDropdown = () => { - if (props.disabled) return; + if (disabled) return; setIsOpen(true); requestAnimationFrame(() => { requestAnimationFrame(() => { @@ -59,14 +61,16 @@ const Dropdown = (props: DropdownProps) => { - + { > {displayValue} - - - + {value?.secondaryLabel && ( + + {value?.secondaryLabel} + + )} + + {showIcon && ( + + + + )} {props.error && {props.error}} {isOpen && ( @@ -96,7 +112,7 @@ const Dropdown = (props: DropdownProps) => { renderItem={({ item, index }) => ( { {item.label} + {value?.secondaryLabel && ( + + {value?.secondaryLabel} + + )} )} showsVerticalScrollIndicator={false} diff --git a/apps/frontend/components/form/SecondaryInputField.tsx b/apps/frontend/components/form/SecondaryInputField.tsx index 787984f..e2a6196 100644 --- a/apps/frontend/components/form/SecondaryInputField.tsx +++ b/apps/frontend/components/form/SecondaryInputField.tsx @@ -11,7 +11,7 @@ const SecondaryInputField = (props: SecondaryInputFieldProps) => { return ( - {props.label} + {props.label && {props.label}} { > { {props.secondarylabel && ( diff --git a/apps/frontend/components/sections/VerifyIdentity.tsx b/apps/frontend/components/sections/VerifyIdentity.tsx index 450aad9..13459c3 100644 --- a/apps/frontend/components/sections/VerifyIdentity.tsx +++ b/apps/frontend/components/sections/VerifyIdentity.tsx @@ -23,6 +23,7 @@ const VerifyIdentity = () => { updateKyc('countryId', item.id)} diff --git a/apps/frontend/hooks/appData/useCountry.tsx b/apps/frontend/hooks/appData/useCountry.tsx index 0389a4f..76e844f 100644 --- a/apps/frontend/hooks/appData/useCountry.tsx +++ b/apps/frontend/hooks/appData/useCountry.tsx @@ -7,10 +7,10 @@ const useCountry = () => { const getCountryById = (countryId: string) => countries.find((country) => country.id === countryId); const createFilterItems = (countries: Country[], labelKey: 'name'): FilterItem[] => [ - ...countries.map((c) => ({ id: c.id, label: c[labelKey] })), + ...countries.map((c) => ({ id: c.id, label: c[labelKey], secondaryLabel: `(${c.phoneCode})` })), ]; - const countryNameFilterItems = useMemo(() => createFilterItems(countries, 'name'), [countries]); + const countryNameFilterItems: FilterItem[] = useMemo(() => createFilterItems(countries, 'name'), [countries]); const getCountryNameFilterItemById = useMemo( () => (id: string) => countryNameFilterItems.find((c) => c.id === id), diff --git a/apps/frontend/types/props.d.ts b/apps/frontend/types/props.d.ts index b41059e..ca796b5 100644 --- a/apps/frontend/types/props.d.ts +++ b/apps/frontend/types/props.d.ts @@ -174,6 +174,7 @@ interface WalletCardProps { interface FilterItem { id: string; label: string; + secondaryLabel?: string; } // ==================== @@ -191,6 +192,8 @@ interface DropdownProps { containerStyle?: string; buttonStyle?: string; textStyle?: string; + right?: JSX.Element; + showIcon?: boolean; onSelect?: (item: FilterItem) => void; } @@ -278,7 +281,7 @@ interface ListEmptyStateProps { } interface SecondaryInputFieldProps { - label: string; + label?: string; value?: string; placeholder?: string; secondarylabel?: string; From 1cb72beaca1e08dc7cb6139ca487bc298c677688 Mon Sep 17 00:00:00 2001 From: leoAshu Date: Wed, 27 Aug 2025 22:50:46 -0400 Subject: [PATCH 2/3] designs PhoneInputField component --- apps/frontend/app/(tabs)/(profile)/edit.tsx | 13 +++++--- apps/frontend/components/filters/Dropdown.tsx | 24 ++++++++------ .../components/form/PhoneInputField.tsx | 32 +++++++++++++++++++ apps/frontend/components/form/index.ts | 1 + .../components/sections/VerifyIdentity.tsx | 5 +-- .../components/sections/VerifyPhone.tsx | 12 +++---- apps/frontend/hooks/appData/useCountry.tsx | 23 ++++++++++--- apps/frontend/types/props.d.ts | 7 ++++ apps/frontend/utils/index.ts | 20 +++--------- 9 files changed, 95 insertions(+), 42 deletions(-) create mode 100644 apps/frontend/components/form/PhoneInputField.tsx diff --git a/apps/frontend/app/(tabs)/(profile)/edit.tsx b/apps/frontend/app/(tabs)/(profile)/edit.tsx index 7247e12..4b22e36 100644 --- a/apps/frontend/app/(tabs)/(profile)/edit.tsx +++ b/apps/frontend/app/(tabs)/(profile)/edit.tsx @@ -1,15 +1,17 @@ -import { InitialsAvatar, InputField, PrimaryButton } from '@/components'; +import { InitialsAvatar, InputField, PhoneInputField, PrimaryButton } from '@/components'; import { AlertStrings, Strings } from '@/constants'; -import { useProfile } from '@/hooks'; +import { useCountry, useProfile } from '@/hooks'; import { useAuthStore } from '@/store'; -import { formatPhoneNumber, validateName } from '@/utils'; +import { validateName } from '@/utils'; import { useState } from 'react'; import { Alert, KeyboardAvoidingView, Platform, ScrollView, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; const Edit = () => { const { user } = useAuthStore(); + const { currentCountry } = useCountry(); const { isLoading, profile, updateProfile } = useProfile(); + const [formData, setFormData] = useState>(profile ?? {}); const updateInfo = (key: string, value: string) => { @@ -53,9 +55,10 @@ const Edit = () => { - diff --git a/apps/frontend/components/filters/Dropdown.tsx b/apps/frontend/components/filters/Dropdown.tsx index 16de832..a362d16 100644 --- a/apps/frontend/components/filters/Dropdown.tsx +++ b/apps/frontend/components/filters/Dropdown.tsx @@ -59,20 +59,23 @@ const Dropdown = (props: DropdownProps) => { {props.title && {props.title}} @@ -81,7 +84,10 @@ const Dropdown = (props: DropdownProps) => { {value?.secondaryLabel && ( diff --git a/apps/frontend/components/form/PhoneInputField.tsx b/apps/frontend/components/form/PhoneInputField.tsx new file mode 100644 index 0000000..eec0282 --- /dev/null +++ b/apps/frontend/components/form/PhoneInputField.tsx @@ -0,0 +1,32 @@ +import { useCountry } from '@/hooks'; +import { formatPhoneNumber } from '@/utils'; +import { Text, View } from 'react-native'; +import { Dropdown } from '../filters'; +import SecondaryInputField from './SecondaryInputField'; + +const PhoneInputField = (props: PhoneInputFieldProps) => { + const { countryPhoneCodeFilterItems, getCountryById, getCountryFilterItemById } = useCountry(); + const countryFilter = getCountryFilterItemById(countryPhoneCodeFilterItems, props.countryId ?? ''); + const disabled = props.disabled ?? false; + + return ( + + {props.label && {props.label}} + + + + + + + + + ); +}; + +export default PhoneInputField; diff --git a/apps/frontend/components/form/index.ts b/apps/frontend/components/form/index.ts index 947cc3b..e0a8d78 100644 --- a/apps/frontend/components/form/index.ts +++ b/apps/frontend/components/form/index.ts @@ -1,4 +1,5 @@ export { default as FileUplaod } from './FileUpload'; export { default as InputField } from './InputField'; +export { default as PhoneInputField } from './PhoneInputField'; export { default as SecondaryInputField } from './SecondaryInputField'; export { default as StepperInput } from './StepperInput'; diff --git a/apps/frontend/components/sections/VerifyIdentity.tsx b/apps/frontend/components/sections/VerifyIdentity.tsx index 13459c3..c2578e0 100644 --- a/apps/frontend/components/sections/VerifyIdentity.tsx +++ b/apps/frontend/components/sections/VerifyIdentity.tsx @@ -6,9 +6,9 @@ import FileUpload from '../form/FileUpload'; const VerifyIdentity = () => { const { kyc, updateKyc } = useKyc(); - const { countryNameFilterItems, getCountryNameFilterItemById } = useCountry(); + const { countryNameFilterItems, getCountryFilterItemById } = useCountry(); - const country = getCountryNameFilterItemById(kyc?.countryId ?? ''); + const country = getCountryFilterItemById(countryNameFilterItems, kyc?.countryId ?? ''); return ( <> @@ -26,6 +26,7 @@ const VerifyIdentity = () => { disabled={false} title={Strings.info.COUNTRY_LABEL} items={countryNameFilterItems} + placeholder={Strings.info.COUNTRY_HINT} onSelect={(item) => updateKyc('countryId', item.id)} /> diff --git a/apps/frontend/components/sections/VerifyPhone.tsx b/apps/frontend/components/sections/VerifyPhone.tsx index e63a1f0..ea2869f 100644 --- a/apps/frontend/components/sections/VerifyPhone.tsx +++ b/apps/frontend/components/sections/VerifyPhone.tsx @@ -1,17 +1,17 @@ import { Strings } from '@/constants'; -import { useKyc } from '@/hooks'; +import { useCountry, useKyc } from '@/hooks'; import { formatPhoneNumber } from '@/utils'; -import { SecondaryInputField } from '../form'; +import { PhoneInputField } from '../form'; const VerifyPhone = () => { const { kyc } = useKyc(); + const { currentCountry } = useCountry(); return ( - ); diff --git a/apps/frontend/hooks/appData/useCountry.tsx b/apps/frontend/hooks/appData/useCountry.tsx index 76e844f..3d8f8d6 100644 --- a/apps/frontend/hooks/appData/useCountry.tsx +++ b/apps/frontend/hooks/appData/useCountry.tsx @@ -6,14 +6,26 @@ const useCountry = () => { const getCountryById = (countryId: string) => countries.find((country) => country.id === countryId); - const createFilterItems = (countries: Country[], labelKey: 'name'): FilterItem[] => [ - ...countries.map((c) => ({ id: c.id, label: c[labelKey], secondaryLabel: `(${c.phoneCode})` })), + const createFilterItems = ( + countries: Country[], + labelKey: keyof Country, + secondaryLabelKey?: keyof Country, + ): FilterItem[] => [ + ...countries.map((c) => ({ + id: c.id, + label: String(c[labelKey] ?? ''), + secondaryLabel: secondaryLabelKey ? `(${String(c[secondaryLabelKey] ?? '')})` : undefined, + })), ]; const countryNameFilterItems: FilterItem[] = useMemo(() => createFilterItems(countries, 'name'), [countries]); + const countryPhoneCodeFilterItems: FilterItem[] = useMemo( + () => createFilterItems(countries, 'iso3', 'phoneCode'), + [countries], + ); - const getCountryNameFilterItemById = useMemo( - () => (id: string) => countryNameFilterItems.find((c) => c.id === id), + const getCountryFilterItemById = useMemo( + () => (countryFilters: FilterItem[], id: string) => countryNameFilterItems.find((c) => c.id === id), [countryNameFilterItems], ); @@ -21,8 +33,9 @@ const useCountry = () => { countries, currentCountry, countryNameFilterItems, + countryPhoneCodeFilterItems, getCountryById, - getCountryNameFilterItemById, + getCountryFilterItemById, }; }; diff --git a/apps/frontend/types/props.d.ts b/apps/frontend/types/props.d.ts index ca796b5..948c745 100644 --- a/apps/frontend/types/props.d.ts +++ b/apps/frontend/types/props.d.ts @@ -320,3 +320,10 @@ interface SelectPayMethodModalProps { onClose?: () => void; onTapPayMethodType?: (id: string) => void; } + +interface PhoneInputFieldProps { + label?: string; + number?: string; + countryId?: string; + disabled?: boolean; +} diff --git a/apps/frontend/utils/index.ts b/apps/frontend/utils/index.ts index 6e1465e..18393bc 100644 --- a/apps/frontend/utils/index.ts +++ b/apps/frontend/utils/index.ts @@ -10,24 +10,14 @@ const getInitialsFromName = (name: string) => { const formatPhoneNumber = (value: string) => { if (!value) return value; - // Remove all non-digit characters - const digits = value.replace(/\D/g, ''); + let digits = value.replace(/\D/g, ''); + if (digits.length > 10) digits = digits.slice(-10); - // Apply formatting — Example: +1 234 567 8901 let formatted = ''; - if (digits.startsWith('1')) { - formatted = '+1'; - if (digits.length > 1) formatted += ' ' + digits.slice(1, 4); - if (digits.length > 4) formatted += ' ' + digits.slice(4, 7); - if (digits.length > 7) formatted += ' ' + digits.slice(7, 11); - } else { - // Generic international fallback - formatted = '+' + digits.slice(0, 2); - if (digits.length > 2) formatted += ' ' + digits.slice(2, 5); - if (digits.length > 5) formatted += ' ' + digits.slice(5, 8); - if (digits.length > 8) formatted += ' ' + digits.slice(8, 12); - } + formatted = digits.slice(0, 3); + if (digits.length > 3) formatted += ' ' + digits.slice(3, 6); + if (digits.length > 6) formatted += ' ' + digits.slice(6, 11); return formatted.trim(); }; From 3aab95eea6cf7583236c84ffbcc0c5bbf2e779b5 Mon Sep 17 00:00:00 2001 From: leoAshu Date: Wed, 27 Aug 2025 23:10:17 -0400 Subject: [PATCH 3/3] uses PhoneInputField in Signup Info Screen --- apps/frontend/app/(auth)/(signup)/info.tsx | 28 +++++++++++++------ .../components/form/PhoneInputField.tsx | 11 ++++++-- apps/frontend/types/props.d.ts | 1 + apps/frontend/utils/index.ts | 8 ++++++ 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/apps/frontend/app/(auth)/(signup)/info.tsx b/apps/frontend/app/(auth)/(signup)/info.tsx index d2aa30b..8500d15 100644 --- a/apps/frontend/app/(auth)/(signup)/info.tsx +++ b/apps/frontend/app/(auth)/(signup)/info.tsx @@ -1,8 +1,15 @@ import { images } from '@/assets'; -import { InputField, PrimaryButton } from '@/components'; +import { InputField, PhoneInputField, PrimaryButton } from '@/components'; import { AlertStrings, Strings } from '@/constants'; -import { useAuth, useMarket } from '@/hooks'; -import { formatPhoneNumber, validateConfirmPassword, validateName, validatePassword, validatePhone } from '@/utils'; +import { useAuth, useCountry, useMarket } from '@/hooks'; +import { + formatPhoneNumber, + trimPhoneNumber, + validateConfirmPassword, + validateName, + validatePassword, + validatePhone, +} from '@/utils'; import { router, useLocalSearchParams } from 'expo-router'; import { useState } from 'react'; import { Alert, Dimensions, Image, KeyboardAvoidingView, Platform, ScrollView, Text, View } from 'react-native'; @@ -11,12 +18,14 @@ import { SafeAreaView } from 'react-native-safe-area-context'; const SignUpInfo = () => { const { fetchAllTickers } = useMarket(); const { signup, isLoading } = useAuth(); + const { currentCountry } = useCountry(); const { email } = useLocalSearchParams<{ email: string }>(); const [formData, setFormData] = useState({ name: '', email: email, password: '', confirmPassword: '', phone: '' }); const submitForm = async () => { - const { name, email, password, confirmPassword, phone } = formData; + const { name, email, password, confirmPassword } = formData; + const phone = trimPhoneNumber(formData.phone); const nameValidationResult = validateName(name); const phoneValidationResult = validatePhone(phone); @@ -64,13 +73,14 @@ const SignUpInfo = () => { value={formData.email} disabled={true} /> - setFormData((prev) => ({ ...prev, phone: value }))} + number={formatPhoneNumber(formData.phone)} + countryId={currentCountry?.id} + onChange={(value) => setFormData((prev) => ({ ...prev, phone: value }))} /> + { - const { countryPhoneCodeFilterItems, getCountryById, getCountryFilterItemById } = useCountry(); + const { countryPhoneCodeFilterItems, getCountryFilterItemById } = useCountry(); const countryFilter = getCountryFilterItemById(countryPhoneCodeFilterItems, props.countryId ?? ''); const disabled = props.disabled ?? false; @@ -18,11 +18,16 @@ const PhoneInputField = (props: PhoneInputFieldProps) => { items={countryPhoneCodeFilterItems} showIcon={false} containerStyle='w-28' - disabled={disabled} + disabled={disabled || countryPhoneCodeFilterItems.length === 1} /> - + diff --git a/apps/frontend/types/props.d.ts b/apps/frontend/types/props.d.ts index 948c745..1224d97 100644 --- a/apps/frontend/types/props.d.ts +++ b/apps/frontend/types/props.d.ts @@ -326,4 +326,5 @@ interface PhoneInputFieldProps { number?: string; countryId?: string; disabled?: boolean; + onChange?: (val: string) => void; } diff --git a/apps/frontend/utils/index.ts b/apps/frontend/utils/index.ts index 18393bc..e48b747 100644 --- a/apps/frontend/utils/index.ts +++ b/apps/frontend/utils/index.ts @@ -22,6 +22,13 @@ const formatPhoneNumber = (value: string) => { return formatted.trim(); }; +const trimPhoneNumber = (value: string) => { + if (!value) return value; + console.log(value.replace(/\D/g, '')); + + return value.replace(/\D/g, ''); +}; + const currencyFormatter = new Intl.NumberFormat('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2, @@ -70,4 +77,5 @@ export { formatPhoneNumber, getInitialsFromName, getMockUserName, + trimPhoneNumber, };