freshyo/apps/user-ui/src/components/AddressForm.tsx
2026-01-30 01:56:41 +05:30

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;