freshyo/apps/user-ui/components/PaymentAndOrderComponent.tsx
2026-01-24 00:13:15 +05:30

394 lines
No EOL
15 KiB
TypeScript

import React, { useState, useMemo } from 'react';
import { View, Alert, Platform } from 'react-native';
import { useRouter } from 'expo-router';
import { tw, MyTextInput, LoadingDialog, MyText, MyTouchableOpacity } from 'common-ui';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
// import RazorpayCheckout from 'react-native-razorpay';
import { trpc } from '@/src/trpc-client';
import { clearLocalCart } from '@/hooks/cart-query-hooks';
import { useQueryClient } from '@tanstack/react-query';
import { FontAwesome5, FontAwesome6 } from '@expo/vector-icons';
interface PaymentAndOrderProps {
selectedAddress: number | null;
selectedSlots: Record<number, number>;
selectedCouponId: number | null;
cartItems: any[];
totalPrice: number;
discountAmount: number;
finalTotal: number;
finalTotalWithDelivery: number;
deliveryCharge: number;
constsData?: any;
selectedCoupons: any[];
isFlashDelivery?: boolean;
onCancel?: () => void;
}
const PaymentAndOrderComponent: React.FC<PaymentAndOrderProps> = ({
selectedAddress,
selectedSlots,
selectedCouponId,
cartItems,
totalPrice,
discountAmount,
finalTotal,
finalTotalWithDelivery,
deliveryCharge,
constsData,
selectedCoupons,
isFlashDelivery = false,
onCancel,
}) => {
const router = useRouter();
const queryClient = useQueryClient();
const [userNotes, setUserNotes] = useState('');
const [isLoadingDialogOpen, setIsLoadingDialogOpen] = useState(false);
const [paymentMethod, setPaymentMethod] = useState<'online' | 'cod'>('cod');
// Helper function to clear cart and invalidate queries
const clearCartAndInvalidateQueries = async (cartType: 'regular' | 'flash' = 'regular') => {
await clearLocalCart(cartType);
queryClient.invalidateQueries({ queryKey: [`local-cart-${cartType}`] });
};
const { data: productsData } = trpc.user.product.getAllProductsSummary.useQuery();
// Memoized flash-eligible product IDs
const flashEligibleProductIds = useMemo(() => {
if (!productsData) return new Set<number>();
return new Set(
productsData
.filter((product: any) => product.isFlashAvailable)
.map((product: any) => product.id)
);
}, [productsData]);
const placeOrderMutation = trpc.user.order.placeOrder.useMutation({
onSuccess: (data) => {
const orders = data.data; // Now an array of orders
const firstOrder = orders[0]; // Use first order for payment flow
if (!firstOrder.isCod) {
// createRazorpayOrderMutation.mutate({ orderId: firstOrder.id.toString() });
} else {
// Clear cart and navigate to success page
clearCartAndInvalidateQueries(isFlashDelivery ? 'flash' : 'regular');
const successPath = isFlashDelivery
? '/(drawer)/(tabs)/flash-delivery/order-success'
: './order-success';
router.replace(`${successPath}?orderId=${firstOrder.id}&totalAmount=${finalTotalWithDelivery}`);
}
},
onError: (error: any) => {
Alert.alert('Error', error.message || 'Failed to place order');
},
onSettled: () => {
setIsLoadingDialogOpen(false);
},
});
// const createRazorpayOrderMutation = trpc.user.payment.createRazorpayOrder.useMutation({
// onSuccess: (paymentData) => {
// initiateRazorpayPayment(paymentData.razorpayOrderId, paymentData.key, finalTotal);
// },
// onError: (error: any) => {
// Alert.alert('Error', error.message || 'Failed to create payment order');
// },
// });
const verifyPaymentMutation = trpc.user.payment.verifyPayment.useMutation({
onSuccess: () => {
const orders = placeOrderMutation.data?.data || [];
const firstOrder = orders[0];
// Clear cart and navigate to success page
clearCartAndInvalidateQueries(isFlashDelivery ? 'flash' : 'regular');
const successPath = isFlashDelivery
? '/(drawer)/(tabs)/flash-delivery/order-success'
: './order-success';
router.replace(`${successPath}?orderId=${firstOrder?.id}&totalAmount=${finalTotalWithDelivery}`);
},
onError: (error: any) => {
Alert.alert('Error', error.message || 'Payment verification failed');
},
});
const markPaymentFailedMutation = trpc.user.payment.markPaymentFailed.useMutation();
const handlePlaceOrder = () => {
if (!selectedAddress) {
Alert.alert('Error', 'Please select an address');
return;
}
const availableItems = cartItems
.filter(item => {
if (item.product?.isOutOfStock) return false;
// For flash delivery, check if product supports flash delivery
if (isFlashDelivery) {
return flashEligibleProductIds.has(item.productId);
}
// For regular delivery, only include items with assigned slots
return selectedSlots[item.id];
})
.map(item => item.id);
if (availableItems.length === 0) {
Alert.alert('Error', 'No valid items to order');
return;
}
setIsLoadingDialogOpen(true);
const orderData = {
selectedItems: availableItems.map(itemId => {
const item = cartItems.find(cartItem => cartItem.id === itemId);
return {
productId: item.productId,
quantity: item.quantity,
slotId: isFlashDelivery ? null : selectedSlots[itemId]
};
}),
addressId: selectedAddress,
paymentMethod: paymentMethod,
couponId: selectedCouponId || undefined,
userNotes: userNotes,
isFlashDelivery: isFlashDelivery,
};
console.log({orderData})
placeOrderMutation.mutate(orderData);
};
// const initiateRazorpayPayment = (razorpayOrderId: string, key: string, amount: number) => {
// const options = {
// key,
// amount: amount * 100,
// currency: 'INR',
// order_id: razorpayOrderId,
// name: 'Meat Farmer',
// description: 'Order Payment',
// prefill: {},
// };
// RazorpayCheckout.open(options)
// .then((data: any) => {
// verifyPaymentMutation.mutate({
// razorpay_payment_id: data.razorpay_payment_id,
// razorpay_order_id: data.razorpay_order_id,
// razorpay_signature: data.razorpay_signature,
// });
// })
// .catch((error: any) => {
// markPaymentFailedMutation.mutate({ merchantOrderId: razorpayOrderId });
// Alert.alert(
// 'Payment Failed',
// 'Payment failed or was cancelled. What would you like to do?',
// [
// {
// text: 'Retry Now',
// onPress: () => {
// const orders = placeOrderMutation.data?.data || [];
// const firstOrder = orders[0];
// const orderId = firstOrder?.id.toString();
// if (orderId) {
// createRazorpayOrderMutation.mutate({ orderId });
// }
// }
// },
// {
// text: 'Retry Later',
// onPress: () => router.push('/(drawer)/(tabs)/me/my-orders')
// }
// ]
// );
// });
// };
return (
<>
{/* Back Button */}
{onCancel && (
<View style={tw`bg-white p-4 rounded-2xl shadow-sm mb-4 border border-gray-100`}>
<MyTouchableOpacity
onPress={onCancel}
style={tw`flex-row items-center`}
>
<MaterialIcons name="arrow-back" size={20} color="#6B7280" />
<MyText style={tw`text-gray-600 ml-2 font-medium`}>Back to Cart</MyText>
</MyTouchableOpacity>
</View>
)}
{/* Special Instructions */}
<View style={tw`bg-white p-5 rounded-2xl shadow-sm mb-4 border border-gray-100`}>
<MyText style={tw`text-base font-bold text-gray-900 mb-3`}>Delivery Instructions</MyText>
<MyTextInput
style={tw`border border-gray-200 rounded-xl p-3 min-h-24 text-base bg-gray-50`}
value={userNotes}
onChangeText={setUserNotes}
placeholder="Any special instructions for the delivery partner..."
multiline
numberOfLines={3}
textAlignVertical="top"
/>
</View>
{/* Payment Method */}
<View style={tw`bg-white p-5 rounded-2xl shadow-sm mb-4 border border-gray-100`}>
<MyText style={tw`text-lg font-bold text-gray-900 mb-4`}>Payment Method</MyText>
<MyTouchableOpacity
disabled={true}
style={tw`flex-row items-center p-4 rounded-xl border mb-3 opacity-50 bg-gray-100`}
>
<View style={tw`w-5 h-5 rounded-full border-2 border-gray-300 items-center justify-center mr-3`}>
{/* No selection dot for disabled state */}
</View>
<View style={tw`w-10 h-10 bg-gray-200 rounded-full items-center justify-center mr-3`}>
<MaterialIcons name="payment" size={20} color="#9CA3AF" />
</View>
<View>
<MyText style={tw`font-bold text-gray-500`}>Pay Online (Coming Soon)</MyText>
<MyText style={tw`text-xs text-gray-400`}>UPI, Cards, Netbanking</MyText>
</View>
</MyTouchableOpacity>
<MyTouchableOpacity
onPress={() => setPaymentMethod('cod')}
style={tw`flex-row items-center p-4 rounded-xl border ${paymentMethod === 'cod' ? 'border-brand500 bg-blue-50' : 'border-gray-200'}`}
>
<View style={tw`w-5 h-5 rounded-full border-2 ${paymentMethod === 'cod' ? 'border-brand500' : 'border-gray-400'} items-center justify-center mr-3`}>
{paymentMethod === 'cod' && <View style={tw`w-2.5 h-2.5 rounded-full bg-brand500`} />}
</View>
<View style={tw`w-10 h-10 bg-green-50 rounded-full items-center justify-center mr-3`}>
<FontAwesome5 name="rupee-sign" size={20} color="#10B981" />
</View>
<View>
<MyText style={tw`font-bold text-gray-900`}>Cash on Delivery</MyText>
<MyText style={tw`text-xs text-gray-500`}>Pay when you receive</MyText>
</View>
</MyTouchableOpacity>
<View style={tw`mt-3 flex-row items-center gap-4`}>
<View style={tw`flex-row items-center`}>
<FontAwesome6 name="wallet" size={12} color="#663399" style={tw`mr-1.5`} />
<MyText style={tw`text-xs text-gray-500`}>UPI accepted during COD</MyText>
</View>
<View style={tw`flex-row items-center`}>
<FontAwesome5 name="money-bill-wave" size={12} color="#10B981" style={tw`mr-1.5`} />
<MyText style={tw`text-xs text-gray-500`}>Cash payment</MyText>
</View>
</View>
</View>
{/* Bill Details */}
<View style={tw`bg-white p-5 rounded-2xl shadow-sm mb-4 border border-gray-100`}>
<MyText style={tw`text-lg font-bold text-gray-900 mb-4`}>Bill Details</MyText>
{/* Item Total */}
<View style={tw`flex-row justify-between items-center mb-2`}>
<MyText style={tw`text-gray-500`}>Item Total</MyText>
<MyText style={tw`text-gray-900 font-medium`}>{totalPrice}</MyText>
</View>
{/* Discount */}
{discountAmount > 0 && (
<View style={tw`flex-row justify-between items-center mb-2`}>
<MyText style={tw`text-gray-500`}>Product Discount</MyText>
<MyText style={tw`text-green-600 font-medium`}>-{discountAmount}</MyText>
</View>
)}
{/* Delivery Fee */}
<View style={tw`flex-row justify-between items-center mb-2`}>
<View style={tw`flex-row items-center`}>
<MyText style={tw`text-gray-500`}>Delivery Fee</MyText>
<MaterialIcons name="info-outline" size={14} color="#9CA3AF" style={tw`ml-1`} />
</View>
<View style={tw`flex-row items-center`}>
{deliveryCharge === 0 && (
isFlashDelivery
? (constsData?.flashDeliveryCharge > 0 && (
<MyText style={tw`text-gray-400 line-through mr-2 text-xs`}>
{constsData?.flashDeliveryCharge}
</MyText>
))
: (constsData?.deliveryCharge > 0 && (
<MyText style={tw`text-gray-400 line-through mr-2 text-xs`}>
{constsData?.deliveryCharge}
</MyText>
))
)}
<MyText style={tw`${deliveryCharge === 0 ? 'text-green-600' : 'text-gray-900'} font-medium`}>
{deliveryCharge === 0 ? 'Free' : `${deliveryCharge}`}
</MyText>
</View>
</View>
{/* Free Delivery Nudge */}
{deliveryCharge > 0 && (() => {
const threshold = isFlashDelivery
? constsData?.flashFreeDeliveryThreshold
: constsData?.freeDeliveryThreshold;
return threshold > 0 && finalTotal < threshold && (
<View style={tw`bg-blue-50 p-2.5 rounded-lg mb-2 flex-row items-center`}>
<MaterialIcons name="shopping-bag" size={16} color="#2563EB" style={tw`mr-2`} />
<MyText style={tw`text-blue-700 text-xs font-medium flex-1`}>
Add products worth {(threshold - finalTotal).toFixed(0)} for free delivery
</MyText>
</View>
);
})()}
{/* Divider */}
<View style={tw`h-px bg-gray-100 my-2`} />
{/* Grand Total */}
<View style={tw`flex-row justify-between items-center`}>
<MyText style={tw`text-lg font-bold text-gray-900`}>To Pay</MyText>
<MyText style={tw`text-xl font-bold text-gray-900`}>{finalTotalWithDelivery}</MyText>
</View>
{/* Savings Banner */}
{(discountAmount > 0 || deliveryCharge === 0) && (
<View style={tw`bg-green-50 rounded-lg p-2 mt-4 flex-row justify-center items-center`}>
<MaterialIcons name="stars" size={16} color="#059669" style={tw`mr-1.5`} />
<MyText style={tw`text-green-700 text-xs font-bold`}>
You saved {discountAmount + (deliveryCharge === 0 ? (
isFlashDelivery
? constsData?.flashDeliveryCharge
: constsData?.deliveryCharge
) : 0)} on this order
</MyText>
</View>
)}
</View>
{/* Bottom Action Bar */}
<View style={tw`bg-white p-5 rounded-2xl shadow-sm mb-4 border border-gray-100`}>
<MyTouchableOpacity
style={tw`bg-brand500 py-4 rounded-xl items-center shadow-md ${!selectedAddress ? 'opacity-50' : ''}`}
disabled={!selectedAddress}
onPress={handlePlaceOrder}
>
<MyText style={tw`text-white font-bold text-lg`}>
Place Order
</MyText>
</MyTouchableOpacity>
</View>
<LoadingDialog
open={isLoadingDialogOpen}
message="Placing your order..."
/>
</>
);
};
export default PaymentAndOrderComponent;