freshyo/apps/user-ui/src/components/AddressForm.tsx
2026-09-05 11:12:47 +05:30

251 lines
No EOL
9.5 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState } from 'react';
import { View, ScrollView, Dimensions } from 'react-native';
import { useMutation } from '@tanstack/react-query';
import { Formik } from 'formik';
import * as Yup from 'yup';
import { tw, MyText, MyTouchableOpacity , Checkbox , MyTextInput , LoadingDialog } from 'common-ui';
import { trpc } from '../trpc-client';
import LocationAttacher from './LocationAttacher';
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
import type { AddressFormProps } from '@packages/shared';
const AddressForm: React.FC<AddressFormProps> = ({ onSuccess, initialValues, isEdit = false }) => {
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitError, setSubmitError] = useState<string | null>(null);
const [showGoogleMapsField, setShowGoogleMapsField] = useState(!!initialValues?.googleMapsUrl);
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: (data) => {
setIsSubmitting(false);
const addressId = data?.data?.id;
// Delay to allow modal to close animation
setTimeout(() => onSuccess(addressId), 350);
},
onError: (error: any) => {
setIsSubmitting(false);
setSubmitError(error.message || 'Failed to save address');
},
});
const updateAddressMutation = trpc.user.address.updateAddress.useMutation({
onSuccess: () => {
setIsSubmitting(false);
// Delay to allow modal to close animation
setTimeout(() => onSuccess(), 350);
},
onError: (error: any) => {
setIsSubmitting(false);
setSubmitError(error.message || 'Failed to update address');
},
});
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 (
<KeyboardAwareScrollView
style={[tw`px-4 pt-4 pb-32 bg-white`, {maxHeight: Dimensions.get('window').height * 0.7}]}
keyboardShouldPersistTaps="always"
keyboardDismissMode="on-drag"
>
<MyText style={tw`text-xl font-bold mb-4`}>{isEdit ? 'Edit Address' : 'Add Address'}</MyText>
{/* Service Area Notice */}
<View style={tw`bg-amber-50 border border-amber-200 rounded-lg p-3 mb-4`}>
<View style={tw`flex-row items-center`}>
<MyText style={tw`text-amber-600 mr-2`}></MyText>
<MyText style={tw`text-amber-800 text-sm flex-1`}>
We currently serve only in Mahabubnagar town
</MyText>
</View>
</View>
{/* Submit Error Message */}
{submitError && (
<View style={tw`bg-red-50 border border-red-200 rounded-lg p-3 mb-4`}>
<MyText style={tw`text-red-600 text-sm`}>{submitError}</MyText>
</View>
)}
<Formik
initialValues={initialValues || {
name: '',
phone: '',
addressLine1: '',
addressLine2: '',
city: 'Mahabubnagar',
state: 'Telangana',
pincode: '509001',
isDefault: false,
latitude: undefined,
longitude: undefined,
googleMapsUrl: '',
}}
validationSchema={validationSchema}
onSubmit={(values) => {
setIsSubmitting(true);
setSubmitError(null);
const payload = {
...values,
latitude: currentLocation?.latitude,
longitude: currentLocation?.longitude,
googleMapsUrl: values.googleMapsUrl || 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>}
<LocationAttacher
initialLocation={currentLocation || undefined}
initialGoogleMapsUrl={values.googleMapsUrl}
googleMapsUrl={values.googleMapsUrl}
onGoogleMapsUrlChange={(url) => setFieldValue('googleMapsUrl', url)}
onLocationChange={(location) => setCurrentLocation(location)}
disabled={false}
/>
{!showGoogleMapsField && (
<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('googleMapsUrl')}
onBlur={handleBlur('googleMapsUrl')}
value={values.googleMapsUrl}
/>
</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 style={tw`h-8`}></View>
</View>
)}
</Formik>
<LoadingDialog
open={isSubmitting}
message={isEdit ? "Updating address..." : "Adding address..."}
/>
</KeyboardAwareScrollView>
);
};
export default AddressForm;