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,
|
DeliveryAction,
|
||||||
RespondToOfferResponse,
|
RespondToOfferResponse,
|
||||||
PendingOfferResponse,
|
PendingOfferResponse,
|
||||||
ActiveOrdersResponse,
|
PickupOrderResponse,
|
||||||
|
DeliveryDetailsResponse,
|
||||||
|
OrderHistoryResponse,
|
||||||
} from '@interfaces';
|
} from '@interfaces';
|
||||||
import { apiClient } from '@services';
|
import { apiClient } from '@services';
|
||||||
|
|
||||||
@ -32,9 +34,29 @@ export const getPendingOffer = async (): Promise<PendingOfferResponse> => {
|
|||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getActiveDeliveries = async (): Promise<ActiveOrdersResponse> => {
|
export const getActiveDeliveries =
|
||||||
const response = await apiClient.get<ActiveOrdersResponse>(
|
async (): Promise<DeliveryDetailsResponse> => {
|
||||||
|
const response = await apiClient.get<DeliveryDetailsResponse>(
|
||||||
'/delivery-partners/deliveries/active',
|
'/delivery-partners/deliveries/active',
|
||||||
);
|
);
|
||||||
return response;
|
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 './kycApi';
|
||||||
export * from './dashboardApi';
|
export * from './dashboardApi';
|
||||||
export * from './deliveryApi';
|
export * from './deliveryApi';
|
||||||
|
export * from './accountApi';
|
||||||
|
|||||||
@ -12,15 +12,16 @@ import { getStyles } from './arrivedAtCustomerScreen.styles';
|
|||||||
export const ArrivedAtCustomerScreen: React.FC = () => {
|
export const ArrivedAtCustomerScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
const styles = getStyles(colors);
|
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 = () => {
|
const handleConfirmArrival = () => {
|
||||||
navigation.navigate(RouteNames.DeliverOrder);
|
// navigation.navigate(RouteNames.DeliverOrder);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!activeJob) return null;
|
// if (!activeJob) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
@ -32,8 +33,8 @@ export const ArrivedAtCustomerScreen: React.FC = () => {
|
|||||||
{/* Arrived Card */}
|
{/* Arrived Card */}
|
||||||
<View style={styles.bottomCard}>
|
<View style={styles.bottomCard}>
|
||||||
<Text style={styles.cardTitle}>You've arrived</Text>
|
<Text style={styles.cardTitle}>You've arrived</Text>
|
||||||
<Text style={styles.customerName}>{activeJob.dropName}</Text>
|
<Text style={styles.customerName}>{activeJob?.dropName}</Text>
|
||||||
<Text style={styles.customerAddress}>{activeJob.dropAddress}</Text>
|
<Text style={styles.customerAddress}>{activeJob?.dropAddress}</Text>
|
||||||
|
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
title="Confirm Arrival"
|
title="Confirm Arrival"
|
||||||
|
|||||||
@ -2,35 +2,46 @@ import React, { useState } from 'react';
|
|||||||
import { View, Text, ScrollView, Alert } from 'react-native';
|
import { View, Text, ScrollView, Alert } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
import { ScreenHeader, PrimaryButton } from '@components';
|
import { ScreenHeader, PrimaryButton } from '@components';
|
||||||
import { useAppDispatch, useAppSelector } from '@store';
|
import { changeOrderStatusThunk, useAppDispatch, useAppSelector } from '@store';
|
||||||
import { completeJob } from '@store/commonReducers/job';
|
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 { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
import { AppStackParamList } from '@navigation/navigationTypes';
|
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||||
import { RouteNames } from '@utils/constants';
|
import { JobStatus, RouteNames } from '@utils/constants';
|
||||||
import { getStyles } from './deliverOrderScreen.styles';
|
import { getStyles } from './deliverOrderScreen.styles';
|
||||||
|
|
||||||
export const DeliverOrderScreen: React.FC = () => {
|
export const DeliverOrderScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
const styles = getStyles(colors);
|
const styles = getStyles(colors);
|
||||||
const dispatch = useAppDispatch();
|
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 [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const items = [
|
// const items = [
|
||||||
{ id: 0, name: 'Cappuccino (Medium)', qty: 1 },
|
// { id: 0, name: 'Cappuccino (Medium)', qty: 1 },
|
||||||
{ id: 1, name: 'Chocolate Croissant', qty: 1 },
|
// { id: 1, name: 'Chocolate Croissant', qty: 1 },
|
||||||
];
|
// ];
|
||||||
|
|
||||||
const handleCompleteDelivery = () => {
|
const handleCompleteDelivery = () => {
|
||||||
setIsLoading(true);
|
dispatch(
|
||||||
setTimeout(() => {
|
changeOrderStatusThunk({
|
||||||
setIsLoading(false);
|
deliveryId: jobId,
|
||||||
dispatch(completeJob());
|
action: JobStatus.Delivered,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.then(() => {
|
||||||
|
// dispatch(completeJob());
|
||||||
navigation.navigate(RouteNames.DeliveryCompleted);
|
navigation.navigate(RouteNames.DeliveryCompleted);
|
||||||
}, 1200);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!activeJob) return null;
|
if (!activeJob) return null;
|
||||||
@ -43,34 +54,46 @@ export const DeliverOrderScreen: React.FC = () => {
|
|||||||
onBack={() => navigation.goBack()}
|
onBack={() => navigation.goBack()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
|
<ScrollView
|
||||||
|
contentContainerStyle={styles.scrollContainer}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
>
|
||||||
{/* Customer Detail Card */}
|
{/* Customer Detail Card */}
|
||||||
<View style={styles.card}>
|
<View style={styles.card}>
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<Text style={styles.label}>Customer Name</Text>
|
<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>
|
||||||
<View style={[styles.row, { marginTop: 10 }]}>
|
<View style={[styles.row, { marginTop: 10 }]}>
|
||||||
<Text style={styles.label}>Order ID</Text>
|
<Text style={styles.label}>Order ID</Text>
|
||||||
<Text style={styles.value}>{activeJob.orderId}</Text>
|
<Text style={styles.value}>{activeJob?.orderId}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Items Summary list */}
|
{/* Items Summary list */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>Items Details</Text>
|
<Text style={styles.sectionTitle}>Items Details</Text>
|
||||||
{items.map((item) => (
|
{activeJob?.order?.orderItems?.map(item => (
|
||||||
<View key={item.id} style={styles.itemRow}>
|
<View key={item.id} style={styles.itemRow}>
|
||||||
<Text style={styles.itemName}>{item.name}</Text>
|
<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>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Payment Collect Details */}
|
{/* Payment Collect Details */}
|
||||||
<View style={styles.paymentCard}>
|
<View style={styles.paymentCard}>
|
||||||
<Text style={styles.paymentLabel}>Cash to Collect</Text>
|
<Text style={styles.paymentLabel}>
|
||||||
<Text style={styles.paymentVal}>₹0.00 (Online Paid)</Text>
|
{activeJob?.order?.paymentMethod === 'WALLET' ||
|
||||||
|
activeJob?.order?.paymentMethod === 'UPI'
|
||||||
|
? 'Already Paid'
|
||||||
|
: 'Collect from Customer'}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.paymentVal}>
|
||||||
|
₹{activeJob?.order?.totalAmount}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
|
|||||||
@ -6,7 +6,6 @@ export * from './dashboardScreen/dashboardScreen';
|
|||||||
export * from './earningsScreen/earningsScreen';
|
export * from './earningsScreen/earningsScreen';
|
||||||
export * from './jobsScreen/jobsScreen';
|
export * from './jobsScreen/jobsScreen';
|
||||||
export * from './inboxScreen/inboxScreen';
|
export * from './inboxScreen/inboxScreen';
|
||||||
export * from './profileScreen/profileScreen';
|
|
||||||
export * from './documentsScreen/documentsScreen';
|
export * from './documentsScreen/documentsScreen';
|
||||||
export * from './newJobRequestScreen/newJobRequestScreen';
|
export * from './newJobRequestScreen/newJobRequestScreen';
|
||||||
export * from './orderAcceptedScreen/orderAcceptedScreen';
|
export * from './orderAcceptedScreen/orderAcceptedScreen';
|
||||||
@ -20,3 +19,4 @@ export * from './deliveryCompletedScreen/deliveryCompletedScreen';
|
|||||||
export * from './setLocationScreen';
|
export * from './setLocationScreen';
|
||||||
export * from './completeProfileScreen';
|
export * from './completeProfileScreen';
|
||||||
export * from './kycScreen';
|
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 { View, Text, ScrollView, TouchableOpacity } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
import { OrderCard, EmptyState } from '@components';
|
import { OrderCard, EmptyState } from '@components';
|
||||||
import { useAppDispatch, useAppSelector } from '@store';
|
import { useAppDispatch, useAppSelector } from '@store';
|
||||||
import { ClipboardIcon } from '@icons';
|
import { ClipboardIcon } from '@icons';
|
||||||
import { getStyles } from './jobsScreen.styles';
|
import { getStyles } from './jobsScreen.styles';
|
||||||
import { getAllActiveDeliveries } from './thunk';
|
import { getAllActiveDeliveries, getCompletedDeliveries } from './thunk';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useFocusEffect, useNavigation } from '@react-navigation/native';
|
||||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
import { AppStackParamList } from '@navigation/navigationTypes';
|
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||||
import { RouteNames } from '@utils/constants';
|
import { RouteNames } from '@utils/constants';
|
||||||
@ -20,45 +20,52 @@ export const JobsScreen: React.FC = () => {
|
|||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<'active' | 'history'>('active');
|
const [activeTab, setActiveTab] = useState<'active' | 'history'>('active');
|
||||||
const { activeJob, jobHistory } = useAppSelector(state => state.job);
|
const { activeJob, jobHistory } = useAppSelector(state => state.job);
|
||||||
const { activeDeliveries } = useAppSelector(state => state.deliveries);
|
const { activeDeliveries, completedDeliveries } = useAppSelector(
|
||||||
|
state => state.deliveries,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
// console.log(activeDeliveries, 'activeDeliveries');
|
||||||
|
|
||||||
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
dispatch(getAllActiveDeliveries());
|
dispatch(getAllActiveDeliveries());
|
||||||
}, [dispatch]);
|
dispatch(getCompletedDeliveries());
|
||||||
|
}, [dispatch]),
|
||||||
|
);
|
||||||
|
|
||||||
// Default history mock jobs
|
// Default history mock jobs
|
||||||
const defaultHistory = [
|
// const defaultHistory = [
|
||||||
{
|
// {
|
||||||
pickupName: 'Cafe Coffee Day',
|
// pickupName: 'Cafe Coffee Day',
|
||||||
pickupAddress: 'MG Road, Bengaluru',
|
// pickupAddress: 'MG Road, Bengaluru',
|
||||||
dropName: 'Rohit Sharma',
|
// dropName: 'Rohit Sharma',
|
||||||
dropAddress: 'Koramangala 4th Block, Bengaluru',
|
// dropAddress: 'Koramangala 4th Block, Bengaluru',
|
||||||
amount: '78.0',
|
// amount: '78.0',
|
||||||
distance: '1.2',
|
// distance: '1.2',
|
||||||
itemCount: 2,
|
// itemCount: 2,
|
||||||
paymentType: 'online',
|
// paymentType: 'online',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
pickupName: 'Pizza Hut',
|
// pickupName: 'Pizza Hut',
|
||||||
pickupAddress: 'Indiranagar, Bengaluru',
|
// pickupAddress: 'Indiranagar, Bengaluru',
|
||||||
dropName: 'Aishwarya Sen',
|
// dropName: 'Aishwarya Sen',
|
||||||
dropAddress: 'Domlur Stage 2, Bengaluru',
|
// dropAddress: 'Domlur Stage 2, Bengaluru',
|
||||||
amount: '95.0',
|
// amount: '95.0',
|
||||||
distance: '3.8',
|
// distance: '3.8',
|
||||||
itemCount: 3,
|
// itemCount: 3,
|
||||||
paymentType: 'cod',
|
// paymentType: 'cod',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
pickupName: 'Burgers & Co',
|
// pickupName: 'Burgers & Co',
|
||||||
pickupAddress: 'Koramangala 5th Block',
|
// pickupAddress: 'Koramangala 5th Block',
|
||||||
dropName: 'Sameer Khan',
|
// dropName: 'Sameer Khan',
|
||||||
dropAddress: 'HSR Layout Sector 3, Bengaluru',
|
// dropAddress: 'HSR Layout Sector 3, Bengaluru',
|
||||||
amount: '110.0',
|
// amount: '110.0',
|
||||||
distance: '4.5',
|
// distance: '4.5',
|
||||||
itemCount: 1,
|
// itemCount: 1,
|
||||||
paymentType: 'online',
|
// paymentType: 'online',
|
||||||
},
|
// },
|
||||||
];
|
// ];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
@ -105,22 +112,30 @@ export const JobsScreen: React.FC = () => {
|
|||||||
>
|
>
|
||||||
{activeTab === 'active' ? (
|
{activeTab === 'active' ? (
|
||||||
// Active job tab
|
// Active job tab
|
||||||
activeDeliveries ? (
|
activeDeliveries?.delivery ? (
|
||||||
<View style={styles.cardWrapper}>
|
<View style={styles.cardWrapper}>
|
||||||
<OrderCard
|
<OrderCard
|
||||||
pickupName={activeDeliveries?.order?.pickupAddress?.city}
|
pickupName={
|
||||||
pickupAddress={
|
activeDeliveries?.delivery?.order?.pickupAddress?.city
|
||||||
activeDeliveries?.order?.pickupAddress?.addressLine1
|
|
||||||
}
|
}
|
||||||
dropName={activeDeliveries?.order?.dropAddress?.city}
|
pickupAddress={
|
||||||
dropAddress={activeDeliveries?.order?.dropAddress?.addressLine1}
|
activeDeliveries?.delivery?.order?.pickupAddress?.addressLine1
|
||||||
amount={activeDeliveries?.order?.totalAmount}
|
}
|
||||||
distance={activeDeliveries?.estimatedDistanceKm}
|
dropName={activeDeliveries?.delivery?.order?.dropAddress?.city}
|
||||||
itemCount={activeDeliveries?.order?.orderItems?.length}
|
dropAddress={
|
||||||
paymentType={activeDeliveries?.order?.paymentMethod}
|
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={() =>
|
onPress={() =>
|
||||||
navigation.navigate(RouteNames.ArrivedAtStore, {
|
navigation.navigate(RouteNames.OrderPickedUp, {
|
||||||
jobId: activeDeliveries?.orderId,
|
jobId: activeDeliveries?.delivery?.id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -136,35 +151,29 @@ export const JobsScreen: React.FC = () => {
|
|||||||
) : (
|
) : (
|
||||||
// History tab
|
// History tab
|
||||||
<View>
|
<View>
|
||||||
{jobHistory.length > 0
|
{completedDeliveries && completedDeliveries?.length > 0 ? (
|
||||||
? jobHistory.map((job, idx) => (
|
completedDeliveries?.map((job, idx) => (
|
||||||
<View key={idx} style={styles.cardWrapper}>
|
<View key={idx} style={styles.cardWrapper}>
|
||||||
<OrderCard
|
<OrderCard
|
||||||
pickupName={job.pickupName}
|
pickupName={
|
||||||
pickupAddress={job.pickupAddress}
|
job.order.merchant.name || job.order.pickupAddress.city
|
||||||
dropName={job.dropName}
|
}
|
||||||
dropAddress={job.dropAddress}
|
pickupAddress={job.order.pickupAddress.addressLine1}
|
||||||
amount={job.amount}
|
dropName={job.order.dropAddress.city}
|
||||||
distance={job.distance}
|
dropAddress={job.order.dropAddress.addressLine1}
|
||||||
itemCount={job.itemCount}
|
amount={job.order.totalAmount}
|
||||||
paymentType={job.paymentType}
|
distance={job.estimatedDistanceKm?.toString() || '0'}
|
||||||
|
// itemCount={job.order?.}
|
||||||
|
// paymentType={job.order.paymentMethod}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
))
|
))
|
||||||
: defaultHistory.map((job, idx) => (
|
) : (
|
||||||
<View key={idx} style={styles.cardWrapper}>
|
<View style={styles.emptyStateContainer}>
|
||||||
<OrderCard
|
<ClipboardIcon size={48} color={colors.placeholder} />
|
||||||
pickupName={job.pickupName}
|
<Text style={styles.emptyStateText}>No history found</Text>
|
||||||
pickupAddress={job.pickupAddress}
|
|
||||||
dropName={job.dropName}
|
|
||||||
dropAddress={job.dropAddress}
|
|
||||||
amount={job.amount}
|
|
||||||
distance={job.distance}
|
|
||||||
itemCount={job.itemCount}
|
|
||||||
paymentType={job.paymentType}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
))}
|
)}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@ -1,17 +1,19 @@
|
|||||||
import { ActiveOrdersResponse } from '@interfaces';
|
import { DeliveryDetailsResponse, OrderHistoryResponse } from '@interfaces';
|
||||||
import { createReducer } from '@reduxjs/toolkit';
|
import { createReducer } from '@reduxjs/toolkit';
|
||||||
import { getAllActiveDeliveries } from './thunk';
|
import { getAllActiveDeliveries, getCompletedDeliveries } from './thunk';
|
||||||
|
|
||||||
export interface ActiveDeliveriesState {
|
export interface ActiveDeliveriesState {
|
||||||
activeDeliveries: ActiveOrdersResponse | null;
|
activeDeliveries: DeliveryDetailsResponse | null;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
|
completedDeliveries: OrderHistoryResponse | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: ActiveDeliveriesState = {
|
const initialState: ActiveDeliveriesState = {
|
||||||
activeDeliveries: null,
|
activeDeliveries: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
|
completedDeliveries: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const activeDeliveriesReducer = createReducer(initialState, builder => {
|
export const activeDeliveriesReducer = createReducer(initialState, builder => {
|
||||||
@ -27,5 +29,16 @@ export const activeDeliveriesReducer = createReducer(initialState, builder => {
|
|||||||
.addCase(getAllActiveDeliveries.rejected, (state, action) => {
|
.addCase(getAllActiveDeliveries.rejected, (state, action) => {
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
state.error = action.payload as string;
|
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';
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
export const getAllActiveDeliveries = createAsyncThunk(
|
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 { PrimaryButton } from '@components';
|
||||||
import { useAppDispatch, useAppSelector } from '@store';
|
import { useAppDispatch, useAppSelector } from '@store';
|
||||||
import { advanceJobStatus } from '@store/commonReducers/job';
|
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 { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
import { AppStackParamList } from '@navigation/navigationTypes';
|
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||||
import { RouteNames, JobStatus } from '@utils/constants';
|
import { RouteNames, JobStatus } from '@utils/constants';
|
||||||
@ -14,27 +14,41 @@ export const LiveTrackingScreen: React.FC = () => {
|
|||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
const styles = getStyles(colors);
|
const styles = getStyles(colors);
|
||||||
const dispatch = useAppDispatch();
|
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 = () => {
|
const handleArrived = () => {
|
||||||
dispatch(advanceJobStatus(JobStatus.ArrivedAtCustomer));
|
dispatch(advanceJobStatus(JobStatus.ArrivedAtCustomer));
|
||||||
navigation.navigate(RouteNames.ArrivedAtCustomer);
|
navigation.navigate(RouteNames.DeliverOrder, {
|
||||||
|
jobId: jobId,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleContact = () => {
|
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 (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
{/* Top Floating ETA Card */}
|
{/* Top Floating ETA Card */}
|
||||||
<View style={styles.etaCard}>
|
<View style={styles.etaCard}>
|
||||||
<Text style={styles.etaTitle}>Arriving in 8 mins</Text>
|
<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>
|
</View>
|
||||||
|
|
||||||
{/* Map Background Placeholder */}
|
{/* Map Background Placeholder */}
|
||||||
@ -45,8 +59,14 @@ export const LiveTrackingScreen: React.FC = () => {
|
|||||||
{/* Customer Location Bottom Card */}
|
{/* Customer Location Bottom Card */}
|
||||||
<View style={styles.bottomCard}>
|
<View style={styles.bottomCard}>
|
||||||
<Text style={styles.cardTitle}>Deliver to</Text>
|
<Text style={styles.cardTitle}>Deliver to</Text>
|
||||||
<Text style={styles.customerName}>{activeJob.dropName}</Text>
|
<Text style={styles.customerName}>
|
||||||
<Text style={styles.customerAddress}>{activeJob.dropAddress}</Text>
|
{activeJob?.order?.customer?.user?.name}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.customerAddress}>
|
||||||
|
{activeJob?.order?.dropAddress?.houseNumber +
|
||||||
|
', ' +
|
||||||
|
activeJob?.order?.dropAddress?.addressLine1}
|
||||||
|
</Text>
|
||||||
|
|
||||||
<View style={styles.btnRow}>
|
<View style={styles.btnRow}>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
|
|||||||
@ -2,9 +2,9 @@ import React from 'react';
|
|||||||
import { View, Text } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
import { PrimaryButton } from '@components';
|
import { PrimaryButton } from '@components';
|
||||||
import { useAppDispatch, useAppSelector } from '@store';
|
import { changeOrderStatusThunk, useAppDispatch, useAppSelector } from '@store';
|
||||||
import { advanceJobStatus } from '@store/commonReducers/job';
|
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 { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
import { AppStackParamList } from '@navigation/navigationTypes';
|
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||||
import { RouteNames, JobStatus } from '@utils/constants';
|
import { RouteNames, JobStatus } from '@utils/constants';
|
||||||
@ -14,13 +14,24 @@ export const OrderPickedUpScreen: React.FC = () => {
|
|||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
const styles = getStyles(colors);
|
const styles = getStyles(colors);
|
||||||
const dispatch = useAppDispatch();
|
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 = () => {
|
const handleStartDelivery = () => {
|
||||||
dispatch(advanceJobStatus(JobStatus.EnRoute));
|
dispatch(
|
||||||
navigation.navigate(RouteNames.LiveTracking);
|
changeOrderStatusThunk({ deliveryId: jobId, action: JobStatus.PickedUp }),
|
||||||
|
);
|
||||||
|
navigation.navigate(RouteNames.LiveTracking, {
|
||||||
|
jobId: jobId,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!activeJob) return null;
|
if (!activeJob) return null;
|
||||||
@ -35,8 +46,18 @@ export const OrderPickedUpScreen: React.FC = () => {
|
|||||||
{/* Deliver Bottom Card */}
|
{/* Deliver Bottom Card */}
|
||||||
<View style={styles.bottomCard}>
|
<View style={styles.bottomCard}>
|
||||||
<Text style={styles.cardTitle}>Deliver to Customer</Text>
|
<Text style={styles.cardTitle}>Deliver to Customer</Text>
|
||||||
<Text style={styles.customerName}>{activeJob.dropName}</Text>
|
<Text style={styles.customerName}>
|
||||||
<Text style={styles.customerAddress}>{activeJob.dropAddress}</Text>
|
{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
|
<PrimaryButton
|
||||||
title="Start Delivery"
|
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 './dashboard';
|
||||||
export * from './delivery';
|
export * from './delivery';
|
||||||
export * from './order';
|
export * from './order';
|
||||||
|
export * from './accountInfo';
|
||||||
|
|||||||
@ -1,24 +1,42 @@
|
|||||||
export interface ActiveOrdersResponse {
|
export interface DeliveryDetailsResponse {
|
||||||
|
delivery: Delivery;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Delivery {
|
||||||
id: string;
|
id: string;
|
||||||
orderId: string;
|
orderId: string;
|
||||||
deliveryPartnerId: string;
|
deliveryPartnerId: string;
|
||||||
deliveryNumber: string;
|
deliveryNumber: string;
|
||||||
status: string;
|
status: DeliveryStatus;
|
||||||
estimatedDistanceKm: string;
|
estimatedDistanceKm: string | null;
|
||||||
actualDistanceKm: string;
|
actualDistanceKm: string | null;
|
||||||
proofOfPickupKey: string;
|
proofOfPickupKey: string | null;
|
||||||
proofOfDeliveryKey: string;
|
proofOfDeliveryKey: string | null;
|
||||||
pickedUpAt: string;
|
pickedUpAt: string | null;
|
||||||
deliveredAt: string;
|
deliveredAt: string | null;
|
||||||
order: {
|
order: DeliveryOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
id: string;
|
||||||
orderNumber: string;
|
orderNumber: string;
|
||||||
customerId: string;
|
customerId: string;
|
||||||
merchantId: string;
|
merchantId: string;
|
||||||
couponCode: string;
|
couponCode: string | null;
|
||||||
appliedPromotionId: string;
|
appliedPromotionId: string | null;
|
||||||
status: string;
|
status: OrderStatus;
|
||||||
orderType: string;
|
orderType: OrderType;
|
||||||
paymentMethodId: string;
|
paymentMethodId: string;
|
||||||
paymentMethod: string;
|
paymentMethod: string;
|
||||||
currency: string;
|
currency: string;
|
||||||
@ -29,84 +47,178 @@ export interface ActiveOrdersResponse {
|
|||||||
taxAmount: string;
|
taxAmount: string;
|
||||||
discountAmount: string;
|
discountAmount: string;
|
||||||
totalAmount: string;
|
totalAmount: string;
|
||||||
pickupAddress: {
|
pickupAddress: DeliveryAddress;
|
||||||
city: string;
|
dropAddress: DeliveryAddress;
|
||||||
label: string;
|
metadata: DeliveryMetadata;
|
||||||
phone: string;
|
version: number;
|
||||||
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;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
deletedAt: string;
|
deletedAt: string | null;
|
||||||
customer: {
|
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;
|
id: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
customerCode: string;
|
customerCode: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
deletedAt: string;
|
deletedAt: string | null;
|
||||||
user: {
|
user: DeliveryUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeliveryUser {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
profileImage: string;
|
profileImage: string | null;
|
||||||
};
|
|
||||||
};
|
|
||||||
// "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[];
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrderItem {
|
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;
|
id: string;
|
||||||
orderId: string;
|
orderId: string;
|
||||||
productId: string;
|
productId: string;
|
||||||
itemType: string;
|
itemType: 'PRODUCT' | 'SERVICE';
|
||||||
name: string;
|
name: string;
|
||||||
quantity: string;
|
quantity: string;
|
||||||
unitPrice: string;
|
unitPrice: string;
|
||||||
totalAmount: 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.OrderAccepted]: undefined;
|
||||||
[RouteNames.ArrivedAtStore]: { jobId: string };
|
[RouteNames.ArrivedAtStore]: { jobId: string };
|
||||||
[RouteNames.ConfirmPickup]: undefined;
|
[RouteNames.ConfirmPickup]: undefined;
|
||||||
[RouteNames.OrderPickedUp]: undefined;
|
[RouteNames.OrderPickedUp]: { jobId: string };
|
||||||
[RouteNames.LiveTracking]: undefined;
|
[RouteNames.LiveTracking]: { jobId: string };
|
||||||
[RouteNames.ArrivedAtCustomer]: { jobId: string };
|
[RouteNames.ArrivedAtCustomer]: { jobId: string };
|
||||||
[RouteNames.DeliverOrder]: undefined;
|
[RouteNames.DeliverOrder]: { jobId: string };
|
||||||
[RouteNames.DeliveryCompleted]: undefined;
|
[RouteNames.DeliveryCompleted]: undefined;
|
||||||
[RouteNames.Documents]: undefined;
|
[RouteNames.Documents]: undefined;
|
||||||
// [RouteNames.JobDetails]: { jobId: string };
|
// [RouteNames.JobDetails]: { jobId: string };
|
||||||
|
|||||||
@ -15,7 +15,7 @@ const STORAGE_KEYS = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// ─── Config ──────────────────────────────────────────────────────────────────
|
// ─── 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 ───────────────────────────────────────────────────────────
|
// ─── Token Helpers ───────────────────────────────────────────────────────────
|
||||||
export const tokenManager = {
|
export const tokenManager = {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { setDeliveryOffer } from '@store/commonReducers/delivery';
|
|||||||
import { RouteNames } from '@utils/constants';
|
import { RouteNames } from '@utils/constants';
|
||||||
|
|
||||||
// Must match the REST API base URL (without trailing slash)
|
// 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;
|
type Callback = (data: any) => void;
|
||||||
|
|
||||||
@ -30,8 +30,12 @@ class SocketService {
|
|||||||
* the store and navigationRef to be injected independently.
|
* the store and navigationRef to be injected independently.
|
||||||
*/
|
*/
|
||||||
bootstrap(store: Store | null, navigationRef: any) {
|
bootstrap(store: Store | null, navigationRef: any) {
|
||||||
if (store) { this.store = store; }
|
if (store) {
|
||||||
if (navigationRef) { this.navigationRef = navigationRef; }
|
this.store = store;
|
||||||
|
}
|
||||||
|
if (navigationRef) {
|
||||||
|
this.navigationRef = navigationRef;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,9 +68,7 @@ class SocketService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ── Listen for delivery offers ─────────────────────────────────────
|
// ── Listen for delivery offers ─────────────────────────────────────
|
||||||
this.socket.on(
|
this.socket.on(SocketEvents.DeliveryOffer, (data: DeliveryOffer) => {
|
||||||
SocketEvents.DeliveryOffer,
|
|
||||||
(data: DeliveryOffer) => {
|
|
||||||
console.log('[Socket] delivery_offer received:', data);
|
console.log('[Socket] delivery_offer received:', data);
|
||||||
|
|
||||||
if (this.store) {
|
if (this.store) {
|
||||||
@ -77,8 +79,7 @@ class SocketService {
|
|||||||
if (this.navigationRef?.isReady()) {
|
if (this.navigationRef?.isReady()) {
|
||||||
this.navigationRef.navigate(RouteNames.NewJobRequest);
|
this.navigationRef.navigate(RouteNames.NewJobRequest);
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,12 +1,19 @@
|
|||||||
import { createReducer, createAction } from '@reduxjs/toolkit';
|
import { createReducer, createAction } from '@reduxjs/toolkit';
|
||||||
import { DeliveryOffer } from '@interfaces';
|
import { DeliveryOffer, PickupOrderResponse } from '@interfaces';
|
||||||
import { respondToOfferThunk, checkPendingOfferThunk } from './thunk';
|
import {
|
||||||
|
respondToOfferThunk,
|
||||||
|
checkPendingOfferThunk,
|
||||||
|
changeOrderStatusThunk,
|
||||||
|
} from './thunk';
|
||||||
|
|
||||||
export interface DeliveryState {
|
export interface DeliveryState {
|
||||||
currentOffer: DeliveryOffer | null;
|
currentOffer: DeliveryOffer | null;
|
||||||
respondLoading: boolean;
|
respondLoading: boolean;
|
||||||
respondError: string | null;
|
respondError: string | null;
|
||||||
pendingOfferLoading: boolean;
|
pendingOfferLoading: boolean;
|
||||||
|
pickupLoading: boolean;
|
||||||
|
pickupError: string | null;
|
||||||
|
pickupOrderResponse: PickupOrderResponse | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: DeliveryState = {
|
const initialState: DeliveryState = {
|
||||||
@ -14,6 +21,9 @@ const initialState: DeliveryState = {
|
|||||||
respondLoading: false,
|
respondLoading: false,
|
||||||
respondError: null,
|
respondError: null,
|
||||||
pendingOfferLoading: false,
|
pendingOfferLoading: false,
|
||||||
|
pickupLoading: false,
|
||||||
|
pickupError: null,
|
||||||
|
pickupOrderResponse: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Dispatched by socketService when a `delivery_offer` event is received. */
|
/** Dispatched by socketService when a `delivery_offer` event is received. */
|
||||||
@ -57,5 +67,17 @@ export const deliveryReducer = createReducer(initialState, builder => {
|
|||||||
})
|
})
|
||||||
.addCase(checkPendingOfferThunk.rejected, state => {
|
.addCase(checkPendingOfferThunk.rejected, state => {
|
||||||
state.pendingOfferLoading = false;
|
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,
|
RespondToOfferResponse,
|
||||||
PendingOfferResponse,
|
PendingOfferResponse,
|
||||||
} from '@interfaces';
|
} from '@interfaces';
|
||||||
import * as deliveryApi from '../../../api/deliveryApi';
|
import { changeOrderStatus, getPendingOffer, respondToOffer } from '@api';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Respond to a delivery offer (ACCEPT / REJECT).
|
* Respond to a delivery offer (ACCEPT / REJECT).
|
||||||
@ -16,7 +16,7 @@ export const respondToOfferThunk = createAsyncThunk<
|
|||||||
'delivery/respondToOffer',
|
'delivery/respondToOffer',
|
||||||
async ({ deliveryId, action }, { rejectWithValue }) => {
|
async ({ deliveryId, action }, { rejectWithValue }) => {
|
||||||
try {
|
try {
|
||||||
return await deliveryApi.respondToOffer(deliveryId, action);
|
return await respondToOffer(deliveryId, action);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(
|
||||||
error.message || 'Failed to respond to delivery offer',
|
error.message || 'Failed to respond to delivery offer',
|
||||||
@ -32,11 +32,27 @@ export const checkPendingOfferThunk = createAsyncThunk<PendingOfferResponse>(
|
|||||||
'delivery/checkPendingOffer',
|
'delivery/checkPendingOffer',
|
||||||
async (_, { rejectWithValue }) => {
|
async (_, { rejectWithValue }) => {
|
||||||
try {
|
try {
|
||||||
return await deliveryApi.getPendingOffer();
|
return await getPendingOffer();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(error.message || 'Failed to check pending offers');
|
||||||
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 { jobReducer } from './commonReducers/job';
|
||||||
import { deliveryReducer } from './commonReducers/delivery';
|
import { deliveryReducer } from './commonReducers/delivery';
|
||||||
import setLocationReducer from '@features/screens/setLocationScreen/reducer';
|
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 { onBoardingCompleteReducer } from '@features/screens/onboardingCompleteScreen';
|
||||||
import { dashBoardReducer } from '@features/screens/dashboardScreen';
|
import { dashBoardReducer } from '@features/screens/dashboardScreen';
|
||||||
import { activeDeliveriesReducer } from '@features/screens/jobsScreen';
|
import { activeDeliveriesReducer } from '@features/screens/jobsScreen';
|
||||||
@ -20,6 +24,7 @@ const rootReducer = combineReducers({
|
|||||||
onboardingComplete: onBoardingCompleteReducer,
|
onboardingComplete: onBoardingCompleteReducer,
|
||||||
dashboard: dashBoardReducer,
|
dashboard: dashBoardReducer,
|
||||||
deliveries: activeDeliveriesReducer,
|
deliveries: activeDeliveriesReducer,
|
||||||
|
accountInfo: profileReducer,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type RootState = ReturnType<typeof rootReducer>;
|
export type RootState = ReturnType<typeof rootReducer>;
|
||||||
|
|||||||
@ -37,10 +37,10 @@ export enum JobStatus {
|
|||||||
NewRequest = 'newRequest',
|
NewRequest = 'newRequest',
|
||||||
Accepted = 'accepted',
|
Accepted = 'accepted',
|
||||||
ArrivedAtStore = 'arrivedAtStore',
|
ArrivedAtStore = 'arrivedAtStore',
|
||||||
PickedUp = 'pickedUp',
|
PickedUp = 'PICKUP',
|
||||||
EnRoute = 'enRoute',
|
EnRoute = 'enRoute',
|
||||||
ArrivedAtCustomer = 'arrivedAtCustomer',
|
ArrivedAtCustomer = 'arrivedAtCustomer',
|
||||||
Delivered = 'delivered',
|
Delivered = 'DELIVER',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SocketEvents {
|
export enum SocketEvents {
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
export const formatCurrency = (amount: number, currency: string = '₹'): string => {
|
export const formatCurrency = (
|
||||||
return `${currency}${amount.toLocaleString('en-IN', {
|
amount: number | undefined,
|
||||||
|
currency: string = '₹',
|
||||||
|
): string => {
|
||||||
|
return `${currency}${amount?.toLocaleString('en-IN', {
|
||||||
minimumFractionDigits: 0,
|
minimumFractionDigits: 0,
|
||||||
maximumFractionDigits: 2,
|
maximumFractionDigits: 2,
|
||||||
})}`;
|
})}`;
|
||||||
@ -7,7 +10,7 @@ export const formatCurrency = (amount: number, currency: string = '₹'): string
|
|||||||
|
|
||||||
export const formatDistance = (km: number): string => {
|
export const formatDistance = (km: number): string => {
|
||||||
if (km < 1) return `${Math.round(km * 1000)} m`;
|
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 => {
|
export const formatDuration = (seconds: number): string => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user