import React, { useState, useEffect } from 'react'; import { Form, Input, Button, DatePicker, Steps, Radio, Card, message, Alert, Descriptions, Breadcrumb, Typography, Modal, Row, Col, Select, Tag } from 'antd'; import { useNavigate, Link } from 'react-router-dom'; import { HomeOutlined, CreditCardOutlined, PlusOutlined, CheckCircleFilled } from '@ant-design/icons'; import dayjs from 'dayjs'; import api from '../utils/api'; import { colors, spacing, fontSize, commonStyles } from '../theme'; const { Option } = Select; const { Step } = Steps; const { TextArea } = Input; const { Title } = Typography; // Card type colors const cardTypeColors = { 'Visa': '#1a1f71', 'Mastercard': '#eb001b', 'American Express': '#006fcf', 'Discover': '#ff6000', 'default': colors.primary, }; const Order = () => { const navigate = useNavigate(); const [currentStep, setCurrentStep] = useState(0); const [loading, setLoading] = useState(false); const [pricing, setPricing] = useState({ price_per_gallon: 0, date: null }); const [totalPrice, setTotalPrice] = useState(0); const [selectedGallons, setSelectedGallons] = useState(100); const [selectedDate, setSelectedDate] = useState(dayjs().add(1, 'day')); const [customerTown, setCustomerTown] = useState(null); const [serviceType, setServiceType] = useState(null); // null, 'same_day', 'emergency' const [primeSelected, setPrimeSelected] = useState(false); const [formData, setFormData] = useState({ gallons_ordered: 100, expected_delivery_date: dayjs().add(1, 'day'), dispatcher_notes: '', payment_type: 1, // credit }); const [notesLength, setNotesLength] = useState(0); // Saved cards state const [savedCards, setSavedCards] = useState([]); const [loadingCards, setLoadingCards] = useState(true); const [selectedCardId, setSelectedCardId] = useState(null); const [addCardModalVisible, setAddCardModalVisible] = useState(false); const [addCardLoading, setAddCardLoading] = useState(false); const [addCardForm] = Form.useForm(); // Generate year options for card expiration const currentYear = new Date().getFullYear(); const yearOptions = Array.from({ length: 11 }, (_, i) => currentYear + i); // Fetch pricing on component mount and set up auto-refresh useEffect(() => { const fetchPricing = async () => { try { const response = await api.get('/info/pricing/current'); setPricing(response.data); } catch (error) { console.error('Failed to fetch pricing:', error); } }; fetchPricing(); // Auto-refresh pricing every 30 seconds const interval = setInterval(fetchPricing, 30000); return () => clearInterval(interval); }, []); // Fetch customer town on component mount useEffect(() => { const fetchCustomerData = async () => { try { const response = await api.get('/auth/me'); setCustomerTown(response.data.customer_town); } catch (error) { console.error('Failed to fetch customer data:', error); } }; fetchCustomerData(); }, []); // Fetch saved cards on component mount useEffect(() => { const fetchSavedCards = async () => { setLoadingCards(true); try { const response = await api.get('/payment/cards'); setSavedCards(response.data); // Auto-select default card if exists const defaultCard = response.data.find(card => card.main_card); if (defaultCard) { setSelectedCardId(defaultCard.id); } else if (response.data.length > 0) { setSelectedCardId(response.data[0].id); } } catch (error) { console.error('Failed to fetch saved cards:', error); } finally { setLoadingCards(false); } }; fetchSavedCards(); }, []); // Calculate total price when gallons, pricing, service type, or prime changes useEffect(() => { const basePrice = pricing.price_per_gallon * formData.gallons_ordered; const surcharge = getSurcharge(); const calculatedTotal = basePrice + surcharge; setTotalPrice(calculatedTotal); }, [pricing.price_per_gallon, formData.gallons_ordered, serviceType, primeSelected]); const next = () => { setCurrentStep(currentStep + 1); }; const prev = () => { setCurrentStep(currentStep - 1); }; const handleGallonsChange = (value) => { setFormData({ ...formData, gallons_ordered: value }); setSelectedGallons(value); }; const handleDateChange = (date) => { setFormData({ ...formData, expected_delivery_date: date }); setSelectedDate(date); }; const handleNotesChange = (e) => { setFormData({ ...formData, dispatcher_notes: e.target.value }); setNotesLength(e.target.value.length); }; const handlePaymentChange = (e) => { setFormData({ ...formData, payment_type: e.target.value }); }; // Towns where 100 gallons is restricted const restrictedTowns = ['Sutton', 'Millbury', 'Paxton', 'Grafton']; // Check if same day service is available (6am-2pm) const isSameDayAvailable = () => { const now = dayjs(); const hour = now.hour(); return hour >= 6 && hour < 14; // 6am to 2pm (14:00) }; // Get surcharge based on service type and prime const getSurcharge = () => { let surcharge = 0; if (serviceType === 'same_day') surcharge += 45; if (serviceType === 'emergency') surcharge += 250; if (primeSelected) surcharge += serviceType === 'emergency' ? 50 : 25; return surcharge; }; // Handle same day service selection const handleSameDaySelect = () => { if (serviceType === 'same_day') { // Deselect same day setServiceType(null); handleDateChange(dayjs().add(1, 'day')); // Reset to tomorrow } else { // Select same day setServiceType('same_day'); handleDateChange(dayjs()); // Set to today } }; // Handle emergency service selection const handleEmergencySelect = () => { if (serviceType === 'emergency') { // Deselect emergency setServiceType(null); handleDateChange(dayjs().add(1, 'day')); // Reset to tomorrow handleGallonsChange(100); // Reset to default gallons } else { // Select emergency setServiceType('emergency'); handleDateChange(dayjs()); // Set to today handleGallonsChange(225); // Force 225 gallons } }; // Helper function to mask card number const maskCardNumber = (cardNumber) => { if (!cardNumber) return ''; const digits = cardNumber.replace(/\s/g, ''); const last4 = digits.slice(-4); return `**** **** **** ${last4}`; }; // Handle add new card const handleAddCard = () => { addCardForm.resetFields(); setAddCardModalVisible(true); }; const handleAddCardSubmit = async () => { try { const values = await addCardForm.validateFields(); setAddCardLoading(true); const response = await api.post('/payment/cards', { card_number: values.card_number.replace(/\s/g, ''), name_on_card: values.name_on_card, expiration_month: values.expiration_month, expiration_year: values.expiration_year, security_number: values.security_number, zip_code: values.zip_code, }); message.success('Card added successfully'); setAddCardModalVisible(false); addCardForm.resetFields(); // Refresh cards and select the new one const cardsResponse = await api.get('/payment/cards'); setSavedCards(cardsResponse.data); setSelectedCardId(response.data.id); } catch (error) { if (error.errorFields) { return; } const errorMsg = error.response?.data?.detail || 'Failed to add card'; message.error(errorMsg); console.error('Error adding card:', error); } finally { setAddCardLoading(false); } }; // Get selected card details const getSelectedCard = () => { return savedCards.find(card => card.id === selectedCardId); }; const onFinish = async () => { setLoading(true); try { if (formData.payment_type === 1) { // Validate card selection if (!selectedCardId) { message.error('Please select a payment card or add a new one'); setLoading(false); return; } // Process credit card payment using saved card const paymentData = { amount: totalPrice, card_id: selectedCardId, }; const paymentResponse = await api.post('/payment/process', paymentData); if (paymentResponse.data.status === 'approved') { message.success(`Payment approved! Transaction ID: ${paymentResponse.data.auth_net_transaction_id}`, 4.5); // Now create the delivery const deliveryData = { gallons_ordered: formData.gallons_ordered, expected_delivery_date: formData.expected_delivery_date.format('YYYY-MM-DD'), dispatcher_notes: formData.dispatcher_notes, payment_type: formData.payment_type, prime: primeSelected ? 1 : 0, same_day: serviceType === 'same_day' ? 1 : 0, emergency: serviceType === 'emergency' ? 1 : 0, pre_charge_amount: totalPrice, }; const deliveryResponse = await api.post('/order/deliveries', deliveryData); message.success('Delivery created successfully!', 4.5); navigate('/'); // Reset form setFormData({ gallons_ordered: 100, expected_delivery_date: dayjs().add(1, 'day'), dispatcher_notes: '', payment_type: 1, }); setServiceType(null); setPrimeSelected(false); setCurrentStep(0); } else { message.error('Payment was declined. Please check your card details.', 4.5); } } else { // Cash payment - just create delivery const data = { gallons_ordered: formData.gallons_ordered, expected_delivery_date: formData.expected_delivery_date.format('YYYY-MM-DD'), dispatcher_notes: formData.dispatcher_notes, payment_type: formData.payment_type, prime: primeSelected ? 1 : 0, same_day: serviceType === 'same_day' ? 1 : 0, emergency: serviceType === 'emergency' ? 1 : 0, pre_charge_amount: totalPrice, }; const response = await api.post('/order/deliveries', data); message.success('Delivery created successfully!', 4.5); navigate('/'); // Reset form setFormData({ gallons_ordered: 100, expected_delivery_date: dayjs().add(1, 'day'), dispatcher_notes: '', payment_type: 1, }); setServiceType(null); setPrimeSelected(false); setCurrentStep(0); } } catch (error) { message.error('Failed to process order: ' + error.response?.data?.detail || error.message, 4.5); } finally { setLoading(false); } }; const steps = [ { title: 'Order Details', content: (