freshyo/apps/user-ui/app/(drawer)/(tabs)/me/my-orders/[id].tsx
2026-01-24 00:13:15 +05:30

430 lines
19 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from "react";
import { View, ScrollView, Alert, TextInput, ActivityIndicator, KeyboardAvoidingView, Platform } from "react-native";
import { Image } from 'expo-image';
import { useLocalSearchParams, useRouter } from "expo-router";
import { AppContainer, MyText, tw, MyTouchableOpacity, theme, BottomDialog } from "common-ui";
import { trpc } from "@/src/trpc-client";
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import Ionicons from '@expo/vector-icons/Ionicons';
import dayjs from "dayjs";
import ComplaintForm from "@/components/ComplaintForm";
export default function OrderDetails() {
const { id } = useLocalSearchParams<{ id: string }>();
const router = useRouter();
const {
data: orderData,
isLoading,
error,
refetch,
} = trpc.user.order.getOrderById.useQuery(
{ orderId: id },
{ enabled: !!id }
);
const [isEditingNotes, setIsEditingNotes] = useState(false);
const [notesText, setNotesText] = useState("");
const [notesInput, setNotesInput] = useState("");
const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
const [cancelReason, setCancelReason] = useState("");
const [complaintDialogOpen, setComplaintDialogOpen] = useState(false);
const updateNotesMutation = trpc.user.order.updateUserNotes.useMutation({
onSuccess: () => {
setNotesText(notesInput);
setIsEditingNotes(false);
refetch();
Alert.alert('Success', 'Notes updated successfully');
},
onError: (error: any) => {
Alert.alert('Error', error.message || 'Failed to update notes');
},
});
const cancelOrderMutation = trpc.user.order.cancelOrder.useMutation({
onSuccess: () => {
setCancelDialogOpen(false);
setCancelReason("");
refetch();
Alert.alert('Success', 'Order cancelled successfully');
},
onError: (error: any) => {
Alert.alert('Error', error.message || 'Failed to cancel order');
},
});
const handleCancelOrder = () => {
if (!cancelReason.trim()) {
Alert.alert('Error', 'Please enter a reason for cancellation');
return;
}
cancelOrderMutation.mutate({ id: order.orderId, reason: cancelReason });
};
useEffect(() => {
if (orderData?.userNotes) {
setNotesText(orderData.userNotes);
setNotesInput(orderData.userNotes);
}
}, [orderData]);
if (isLoading) {
return (
<AppContainer>
<View style={tw`flex-1 justify-center items-center bg-white`}>
<MyText style={tw`text-slate-400 font-medium`}>Loading details...</MyText>
</View>
</AppContainer>
);
}
if (error || !orderData) {
return (
<AppContainer>
<View style={tw`flex-1 justify-center items-center p-8 bg-white`}>
<MaterialIcons name="error-outline" size={48} color={theme.colors.red1} />
<MyText style={tw`text-slate-900 text-lg font-bold mt-4`}>Failed to load</MyText>
<MyTouchableOpacity
onPress={() => router.back()}
style={tw`mt-6 bg-slate-900 px-6 py-2 rounded-xl`}
>
<MyText style={tw`text-white font-bold`}>Go Back</MyText>
</MyTouchableOpacity>
</View>
</AppContainer>
);
}
const order = orderData;
const getStatusConfig = (status: string) => {
const s = status.toLowerCase();
switch (s) {
case "delivered":
case "success":
return { label: "Delivered", color: "#10B981" };
case "cancelled":
case "failed":
return { label: "Cancelled", color: "#EF4444" };
case "pending":
case "processing":
return { label: "Pending", color: "#F59E0B" };
default:
return { label: status, color: theme.colors.brand500 };
}
};
const subtotal = order.items.reduce((sum, item) => sum + item.amount, 0);
const discountAmount = order.discountAmount || 0;
const totalAmount = order.orderAmount;
const statusConfig = getStatusConfig(order.deliveryStatus || "pending");
return (
<AppContainer>
<View style={tw`flex-1 bg-slate-50`}>
{/* Simple Header */}
<View style={tw`bg-white px-6 pt-4 pb-4 border-b border-slate-100 flex-row items-center justify-between`}>
<View style={tw`flex-row items-center`}>
<View>
<MyText style={tw`text-slate-900 font-bold text-lg`}>Order #{order.orderId}</MyText>
<MyText style={tw`text-slate-400 text-xs`}>{dayjs(order.orderDate).format("DD MMM, h:mm A")}</MyText>
{order.isFlashDelivery && (
<MyText style={tw`text-amber-600 text-xs font-bold mt-1`}> 30-Minute Flash Delivery</MyText>
)}
</View>
</View>
<View style={tw`flex-row items-center gap-2`}>
<View style={[tw`px-3 py-1 rounded-full`, { backgroundColor: statusConfig.color + '10' }]}>
<MyText style={[tw`text-[10px] font-bold uppercase`, { color: statusConfig.color }]}>
{statusConfig.label}
</MyText>
</View>
{order.isFlashDelivery && (
<View style={tw`px-2 py-1 bg-amber-100 rounded-full border border-amber-200`}>
<MyText style={tw`text-[10px] font-black text-amber-700 uppercase`}></MyText>
</View>
)}
</View>
</View>
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={tw`p-4 pb-12`}>
{/* Flash Delivery Banner */}
{order.isFlashDelivery && (
<View style={tw`bg-gradient-to-r from-amber-50 to-yellow-50 border border-amber-200 rounded-2xl p-4 mb-4`}>
<View style={tw`flex-row items-center`}>
<MaterialIcons name="bolt" size={24} color="#D97706" />
<View style={tw`ml-3 flex-1`}>
<MyText style={tw`text-amber-900 font-bold text-sm`}>Flash Delivery Order</MyText>
<MyText style={tw`text-amber-700 text-xs mt-1`}>
Your order will be delivered within 30 minutes of placement
</MyText>
</View>
</View>
</View>
)}
{/* Main Info Card */}
<View style={tw`bg-white rounded-2xl p-5 mb-4 border border-slate-100`}>
<View style={tw`flex-row items-center justify-between mb-4`}>
<View style={tw`flex-row items-center`}>
<View style={tw`w-10 h-10 rounded-full bg-slate-50 items-center justify-center mr-3`}>
<MaterialIcons name="payments" size={20} color="#64748B" />
</View>
<View>
<MyText style={tw`text-slate-400 text-[10px] font-bold uppercase`}>Payment Method</MyText>
<MyText style={tw`text-slate-900 font-bold`}>
{order.paymentMode?.toUpperCase() === "COD" ? "Cash on Delivery" : order.paymentMode}
</MyText>
</View>
</View>
<View style={tw`items-end`}>
<MyText style={tw`text-slate-400 text-[10px] font-bold uppercase`}>Status</MyText>
<MyText style={tw`text-slate-900 font-bold capitalize`}>{order.paymentStatus}</MyText>
</View>
</View>
{(order.deliveryDate || order.isFlashDelivery) && ["delivered", "success"].includes(order.deliveryStatus?.toLowerCase() || "") && (
<View style={tw`flex-row items-center pt-4 border-t border-slate-50`}>
{order.isFlashDelivery ? (
<MaterialIcons name="bolt" size={18} color="#D97706" />
) : (
<MaterialIcons name="event-available" size={18} color="#10B981" />
)}
<MyText style={[tw`text-xs ml-2 font-medium`, order.isFlashDelivery ? tw`text-amber-700` : tw`text-slate-600`]}>
{order.isFlashDelivery
? `Flash Delivered on ${dayjs(order.createdAt || order.orderDate).add(30, 'minutes').format("DD MMM YYYY, h:mm A")}`
: `Delivered on ${dayjs(order.deliveryDate).format("DD MMM YYYY, h:mm A")}`
}
</MyText>
</View>
)}
{/* Flash Delivery Info */}
{order.isFlashDelivery && !["delivered", "success"].includes(order.deliveryStatus?.toLowerCase() || "") && (
<View style={tw`flex-row items-center pt-4 border-t border-slate-50`}>
<MaterialIcons name="bolt" size={18} color="#D97706" />
<MyText style={tw`text-amber-700 text-xs ml-2 font-medium`}>
Flash Delivery: {dayjs(order.createdAt || order.orderDate).add(30, 'minutes').format("DD MMM YYYY, h:mm A")}
</MyText>
</View>
)}
</View>
{/* Special Instructions */}
<View style={tw`bg-white rounded-2xl p-5 mb-4 border border-slate-100`}>
<View style={tw`flex-row items-center justify-between mb-3`}>
<MyText style={tw`text-slate-400 text-[10px] font-bold uppercase`}>Special Instructions</MyText>
{isEditingNotes ? (
<View style={tw`flex-row gap-2`}>
<MyTouchableOpacity
onPress={() => {
setIsEditingNotes(false);
setNotesInput(notesText);
}}
style={tw`px-3 py-1 bg-slate-100 rounded-lg`}
>
<MyText style={tw`text-slate-600 text-xs font-bold`}>Cancel</MyText>
</MyTouchableOpacity>
<MyTouchableOpacity
onPress={() => {
updateNotesMutation.mutate({ id: order.orderId, userNotes: notesInput });
}}
disabled={updateNotesMutation.isPending}
style={tw`px-3 py-1 bg-brand500 rounded-lg`}
>
<MyText style={tw`text-white text-xs font-bold`}>
{updateNotesMutation.isPending ? 'Saving...' : 'Save'}
</MyText>
</MyTouchableOpacity>
</View>
) : (
<MyTouchableOpacity
onPress={() => {
setNotesInput(notesText || "");
setIsEditingNotes(true);
}}
style={tw`flex-row items-center px-2 py-1 bg-slate-50 rounded-lg`}
>
<MaterialIcons name="edit" size={12} color="#64748B" />
<MyText style={tw`text-slate-500 text-xs ml-1 font-medium`}>
{notesText ? 'Edit' : 'Add'}
</MyText>
</MyTouchableOpacity>
)}
</View>
{isEditingNotes ? (
<TextInput
style={tw`bg-slate-50 border border-slate-200 rounded-xl p-3 min-h-20 text-sm text-slate-700`}
value={notesInput}
onChangeText={setNotesInput}
placeholder="Add special delivery instructions..."
placeholderTextColor="#94A3B8"
multiline
numberOfLines={4}
textAlignVertical="top"
/>
) : (
<MyText style={tw`text-slate-700 text-sm leading-5`}>
{notesText || "No instructions added"}
</MyText>
)}
</View>
{/* Cancellation Detail */}
{order.cancelReason && (
<View style={tw`bg-rose-50 border border-rose-100 rounded-2xl p-5 mb-4`}>
<MyText style={tw`text-rose-700 text-[10px] font-bold uppercase mb-2`}>Cancellation Reason</MyText>
<MyText style={tw`text-rose-900 text-sm font-medium`}>{order.cancelReason}</MyText>
{order.refundAmount && (
<View style={tw`mt-3 pt-3 border-t border-rose-200/50 flex-row justify-between items-center`}>
<MyText style={tw`text-rose-700 text-xs`}>Refund Amount</MyText>
<MyText style={tw`text-rose-900 font-bold text-base`}>{order.refundAmount}</MyText>
</View>
)}
</View>
)}
{/* Items Section */}
<View style={tw`mb-2 px-1`}>
<MyText style={tw`text-slate-900 font-bold text-base mb-3`}>Order Items</MyText>
</View>
{order.items.map((item: any, index: number) => (
<View key={index} style={tw`bg-white rounded-2xl p-3 mb-3 border border-slate-100 flex-row items-center shadow-sm`}>
<View style={tw`w-14 h-14 rounded-xl bg-slate-50 border border-slate-100 p-1 mr-4`}>
<Image
source={{ uri: item.image || undefined }}
style={tw`w-full h-full rounded-lg`}
contentFit="cover"
/>
</View>
<View style={tw`flex-1`}>
<MyText style={tw`text-slate-900 font-bold text-sm`} numberOfLines={1}>{item.productName}</MyText>
<MyText style={tw`text-slate-400 text-xs mt-1`}>{item.quantity} × {item.price}</MyText>
</View>
<MyText style={tw`text-slate-900 font-bold text-base ml-2`}>{item.amount}</MyText>
</View>
))}
{/* Coupon */}
{order.couponCode && (
<View style={tw`bg-emerald-50 border border-emerald-100 rounded-2xl p-4 mb-4 mt-2 flex-row items-center justify-between`}>
<View style={tw`flex-row items-center`}>
<MaterialIcons name="local-offer" size={20} color="#059669" />
<View style={tw`ml-3`}>
<MyText style={tw`text-emerald-900 font-bold text-sm`}>{order.couponCode}</MyText>
<MyText style={tw`text-emerald-600 text-[10px]`}>Coupon Applied</MyText>
</View>
</View>
<MyText style={tw`text-emerald-700 font-bold`}>-{order.discountAmount}</MyText>
</View>
)}
{/* Summary Section */}
<View style={tw`bg-white rounded-2xl p-5 mb-6 border border-slate-100`}>
<View style={tw`flex-row justify-between mb-3`}>
<MyText style={tw`text-slate-500 text-sm`}>Subtotal</MyText>
<MyText style={tw`text-slate-900 font-medium`}>{subtotal}</MyText>
</View>
{discountAmount > 0 && (
<View style={tw`flex-row justify-between mb-3`}>
<MyText style={tw`text-emerald-600 text-sm`}>Discount</MyText>
<MyText style={tw`text-emerald-600 font-medium`}>-{discountAmount}</MyText>
</View>
)}
<View style={tw`pt-4 border-t border-slate-50 flex-row justify-between items-center`}>
<MyText style={tw`text-slate-900 font-bold text-base`}>Total Amount</MyText>
<MyText style={tw`text-slate-900 font-bold text-xl`}>{totalAmount}</MyText>
</View>
</View>
{/* Footer Actions */}
<View style={tw`flex-row gap-3`}>
<MyTouchableOpacity
onPress={() => router.back()}
style={tw`flex-1 bg-slate-100 py-3.5 rounded-xl items-center`}
>
<MyText style={tw`text-slate-600 font-bold`}>Dismiss</MyText>
</MyTouchableOpacity>
<MyTouchableOpacity
onPress={() => setComplaintDialogOpen(true)}
style={[tw`flex-1 py-3.5 rounded-xl items-center`, { backgroundColor: theme.colors.brand600 }]}
>
<MyText style={tw`text-white font-bold`}>Raise Complaint</MyText>
</MyTouchableOpacity>
</View>
{/* Additional Actions */}
<View style={tw`mt-4`}>
{order.deliveryStatus !== 'success' && order.deliveryStatus !== 'cancelled' && (
<MyTouchableOpacity
onPress={() => setCancelDialogOpen(true)}
style={tw`bg-red-50 border border-red-100 py-3.5 rounded-xl items-center flex-row justify-center`}
>
<MaterialIcons name="cancel" size={18} color="#DC2626" />
<MyText style={tw`text-red-600 font-bold ml-2`}>Cancel Order</MyText>
</MyTouchableOpacity>
)}
</View>
</ScrollView>
{/* Cancel Order Dialog */}
<BottomDialog open={cancelDialogOpen} onClose={() => setCancelDialogOpen(false)}>
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : undefined}>
<View style={tw`p-6`}>
<View style={tw`flex-row justify-between items-center mb-4`}>
<MyText style={tw`text-xl font-bold text-gray-900`}>Cancel Order</MyText>
<MyTouchableOpacity onPress={() => setCancelDialogOpen(false)}>
<MaterialIcons name="close" size={24} color="#9CA3AF" />
</MyTouchableOpacity>
</View>
<View style={tw`bg-red-50 p-4 rounded-xl mb-4 border border-red-100`}>
<MyText style={tw`text-red-800 text-sm`}>
Are you sure you want to cancel this order? This action cannot be undone.
</MyText>
</View>
<MyText style={tw`text-gray-700 font-medium mb-2`}>Reason for cancellation</MyText>
<TextInput
style={tw`bg-gray-50 border border-gray-200 rounded-xl p-4 min-h-24 text-base text-gray-800 mb-6`}
value={cancelReason}
onChangeText={setCancelReason}
placeholder="Please tell us why you are cancelling..."
placeholderTextColor="#9CA3AF"
multiline
numberOfLines={3}
textAlignVertical="top"
/>
<MyTouchableOpacity
style={tw`bg-red-600 py-4 rounded-xl shadow-sm items-center ${cancelOrderMutation.isPending ? 'opacity-70' : ''}`}
onPress={handleCancelOrder}
disabled={cancelOrderMutation.isPending}
>
{cancelOrderMutation.isPending ? (
<ActivityIndicator color="white" />
) : (
<MyText style={tw`text-white font-bold text-lg`}>Confirm Cancellation</MyText>
)}
</MyTouchableOpacity>
</View>
</KeyboardAvoidingView>
</BottomDialog>
{/* Raise Complaint Dialog */}
<BottomDialog open={complaintDialogOpen} onClose={() => setComplaintDialogOpen(false)}>
<ComplaintForm
open={complaintDialogOpen}
onClose={() => {
setComplaintDialogOpen(false);
refetch();
}}
orderId={order.orderId}
/>
</BottomDialog>
</View>
</AppContainer>
);
}