feat: implement delivery workflow screens, state management, and API services for order tracking and fulfillment
This commit is contained in:
parent
98e28742de
commit
55bab67d91
10
app/api/accountApi.ts
Normal file
10
app/api/accountApi.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { apiClient } from '@services';
|
||||
import { DeliveryPartnerProfileResponse } from '@interfaces';
|
||||
|
||||
export const getAccountInfo =
|
||||
async (): Promise<DeliveryPartnerProfileResponse> => {
|
||||
const response = await apiClient.get<DeliveryPartnerProfileResponse>(
|
||||
'/account-info',
|
||||
);
|
||||
return response;
|
||||
};
|
||||
@ -2,7 +2,9 @@ import {
|
||||
DeliveryAction,
|
||||
RespondToOfferResponse,
|
||||
PendingOfferResponse,
|
||||
ActiveOrdersResponse,
|
||||
PickupOrderResponse,
|
||||
DeliveryDetailsResponse,
|
||||
OrderHistoryResponse,
|
||||
} from '@interfaces';
|
||||
import { apiClient } from '@services';
|
||||
|
||||
@ -32,9 +34,29 @@ export const getPendingOffer = async (): Promise<PendingOfferResponse> => {
|
||||
return response;
|
||||
};
|
||||
|
||||
export const getActiveDeliveries = async (): Promise<ActiveOrdersResponse> => {
|
||||
const response = await apiClient.get<ActiveOrdersResponse>(
|
||||
'/delivery-partners/deliveries/active',
|
||||
export const getActiveDeliveries =
|
||||
async (): Promise<DeliveryDetailsResponse> => {
|
||||
const response = await apiClient.get<DeliveryDetailsResponse>(
|
||||
'/delivery-partners/deliveries/active',
|
||||
);
|
||||
return response;
|
||||
};
|
||||
|
||||
export const changeOrderStatus = async (
|
||||
deliveryId: string,
|
||||
action: string,
|
||||
proofKey?: string,
|
||||
): Promise<PickupOrderResponse> => {
|
||||
const response = await apiClient.post<PickupOrderResponse>(
|
||||
`/delivery-partners/deliveries/${deliveryId}/status`,
|
||||
{ action, proofKey },
|
||||
);
|
||||
return response;
|
||||
};
|
||||
|
||||
export const completedDeliveries = async (): Promise<OrderHistoryResponse> => {
|
||||
const response = await apiClient.get<OrderHistoryResponse>(
|
||||
'/delivery-partners/deliveries/history',
|
||||
);
|
||||
return response;
|
||||
};
|
||||
|
||||
@ -3,3 +3,4 @@ export * from './onBoardApi';
|
||||
export * from './kycApi';
|
||||
export * from './dashboardApi';
|
||||
export * from './deliveryApi';
|
||||
export * from './accountApi';
|
||||
|
||||
@ -12,15 +12,16 @@ import { getStyles } from './arrivedAtCustomerScreen.styles';
|
||||
export const ArrivedAtCustomerScreen: React.FC = () => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||
|
||||
const activeJob = useAppSelector((state) => state.job.activeJob);
|
||||
const activeJob = useAppSelector(state => state.job.activeJob);
|
||||
|
||||
const handleConfirmArrival = () => {
|
||||
navigation.navigate(RouteNames.DeliverOrder);
|
||||
// navigation.navigate(RouteNames.DeliverOrder);
|
||||
};
|
||||
|
||||
if (!activeJob) return null;
|
||||
// if (!activeJob) return null;
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
@ -32,8 +33,8 @@ export const ArrivedAtCustomerScreen: React.FC = () => {
|
||||
{/* Arrived Card */}
|
||||
<View style={styles.bottomCard}>
|
||||
<Text style={styles.cardTitle}>You've arrived</Text>
|
||||
<Text style={styles.customerName}>{activeJob.dropName}</Text>
|
||||
<Text style={styles.customerAddress}>{activeJob.dropAddress}</Text>
|
||||
<Text style={styles.customerName}>{activeJob?.dropName}</Text>
|
||||
<Text style={styles.customerAddress}>{activeJob?.dropAddress}</Text>
|
||||
|
||||
<PrimaryButton
|
||||
title="Confirm Arrival"
|
||||
|
||||
@ -2,35 +2,46 @@ import React, { useState } from 'react';
|
||||
import { View, Text, ScrollView, Alert } from 'react-native';
|
||||
import { useAppTheme } from '@theme';
|
||||
import { ScreenHeader, PrimaryButton } from '@components';
|
||||
import { useAppDispatch, useAppSelector } from '@store';
|
||||
import { changeOrderStatusThunk, useAppDispatch, useAppSelector } from '@store';
|
||||
import { completeJob } from '@store/commonReducers/job';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||
import { RouteNames } from '@utils/constants';
|
||||
import { JobStatus, RouteNames } from '@utils/constants';
|
||||
import { getStyles } from './deliverOrderScreen.styles';
|
||||
|
||||
export const DeliverOrderScreen: React.FC = () => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
const dispatch = useAppDispatch();
|
||||
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||
const route =
|
||||
useRoute<RouteProp<AppStackParamList, RouteNames.OrderPickedUp>>();
|
||||
const { jobId } = route.params;
|
||||
|
||||
const { activeDeliveries } = useAppSelector(state => state.deliveries);
|
||||
const activeJob = activeDeliveries?.delivery;
|
||||
|
||||
const activeJob = useAppSelector((state) => state.job.activeJob);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const items = [
|
||||
{ id: 0, name: 'Cappuccino (Medium)', qty: 1 },
|
||||
{ id: 1, name: 'Chocolate Croissant', qty: 1 },
|
||||
];
|
||||
// const items = [
|
||||
// { id: 0, name: 'Cappuccino (Medium)', qty: 1 },
|
||||
// { id: 1, name: 'Chocolate Croissant', qty: 1 },
|
||||
// ];
|
||||
|
||||
const handleCompleteDelivery = () => {
|
||||
setIsLoading(true);
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
dispatch(completeJob());
|
||||
navigation.navigate(RouteNames.DeliveryCompleted);
|
||||
}, 1200);
|
||||
dispatch(
|
||||
changeOrderStatusThunk({
|
||||
deliveryId: jobId,
|
||||
action: JobStatus.Delivered,
|
||||
}),
|
||||
)
|
||||
.unwrap()
|
||||
.then(() => {
|
||||
// dispatch(completeJob());
|
||||
navigation.navigate(RouteNames.DeliveryCompleted);
|
||||
});
|
||||
};
|
||||
|
||||
if (!activeJob) return null;
|
||||
@ -43,34 +54,46 @@ export const DeliverOrderScreen: React.FC = () => {
|
||||
onBack={() => navigation.goBack()}
|
||||
/>
|
||||
|
||||
<ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContainer}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* Customer Detail Card */}
|
||||
<View style={styles.card}>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.label}>Customer Name</Text>
|
||||
<Text style={styles.value}>{activeJob.dropName}</Text>
|
||||
<Text style={styles.value}>
|
||||
{activeJob?.order?.customer?.user?.name}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[styles.row, { marginTop: 10 }]}>
|
||||
<Text style={styles.label}>Order ID</Text>
|
||||
<Text style={styles.value}>{activeJob.orderId}</Text>
|
||||
<Text style={styles.value}>{activeJob?.orderId}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Items Summary list */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Items Details</Text>
|
||||
{items.map((item) => (
|
||||
{activeJob?.order?.orderItems?.map(item => (
|
||||
<View key={item.id} style={styles.itemRow}>
|
||||
<Text style={styles.itemName}>{item.name}</Text>
|
||||
<Text style={styles.itemQty}>x{item.qty}</Text>
|
||||
<Text style={styles.itemQty}>x{item.quantity}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* Payment Collect Details */}
|
||||
<View style={styles.paymentCard}>
|
||||
<Text style={styles.paymentLabel}>Cash to Collect</Text>
|
||||
<Text style={styles.paymentVal}>₹0.00 (Online Paid)</Text>
|
||||
<Text style={styles.paymentLabel}>
|
||||
{activeJob?.order?.paymentMethod === 'WALLET' ||
|
||||
activeJob?.order?.paymentMethod === 'UPI'
|
||||
? 'Already Paid'
|
||||
: 'Collect from Customer'}
|
||||
</Text>
|
||||
<Text style={styles.paymentVal}>
|
||||
₹{activeJob?.order?.totalAmount}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<PrimaryButton
|
||||
|
||||
@ -6,7 +6,6 @@ export * from './dashboardScreen/dashboardScreen';
|
||||
export * from './earningsScreen/earningsScreen';
|
||||
export * from './jobsScreen/jobsScreen';
|
||||
export * from './inboxScreen/inboxScreen';
|
||||
export * from './profileScreen/profileScreen';
|
||||
export * from './documentsScreen/documentsScreen';
|
||||
export * from './newJobRequestScreen/newJobRequestScreen';
|
||||
export * from './orderAcceptedScreen/orderAcceptedScreen';
|
||||
@ -20,3 +19,4 @@ export * from './deliveryCompletedScreen/deliveryCompletedScreen';
|
||||
export * from './setLocationScreen';
|
||||
export * from './completeProfileScreen';
|
||||
export * from './kycScreen';
|
||||
export * from './profileScreen';
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { View, Text, ScrollView, TouchableOpacity } from 'react-native';
|
||||
import { useAppTheme } from '@theme';
|
||||
import { OrderCard, EmptyState } from '@components';
|
||||
import { useAppDispatch, useAppSelector } from '@store';
|
||||
import { ClipboardIcon } from '@icons';
|
||||
import { getStyles } from './jobsScreen.styles';
|
||||
import { getAllActiveDeliveries } from './thunk';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { getAllActiveDeliveries, getCompletedDeliveries } from './thunk';
|
||||
import { useFocusEffect, useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||
import { RouteNames } from '@utils/constants';
|
||||
@ -20,45 +20,52 @@ export const JobsScreen: React.FC = () => {
|
||||
|
||||
const [activeTab, setActiveTab] = useState<'active' | 'history'>('active');
|
||||
const { activeJob, jobHistory } = useAppSelector(state => state.job);
|
||||
const { activeDeliveries } = useAppSelector(state => state.deliveries);
|
||||
const { activeDeliveries, completedDeliveries } = useAppSelector(
|
||||
state => state.deliveries,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getAllActiveDeliveries());
|
||||
}, [dispatch]);
|
||||
// console.log(activeDeliveries, 'activeDeliveries');
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
dispatch(getAllActiveDeliveries());
|
||||
dispatch(getCompletedDeliveries());
|
||||
}, [dispatch]),
|
||||
);
|
||||
|
||||
// Default history mock jobs
|
||||
const defaultHistory = [
|
||||
{
|
||||
pickupName: 'Cafe Coffee Day',
|
||||
pickupAddress: 'MG Road, Bengaluru',
|
||||
dropName: 'Rohit Sharma',
|
||||
dropAddress: 'Koramangala 4th Block, Bengaluru',
|
||||
amount: '78.0',
|
||||
distance: '1.2',
|
||||
itemCount: 2,
|
||||
paymentType: 'online',
|
||||
},
|
||||
{
|
||||
pickupName: 'Pizza Hut',
|
||||
pickupAddress: 'Indiranagar, Bengaluru',
|
||||
dropName: 'Aishwarya Sen',
|
||||
dropAddress: 'Domlur Stage 2, Bengaluru',
|
||||
amount: '95.0',
|
||||
distance: '3.8',
|
||||
itemCount: 3,
|
||||
paymentType: 'cod',
|
||||
},
|
||||
{
|
||||
pickupName: 'Burgers & Co',
|
||||
pickupAddress: 'Koramangala 5th Block',
|
||||
dropName: 'Sameer Khan',
|
||||
dropAddress: 'HSR Layout Sector 3, Bengaluru',
|
||||
amount: '110.0',
|
||||
distance: '4.5',
|
||||
itemCount: 1,
|
||||
paymentType: 'online',
|
||||
},
|
||||
];
|
||||
// const defaultHistory = [
|
||||
// {
|
||||
// pickupName: 'Cafe Coffee Day',
|
||||
// pickupAddress: 'MG Road, Bengaluru',
|
||||
// dropName: 'Rohit Sharma',
|
||||
// dropAddress: 'Koramangala 4th Block, Bengaluru',
|
||||
// amount: '78.0',
|
||||
// distance: '1.2',
|
||||
// itemCount: 2,
|
||||
// paymentType: 'online',
|
||||
// },
|
||||
// {
|
||||
// pickupName: 'Pizza Hut',
|
||||
// pickupAddress: 'Indiranagar, Bengaluru',
|
||||
// dropName: 'Aishwarya Sen',
|
||||
// dropAddress: 'Domlur Stage 2, Bengaluru',
|
||||
// amount: '95.0',
|
||||
// distance: '3.8',
|
||||
// itemCount: 3,
|
||||
// paymentType: 'cod',
|
||||
// },
|
||||
// {
|
||||
// pickupName: 'Burgers & Co',
|
||||
// pickupAddress: 'Koramangala 5th Block',
|
||||
// dropName: 'Sameer Khan',
|
||||
// dropAddress: 'HSR Layout Sector 3, Bengaluru',
|
||||
// amount: '110.0',
|
||||
// distance: '4.5',
|
||||
// itemCount: 1,
|
||||
// paymentType: 'online',
|
||||
// },
|
||||
// ];
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
@ -105,22 +112,30 @@ export const JobsScreen: React.FC = () => {
|
||||
>
|
||||
{activeTab === 'active' ? (
|
||||
// Active job tab
|
||||
activeDeliveries ? (
|
||||
activeDeliveries?.delivery ? (
|
||||
<View style={styles.cardWrapper}>
|
||||
<OrderCard
|
||||
pickupName={activeDeliveries?.order?.pickupAddress?.city}
|
||||
pickupAddress={
|
||||
activeDeliveries?.order?.pickupAddress?.addressLine1
|
||||
pickupName={
|
||||
activeDeliveries?.delivery?.order?.pickupAddress?.city
|
||||
}
|
||||
dropName={activeDeliveries?.order?.dropAddress?.city}
|
||||
dropAddress={activeDeliveries?.order?.dropAddress?.addressLine1}
|
||||
amount={activeDeliveries?.order?.totalAmount}
|
||||
distance={activeDeliveries?.estimatedDistanceKm}
|
||||
itemCount={activeDeliveries?.order?.orderItems?.length}
|
||||
paymentType={activeDeliveries?.order?.paymentMethod}
|
||||
pickupAddress={
|
||||
activeDeliveries?.delivery?.order?.pickupAddress?.addressLine1
|
||||
}
|
||||
dropName={activeDeliveries?.delivery?.order?.dropAddress?.city}
|
||||
dropAddress={
|
||||
activeDeliveries?.delivery?.order?.dropAddress?.addressLine1
|
||||
}
|
||||
amount={activeDeliveries?.delivery?.order?.totalAmount}
|
||||
distance={
|
||||
activeDeliveries?.delivery?.estimatedDistanceKm || '0'
|
||||
}
|
||||
itemCount={
|
||||
activeDeliveries?.delivery?.order?.orderItems?.length
|
||||
}
|
||||
paymentType={activeDeliveries?.delivery?.order?.paymentMethod}
|
||||
onPress={() =>
|
||||
navigation.navigate(RouteNames.ArrivedAtStore, {
|
||||
jobId: activeDeliveries?.orderId,
|
||||
navigation.navigate(RouteNames.OrderPickedUp, {
|
||||
jobId: activeDeliveries?.delivery?.id,
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -136,35 +151,29 @@ export const JobsScreen: React.FC = () => {
|
||||
) : (
|
||||
// History tab
|
||||
<View>
|
||||
{jobHistory.length > 0
|
||||
? jobHistory.map((job, idx) => (
|
||||
<View key={idx} style={styles.cardWrapper}>
|
||||
<OrderCard
|
||||
pickupName={job.pickupName}
|
||||
pickupAddress={job.pickupAddress}
|
||||
dropName={job.dropName}
|
||||
dropAddress={job.dropAddress}
|
||||
amount={job.amount}
|
||||
distance={job.distance}
|
||||
itemCount={job.itemCount}
|
||||
paymentType={job.paymentType}
|
||||
/>
|
||||
</View>
|
||||
))
|
||||
: defaultHistory.map((job, idx) => (
|
||||
<View key={idx} style={styles.cardWrapper}>
|
||||
<OrderCard
|
||||
pickupName={job.pickupName}
|
||||
pickupAddress={job.pickupAddress}
|
||||
dropName={job.dropName}
|
||||
dropAddress={job.dropAddress}
|
||||
amount={job.amount}
|
||||
distance={job.distance}
|
||||
itemCount={job.itemCount}
|
||||
paymentType={job.paymentType}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
{completedDeliveries && completedDeliveries?.length > 0 ? (
|
||||
completedDeliveries?.map((job, idx) => (
|
||||
<View key={idx} style={styles.cardWrapper}>
|
||||
<OrderCard
|
||||
pickupName={
|
||||
job.order.merchant.name || job.order.pickupAddress.city
|
||||
}
|
||||
pickupAddress={job.order.pickupAddress.addressLine1}
|
||||
dropName={job.order.dropAddress.city}
|
||||
dropAddress={job.order.dropAddress.addressLine1}
|
||||
amount={job.order.totalAmount}
|
||||
distance={job.estimatedDistanceKm?.toString() || '0'}
|
||||
// itemCount={job.order?.}
|
||||
// paymentType={job.order.paymentMethod}
|
||||
/>
|
||||
</View>
|
||||
))
|
||||
) : (
|
||||
<View style={styles.emptyStateContainer}>
|
||||
<ClipboardIcon size={48} color={colors.placeholder} />
|
||||
<Text style={styles.emptyStateText}>No history found</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
import { ActiveOrdersResponse } from '@interfaces';
|
||||
import { DeliveryDetailsResponse, OrderHistoryResponse } from '@interfaces';
|
||||
import { createReducer } from '@reduxjs/toolkit';
|
||||
import { getAllActiveDeliveries } from './thunk';
|
||||
import { getAllActiveDeliveries, getCompletedDeliveries } from './thunk';
|
||||
|
||||
export interface ActiveDeliveriesState {
|
||||
activeDeliveries: ActiveOrdersResponse | null;
|
||||
activeDeliveries: DeliveryDetailsResponse | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
completedDeliveries: OrderHistoryResponse | null;
|
||||
}
|
||||
|
||||
const initialState: ActiveDeliveriesState = {
|
||||
activeDeliveries: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
completedDeliveries: null,
|
||||
};
|
||||
|
||||
export const activeDeliveriesReducer = createReducer(initialState, builder => {
|
||||
@ -27,5 +29,16 @@ export const activeDeliveriesReducer = createReducer(initialState, builder => {
|
||||
.addCase(getAllActiveDeliveries.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload as string;
|
||||
})
|
||||
.addCase(getCompletedDeliveries.pending, state => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(getCompletedDeliveries.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.completedDeliveries = action.payload;
|
||||
})
|
||||
.addCase(getCompletedDeliveries.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload as string;
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getActiveDeliveries } from '@api';
|
||||
import { completedDeliveries, getActiveDeliveries } from '@api';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
|
||||
export const getAllActiveDeliveries = createAsyncThunk(
|
||||
@ -12,3 +12,15 @@ export const getAllActiveDeliveries = createAsyncThunk(
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const getCompletedDeliveries = createAsyncThunk(
|
||||
'jobs/getCompletedDeliveries',
|
||||
async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await completedDeliveries();
|
||||
return response;
|
||||
} catch (error) {
|
||||
return rejectWithValue(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -4,7 +4,7 @@ import { useAppTheme } from '@theme';
|
||||
import { PrimaryButton } from '@components';
|
||||
import { useAppDispatch, useAppSelector } from '@store';
|
||||
import { advanceJobStatus } from '@store/commonReducers/job';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||
import { RouteNames, JobStatus } from '@utils/constants';
|
||||
@ -14,27 +14,41 @@ export const LiveTrackingScreen: React.FC = () => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
const dispatch = useAppDispatch();
|
||||
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||
const route =
|
||||
useRoute<RouteProp<AppStackParamList, RouteNames.OrderPickedUp>>();
|
||||
const { jobId } = route.params;
|
||||
|
||||
const activeJob = useAppSelector((state) => state.job.activeJob);
|
||||
const { activeDeliveries } = useAppSelector(state => state.deliveries);
|
||||
const activeJob = activeDeliveries?.delivery;
|
||||
|
||||
const handleArrived = () => {
|
||||
dispatch(advanceJobStatus(JobStatus.ArrivedAtCustomer));
|
||||
navigation.navigate(RouteNames.ArrivedAtCustomer);
|
||||
navigation.navigate(RouteNames.DeliverOrder, {
|
||||
jobId: jobId,
|
||||
});
|
||||
};
|
||||
|
||||
const handleContact = () => {
|
||||
Alert.alert('Contact Customer', `Calling ${activeJob?.dropName || 'Customer'} (+91 99887 76655)...`);
|
||||
Alert.alert(
|
||||
'Contact Customer',
|
||||
`Calling ${activeJob?.order?.customer?.user?.name || 'Customer'} at ${
|
||||
activeJob?.order?.customer?.user?.phone
|
||||
} `,
|
||||
);
|
||||
};
|
||||
|
||||
if (!activeJob) return null;
|
||||
// if (!activeJob) return null;
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* Top Floating ETA Card */}
|
||||
<View style={styles.etaCard}>
|
||||
<Text style={styles.etaTitle}>Arriving in 8 mins</Text>
|
||||
<Text style={styles.etaSub}>Distance: {activeJob.distance} km • Speed: 24 km/h</Text>
|
||||
<Text style={styles.etaSub}>
|
||||
Distance: {activeJob?.estimatedDistanceKm} km • Speed: 24 km/h
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Map Background Placeholder */}
|
||||
@ -45,8 +59,14 @@ export const LiveTrackingScreen: React.FC = () => {
|
||||
{/* Customer Location Bottom Card */}
|
||||
<View style={styles.bottomCard}>
|
||||
<Text style={styles.cardTitle}>Deliver to</Text>
|
||||
<Text style={styles.customerName}>{activeJob.dropName}</Text>
|
||||
<Text style={styles.customerAddress}>{activeJob.dropAddress}</Text>
|
||||
<Text style={styles.customerName}>
|
||||
{activeJob?.order?.customer?.user?.name}
|
||||
</Text>
|
||||
<Text style={styles.customerAddress}>
|
||||
{activeJob?.order?.dropAddress?.houseNumber +
|
||||
', ' +
|
||||
activeJob?.order?.dropAddress?.addressLine1}
|
||||
</Text>
|
||||
|
||||
<View style={styles.btnRow}>
|
||||
<PrimaryButton
|
||||
|
||||
@ -2,9 +2,9 @@ import React from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
import { useAppTheme } from '@theme';
|
||||
import { PrimaryButton } from '@components';
|
||||
import { useAppDispatch, useAppSelector } from '@store';
|
||||
import { changeOrderStatusThunk, useAppDispatch, useAppSelector } from '@store';
|
||||
import { advanceJobStatus } from '@store/commonReducers/job';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||
import { RouteNames, JobStatus } from '@utils/constants';
|
||||
@ -14,13 +14,24 @@ export const OrderPickedUpScreen: React.FC = () => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
const dispatch = useAppDispatch();
|
||||
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||
const route =
|
||||
useRoute<RouteProp<AppStackParamList, RouteNames.OrderPickedUp>>();
|
||||
const { jobId } = route.params;
|
||||
// console.log(jobId, 'jobId');
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||
|
||||
const activeJob = useAppSelector((state) => state.job.activeJob);
|
||||
// const activeJob = useAppSelector(state => state.job.activeJob);
|
||||
const { activeDeliveries } = useAppSelector(state => state.deliveries);
|
||||
const activeJob = activeDeliveries?.delivery;
|
||||
|
||||
const handleStartDelivery = () => {
|
||||
dispatch(advanceJobStatus(JobStatus.EnRoute));
|
||||
navigation.navigate(RouteNames.LiveTracking);
|
||||
dispatch(
|
||||
changeOrderStatusThunk({ deliveryId: jobId, action: JobStatus.PickedUp }),
|
||||
);
|
||||
navigation.navigate(RouteNames.LiveTracking, {
|
||||
jobId: jobId,
|
||||
});
|
||||
};
|
||||
|
||||
if (!activeJob) return null;
|
||||
@ -35,8 +46,18 @@ export const OrderPickedUpScreen: React.FC = () => {
|
||||
{/* Deliver Bottom Card */}
|
||||
<View style={styles.bottomCard}>
|
||||
<Text style={styles.cardTitle}>Deliver to Customer</Text>
|
||||
<Text style={styles.customerName}>{activeJob.dropName}</Text>
|
||||
<Text style={styles.customerAddress}>{activeJob.dropAddress}</Text>
|
||||
<Text style={styles.customerName}>
|
||||
{activeJob?.order?.customer?.user?.name}
|
||||
</Text>
|
||||
<Text style={styles.customerAddress}>
|
||||
{activeJob?.order?.dropAddress?.houseNumber},{' '}
|
||||
{activeJob?.order?.dropAddress?.addressLine1}
|
||||
{activeJob?.order?.dropAddress?.city},{' '}
|
||||
{activeJob?.order?.dropAddress?.state},
|
||||
{activeJob?.order?.dropAddress?.postalCode}
|
||||
{'\n'}
|
||||
{activeJob?.order?.customer?.user?.phone}
|
||||
</Text>
|
||||
|
||||
<PrimaryButton
|
||||
title="Start Delivery"
|
||||
|
||||
4
app/features/screens/profileScreen/index.ts
Normal file
4
app/features/screens/profileScreen/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './profileScreen';
|
||||
export * from './profileScreen.styles';
|
||||
export * from './thunk';
|
||||
export * from './reducer';
|
||||
31
app/features/screens/profileScreen/reducer.ts
Normal file
31
app/features/screens/profileScreen/reducer.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { createReducer, createSlice } from '@reduxjs/toolkit';
|
||||
import { getAccountInfoThunk } from './thunk';
|
||||
import { DeliveryPartnerProfileResponse } from '@interfaces';
|
||||
|
||||
interface ProfileState {
|
||||
profile: DeliveryPartnerProfileResponse | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const initialState: ProfileState = {
|
||||
profile: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
export const profileReducer = createReducer(initialState, builder => {
|
||||
builder
|
||||
.addCase(getAccountInfoThunk.pending, state => {
|
||||
state.loading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(getAccountInfoThunk.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.profile = action.payload;
|
||||
})
|
||||
.addCase(getAccountInfoThunk.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload as string;
|
||||
});
|
||||
});
|
||||
14
app/features/screens/profileScreen/thunk.ts
Normal file
14
app/features/screens/profileScreen/thunk.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { getAccountInfo } from '@api';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
|
||||
export const getAccountInfoThunk = createAsyncThunk(
|
||||
'account/getAccountInfo',
|
||||
async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await getAccountInfo();
|
||||
return response;
|
||||
} catch (error) {
|
||||
return rejectWithValue(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
34
app/interfaces/accountInfo.ts
Normal file
34
app/interfaces/accountInfo.ts
Normal file
@ -0,0 +1,34 @@
|
||||
export interface DeliveryPartnerProfileResponse {
|
||||
id: string;
|
||||
userId: string;
|
||||
partnerCode: string;
|
||||
kycStatus: string;
|
||||
status: string;
|
||||
availabilityStatus: string;
|
||||
rating: string;
|
||||
currentLatitude: string;
|
||||
currentLongitude: string;
|
||||
lastLocationAt: string;
|
||||
user: DeliveryPartnerUser;
|
||||
vehicles: DeliveryPartnerVehicle[];
|
||||
completedDeliveries: number;
|
||||
}
|
||||
|
||||
export interface DeliveryPartnerUser {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
profileImage: string | null;
|
||||
roleEnum: string;
|
||||
}
|
||||
|
||||
export interface DeliveryPartnerVehicle {
|
||||
id: string;
|
||||
deliveryPartnerId: string;
|
||||
vehicleNumber: string;
|
||||
vehicleType: string;
|
||||
status: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@ -4,3 +4,4 @@ export * from './kyc';
|
||||
export * from './dashboard';
|
||||
export * from './delivery';
|
||||
export * from './order';
|
||||
export * from './accountInfo';
|
||||
|
||||
@ -1,112 +1,224 @@
|
||||
export interface ActiveOrdersResponse {
|
||||
export interface DeliveryDetailsResponse {
|
||||
delivery: Delivery;
|
||||
}
|
||||
|
||||
export interface Delivery {
|
||||
id: string;
|
||||
orderId: string;
|
||||
deliveryPartnerId: string;
|
||||
deliveryNumber: string;
|
||||
status: string;
|
||||
estimatedDistanceKm: string;
|
||||
actualDistanceKm: string;
|
||||
proofOfPickupKey: string;
|
||||
proofOfDeliveryKey: string;
|
||||
pickedUpAt: string;
|
||||
deliveredAt: string;
|
||||
order: {
|
||||
id: string;
|
||||
orderNumber: string;
|
||||
customerId: string;
|
||||
merchantId: string;
|
||||
couponCode: string;
|
||||
appliedPromotionId: string;
|
||||
status: string;
|
||||
orderType: string;
|
||||
paymentMethodId: string;
|
||||
paymentMethod: string;
|
||||
currency: string;
|
||||
subtotal: string;
|
||||
deliveryFee: string;
|
||||
platformFee: string;
|
||||
serviceFee: string;
|
||||
taxAmount: string;
|
||||
discountAmount: string;
|
||||
totalAmount: string;
|
||||
pickupAddress: {
|
||||
city: string;
|
||||
label: string;
|
||||
phone: string;
|
||||
state: string;
|
||||
landmark: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
postalCode: string;
|
||||
houseNumber: string;
|
||||
addressLine1: string;
|
||||
};
|
||||
dropAddress: {
|
||||
city: string;
|
||||
label: string;
|
||||
phone: string;
|
||||
state: string;
|
||||
landmark: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
postalCode: string;
|
||||
houseNumber: string;
|
||||
addressLine1: string;
|
||||
};
|
||||
// "metadata": {
|
||||
// "idempotencyKey": "idemp-key-12345",
|
||||
// "trackingHistory": {
|
||||
// "CONFIRMED": "2026-07-13T10:21:35.863Z"
|
||||
// },
|
||||
// "rejectedDriverIds": [
|
||||
// "e5f639a8-f0cf-4357-ab45-919ebc3012bd"
|
||||
// ]
|
||||
// },
|
||||
version: 1;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string;
|
||||
customer: {
|
||||
id: string;
|
||||
userId: string;
|
||||
customerCode: string;
|
||||
createdAt: string;
|
||||
deletedAt: string;
|
||||
user: {
|
||||
id: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
profileImage: string;
|
||||
};
|
||||
};
|
||||
// "merchant": {
|
||||
// "id": "f53b10bf-1d72-42e6-9d4e-b78032ad051b",
|
||||
// "name": "eererer",
|
||||
// "imageUrl": null,
|
||||
// "addresses": [
|
||||
// {
|
||||
// "id": "5476dd01-4b9c-4597-ad0f-1898ad333af6",
|
||||
// "mapAddress": "Bantra, Jaynagar - I, South 24 Parganas, West Bengal, India",
|
||||
// "latitude": "22.204311",
|
||||
// "longitude": "88.509444",
|
||||
// "city": "New York",
|
||||
// "landmark": "Near Central Park",
|
||||
// "phone": "+19999999999"
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
orderItems: OrderItem[];
|
||||
};
|
||||
status: DeliveryStatus;
|
||||
estimatedDistanceKm: string | null;
|
||||
actualDistanceKm: string | null;
|
||||
proofOfPickupKey: string | null;
|
||||
proofOfDeliveryKey: string | null;
|
||||
pickedUpAt: string | null;
|
||||
deliveredAt: string | null;
|
||||
order: DeliveryOrder;
|
||||
}
|
||||
|
||||
export interface OrderItem {
|
||||
export type DeliveryStatus =
|
||||
| 'PENDING'
|
||||
| 'ACCEPTED'
|
||||
| 'ARRIVED_AT_PICKUP'
|
||||
| 'OUT_FOR_DELIVERY'
|
||||
| 'PICKED_UP'
|
||||
| 'IN_TRANSIT'
|
||||
| 'ARRIVED_AT_DROPOFF'
|
||||
| 'DELIVERED'
|
||||
| 'CANCELLED';
|
||||
|
||||
export interface DeliveryOrder {
|
||||
id: string;
|
||||
orderNumber: string;
|
||||
customerId: string;
|
||||
merchantId: string;
|
||||
couponCode: string | null;
|
||||
appliedPromotionId: string | null;
|
||||
status: OrderStatus;
|
||||
orderType: OrderType;
|
||||
paymentMethodId: string;
|
||||
paymentMethod: string;
|
||||
currency: string;
|
||||
subtotal: string;
|
||||
deliveryFee: string;
|
||||
platformFee: string;
|
||||
serviceFee: string;
|
||||
taxAmount: string;
|
||||
discountAmount: string;
|
||||
totalAmount: string;
|
||||
pickupAddress: DeliveryAddress;
|
||||
dropAddress: DeliveryAddress;
|
||||
metadata: DeliveryMetadata;
|
||||
version: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
customer: DeliveryCustomer;
|
||||
merchant: DeliveryMerchant;
|
||||
orderItems: DeliveryOrderItem[];
|
||||
}
|
||||
|
||||
export type OrderStatus =
|
||||
| 'PLACED'
|
||||
| 'CONFIRMED'
|
||||
| 'PREPARING'
|
||||
| 'READY_FOR_PICKUP'
|
||||
| 'OUT_FOR_DELIVERY'
|
||||
| 'DELIVERED'
|
||||
| 'CANCELLED';
|
||||
|
||||
export type OrderType = 'DELIVERY' | 'PICKUP';
|
||||
|
||||
export interface DeliveryAddress {
|
||||
city: string;
|
||||
label: string | null;
|
||||
phone: string;
|
||||
state: string;
|
||||
landmark: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
postalCode: string;
|
||||
houseNumber: string;
|
||||
addressLine1: string;
|
||||
}
|
||||
|
||||
export interface DeliveryMetadata {
|
||||
idempotencyKey: string;
|
||||
trackingHistory: TrackingHistory;
|
||||
}
|
||||
|
||||
export interface TrackingHistory {
|
||||
PLACED?: string;
|
||||
CONFIRMED?: string;
|
||||
PREPARING?: string;
|
||||
READY_FOR_PICKUP?: string;
|
||||
PICKED_UP?: string;
|
||||
OUT_FOR_DELIVERY?: string;
|
||||
DELIVERED?: string;
|
||||
CANCELLED?: string;
|
||||
}
|
||||
|
||||
export interface DeliveryCustomer {
|
||||
id: string;
|
||||
userId: string;
|
||||
customerCode: string;
|
||||
createdAt: string;
|
||||
deletedAt: string | null;
|
||||
user: DeliveryUser;
|
||||
}
|
||||
|
||||
export interface DeliveryUser {
|
||||
id: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
profileImage: string | null;
|
||||
}
|
||||
|
||||
export interface DeliveryMerchant {
|
||||
id: string;
|
||||
name: string;
|
||||
imageUrl: string | null;
|
||||
addresses: MerchantAddress[];
|
||||
}
|
||||
|
||||
export interface MerchantAddress {
|
||||
id: string;
|
||||
mapAddress: string;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
city: string;
|
||||
landmark: string;
|
||||
phone: string;
|
||||
}
|
||||
|
||||
export interface DeliveryOrderItem {
|
||||
id: string;
|
||||
orderId: string;
|
||||
productId: string;
|
||||
itemType: string;
|
||||
itemType: 'PRODUCT' | 'SERVICE';
|
||||
name: string;
|
||||
quantity: string;
|
||||
unitPrice: string;
|
||||
totalAmount: string;
|
||||
attributes: object;
|
||||
attributes: Record<string, any>;
|
||||
}
|
||||
|
||||
//pickup
|
||||
export interface PickupOrderResponse {
|
||||
message: string;
|
||||
delivery: PickupDelivery;
|
||||
}
|
||||
|
||||
export interface PickupDelivery {
|
||||
id: string;
|
||||
orderId: string;
|
||||
deliveryPartnerId: string;
|
||||
deliveryNumber: string;
|
||||
status: DeliveryStatus;
|
||||
estimatedDistanceKm: number | null;
|
||||
actualDistanceKm: number | null;
|
||||
proofOfPickupKey: string | null;
|
||||
proofOfDeliveryKey: string | null;
|
||||
pickedUpAt: string | null;
|
||||
deliveredAt: string | null;
|
||||
order: PickupOrder;
|
||||
}
|
||||
|
||||
export interface PickupOrder {
|
||||
id: string;
|
||||
orderNumber: string;
|
||||
dropAddress: DeliveryAddress;
|
||||
customer: PickupCustomer;
|
||||
}
|
||||
|
||||
export interface PickupCustomer {
|
||||
id: string;
|
||||
userId: string;
|
||||
customerCode: string;
|
||||
createdAt: string;
|
||||
deletedAt: string | null;
|
||||
user: PickupUser;
|
||||
}
|
||||
|
||||
export interface PickupUser {
|
||||
name: string;
|
||||
phone: string;
|
||||
}
|
||||
|
||||
//order History
|
||||
export type OrderHistoryResponse = OrderHistoryItem[];
|
||||
|
||||
export interface OrderHistoryItem {
|
||||
id: string;
|
||||
orderId: string;
|
||||
deliveryPartnerId: string;
|
||||
deliveryNumber: string;
|
||||
status: DeliveryStatus;
|
||||
estimatedDistanceKm: number | null;
|
||||
actualDistanceKm: string | null;
|
||||
proofOfPickupKey: string | null;
|
||||
proofOfDeliveryKey: string | null;
|
||||
pickedUpAt: string | null;
|
||||
deliveredAt: string | null;
|
||||
order: OrderHistoryOrder;
|
||||
}
|
||||
|
||||
export interface OrderHistoryOrder {
|
||||
id: string;
|
||||
orderNumber: string;
|
||||
subtotal: string;
|
||||
deliveryFee: string;
|
||||
totalAmount: string;
|
||||
currency: string;
|
||||
pickupAddress: DeliveryAddress;
|
||||
dropAddress: DeliveryAddress;
|
||||
createdAt: string;
|
||||
merchant: OrderHistoryMerchant;
|
||||
}
|
||||
|
||||
export interface OrderHistoryMerchant {
|
||||
id: string;
|
||||
name: string;
|
||||
imageUrl: string | null;
|
||||
}
|
||||
|
||||
@ -30,10 +30,10 @@ export type AppStackParamList = {
|
||||
[RouteNames.OrderAccepted]: undefined;
|
||||
[RouteNames.ArrivedAtStore]: { jobId: string };
|
||||
[RouteNames.ConfirmPickup]: undefined;
|
||||
[RouteNames.OrderPickedUp]: undefined;
|
||||
[RouteNames.LiveTracking]: undefined;
|
||||
[RouteNames.OrderPickedUp]: { jobId: string };
|
||||
[RouteNames.LiveTracking]: { jobId: string };
|
||||
[RouteNames.ArrivedAtCustomer]: { jobId: string };
|
||||
[RouteNames.DeliverOrder]: undefined;
|
||||
[RouteNames.DeliverOrder]: { jobId: string };
|
||||
[RouteNames.DeliveryCompleted]: undefined;
|
||||
[RouteNames.Documents]: undefined;
|
||||
// [RouteNames.JobDetails]: { jobId: string };
|
||||
|
||||
@ -15,7 +15,7 @@ const STORAGE_KEYS = {
|
||||
} as const;
|
||||
|
||||
// ─── Config ──────────────────────────────────────────────────────────────────
|
||||
const BASE_URL = 'https://8415-202-8-116-13.ngrok-free.app'; // TODO: replace with your actual base URL
|
||||
const BASE_URL = 'https://157a-202-8-116-13.ngrok-free.app'; // TODO: replace with your actual base URL
|
||||
|
||||
// ─── Token Helpers ───────────────────────────────────────────────────────────
|
||||
export const tokenManager = {
|
||||
|
||||
@ -6,7 +6,7 @@ import { setDeliveryOffer } from '@store/commonReducers/delivery';
|
||||
import { RouteNames } from '@utils/constants';
|
||||
|
||||
// Must match the REST API base URL (without trailing slash)
|
||||
const BASE_URL = 'https://8415-202-8-116-13.ngrok-free.app';
|
||||
const BASE_URL = 'https://157a-202-8-116-13.ngrok-free.app';
|
||||
|
||||
type Callback = (data: any) => void;
|
||||
|
||||
@ -30,8 +30,12 @@ class SocketService {
|
||||
* the store and navigationRef to be injected independently.
|
||||
*/
|
||||
bootstrap(store: Store | null, navigationRef: any) {
|
||||
if (store) { this.store = store; }
|
||||
if (navigationRef) { this.navigationRef = navigationRef; }
|
||||
if (store) {
|
||||
this.store = store;
|
||||
}
|
||||
if (navigationRef) {
|
||||
this.navigationRef = navigationRef;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,21 +68,18 @@ class SocketService {
|
||||
});
|
||||
|
||||
// ── Listen for delivery offers ─────────────────────────────────────
|
||||
this.socket.on(
|
||||
SocketEvents.DeliveryOffer,
|
||||
(data: DeliveryOffer) => {
|
||||
console.log('[Socket] delivery_offer received:', data);
|
||||
this.socket.on(SocketEvents.DeliveryOffer, (data: DeliveryOffer) => {
|
||||
console.log('[Socket] delivery_offer received:', data);
|
||||
|
||||
if (this.store) {
|
||||
this.store.dispatch(setDeliveryOffer(data));
|
||||
}
|
||||
if (this.store) {
|
||||
this.store.dispatch(setDeliveryOffer(data));
|
||||
}
|
||||
|
||||
// Navigate to NewJobRequest screen regardless of current screen
|
||||
if (this.navigationRef?.isReady()) {
|
||||
this.navigationRef.navigate(RouteNames.NewJobRequest);
|
||||
}
|
||||
},
|
||||
);
|
||||
// Navigate to NewJobRequest screen regardless of current screen
|
||||
if (this.navigationRef?.isReady()) {
|
||||
this.navigationRef.navigate(RouteNames.NewJobRequest);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
import { createReducer, createAction } from '@reduxjs/toolkit';
|
||||
import { DeliveryOffer } from '@interfaces';
|
||||
import { respondToOfferThunk, checkPendingOfferThunk } from './thunk';
|
||||
import { DeliveryOffer, PickupOrderResponse } from '@interfaces';
|
||||
import {
|
||||
respondToOfferThunk,
|
||||
checkPendingOfferThunk,
|
||||
changeOrderStatusThunk,
|
||||
} from './thunk';
|
||||
|
||||
export interface DeliveryState {
|
||||
currentOffer: DeliveryOffer | null;
|
||||
respondLoading: boolean;
|
||||
respondError: string | null;
|
||||
pendingOfferLoading: boolean;
|
||||
pickupLoading: boolean;
|
||||
pickupError: string | null;
|
||||
pickupOrderResponse: PickupOrderResponse | null;
|
||||
}
|
||||
|
||||
const initialState: DeliveryState = {
|
||||
@ -14,6 +21,9 @@ const initialState: DeliveryState = {
|
||||
respondLoading: false,
|
||||
respondError: null,
|
||||
pendingOfferLoading: false,
|
||||
pickupLoading: false,
|
||||
pickupError: null,
|
||||
pickupOrderResponse: null,
|
||||
};
|
||||
|
||||
/** Dispatched by socketService when a `delivery_offer` event is received. */
|
||||
@ -57,5 +67,17 @@ export const deliveryReducer = createReducer(initialState, builder => {
|
||||
})
|
||||
.addCase(checkPendingOfferThunk.rejected, state => {
|
||||
state.pendingOfferLoading = false;
|
||||
})
|
||||
.addCase(changeOrderStatusThunk.pending, state => {
|
||||
state.pickupLoading = true;
|
||||
state.pickupError = null;
|
||||
})
|
||||
.addCase(changeOrderStatusThunk.fulfilled, (state, action) => {
|
||||
state.pickupLoading = false;
|
||||
state.pickupOrderResponse = action.payload;
|
||||
})
|
||||
.addCase(changeOrderStatusThunk.rejected, (state, action) => {
|
||||
state.pickupLoading = false;
|
||||
state.pickupError = action.payload as string;
|
||||
});
|
||||
});
|
||||
|
||||
@ -4,7 +4,7 @@ import {
|
||||
RespondToOfferResponse,
|
||||
PendingOfferResponse,
|
||||
} from '@interfaces';
|
||||
import * as deliveryApi from '../../../api/deliveryApi';
|
||||
import { changeOrderStatus, getPendingOffer, respondToOffer } from '@api';
|
||||
|
||||
/**
|
||||
* Respond to a delivery offer (ACCEPT / REJECT).
|
||||
@ -16,7 +16,7 @@ export const respondToOfferThunk = createAsyncThunk<
|
||||
'delivery/respondToOffer',
|
||||
async ({ deliveryId, action }, { rejectWithValue }) => {
|
||||
try {
|
||||
return await deliveryApi.respondToOffer(deliveryId, action);
|
||||
return await respondToOffer(deliveryId, action);
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(
|
||||
error.message || 'Failed to respond to delivery offer',
|
||||
@ -32,11 +32,27 @@ export const checkPendingOfferThunk = createAsyncThunk<PendingOfferResponse>(
|
||||
'delivery/checkPendingOffer',
|
||||
async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
return await deliveryApi.getPendingOffer();
|
||||
return await getPendingOffer();
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(
|
||||
error.message || 'Failed to check pending offers',
|
||||
);
|
||||
return rejectWithValue(error.message || 'Failed to check pending offers');
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const changeOrderStatusThunk = createAsyncThunk(
|
||||
'delivery/changeOrderStatus',
|
||||
async (
|
||||
{
|
||||
deliveryId,
|
||||
action,
|
||||
proofKey,
|
||||
}: { deliveryId: string; action: string; proofKey?: string },
|
||||
{ rejectWithValue },
|
||||
) => {
|
||||
try {
|
||||
return await changeOrderStatus(deliveryId, action, proofKey);
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || 'Failed to change order status');
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -4,7 +4,11 @@ import { earningsReducer } from './commonReducers/earnings';
|
||||
import { jobReducer } from './commonReducers/job';
|
||||
import { deliveryReducer } from './commonReducers/delivery';
|
||||
import setLocationReducer from '@features/screens/setLocationScreen/reducer';
|
||||
import { completeProfileReducer, kycReducer } from '@features/screens';
|
||||
import {
|
||||
completeProfileReducer,
|
||||
kycReducer,
|
||||
profileReducer,
|
||||
} from '@features/screens';
|
||||
import { onBoardingCompleteReducer } from '@features/screens/onboardingCompleteScreen';
|
||||
import { dashBoardReducer } from '@features/screens/dashboardScreen';
|
||||
import { activeDeliveriesReducer } from '@features/screens/jobsScreen';
|
||||
@ -20,6 +24,7 @@ const rootReducer = combineReducers({
|
||||
onboardingComplete: onBoardingCompleteReducer,
|
||||
dashboard: dashBoardReducer,
|
||||
deliveries: activeDeliveriesReducer,
|
||||
accountInfo: profileReducer,
|
||||
});
|
||||
|
||||
export type RootState = ReturnType<typeof rootReducer>;
|
||||
|
||||
@ -37,10 +37,10 @@ export enum JobStatus {
|
||||
NewRequest = 'newRequest',
|
||||
Accepted = 'accepted',
|
||||
ArrivedAtStore = 'arrivedAtStore',
|
||||
PickedUp = 'pickedUp',
|
||||
PickedUp = 'PICKUP',
|
||||
EnRoute = 'enRoute',
|
||||
ArrivedAtCustomer = 'arrivedAtCustomer',
|
||||
Delivered = 'delivered',
|
||||
Delivered = 'DELIVER',
|
||||
}
|
||||
|
||||
export enum SocketEvents {
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
export const formatCurrency = (amount: number, currency: string = '₹'): string => {
|
||||
return `${currency}${amount.toLocaleString('en-IN', {
|
||||
export const formatCurrency = (
|
||||
amount: number | undefined,
|
||||
currency: string = '₹',
|
||||
): string => {
|
||||
return `${currency}${amount?.toLocaleString('en-IN', {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 2,
|
||||
})}`;
|
||||
@ -7,7 +10,7 @@ export const formatCurrency = (amount: number, currency: string = '₹'): string
|
||||
|
||||
export const formatDistance = (km: number): string => {
|
||||
if (km < 1) return `${Math.round(km * 1000)} m`;
|
||||
return `${km.toFixed(1)} km`;
|
||||
return `${km?.toFixed(1)} km`;
|
||||
};
|
||||
|
||||
export const formatDuration = (seconds: number): string => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user