diff --git a/apps/backend/assets/public/demo.txt b/apps/backend/assets/public/demo.txt new file mode 100644 index 0000000..2669d4e --- /dev/null +++ b/apps/backend/assets/public/demo.txt @@ -0,0 +1 @@ +This is a demo file. \ No newline at end of file diff --git a/apps/backend/assets/public/halal.jpg b/apps/backend/assets/public/halal.jpg new file mode 100644 index 0000000..76baf5f Binary files /dev/null and b/apps/backend/assets/public/halal.jpg differ diff --git a/apps/backend/assets/public/preservs.jpg b/apps/backend/assets/public/preservs.jpg new file mode 100644 index 0000000..0cfcb48 Binary files /dev/null and b/apps/backend/assets/public/preservs.jpg differ diff --git a/apps/backend/index.ts b/apps/backend/index.ts index 2bb5332..5f695b3 100755 --- a/apps/backend/index.ts +++ b/apps/backend/index.ts @@ -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); diff --git a/apps/user-ui/components/CheckoutAddressSelector.tsx b/apps/user-ui/components/CheckoutAddressSelector.tsx index 5d5b456..df91838 100644 --- a/apps/user-ui/components/CheckoutAddressSelector.tsx +++ b/apps/user-ui/components/CheckoutAddressSelector.tsx @@ -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 = ({ onAddressSelect, }) => { const [showAddAddress, setShowAddAddress] = useState(false); + const [editingLocationAddressId, setEditingLocationAddressId] = useState(null); + const [locationLoading, setLocationLoading] = useState(false); const queryClient = useQueryClient(); const scrollViewRef = useRef(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 = ({ 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 ( <> @@ -80,19 +135,19 @@ const CheckoutAddressSelector: React.FC = ({ ) : ( - {sortedAddresses.map((address) => ( { - 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 = ({ ))} )} + + {/* Attach Location for selected address - outside the white box */} + {selectedAddress && (() => { + const selectedAddr = sortedAddresses.find(a => a.id === selectedAddress); + return selectedAddr && !selectedAddr.latitude && !selectedAddr.longitude ? ( + handleAttachLocation(selectedAddr)} + disabled={locationLoading && editingLocationAddressId === selectedAddr.id} + style={tw`mt-3 py-2 px-3 bg-blue-50 rounded-lg self-start`} + > + + {locationLoading && editingLocationAddressId === selectedAddr.id ? 'Attaching...' : '+ Attach Current Location'} + + + ) : null; + })()} + {/* setShowAddAddress(false)}> */} setShowAddAddress(false)}> @@ -140,11 +212,11 @@ const CheckoutAddressSelector: React.FC = ({ } queryClient.invalidateQueries(); }} - /> - + /> + {/* */} ); }; -export default CheckoutAddressSelector; \ No newline at end of file +export default CheckoutAddressSelector; diff --git a/apps/user-ui/components/NextOrderGlimpse.tsx b/apps/user-ui/components/NextOrderGlimpse.tsx index 972f5d3..f3d154b 100644 --- a/apps/user-ui/components/NextOrderGlimpse.tsx +++ b/apps/user-ui/components/NextOrderGlimpse.tsx @@ -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() { )} + + {/* Support Mobile Number */} + {essentialConsts?.supportMobile && ( + Linking.openURL(`tel:${essentialConsts.supportMobile}`)} + > + + + Need help? Call {essentialConsts.supportMobile} + + + )} );