285 lines
No EOL
10 KiB
TypeScript
285 lines
No EOL
10 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { View, Alert, ScrollView } from 'react-native';
|
|
import { useMutation } from '@tanstack/react-query';
|
|
import { Formik } from 'formik';
|
|
import * as Yup from 'yup';
|
|
import * as Location from 'expo-location';
|
|
import { tw, MyText, MyTouchableOpacity , Checkbox , MyTextInput , LoadingDialog } from 'common-ui';
|
|
import { trpc } from '../trpc-client';
|
|
|
|
interface AddressFormProps {
|
|
onSuccess: () => void;
|
|
initialValues?: {
|
|
id?: number;
|
|
name: string;
|
|
phone: string;
|
|
addressLine1: string;
|
|
addressLine2: string;
|
|
city: string;
|
|
state: string;
|
|
pincode: string;
|
|
isDefault: boolean;
|
|
latitude?: number;
|
|
longitude?: number;
|
|
googleMapsLocation?: string;
|
|
};
|
|
isEdit?: boolean;
|
|
}
|
|
|
|
const AddressForm: React.FC<AddressFormProps> = ({ onSuccess, initialValues, isEdit = false }) => {
|
|
const [locationLoading, setLocationLoading] = useState(false);
|
|
const [locationError, setLocationError] = useState<string | null>(null);
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
const [showGoogleMapsField, setShowGoogleMapsField] = useState(false);
|
|
const [currentLocation, setCurrentLocation] = useState<{ latitude: number; longitude: number } | null>(
|
|
initialValues?.latitude && initialValues?.longitude
|
|
? { latitude: initialValues.latitude, longitude: initialValues.longitude }
|
|
: null
|
|
);
|
|
|
|
const createAddressMutation = trpc.user.address.createAddress.useMutation({
|
|
onSuccess: () => {
|
|
setIsSubmitting(false);
|
|
setTimeout(() => onSuccess(), 100);
|
|
},
|
|
onError: (error: any) => {
|
|
setIsSubmitting(false);
|
|
Alert.alert('Error', error.message || 'Failed to save address');
|
|
},
|
|
});
|
|
|
|
const updateAddressMutation = trpc.user.address.updateAddress.useMutation({
|
|
onSuccess: () => {
|
|
setIsSubmitting(false);
|
|
setTimeout(() => onSuccess(), 100);
|
|
},
|
|
onError: (error: any) => {
|
|
setIsSubmitting(false);
|
|
Alert.alert('Error', error.message || 'Failed to update address');
|
|
},
|
|
});
|
|
|
|
const attachCurrentLocation = async () => {
|
|
setLocationLoading(true);
|
|
setLocationError(null);
|
|
|
|
try {
|
|
const { status } = await Location.requestForegroundPermissionsAsync();
|
|
|
|
if (status !== 'granted') {
|
|
setLocationError('Location Permission denied');
|
|
return;
|
|
}
|
|
|
|
const location = await Location.getCurrentPositionAsync({
|
|
accuracy: Location.Accuracy.High,
|
|
});
|
|
|
|
const { latitude, longitude } = location.coords;
|
|
setCurrentLocation({ latitude, longitude });
|
|
} catch (error) {
|
|
console.error('Location error:', error);
|
|
setLocationError('Unable to fetch location. Please check your GPS settings.');
|
|
} finally {
|
|
setLocationLoading(false);
|
|
}
|
|
};
|
|
|
|
const validationSchema = Yup.object({
|
|
name: Yup.string().required('Name is required'),
|
|
phone: Yup.string().required('Phone is required').matches(/^\d{10}$/, 'Phone must be 10 digits'),
|
|
addressLine1: Yup.string().required('Address Line 1 is required'),
|
|
addressLine2: Yup.string(),
|
|
city: Yup.string().required('City is required'),
|
|
state: Yup.string().required('State is required'),
|
|
pincode: Yup.string().required('Pincode is required').matches(/^\d{6}$/, 'Pincode must be 6 digits'),
|
|
isDefault: Yup.boolean(),
|
|
});
|
|
|
|
return (
|
|
<ScrollView style={tw`p-4`}>
|
|
<MyText style={tw`text-xl font-bold mb-4`}>{isEdit ? 'Edit Address' : 'Add Address'}</MyText>
|
|
<Formik
|
|
initialValues={initialValues || {
|
|
name: '',
|
|
phone: '',
|
|
addressLine1: '',
|
|
addressLine2: '',
|
|
city: 'Mahabubnagar',
|
|
state: 'Telangana',
|
|
pincode: '509001',
|
|
isDefault: false,
|
|
latitude: undefined,
|
|
longitude: undefined,
|
|
googleMapsLocation: '',
|
|
}}
|
|
validationSchema={validationSchema}
|
|
onSubmit={(values) => {
|
|
setIsSubmitting(true);
|
|
const payload = {
|
|
...values,
|
|
latitude: currentLocation?.latitude,
|
|
longitude: currentLocation?.longitude,
|
|
googleMapsLocation: values.googleMapsLocation || undefined,
|
|
};
|
|
if (isEdit && initialValues?.id) {
|
|
updateAddressMutation.mutate({ id: initialValues.id, ...payload });
|
|
} else {
|
|
createAddressMutation.mutate(payload);
|
|
}
|
|
}}
|
|
>
|
|
{({ handleChange, handleBlur, handleSubmit, values, errors, touched, setFieldValue }) => (
|
|
<View style={tw`flex-col gap-2`}>
|
|
<MyTextInput
|
|
placeholder="Name"
|
|
shrunkPadding={true}
|
|
onChangeText={handleChange('name')}
|
|
onBlur={handleBlur('name')}
|
|
value={values.name}
|
|
/>
|
|
{touched.name && errors.name && <MyText style={tw`text-red-500 mb-2`}>{errors.name}</MyText>}
|
|
|
|
<MyTextInput
|
|
placeholder="Phone"
|
|
shrunkPadding={true}
|
|
keyboardType="phone-pad"
|
|
onChangeText={handleChange('phone')}
|
|
onBlur={handleBlur('phone')}
|
|
value={values.phone}
|
|
/>
|
|
{touched.phone && errors.phone && <MyText style={tw`text-red-500 mb-2`}>{errors.phone}</MyText>}
|
|
|
|
<MyTextInput
|
|
placeholder="Address Line 1"
|
|
shrunkPadding={true}
|
|
onChangeText={handleChange('addressLine1')}
|
|
onBlur={handleBlur('addressLine1')}
|
|
value={values.addressLine1}
|
|
/>
|
|
{touched.addressLine1 && errors.addressLine1 && <MyText style={tw`text-red-500 mb-2`}>{errors.addressLine1}</MyText>}
|
|
|
|
<MyTextInput
|
|
placeholder="Address Line 2 (Optional)"
|
|
shrunkPadding={true}
|
|
onChangeText={handleChange('addressLine2')}
|
|
onBlur={handleBlur('addressLine2')}
|
|
value={values.addressLine2}
|
|
/>
|
|
|
|
<MyTextInput
|
|
placeholder="City"
|
|
shrunkPadding={true}
|
|
onChangeText={handleChange('city')}
|
|
onBlur={handleBlur('city')}
|
|
value={values.city}
|
|
disabled={true}
|
|
/>
|
|
{touched.city && errors.city && <MyText style={tw`text-red-500 mb-2`}>{errors.city}</MyText>}
|
|
|
|
<MyTextInput
|
|
placeholder="State"
|
|
shrunkPadding={true}
|
|
onChangeText={handleChange('state')}
|
|
onBlur={handleBlur('state')}
|
|
value={values.state}
|
|
disabled={true}
|
|
/>
|
|
{touched.state && errors.state && <MyText style={tw`text-red-500 mb-2`}>{errors.state}</MyText>}
|
|
|
|
<MyTextInput
|
|
placeholder="Pincode"
|
|
shrunkPadding={true}
|
|
keyboardType="numeric"
|
|
onChangeText={handleChange('pincode')}
|
|
onBlur={handleBlur('pincode')}
|
|
value={values.pincode}
|
|
disabled={true}
|
|
/>
|
|
{touched.pincode && errors.pincode && <MyText style={tw`text-red-500 mb-2`}>{errors.pincode}</MyText>}
|
|
|
|
{locationLoading ? (
|
|
<MyText style={tw`text-blue-500 text-sm mb-2`}>Fetching location...</MyText>
|
|
) : locationError ? (
|
|
<MyText style={tw`text-red-500 text-sm mb-2`}>{locationError}</MyText>
|
|
) : currentLocation ? (
|
|
<View style={tw`flex-row items-center mb-4`}>
|
|
<MyText style={tw`text-green-600 text-sm font-medium`}>Current Location Attached</MyText>
|
|
<MyTouchableOpacity
|
|
onPress={() => attachCurrentLocation()}
|
|
disabled={locationLoading}
|
|
style={tw`ml-4`}
|
|
>
|
|
<MyText style={tw`text-blue-500 text-sm font-medium`}>Change</MyText>
|
|
</MyTouchableOpacity>
|
|
</View>
|
|
) : (
|
|
<MyTouchableOpacity
|
|
onPress={() => attachCurrentLocation()}
|
|
disabled={locationLoading}
|
|
style={tw`mb-4`}
|
|
>
|
|
<MyText style={tw`text-blue-500 text-sm font-medium`}>
|
|
Attach Current Location
|
|
</MyText>
|
|
</MyTouchableOpacity>
|
|
)}
|
|
|
|
<MyTouchableOpacity
|
|
onPress={() => setShowGoogleMapsField(true)}
|
|
disabled={false}
|
|
style={tw`mb-1`}
|
|
>
|
|
<MyText style={tw`text-blue-500 text-sm font-medium`}>
|
|
Attach with Google Maps
|
|
</MyText>
|
|
</MyTouchableOpacity>
|
|
|
|
{showGoogleMapsField && (
|
|
<View style={tw`mb-2`}>
|
|
<MyText style={tw`text-gray-500 text-xs mb-2`}>
|
|
1. Open Google Maps and Find location{'\n'}
|
|
2. Long press the desired location{'\n'}
|
|
3. Click on Share and Click on Copy{'\n'}
|
|
4. Paste the copied url here in the field.
|
|
</MyText>
|
|
<MyTextInput
|
|
placeholder="Google Maps Shared URL"
|
|
shrunkPadding={true}
|
|
onChangeText={handleChange('googleMapsLocation')}
|
|
onBlur={handleBlur('googleMapsLocation')}
|
|
value={values.googleMapsLocation}
|
|
/>
|
|
</View>
|
|
)}
|
|
|
|
<View style={tw`flex-row items-center mb-4`}>
|
|
<Checkbox
|
|
checked={values.isDefault}
|
|
onPress={() => setFieldValue('isDefault', !values.isDefault)}
|
|
/>
|
|
<MyText style={tw`ml-2`}>Set as default address</MyText>
|
|
</View>
|
|
|
|
<MyTouchableOpacity
|
|
style={tw`bg-indigo-600 p-3 rounded ${isSubmitting ? 'opacity-50' : ''}`}
|
|
onPress={() => handleSubmit()}
|
|
disabled={isSubmitting}
|
|
>
|
|
<MyText style={tw`text-white text-center font-bold`}>
|
|
{isSubmitting ? (isEdit ? 'Updating...' : 'Adding...') : (isEdit ? 'Update Address' : 'Add Address')}
|
|
</MyText>
|
|
</MyTouchableOpacity>
|
|
</View>
|
|
)}
|
|
</Formik>
|
|
|
|
<LoadingDialog
|
|
open={isSubmitting}
|
|
message={isEdit ? "Updating address..." : "Adding address..."}
|
|
/>
|
|
</ScrollView>
|
|
);
|
|
};
|
|
|
|
export default AddressForm; |