222 lines
8.9 KiB
TypeScript
222 lines
8.9 KiB
TypeScript
import React, { useState, useEffect, useRef } from 'react';
|
|
import { View, Text, TouchableOpacity, ScrollView, Alert } from 'react-native';
|
|
import { tw, BottomDialog, RawBottomDialog } from 'common-ui';
|
|
import { useQueryClient } from '@tanstack/react-query';
|
|
import AddressForm from '@/src/components/AddressForm';
|
|
import LocationAttacher from '@/src/components/LocationAttacher';
|
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
|
import { trpc } from '@/src/trpc-client';
|
|
import * as Location from 'expo-location';
|
|
|
|
interface AddressSelectorProps {
|
|
selectedAddress: number | null;
|
|
onAddressSelect: (addressId: number) => void;
|
|
}
|
|
|
|
const CheckoutAddressSelector: React.FC<AddressSelectorProps> = ({
|
|
selectedAddress,
|
|
onAddressSelect,
|
|
}) => {
|
|
const [showAddAddress, setShowAddAddress] = useState(false);
|
|
const [editingLocationAddressId, setEditingLocationAddressId] = useState<number | null>(null);
|
|
const [locationLoading, setLocationLoading] = useState(false);
|
|
const queryClient = useQueryClient();
|
|
const scrollViewRef = useRef<ScrollView>(null);
|
|
const { data: addresses } = trpc.user.address.getUserAddresses.useQuery();
|
|
|
|
const updateAddressMutation = trpc.user.address.updateAddress.useMutation({
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['user.address.getUserAddresses'] });
|
|
setEditingLocationAddressId(null);
|
|
Alert.alert('Success', 'Location attached successfully');
|
|
},
|
|
onError: (error: any) => {
|
|
setEditingLocationAddressId(null);
|
|
Alert.alert('Error', error.message || 'Failed to attach location');
|
|
},
|
|
});
|
|
|
|
// Sort addresses with selected first, then default, then others
|
|
const sortedAddresses = React.useMemo(() => {
|
|
if (!addresses?.data) return [];
|
|
return [...addresses.data].sort((a, b) => {
|
|
// Selected address comes first
|
|
if (selectedAddress === a.id && selectedAddress !== b.id) return -1;
|
|
if (selectedAddress === b.id && selectedAddress !== a.id) return 1;
|
|
|
|
// Then default address (if not already selected)
|
|
if (a.isDefault && !b.isDefault) return -1;
|
|
if (!a.isDefault && b.isDefault) return 1;
|
|
|
|
// Maintain stable sort by id for other addresses
|
|
return a.id - b.id;
|
|
});
|
|
}, [addresses?.data, selectedAddress]);
|
|
|
|
// Auto-select default address when addresses are loaded and none is selected
|
|
useEffect(() => {
|
|
if (sortedAddresses.length > 0 && selectedAddress === null) {
|
|
const defaultAddress = sortedAddresses.find(addr => addr.isDefault);
|
|
if (defaultAddress) {
|
|
onAddressSelect(defaultAddress.id);
|
|
}
|
|
}
|
|
}, [sortedAddresses, selectedAddress, onAddressSelect]);
|
|
|
|
// Reset scroll to left when address is selected
|
|
const resetScrollToLeft = () => {
|
|
scrollViewRef.current?.scrollTo({ x: 0, y: 0, animated: true });
|
|
};
|
|
|
|
const handleAttachLocation = async (address: any) => {
|
|
setEditingLocationAddressId(address.id);
|
|
setLocationLoading(true);
|
|
try {
|
|
const { status } = await Location.requestForegroundPermissionsAsync();
|
|
|
|
if (status !== 'granted') {
|
|
Alert.alert('Permission Denied', 'Location permission is required to attach your current location');
|
|
setLocationLoading(false);
|
|
setEditingLocationAddressId(null);
|
|
return;
|
|
}
|
|
|
|
const location = await Location.getCurrentPositionAsync({
|
|
accuracy: Location.Accuracy.High,
|
|
});
|
|
|
|
const { latitude, longitude } = location.coords;
|
|
|
|
updateAddressMutation.mutate({
|
|
id: address.id,
|
|
name: address.name,
|
|
phone: address.phone,
|
|
addressLine1: address.addressLine1,
|
|
addressLine2: address.addressLine2 || '',
|
|
city: address.city,
|
|
state: address.state,
|
|
pincode: address.pincode,
|
|
isDefault: address.isDefault,
|
|
latitude,
|
|
longitude,
|
|
});
|
|
} catch (error) {
|
|
Alert.alert('Error', 'Unable to fetch location. Please check your GPS settings.');
|
|
setLocationLoading(false);
|
|
setEditingLocationAddressId(null);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<View style={tw`bg-white p-5 rounded-2xl shadow-sm mb-4 border border-gray-100`}>
|
|
<View style={tw`flex-row justify-between items-center mb-3`}>
|
|
<View style={tw`flex-row items-center`}>
|
|
<View
|
|
style={tw`w-8 h-8 bg-blue-50 rounded-full items-center justify-center mr-3`}
|
|
>
|
|
<MaterialIcons name="location-on" size={18} color="#3B82F6" />
|
|
</View>
|
|
<Text style={tw`text-base font-bold text-gray-900`}>
|
|
Delivery Address
|
|
</Text>
|
|
</View>
|
|
<TouchableOpacity onPress={() => setShowAddAddress(true)}>
|
|
<Text style={tw`text-brand500 font-bold text-sm`}>+ Add New</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{(!sortedAddresses || sortedAddresses.length === 0) ? (
|
|
<View style={tw`bg-gray-50 p-6 rounded-xl border border-gray-200 border-dashed items-center justify-center`}>
|
|
<MaterialIcons name="location-off" size={32} color="#9CA3AF" />
|
|
<Text style={tw`text-gray-500 mt-2`}>No addresses found</Text>
|
|
<TouchableOpacity onPress={() => setShowAddAddress(true)} style={tw`mt-3 bg-brand500 px-4 py-2 rounded-lg`}>
|
|
<Text style={tw`text-white font-bold text-sm`}>Add Address</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
) : (
|
|
<ScrollView
|
|
ref={scrollViewRef}
|
|
horizontal
|
|
showsHorizontalScrollIndicator={false}
|
|
style={tw`pb-2`}
|
|
>
|
|
{sortedAddresses.map((address) => (
|
|
<TouchableOpacity
|
|
key={address.id}
|
|
onPress={() => {
|
|
onAddressSelect(address.id);
|
|
resetScrollToLeft();
|
|
}}
|
|
style={tw`w-72 p-4 mr-3 bg-gray-50 rounded-xl border-2 ${selectedAddress === address.id ? 'border-brand500 bg-blue-50' : 'border-gray-200'
|
|
} shadow-sm`}
|
|
>
|
|
<View style={tw`flex-row justify-between items-start mb-2`}>
|
|
<View style={tw`flex-row items-center`}>
|
|
<MaterialIcons
|
|
name={address.name.toLowerCase().includes('home') ? 'home' : address.name.toLowerCase().includes('work') ? 'work' : 'location-on'}
|
|
size={20}
|
|
color={selectedAddress === address.id ? '#EC4899' : '#6B7280'}
|
|
/>
|
|
<Text style={tw`font-bold ml-2 ${selectedAddress === address.id ? 'text-brand500' : 'text-gray-900'}`}>
|
|
{address.name}
|
|
</Text>
|
|
</View>
|
|
{selectedAddress === address.id && (
|
|
<View style={tw`bg-brand500 w-5 h-5 rounded-full items-center justify-center`}>
|
|
<MaterialIcons name="check" size={14} color="white" />
|
|
</View>
|
|
)}
|
|
</View>
|
|
<Text style={tw`text-gray-600 text-sm leading-5 mb-1`} numberOfLines={2}>
|
|
{address.addressLine1}{address.addressLine2 ? `, ${address.addressLine2}` : ''}
|
|
</Text>
|
|
<Text style={tw`text-gray-600 text-sm mb-1`}>
|
|
{address.city}, {address.state} - {address.pincode}
|
|
</Text>
|
|
<Text style={tw`text-gray-500 text-xs mt-2`}>
|
|
Phone: {address.phone}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</ScrollView>
|
|
)}
|
|
|
|
{/* Attach Location for selected address - outside the white box */}
|
|
{selectedAddress && (() => {
|
|
const selectedAddr = sortedAddresses.find(a => a.id === selectedAddress);
|
|
return selectedAddr && !selectedAddr.latitude && !selectedAddr.longitude ? (
|
|
<TouchableOpacity
|
|
onPress={() => handleAttachLocation(selectedAddr)}
|
|
disabled={locationLoading && editingLocationAddressId === selectedAddr.id}
|
|
style={tw`mt-3 py-2 px-3 bg-blue-50 rounded-lg self-start`}
|
|
>
|
|
<Text style={tw`text-blue-600 text-sm font-bold`}>
|
|
{locationLoading && editingLocationAddressId === selectedAddr.id ? 'Attaching...' : '+ Attach Current Location'}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
) : null;
|
|
})()}
|
|
</View>
|
|
|
|
|
|
{/* <BottomDialog open={showAddAddress} onClose={() => setShowAddAddress(false)}> */}
|
|
<RawBottomDialog open={showAddAddress} onClose={() => setShowAddAddress(false)}>
|
|
|
|
<AddressForm
|
|
onSuccess={(addressId) => {
|
|
setShowAddAddress(false);
|
|
// Auto-select the newly added address
|
|
if (addressId) {
|
|
onAddressSelect(addressId);
|
|
}
|
|
queryClient.invalidateQueries();
|
|
}}
|
|
/>
|
|
</RawBottomDialog>
|
|
{/* </BottomDialog> */}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default CheckoutAddressSelector;
|