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}`)
|
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
|
// Global error handler
|
||||||
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
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 { tw, BottomDialog, RawBottomDialog } from 'common-ui';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import AddressForm from '@/src/components/AddressForm';
|
import AddressForm from '@/src/components/AddressForm';
|
||||||
|
import LocationAttacher from '@/src/components/LocationAttacher';
|
||||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||||
import { trpc } from '@/src/trpc-client';
|
import { trpc } from '@/src/trpc-client';
|
||||||
|
import * as Location from 'expo-location';
|
||||||
|
|
||||||
interface AddressSelectorProps {
|
interface AddressSelectorProps {
|
||||||
selectedAddress: number | null;
|
selectedAddress: number | null;
|
||||||
|
|
@ -16,10 +18,24 @@ const CheckoutAddressSelector: React.FC<AddressSelectorProps> = ({
|
||||||
onAddressSelect,
|
onAddressSelect,
|
||||||
}) => {
|
}) => {
|
||||||
const [showAddAddress, setShowAddAddress] = useState(false);
|
const [showAddAddress, setShowAddAddress] = useState(false);
|
||||||
|
const [editingLocationAddressId, setEditingLocationAddressId] = useState<number | null>(null);
|
||||||
|
const [locationLoading, setLocationLoading] = useState(false);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const scrollViewRef = useRef<ScrollView>(null);
|
const scrollViewRef = useRef<ScrollView>(null);
|
||||||
const { data: addresses } = trpc.user.address.getUserAddresses.useQuery();
|
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
|
// Sort addresses with selected first, then default, then others
|
||||||
const sortedAddresses = React.useMemo(() => {
|
const sortedAddresses = React.useMemo(() => {
|
||||||
if (!addresses?.data) return [];
|
if (!addresses?.data) return [];
|
||||||
|
|
@ -52,6 +68,45 @@ const CheckoutAddressSelector: React.FC<AddressSelectorProps> = ({
|
||||||
scrollViewRef.current?.scrollTo({ x: 0, y: 0, animated: true });
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<View style={tw`bg-white p-5 rounded-2xl shadow-sm mb-4 border border-gray-100`}>
|
<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>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
ref={scrollViewRef}
|
ref={scrollViewRef}
|
||||||
horizontal
|
horizontal
|
||||||
showsHorizontalScrollIndicator={false}
|
showsHorizontalScrollIndicator={false}
|
||||||
style={tw`pb-2`}
|
style={tw`pb-2`}
|
||||||
>
|
>
|
||||||
{sortedAddresses.map((address) => (
|
{sortedAddresses.map((address) => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={address.id}
|
key={address.id}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
onAddressSelect(address.id);
|
onAddressSelect(address.id);
|
||||||
resetScrollToLeft();
|
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'
|
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`}
|
} shadow-sm`}
|
||||||
>
|
>
|
||||||
|
|
@ -126,8 +181,25 @@ const CheckoutAddressSelector: React.FC<AddressSelectorProps> = ({
|
||||||
))}
|
))}
|
||||||
</ScrollView>
|
</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>
|
</View>
|
||||||
|
|
||||||
|
|
||||||
{/* <BottomDialog open={showAddAddress} onClose={() => setShowAddAddress(false)}> */}
|
{/* <BottomDialog open={showAddAddress} onClose={() => setShowAddAddress(false)}> */}
|
||||||
<RawBottomDialog open={showAddAddress} onClose={() => setShowAddAddress(false)}>
|
<RawBottomDialog open={showAddAddress} onClose={() => setShowAddAddress(false)}>
|
||||||
|
|
||||||
|
|
@ -140,11 +212,11 @@ const CheckoutAddressSelector: React.FC<AddressSelectorProps> = ({
|
||||||
}
|
}
|
||||||
queryClient.invalidateQueries();
|
queryClient.invalidateQueries();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</RawBottomDialog>
|
</RawBottomDialog>
|
||||||
{/* </BottomDialog> */}
|
{/* </BottomDialog> */}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CheckoutAddressSelector;
|
export default CheckoutAddressSelector;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
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 { useRouter } from 'expo-router';
|
||||||
import { tw, MyText } from 'common-ui';
|
import { tw, MyText } from 'common-ui';
|
||||||
import { useNavigationTarget } from 'common-ui/hooks/useNavigationTarget';
|
import { useNavigationTarget } from 'common-ui/hooks/useNavigationTarget';
|
||||||
|
|
@ -8,6 +8,7 @@ import dayjs from 'dayjs';
|
||||||
import { trpc } from '@/src/trpc-client';
|
import { trpc } from '@/src/trpc-client';
|
||||||
import { Image } from 'expo-image';
|
import { Image } from 'expo-image';
|
||||||
import { orderStatusManipulator } from '@/src/lib/string-manipulators';
|
import { orderStatusManipulator } from '@/src/lib/string-manipulators';
|
||||||
|
import { useGetEssentialConsts } from '@/src/api-hooks/essential-consts.api';
|
||||||
|
|
||||||
interface OrderItem {
|
interface OrderItem {
|
||||||
productName: string;
|
productName: string;
|
||||||
|
|
@ -47,23 +48,15 @@ export default function NextOrderGlimpse() {
|
||||||
pageSize: 50,
|
pageSize: 50,
|
||||||
});
|
});
|
||||||
|
|
||||||
const allOrders: Order[] = ordersData?.data || [];
|
const { data: essentialConsts } = useGetEssentialConsts();
|
||||||
|
|
||||||
const now = dayjs();
|
const allOrders: Order[] = ordersData?.data || [];
|
||||||
|
|
||||||
const upcomingOrders = allOrders.filter(order => {
|
const upcomingOrders = allOrders.filter(order => {
|
||||||
if (order.orderStatus.toLowerCase() === 'cancelled') return false;
|
if (order.orderStatus.toLowerCase() === 'cancelled') return false;
|
||||||
if (order.deliveryStatus.toLowerCase() === 'success') return false;
|
if (order.deliveryStatus.toLowerCase() === 'success') return false;
|
||||||
|
|
||||||
if (order.isFlashDelivery) {
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (order.deliveryDate) {
|
|
||||||
return dayjs(order.deliveryDate).isAfter(now);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
upcomingOrders.sort((a, b) => {
|
upcomingOrders.sort((a, b) => {
|
||||||
|
|
@ -177,6 +170,19 @@ export default function NextOrderGlimpse() {
|
||||||
<Ionicons name="chevron-forward" size={14} color="#D97706" style={tw`ml-auto`} />
|
<Ionicons name="chevron-forward" size={14} color="#D97706" style={tw`ml-auto`} />
|
||||||
</View>
|
</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>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue