// cart.jsx — Cart drawer, checkout, confirmation const CopyChip = ({ value }) => { const [copied, setCopied] = useState(false); const onCopy = () => { const txt = value.replace(/\s+/g,''); if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(txt); } else { const t = document.createElement('textarea'); t.value = txt; document.body.appendChild(t); t.select(); try { document.execCommand('copy'); } catch(e){} document.body.removeChild(t); } setCopied(true); setTimeout(()=>setCopied(false), 1500); }; return ( ); }; const sendOrderToGoogleSheet = async (orderData) => { const url = window.SJ_DATA?.brand?.googleSheetsWebhook; if (!url) { console.warn('Google Sheets webhook URL is not configured'); return false; } const body = JSON.stringify(orderData); try { console.log('Sending Google Sheets webhook (cors / text/plain)...', url, orderData); const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'text/plain' }, body, keepalive: true, mode: 'cors', }); if (response.ok) { const text = await response.text(); console.log('Google Sheets webhook success:', text); try { const parsed = JSON.parse(text); if (parsed.success) return true; } catch (e) { return true; } } console.warn('Google Sheets webhook returned non-ok status:', response.status, response.statusText); } catch (err) { console.warn('Google Sheets webhook primary request failed. Retrying with no-cors...', err); try { await fetch(url, { method: 'POST', headers: { 'Content-Type': 'text/plain' }, body, keepalive: true, mode: 'no-cors', }); console.log('Google Sheets webhook sent via no-cors fallback'); return true; } catch (fallbackErr) { console.warn('Google Sheets webhook no-cors fallback failed:', fallbackErr); } if (navigator.sendBeacon) { try { const blob = new Blob([body], { type: 'text/plain' }); const beaconResult = navigator.sendBeacon(url, blob); console.log('Google Sheets beacon fallback result:', beaconResult); if (beaconResult) return true; } catch (beaconErr) { console.warn('Google Sheets beacon fallback failed:', beaconErr); } } } return false; }; const CartDrawer = ({ open, onClose, cart, updateQty, removeItem, goto }) => { const subtotal = cart.reduce((s,it)=>s + it.price * it.qty, 0); return ( <>
); }; const CheckoutPage = ({ cart, goto, onPlace }) => { const [pay, setPay] = useState('razorpay'); const [form, setForm] = useState({ name:'', phone:'', email:'', line1:'', line2:'', city:'', state:'', pin:'' }); useEffect(() => { if (window.fbq) { const total = cart.reduce((s, it) => s + ((it.price || 0) * (it.qty || 0)), 0); const ids = cart.map(it => String(it.id || it.sku || it.slug || "")); const count = cart.reduce((s, it) => s + (it.qty || 0), 0); window.fbq("track", "InitiateCheckout", { value: total, currency: "INR", content_ids: ids, content_type: "product", num_items: count }); } }, []); const [webhookStatus, setWebhookStatus] = useState(null); const [payProcessing, setPayProcessing] = useState(false); const upd = (k) => (e) => setForm(f => ({...f, [k]: e.target.value})); const subtotal = cart.reduce((s,it)=>s + it.price * it.qty, 0); const MIN_ORDER = 220; const belowMin = subtotal < MIN_ORDER; const codFee = pay === 'cod' ? 50 : 0; const partialPrepay = pay === 'cod' ? 100 : 0; const prepaidDiscount = pay === 'razorpay' ? Math.round(subtotal * 0.05) : 0; const total = subtotal + codFee - prepaidDiscount; const codBalance = total - partialPrepay; const submitOrder = async (e) => { e.preventDefault(); const orderId = 'SJ' + Date.now().toString().slice(-6); const lines = cart.map(it => `• ${it.name} (${it.weight}) × ${it.qty} — ${fmt(it.price * it.qty)}`).join('\n'); let payLabel = 'Paid Online via Razorpay'; if (pay === 'cod') { payLabel = `Cash on Delivery (₹50 COD fee)\n• Partial prepayment: ${fmt(partialPrepay)} (Paid Online via Razorpay)\n• Balance on delivery: ${fmt(codBalance)}`; } const msg = `*New Order — Shudh Jain* Order #${orderId} *Customer* ${form.name} ${form.phone}${form.email ? `\n${form.email}` : ''} *Shipping* ${form.line1}${form.line2 ? `, ${form.line2}` : ''} ${form.city}, ${form.state} — ${form.pin} *Items* ${lines} *Subtotal:* ${fmt(subtotal)}${codFee ? `\n*COD fee:* ${fmt(codFee)}` : ''} *Total:* ${fmt(total)} *Payment* ${payLabel} (Sent from shudhjain.com)`; const orderData = { orderId, createdAt: new Date().toLocaleString('en-IN', { timeZone: 'Asia/Kolkata' }), razorpayPaymentId: '', paymentStatus: 'pending', customer: { name: form.name, phone: form.phone, email: form.email, }, shipping: { line1: form.line1, line2: form.line2, city: form.city, state: form.state, pin: form.pin, }, payment: { method: pay, subtotal, codFee, total, partialPrepay, codBalance, }, items: cart.map(it => ({ name: it.name, weight: it.weight, qty: it.qty, unitPrice: it.price, total: it.price * it.qty })), source: window.location.href, }; if (pay === 'razorpay' || pay === 'cod') { if (!window.Razorpay) { alert("Razorpay payment gateway load nahi hua. Internet connection check karein."); return; } setPayProcessing(true); // 1. Send pending order details to Google Sheet immediately console.log('Sending pending order to Google Sheets first...'); await sendOrderToGoogleSheet(orderData); const rzpAmount = pay === 'cod' ? partialPrepay : total; const rzpOptions = { key: window.SJ_DATA?.brand?.razorpayKeyId || "rzp_test_yourKeyHere", amount: rzpAmount * 100, // amount in paise currency: "INR", name: "Shudh Jain", description: pay === 'cod' ? `COD Prepayment for Order #${orderId}` : `Order #${orderId}`, image: "assets/logo.jpeg", prefill: { name: form.name, contact: form.phone, email: form.email || "", }, notes: { orderId: orderId, address: `${form.line1}, ${form.city}, ${form.state} - ${form.pin}`, }, handler: async function (response) { console.log('Razorpay payment success:', response); const paymentId = response.razorpay_payment_id; const statusVal = pay === 'cod' ? 'partially_paid' : 'paid'; const orderDataPaid = { ...orderData, razorpayPaymentId: paymentId, paymentStatus: statusVal, payment: { ...orderData.payment, paymentId: paymentId, status: statusVal, } }; const webhookSaved = await sendOrderToGoogleSheet(orderDataPaid); setWebhookStatus(webhookSaved ? 'saved' : 'failed'); let paidMsg = msg; if (pay === 'cod') { paidMsg += `\n\n*Payment Details*\n• Status: PARTIALLY PAID (via Razorpay)\n• Razorpay Payment ID: ${paymentId}\n• Paid Online: ${fmt(partialPrepay)}\n• Balance on Delivery: ${fmt(codBalance)}`; } else { paidMsg += `\n\n*Payment Details*\n• Status: PAID (via Razorpay)\n• Razorpay Payment ID: ${paymentId}`; } const url = `https://wa.me/${window.SJ_DATA.brand.whatsapp}?text=${encodeURIComponent(paidMsg)}`; window.open(url, '_blank'); setPayProcessing(false); if (window.fbq) { const __ids = cart.map(it => String(it.id || it.sku || it.slug || "")); const __count = cart.reduce((s, it) => s + (it.qty || 0), 0); window.fbq("track", "Purchase", { value: rzpAmount, currency: "INR", content_ids: __ids, content_type: "product", num_items: __count }); } onPlace({ pay, total, subtotal, codFee, orderId, paymentId, ...form }); }, modal: { ondismiss: function () { console.log('Razorpay modal dismissed by user'); setPayProcessing(false); }, escape: true, confirm_close: true, }, theme: { color: "#1f5f3d", // Brand green color } }; console.log('Opening Razorpay with options:', { ...rzpOptions, key: '***hidden***' }); const rzp = new window.Razorpay(rzpOptions); rzp.on('payment.failed', function (response) { console.error('Razorpay payment failed:', response.error); setPayProcessing(false); alert("Payment fail ho gaya: " + (response.error?.description || "Unknown error") + "\n\nDobara try karein."); }); rzp.open(); } }; if (cart.length === 0) { return (
🫙

Your cart is empty

Add a jar before you checkout.

); } const upiBlock = (amount, label) => (
Scan to pay with any UPI app
Amount to pay {fmt(amount)} {label && {label}}
UPI ID
Mobile (UPI)
Account holderAnil Jain · SBI

Scan QR with any UPI app — GPay, PhonePe, Paytm, BHIM. After paying, share the screenshot on WhatsApp.

); return (
Almost there

Checkout

1. Contact

{ const v = e.target.value.replace(/[^0-9+\s-]/g, '').slice(0, 15); setForm(f => ({...f, phone: v})); }} placeholder="+91 ..." maxLength={15}/>

2. Shipping address

{ const v = e.target.value.replace(/\D/g,'').slice(0,6); setForm(f => ({...f, pin: v})); if (v.length === 6) { // 1. Resolve State offline for ALL Indian pincodes const getStateByPin = (pin) => { const prefix2 = pin.slice(0, 2); const mapping2 = { '11': 'Delhi', '12': 'Haryana', '13': 'Haryana', '14': 'Punjab', '15': 'Punjab', '16': 'Punjab', '17': 'Himachal Pradesh', '18': 'Jammu & Kashmir', '19': 'Jammu & Kashmir', '20': 'Uttar Pradesh', '21': 'Uttar Pradesh', '22': 'Uttar Pradesh', '23': 'Uttar Pradesh', '24': 'Uttar Pradesh', '25': 'Uttar Pradesh', '26': 'Uttar Pradesh', '27': 'Uttar Pradesh', '28': 'Uttar Pradesh', '30': 'Rajasthan', '31': 'Rajasthan', '32': 'Rajasthan', '33': 'Rajasthan', '34': 'Rajasthan', '36': 'Gujarat', '37': 'Gujarat', '38': 'Gujarat', '39': 'Gujarat', '40': 'Maharashtra', '41': 'Maharashtra', '42': 'Maharashtra', '43': 'Maharashtra', '44': 'Maharashtra', '45': 'Madhya Pradesh', '46': 'Madhya Pradesh', '47': 'Madhya Pradesh', '48': 'Madhya Pradesh', '49': 'Chhattisgarh', '50': 'Telangana', '51': 'Andhra Pradesh', '52': 'Andhra Pradesh', '53': 'Andhra Pradesh', '56': 'Karnataka', '57': 'Karnataka', '58': 'Karnataka', '59': 'Karnataka', '60': 'Tamil Nadu', '61': 'Tamil Nadu', '62': 'Tamil Nadu', '63': 'Tamil Nadu', '64': 'Tamil Nadu', '67': 'Kerala', '68': 'Kerala', '69': 'Kerala', '70': 'West Bengal', '71': 'West Bengal', '72': 'West Bengal', '73': 'West Bengal', '74': 'West Bengal', '75': 'Odisha', '76': 'Odisha', '77': 'Odisha', '78': 'Assam', '80': 'Bihar', '81': 'Bihar', '82': 'Bihar', '83': 'Bihar', '84': 'Bihar', '85': 'Bihar' }; if (mapping2[prefix2]) return mapping2[prefix2]; const prefix3 = pin.slice(0, 3); const mapping3 = { '246': 'Uttarakhand', '247': 'Uttarakhand', '248': 'Uttarakhand', '249': 'Uttarakhand', '825': 'Jharkhand', '826': 'Jharkhand', '827': 'Jharkhand', '828': 'Jharkhand', '829': 'Jharkhand', '831': 'Jharkhand', '832': 'Jharkhand', '833': 'Jharkhand', '834': 'Jharkhand', '835': 'Jharkhand', '814': 'Jharkhand', '815': 'Jharkhand', '816': 'Jharkhand', '737': 'Sikkim', '790': 'Arunachal Pradesh', '791': 'Arunachal Pradesh', '792': 'Arunachal Pradesh', '795': 'Manipur', '793': 'Meghalaya', '794': 'Meghalaya', '796': 'Mizoram', '797': 'Nagaland', '798': 'Nagaland', '799': 'Tripura', '403': 'Goa', '605': 'Puducherry', '682': 'Lakshadweep', '744': 'Andaman & Nicobar Islands' }; return mapping3[prefix3] || ''; }; // 2. Resolve popular cities offline for best experience const getCityByPin = (pin) => { const prefix3 = pin.slice(0, 3); const mappingCity = { '452': 'Indore', '453': 'Indore Rural', '462': 'Bhopal', '474': 'Gwalior', '482': 'Jabalpur', '110': 'New Delhi', '400': 'Mumbai', '411': 'Pune', '560': 'Bengaluru', '380': 'Ahmedabad', '500': 'Hyderabad', '700': 'Kolkata', '302': 'Jaipur', '395': 'Surat', '201': 'Noida', '122': 'Gurugram', '600': 'Chennai' }; return mappingCity[prefix3] || ''; }; const offlineState = getStateByPin(v); const offlineCity = getCityByPin(v); // Pre-fill State and City offline immediately if (offlineState) { setForm(f => ({ ...f, state: offlineState, city: offlineCity || f.city, _pinLoading: true, _pinErr: false })); } else { setForm(f => ({ ...f, _pinLoading: true, _pinErr: false })); } // Try online enrichment (CORS/network may fail, but state is already resolved offline!) fetch(`https://api.postalpincode.in/pincode/${v}`) .then(r => r.json()) .then(d => { const po = d && d[0] && d[0].PostOffice && d[0].PostOffice[0]; if (po) { setForm(f => ({ ...f, city: po.District || offlineCity || f.city, state: po.State || offlineState || f.state, _pinLoading: false, _pinErr: false })); } else { setForm(f => ({ ...f, _pinLoading: false })); } }) .catch(() => { // Offline fallback is already active, just stop loading spinner silently! setForm(f => ({ ...f, _pinLoading: false })); }); } }} maxLength={6} placeholder="452001" inputMode="numeric"/> {form._pinLoading && Looking up city…} {!form._pinLoading && form.city && form.pin.length===6 && ✓ {form.city}, {form.state}}

3. Payment

{[ { id:'razorpay', t:'Pay Online (Cards / UPI / Netbanking)', s:'Fast & Secure checkout via Razorpay', rec:true }, { id:'cod', t:'Cash on Delivery', s:'+ ₹50 COD charge · ₹100 partial prepayment online' }, ].map(p=>( ))}
{pay === 'cod' && (
Cash on Delivery — how it works
  1. Pay {fmt(partialPrepay)} online now via Razorpay (UPI, Card, Netbanking) to confirm your order.
  2. Pay the balance {fmt(codBalance)} in cash or UPI when our courier delivers.
  3. A flat ₹50 COD charge is included in your total.
)}
Pay online (UPI/Card) — Save {fmt(prepaidDiscount)} on this order!
{!belowMin && !payProcessing && ( )}

{pay === 'razorpay' || pay === 'cod' ? 'Clicking "Pay" will open the secure Razorpay payment modal.' : 'Clicking will open WhatsApp with your order details pre-filled. Just hit send.' }

{webhookStatus === 'saved' && (
Order sent to Google Sheets successfully.
)} {webhookStatus === 'failed' && (
Unable to send order to Google Sheets from browser. Check console or network.
)} {belowMin && (
Minimum order is {fmt(MIN_ORDER)}. Add {fmt(MIN_ORDER - subtotal)} more to your cart to continue. {e.preventDefault();goto('shop');}} style={{color:'var(--saffron-dark)',textDecoration:'underline'}}>Browse products
)}

By placing this order you agree to our terms.

); }; const ConfirmationPage = ({ order, goto }) => (
Order confirmed · #SJ{Math.floor(Math.random()*9000)+1000}

Dhanyavaad! 🙏

Your order has been sent to us on WhatsApp. We'll confirm shortly and ship within 24 hours.{order && order.orderId ? <>
Order #{order.orderId} : null}

What happens next

{[ {t:'Order received', s:'Just now', done:true}, {t:'Packed in Indore', s:'Within 24 hours', done:false}, {t:'Out for delivery', s:'4–7 business days', done:false}, {t:'Enjoy with parathas 🫓', s:'You earned it', done:false}, ].map((s,i)=>(
{s.done ? : {i+1}}
{s.t}
{s.s}
))}
Message on WhatsApp
); Object.assign(window, { CartDrawer, CheckoutPage, ConfirmationPage });