This commit is contained in:
shafi54 2026-02-21 19:42:18 +05:30
parent a875e63751
commit 04ea8c9284
6 changed files with 110 additions and 22 deletions

View file

@ -0,0 +1 @@
This is a demo file.

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -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);

View file

@ -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`}>
@ -90,9 +145,9 @@ const CheckoutAddressSelector: React.FC<AddressSelectorProps> = ({
<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,8 +212,8 @@ const CheckoutAddressSelector: React.FC<AddressSelectorProps> = ({
} }
queryClient.invalidateQueries(); queryClient.invalidateQueries();
}} }}
/> />
</RawBottomDialog> </RawBottomDialog>
{/* </BottomDialog> */} {/* </BottomDialog> */}
</> </>
); );

View file

@ -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>
); );