Overview
The shipment details screen provides riders with comprehensive information about individual delivery orders, including route details, contact information, payment summary, and status update capabilities.
Shipment Detail Screen
Main Component
The shipment detail view displays all relevant information for a delivery order.
src/app/(parcel)/(stack)/shipments/[reference]/index.tsx
import { getShipmentByReferenceQueryOptions } from "@/lib/tanstack-query/query-options/shipment" ;
import { UpdateStatusModal } from "@/modules/dashboard/parcels/riders/shipment/update-status-modal" ;
import { formatCurrency } from "@/utils/currency" ;
import { useQuery } from "@tanstack/react-query" ;
import { useLocalSearchParams , useRouter } from "expo-router" ;
import { Button } from "heroui-native" ;
import { Linking , Pressable , ScrollView , Text , View } from "react-native" ;
export default function ShipmentDetail () {
const { reference } = useLocalSearchParams ();
const router = useRouter ();
const [ isUpdateStatusModalVisible , setIsUpdateStatusModalVisible ] = useState ( false );
const { data , isLoading } = useQuery (
getShipmentByReferenceQueryOptions ( reference as string ),
);
const shipment = data ?. data ;
const isDelivered = shipment . status === ShipmentStatus . DELIVERED ;
const handleCall = ( phone : string ) => {
Linking . openURL ( `tel: ${ phone } ` );
};
const handleMessage = ( phone : string ) => {
Linking . openURL ( `sms: ${ phone } ` );
};
return (
< View className = "flex-1 bg-white" >
< ScrollView className = "flex-1" >
{ /* Delivered Success Banner */ }
{ isDelivered && (
< View className = "bg-green-50 p-6 border-b border-green-100" >
< View className = "items-center" >
< View className = "bg-green-100 rounded-full p-4 mb-3" >
< Ionicons name = "checkmark-circle" size = { 56 } color = "#15803d" />
</ View >
< Text className = "text-2xl text-green-800 font-bold mb-2" >
Successfully Delivered!
</ Text >
</ View >
</ View >
) }
{ /* Route Information */ }
< RouteInformation shipment = { shipment } />
{ /* Contact Cards */ }
< ContactCards
shipment = { shipment }
onCall = { handleCall }
onMessage = { handleMessage }
/>
{ /* Shipment Details */ }
< ShipmentDetails shipment = { shipment } />
{ /* Payment Information */ }
< PaymentSummary shipment = { shipment } />
</ ScrollView >
{ /* Update Status Button */ }
< View className = "p-4 border-t border-gray-100 bg-white" >
{ ! isDelivered && (
< Button
onPress = { () => setIsUpdateStatusModalVisible ( true ) }
size = "lg"
>
< Ionicons name = "checkmark-circle" size = { 20 } color = "white" />
< Text className = "text-white font-semibold ml-2" > Update Status </ Text >
</ Button >
) }
</ View >
< UpdateStatusModal
visible = { isUpdateStatusModalVisible }
onClose = { () => setIsUpdateStatusModalVisible ( false ) }
shipmentId = { shipment ?. id }
shipmentReference = { shipment ?. reference }
currentStatus = { shipment ?. status }
/>
</ View >
);
}
The route section displays pickup and drop-off locations with a visual route indicator.
< View className = "bg-white rounded-2xl p-4 mb-4 border border-gray-100" >
< Text className = "text-base text-secondary font-bold mb-4" >
Delivery Route
</ Text >
< View className = "flex-row items-start" >
{ /* Route Line */ }
< View className = "items-center mr-3 pt-1" >
< View className = "w-3 h-3 rounded-full bg-accent" />
< View className = "w-0.5 h-16 bg-gray-200 my-1" />
< View className = "w-3 h-3 rounded-full bg-accent" />
</ View >
{ /* Addresses */ }
< View className = "flex-1" >
{ /* Pickup Location */ }
< View className = "mb-6" >
< View className = "flex-row items-center gap-2 mb-2" >
< Ionicons name = "location" size = { 16 } color = "#f97316" />
< Text className = "text-xs text-gray-500 font-semibold uppercase" >
Pickup Location
</ Text >
</ View >
< Text className = "text-base text-secondary font-semibold mb-1" >
{ shipment . pickupArea }
</ Text >
< Text className = "text-sm text-gray-600" > { shipment . pickupCity } </ Text >
{ shipment . pickupDate && (
< View className = "flex-row items-center gap-1 mt-2" >
< Ionicons name = "time-outline" size = { 14 } color = "#6b7280" />
< Text className = "text-xs text-gray-500" >
{ formatDate ( shipment . pickupDate ) }
</ Text >
</ View >
) }
</ View >
{ /* Drop-off Location */ }
< View >
< View className = "flex-row items-center gap-2 mb-2" >
< Ionicons name = "location" size = { 16 } color = "#6b7280" />
< Text className = "text-xs text-gray-500 font-semibold uppercase" >
Drop-off Location
</ Text >
</ View >
< Text className = "text-base text-secondary font-semibold mb-1" >
{ shipment . dropOffArea }
</ Text >
< Text className = "text-sm text-gray-600" > { shipment . dropOffCity } </ Text >
</ View >
</ View >
</ View >
</ View >
Riders can quickly call or message the sender and recipient directly from the shipment details.
{ /* Sender Contact */ }
< View className = "bg-white rounded-2xl p-4 mb-3 border border-gray-100" >
< View className = "flex-row items-center justify-between" >
< View className = "flex-1" >
< View className = "flex-row items-center gap-2 mb-2" >
< View className = "bg-accent/10 rounded-full p-2" >
< Ionicons name = "person" size = { 16 } color = "#f97316" />
</ View >
< View className = "flex-1" >
< Text className = "text-xs text-gray-500 font-medium" > Sender </ Text >
< Text className = "text-base text-secondary font-semibold" >
{ shipment . senderPhone }
</ Text >
</ View >
</ View >
</ View >
< View className = "flex-row gap-2" >
< Pressable
onPress = { () => handleCall ( shipment . senderPhone ) }
className = "bg-accent/10 rounded-full p-3"
>
< Ionicons name = "call" size = { 20 } color = "#f97316" />
</ Pressable >
< Pressable
onPress = { () => handleMessage ( shipment . senderPhone ) }
className = "bg-secondary/10 rounded-full p-3"
>
< Ionicons name = "chatbubble" size = { 20 } color = "#1e293b" />
</ Pressable >
</ View >
</ View >
</ View >
Call Tap the phone icon to initiate a call using the device’s phone app.
Message Tap the message icon to open the SMS app with the contact pre-filled.
Status Updates
Riders can update the shipment status through a modal interface.
src/modules/dashboard/parcels/riders/shipment/update-shipment-status-form.tsx
import { riderUpdateStatusOptions } from "@/constants/ride-status" ;
import { api } from "@/services/api" ;
import { useMutation } from "@tanstack/react-query" ;
import { Button } from "heroui-native" ;
import { useForm } from "react-hook-form" ;
export function UpdateShipmentStatusForm ( props : UpdateShipmentStatusFormProps ) {
const { currentStatus , shipmentId , onUpdateStatus } = props ;
const form = useForm < UpdateShipmentStatusField >({
resolver: zodResolver ( updateShipmentStatusSchema ),
defaultValues: { status: currentStatus },
});
const updateStatusMutation = useMutation ({
mutationFn : ( data : FormData ) => api . shipments . updateStatus ( shipmentId , data ),
});
const handleStatusUpdate = async ( data : UpdateShipmentStatusField ) => {
const formData = new FormData ();
formData . append ( "status" , data . status );
formData . append ( "description" , data . reason || "" );
formData . append ( "confirmationCode" , data . confirmationCode || "" );
formData . append ( "isPaid" , data . paid === true ? "true" : "false" );
if ( data . photo ) {
const photo = {
uri: data . photo [ 0 ]. uri ,
type: data . photo [ 0 ]. type ?? "application/octet-stream" ,
name: data . photo [ 0 ]. name || "photo.jpg" ,
};
formData . append ( "photo" , photo );
}
await updateStatusMutation . mutateAsync ( formData );
toast . show ({ variant: "success" , label: "Status updated successfully" });
onUpdateStatus ?.();
};
return (
< View className = "p-4 gap-2" >
{ riderUpdateStatusOptions . map (( option ) => (
< Pressable
key = { option . value }
onPress = { () => form . setValue ( "status" , option . value ) }
className = "border rounded-2xl p-4"
>
< View className = "flex-row items-center gap-3" >
< View className = "bg-gray-100 rounded-full p-3" >
< Ionicons name = { option . icon } size = { 24 } />
</ View >
< View className = "flex-1" >
< Text className = "text-base font-semibold" > { option . label } </ Text >
< Text className = "text-sm text-gray-600" > { option . description } </ Text >
</ View >
</ View >
</ Pressable >
)) }
</ View >
);
}
Status Options
Pickup Confirmed
In Transit
Out for Delivery
Delivered
Failed Delivery
Mark when the package has been picked up from the sender. Requires proof of delivery photo.
Update when the package is being transported to the destination.
Set when the rider is on the way to deliver to the recipient.
Final status confirming successful delivery. Requires confirmation code and payment status.
Mark if delivery attempt was unsuccessful. Requires reason.
Photo Upload for Status Updates
For certain status changes (like Pickup Confirmed), riders can upload photos.
{ selectedStatus === ShipmentStatus . PICKUP_CONFIRMED && (
< ImagePicker
label = "Proof of Delivery"
description = "Upload clear photos of the delivered parcel"
allowsMultiple = { false }
maxImages = { 1 }
onImagesChange = { ( images ) => form . setValue ( "photo" , images ) }
allowedImageTypes = { [ "jpg" , "jpeg" , "png" ] }
/>
)}
Photo uploads are automatically compressed and formatted for optimal API submission.
Payment Summary
The payment section displays cost breakdown and rider commission.
< View className = "bg-accent/5 rounded-2xl p-4 mb-4 border border-accent/20" >
< Text className = "text-base text-secondary font-bold mb-4" >
Payment Summary
</ Text >
< View className = "gap-3" >
< View className = "flex-row items-center justify-between" >
< Text className = "text-sm text-gray-600" > Pickup Fee </ Text >
< Text className = "text-sm text-secondary font-semibold" >
{ formatCurrency ( shipment . shipmentCost . pickupFee , "GHS" ) }
</ Text >
</ View >
< View className = "flex-row items-center justify-between" >
< Text className = "text-sm text-gray-600" > Delivery Fee </ Text >
< Text className = "text-sm text-secondary font-semibold" >
{ formatCurrency ( shipment . shipmentCost . deliveryFee , "GHS" ) }
</ Text >
</ View >
< View className = "h-px bg-accent/20 my-1" />
< View className = "flex-row items-center justify-between" >
< Text className = "text-base text-secondary font-bold" > Total Cost </ Text >
< Text className = "text-xl text-accent font-bold" >
{ formatCurrency ( shipment . shipmentCost . totalCost , "GHS" ) }
</ Text >
</ View >
< View className = "flex-row items-center justify-between" >
< Text className = "text-sm text-gray-600" > Your Commission </ Text >
< Text className = "text-base text-accent font-bold" >
{ formatCurrency ( riderCommission , "GHS" ) }
</ Text >
</ View >
</ View >
</ View >
Commission Calculation
const commission = shipment . shipmentCost ?. riderCommission || 0 ;
const totalCost = shipment . shipmentCost ?. totalCost || 0 ;
const riderCommission = ( commission / 100 ) * totalCost || 0 ;
Shipment Details
Additional shipment information is displayed in a structured format.
Shows the mode of shipment (Bike, Van, or Cart) with an appropriate icon.
Indicates the delivery type (standard, express, etc.).
Displays when the shipment order was created.
Delivered State
When a shipment is marked as delivered, the screen shows a success banner.
{ isDelivered && (
< View className = "bg-green-50 p-6 border-b border-green-100" >
< View className = "items-center" >
< View className = "bg-green-100 rounded-full p-4 mb-3" >
< Ionicons name = "checkmark-circle" size = { 56 } color = "#15803d" />
</ View >
< Text className = "text-2xl text-green-800 font-bold mb-2" >
Successfully Delivered!
</ Text >
< Text className = "text-sm text-green-700 text-center mb-1" >
This package has been delivered to the recipient
</ Text >
< Text className = "text-xs text-green-600" >
Reference: # { shipment . reference }
</ Text >
</ View >
</ View >
)}
Deliveries List Browse all available deliveries.
Transactions View earnings from completed shipments.