Skip to main content

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

Route Information

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>
Route Information Display

Contact Information

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>

Contact Actions

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

Mark when the package has been picked up from the sender. Requires proof of delivery photo.

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.
Shows any additional notes or instructions from the sender.

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.

Build docs developers (and LLMs) love