enh
This commit is contained in:
parent
ca8297af9b
commit
001dd62aa5
2 changed files with 75 additions and 16 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { View, Alert, ScrollView } from 'react-native';
|
import { View, ScrollView } from 'react-native';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
@ -30,39 +30,44 @@ const AddressForm: React.FC<AddressFormProps> = ({ onSuccess, initialValues, isE
|
||||||
const [locationLoading, setLocationLoading] = useState(false);
|
const [locationLoading, setLocationLoading] = useState(false);
|
||||||
const [locationError, setLocationError] = useState<string | null>(null);
|
const [locationError, setLocationError] = useState<string | null>(null);
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
const [submitError, setSubmitError] = useState<string | null>(null);
|
||||||
const [showGoogleMapsField, setShowGoogleMapsField] = useState(!!initialValues?.googleMapsUrl);
|
const [showGoogleMapsField, setShowGoogleMapsField] = useState(!!initialValues?.googleMapsUrl);
|
||||||
const [currentLocation, setCurrentLocation] = useState<{ latitude: number; longitude: number } | null>(
|
const [currentLocation, setCurrentLocation] = useState<{ latitude: number; longitude: number } | null>(
|
||||||
initialValues?.latitude && initialValues?.longitude
|
initialValues?.latitude && initialValues?.longitude
|
||||||
? { latitude: initialValues.latitude, longitude: initialValues.longitude }
|
? { latitude: initialValues.latitude, longitude: initialValues.longitude }
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
const [locationSuccess, setLocationSuccess] = useState(false);
|
||||||
|
|
||||||
const createAddressMutation = trpc.user.address.createAddress.useMutation({
|
const createAddressMutation = trpc.user.address.createAddress.useMutation({
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
const addressId = data?.data?.id;
|
const addressId = data?.data?.id;
|
||||||
setTimeout(() => onSuccess(addressId), 100);
|
// Delay to allow modal to close animation
|
||||||
|
setTimeout(() => onSuccess(addressId), 350);
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
Alert.alert('Error', error.message || 'Failed to save address');
|
setSubmitError(error.message || 'Failed to save address');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateAddressMutation = trpc.user.address.updateAddress.useMutation({
|
const updateAddressMutation = trpc.user.address.updateAddress.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
setTimeout(() => onSuccess(), 100);
|
// Delay to allow modal to close animation
|
||||||
|
setTimeout(() => onSuccess(), 350);
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
Alert.alert('Error', error.message || 'Failed to update address');
|
setSubmitError(error.message || 'Failed to update address');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const attachCurrentLocation = async () => {
|
const attachCurrentLocation = async () => {
|
||||||
setLocationLoading(true);
|
setLocationLoading(true);
|
||||||
setLocationError(null);
|
setLocationError(null);
|
||||||
|
setLocationSuccess(false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { status } = await Location.requestForegroundPermissionsAsync();
|
const { status } = await Location.requestForegroundPermissionsAsync();
|
||||||
|
|
@ -78,7 +83,9 @@ const AddressForm: React.FC<AddressFormProps> = ({ onSuccess, initialValues, isE
|
||||||
|
|
||||||
const { latitude, longitude } = location.coords;
|
const { latitude, longitude } = location.coords;
|
||||||
setCurrentLocation({ latitude, longitude });
|
setCurrentLocation({ latitude, longitude });
|
||||||
Alert.alert('Success', 'Location attached successfully');
|
setLocationSuccess(true);
|
||||||
|
// Clear success message after 3 seconds
|
||||||
|
setTimeout(() => setLocationSuccess(false), 3000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setLocationError('Unable to fetch location. Please check your GPS settings.');
|
setLocationError('Unable to fetch location. Please check your GPS settings.');
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -98,8 +105,19 @@ const AddressForm: React.FC<AddressFormProps> = ({ onSuccess, initialValues, isE
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView style={tw`p-4`}>
|
<ScrollView
|
||||||
|
style={tw`p-4`}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
|
keyboardDismissMode="on-drag"
|
||||||
|
>
|
||||||
<MyText style={tw`text-xl font-bold mb-4`}>{isEdit ? 'Edit Address' : 'Add Address'}</MyText>
|
<MyText style={tw`text-xl font-bold mb-4`}>{isEdit ? 'Edit Address' : 'Add Address'}</MyText>
|
||||||
|
|
||||||
|
{/* 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
|
<Formik
|
||||||
initialValues={initialValues || {
|
initialValues={initialValues || {
|
||||||
name: '',
|
name: '',
|
||||||
|
|
@ -116,9 +134,10 @@ const AddressForm: React.FC<AddressFormProps> = ({ onSuccess, initialValues, isE
|
||||||
}}
|
}}
|
||||||
validationSchema={validationSchema}
|
validationSchema={validationSchema}
|
||||||
onSubmit={(values) => {
|
onSubmit={(values) => {
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
const payload = {
|
setSubmitError(null);
|
||||||
...values,
|
const payload = {
|
||||||
|
...values,
|
||||||
latitude: currentLocation?.latitude,
|
latitude: currentLocation?.latitude,
|
||||||
longitude: currentLocation?.longitude,
|
longitude: currentLocation?.longitude,
|
||||||
googleMapsUrl: values.googleMapsUrl || undefined,
|
googleMapsUrl: values.googleMapsUrl || undefined,
|
||||||
|
|
@ -203,9 +222,24 @@ const AddressForm: React.FC<AddressFormProps> = ({ onSuccess, initialValues, isE
|
||||||
<MyText style={tw`text-blue-500 text-sm mb-2`}>Fetching location...</MyText>
|
<MyText style={tw`text-blue-500 text-sm mb-2`}>Fetching location...</MyText>
|
||||||
) : locationError ? (
|
) : locationError ? (
|
||||||
<MyText style={tw`text-red-500 text-sm mb-2`}>{locationError}</MyText>
|
<MyText style={tw`text-red-500 text-sm mb-2`}>{locationError}</MyText>
|
||||||
|
) : locationSuccess ? (
|
||||||
|
<View style={tw`flex-row items-center mb-4`}>
|
||||||
|
<View style={tw`bg-green-100 px-3 py-1 rounded-full`}>
|
||||||
|
<MyText style={tw`text-green-600 text-sm font-medium`}>✓ Location Attached</MyText>
|
||||||
|
</View>
|
||||||
|
<MyTouchableOpacity
|
||||||
|
onPress={() => attachCurrentLocation()}
|
||||||
|
disabled={locationLoading}
|
||||||
|
style={tw`ml-4`}
|
||||||
|
>
|
||||||
|
<MyText style={tw`text-blue-500 text-sm font-medium`}>Attach Current</MyText>
|
||||||
|
</MyTouchableOpacity>
|
||||||
|
</View>
|
||||||
) : currentLocation ? (
|
) : currentLocation ? (
|
||||||
<View style={tw`flex-row items-center mb-4`}>
|
<View style={tw`flex-row items-center mb-4`}>
|
||||||
<MyText style={tw`text-green-600 text-sm font-medium`}>Location Attached</MyText>
|
<View style={tw`bg-green-100 px-3 py-1 rounded-full`}>
|
||||||
|
<MyText style={tw`text-green-600 text-sm font-medium`}>✓ Location Attached</MyText>
|
||||||
|
</View>
|
||||||
<MyTouchableOpacity
|
<MyTouchableOpacity
|
||||||
onPress={() => attachCurrentLocation()}
|
onPress={() => attachCurrentLocation()}
|
||||||
disabled={locationLoading}
|
disabled={locationLoading}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { ReactNode, useState } from 'react';
|
import React, { ReactNode, useState } from 'react';
|
||||||
import { Modal, View, TouchableOpacity, StyleSheet, Animated, Easing, Dimensions, TextInput } from 'react-native';
|
import { Modal, View, TouchableOpacity, StyleSheet, Animated, Easing, Dimensions, TextInput, KeyboardAvoidingView, Platform, ScrollView } from 'react-native';
|
||||||
import MyText from './text';
|
import MyText from './text';
|
||||||
import { MyButton } from 'common-ui';
|
import { MyButton } from 'common-ui';
|
||||||
|
|
||||||
|
|
@ -53,7 +53,20 @@ export const BottomDialog: React.FC<DialogProps> = ({ open, onClose, children, e
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<View style={styles.handle} />
|
<View style={styles.handle} />
|
||||||
{children}
|
<KeyboardAvoidingView
|
||||||
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
|
style={styles.keyboardAvoidingContainer}
|
||||||
|
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 0}
|
||||||
|
>
|
||||||
|
<ScrollView
|
||||||
|
style={styles.scrollContainer}
|
||||||
|
contentContainerStyle={styles.scrollContent}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
|
showsVerticalScrollIndicator={true}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ScrollView>
|
||||||
|
</KeyboardAvoidingView>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
@ -71,19 +84,18 @@ const styles = StyleSheet.create({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
// top: SCREEN_HEIGHT * 0.3,
|
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
borderTopLeftRadius: 18,
|
borderTopLeftRadius: 18,
|
||||||
borderTopRightRadius: 18,
|
borderTopRightRadius: 18,
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
// paddingTop: 16,
|
|
||||||
paddingBottom: 0,
|
paddingBottom: 0,
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
shadowColor: '#000',
|
shadowColor: '#000',
|
||||||
shadowOffset: { width: 0, height: -2 },
|
shadowOffset: { width: 0, height: -2 },
|
||||||
shadowOpacity: 0.2,
|
shadowOpacity: 0.2,
|
||||||
shadowRadius: 8,
|
shadowRadius: 8,
|
||||||
|
maxHeight: SCREEN_HEIGHT * 0.85, // Limit max height so it doesn't cover entire screen
|
||||||
},
|
},
|
||||||
handle: {
|
handle: {
|
||||||
width: 40,
|
width: 40,
|
||||||
|
|
@ -91,7 +103,20 @@ const styles = StyleSheet.create({
|
||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
backgroundColor: '#ccc',
|
backgroundColor: '#ccc',
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
// marginBottom: 12,
|
marginTop: 12,
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
keyboardAvoidingContainer: {
|
||||||
|
flex: 1,
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
scrollContainer: {
|
||||||
|
flex: 1,
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
scrollContent: {
|
||||||
|
flexGrow: 1,
|
||||||
|
paddingBottom: Platform.OS === 'ios' ? 40 : 20, // Extra padding for iOS keyboard
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue