enh
This commit is contained in:
parent
a875e63751
commit
04ea8c9284
6 changed files with 110 additions and 22 deletions
1
apps/backend/assets/public/demo.txt
Normal file
1
apps/backend/assets/public/demo.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
This is a demo file.
|
||||
BIN
apps/backend/assets/public/halal.jpg
Normal file
BIN
apps/backend/assets/public/halal.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
BIN
apps/backend/assets/public/preservs.jpg
Normal file
BIN
apps/backend/assets/public/preservs.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
|
|
@ -163,6 +163,15 @@ if (fs.existsSync(fallbackUiIndex)) {
|
|||
console.warn(`Fallback UI build not found at ${fallbackUiIndex}`)
|
||||
}
|
||||
|
||||
// Serve /assets/public folder at /assets route
|
||||
const assetsPublicDir = path.resolve(__dirname, './assets/public');
|
||||
if (fs.existsSync(assetsPublicDir)) {
|
||||
app.use('/assets', express.static(assetsPublicDir));
|
||||
console.log('Serving /assets from', assetsPublicDir);
|
||||
} else {
|
||||
console.warn('Assets public folder not found at', assetsPublicDir);
|
||||
}
|
||||
|
||||
// Global error handler
|
||||
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
console.error(err);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { View, Text, TouchableOpacity, ScrollView } from 'react-native';
|
||||
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;
|
||||
|
|
@ -16,10 +18,24 @@ const CheckoutAddressSelector: React.FC<AddressSelectorProps> = ({
|
|||
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 [];
|
||||
|
|
@ -52,6 +68,45 @@ const CheckoutAddressSelector: React.FC<AddressSelectorProps> = ({
|
|||
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`}>
|
||||
|
|
@ -80,19 +135,19 @@ const CheckoutAddressSelector: React.FC<AddressSelectorProps> = ({
|
|||
</TouchableOpacity>
|
||||
</View>
|
||||
) : (
|
||||
<ScrollView
|
||||
<ScrollView
|
||||
ref={scrollViewRef}
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
style={tw`pb-2`}
|
||||
>
|
||||
{sortedAddresses.map((address) => (
|
||||
<TouchableOpacity
|
||||
key={address.id}
|
||||
onPress={() => {
|
||||
onAddressSelect(address.id);
|
||||
resetScrollToLeft();
|
||||
}}
|
||||
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`}
|
||||
>
|
||||
|
|
@ -126,8 +181,25 @@ const CheckoutAddressSelector: React.FC<AddressSelectorProps> = ({
|
|||
))}
|
||||
</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)}>
|
||||
|
||||
|
|
@ -140,11 +212,11 @@ const CheckoutAddressSelector: React.FC<AddressSelectorProps> = ({
|
|||
}
|
||||
queryClient.invalidateQueries();
|
||||
}}
|
||||
/>
|
||||
</RawBottomDialog>
|
||||
/>
|
||||
</RawBottomDialog>
|
||||
{/* </BottomDialog> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CheckoutAddressSelector;
|
||||
export default CheckoutAddressSelector;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { View, TouchableOpacity, ActivityIndicator } from 'react-native';
|
||||
import { View, TouchableOpacity, ActivityIndicator, Linking } from 'react-native';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { tw, MyText } from 'common-ui';
|
||||
import { useNavigationTarget } from 'common-ui/hooks/useNavigationTarget';
|
||||
|
|
@ -8,6 +8,7 @@ import dayjs from 'dayjs';
|
|||
import { trpc } from '@/src/trpc-client';
|
||||
import { Image } from 'expo-image';
|
||||
import { orderStatusManipulator } from '@/src/lib/string-manipulators';
|
||||
import { useGetEssentialConsts } from '@/src/api-hooks/essential-consts.api';
|
||||
|
||||
interface OrderItem {
|
||||
productName: string;
|
||||
|
|
@ -47,23 +48,15 @@ export default function NextOrderGlimpse() {
|
|||
pageSize: 50,
|
||||
});
|
||||
|
||||
const allOrders: Order[] = ordersData?.data || [];
|
||||
const { data: essentialConsts } = useGetEssentialConsts();
|
||||
|
||||
const now = dayjs();
|
||||
const allOrders: Order[] = ordersData?.data || [];
|
||||
|
||||
const upcomingOrders = allOrders.filter(order => {
|
||||
if (order.orderStatus.toLowerCase() === 'cancelled') return false;
|
||||
if (order.deliveryStatus.toLowerCase() === 'success') return false;
|
||||
|
||||
if (order.isFlashDelivery) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (order.deliveryDate) {
|
||||
return dayjs(order.deliveryDate).isAfter(now);
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
upcomingOrders.sort((a, b) => {
|
||||
|
|
@ -177,6 +170,19 @@ export default function NextOrderGlimpse() {
|
|||
<Ionicons name="chevron-forward" size={14} color="#D97706" style={tw`ml-auto`} />
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Support Mobile Number */}
|
||||
{essentialConsts?.supportMobile && (
|
||||
<TouchableOpacity
|
||||
style={tw`flex-row items-center mt-3 pt-3 border-t border-amber-200`}
|
||||
onPress={() => Linking.openURL(`tel:${essentialConsts.supportMobile}`)}
|
||||
>
|
||||
<MaterialIcons name="phone-in-talk" size={14} color="#D97706" style={tw`mr-1.5`} />
|
||||
<MyText style={tw`text-xs text-amber-700 font-medium`}>
|
||||
Need help? Call {essentialConsts.supportMobile}
|
||||
</MyText>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue