|
| 1 | +import React, { useState } from 'react'; |
| 2 | +import { View, Text, ScrollView, TouchableOpacity, TextInput, StyleSheet, Alert } from 'react-native'; |
| 3 | +import { SafeAreaView } from 'react-native-safe-area-context'; |
| 4 | +import { Check, ChevronRight } from 'lucide-react-native'; |
| 5 | +import { colors } from '../theme/colors'; |
| 6 | +import { useApp } from '../context/AppContext'; |
| 7 | + |
| 8 | +const STEPS = ['MY BAG', 'ADDRESS', 'PAYMENT']; |
| 9 | +const PAYMENT_OPTIONS = [ |
| 10 | + { id: 'cod', label: 'Cash on Delivery' }, |
| 11 | + { id: 'upi', label: 'UPI / GPay / PhonePe' }, |
| 12 | + { id: 'card', label: 'Credit / Debit Card' }, |
| 13 | +]; |
| 14 | + |
| 15 | +export default function CheckoutScreen({ navigation }) { |
| 16 | + const { cartItems, cartTotal, placeOrder } = useApp(); |
| 17 | + const [step, setStep] = useState(0); |
| 18 | + const [address, setAddress] = useState({ name: '', phone: '', street: '', pincode: '', city: '', state: '' }); |
| 19 | + const [payment, setPayment] = useState('cod'); |
| 20 | + const [done, setDone] = useState(false); |
| 21 | + const shipping = cartTotal > 999 ? 0 : 99; |
| 22 | + |
| 23 | + const handlePlaceOrder = async () => { |
| 24 | + await placeOrder(cartItems); |
| 25 | + setDone(true); |
| 26 | + }; |
| 27 | + |
| 28 | + if (done) { |
| 29 | + return ( |
| 30 | + <SafeAreaView style={styles.safe}> |
| 31 | + <View style={styles.successBox}> |
| 32 | + <View style={styles.successIcon}><Check size={36} color={colors.white} /></View> |
| 33 | + <Text style={styles.successTitle}>Order Placed!</Text> |
| 34 | + <Text style={styles.successMsg}>Your order has been placed successfully. You'll receive a confirmation soon.</Text> |
| 35 | + <TouchableOpacity style={styles.continueBtn} onPress={() => navigation.navigate('Home')}> |
| 36 | + <Text style={styles.continueTxt}>CONTINUE SHOPPING</Text> |
| 37 | + </TouchableOpacity> |
| 38 | + </View> |
| 39 | + </SafeAreaView> |
| 40 | + ); |
| 41 | + } |
| 42 | + |
| 43 | + return ( |
| 44 | + <SafeAreaView style={styles.safe}> |
| 45 | + <View style={styles.topBar}> |
| 46 | + <Text style={styles.title}>CHECKOUT</Text> |
| 47 | + </View> |
| 48 | + |
| 49 | + <View style={styles.stepsRow}> |
| 50 | + {STEPS.map((s, i) => ( |
| 51 | + <React.Fragment key={s}> |
| 52 | + <View style={styles.stepItem}> |
| 53 | + <View style={[styles.stepCircle, i <= step && styles.stepCircleActive, i < step && styles.stepCircleDone]}> |
| 54 | + {i < step |
| 55 | + ? <Check size={12} color={colors.white} /> |
| 56 | + : <Text style={[styles.stepNum, i <= step && styles.stepNumActive]}>{i + 1}</Text> |
| 57 | + } |
| 58 | + </View> |
| 59 | + <Text style={[styles.stepLabel, i <= step && styles.stepLabelActive]}>{s}</Text> |
| 60 | + </View> |
| 61 | + {i < STEPS.length - 1 && <View style={[styles.stepLine, i < step && styles.stepLineDone]} />} |
| 62 | + </React.Fragment> |
| 63 | + ))} |
| 64 | + </View> |
| 65 | + |
| 66 | + <ScrollView contentContainerStyle={styles.body} showsVerticalScrollIndicator={false}> |
| 67 | + |
| 68 | + {step === 0 && ( |
| 69 | + <View> |
| 70 | + {cartItems.map((item, i) => ( |
| 71 | + <View key={i} style={styles.bagItem}> |
| 72 | + <View style={styles.bagInfo}> |
| 73 | + <Text style={styles.bagName}>{item.name}</Text> |
| 74 | + <Text style={styles.bagMeta}>Size: UK {item.size} · Qty: {item.qty}</Text> |
| 75 | + </View> |
| 76 | + <Text style={styles.bagPrice}>₹ {(item.price * item.qty).toLocaleString('en-IN')}</Text> |
| 77 | + </View> |
| 78 | + ))} |
| 79 | + <View style={styles.billing}> |
| 80 | + <View style={styles.billRow}><Text style={styles.billLabel}>Cart Total</Text><Text style={styles.billVal}>₹ {cartTotal.toLocaleString('en-IN')}</Text></View> |
| 81 | + <View style={styles.billRow}><Text style={styles.billLabel}>Shipping</Text><Text style={[styles.billVal, { color: colors.success }]}>{shipping === 0 ? 'FREE' : `₹ ${shipping}`}</Text></View> |
| 82 | + <View style={[styles.billRow, styles.totalRow]}><Text style={styles.totalLabel}>Total</Text><Text style={styles.totalVal}>₹ {(cartTotal + shipping).toLocaleString('en-IN')}</Text></View> |
| 83 | + </View> |
| 84 | + <TouchableOpacity style={styles.nextBtn} onPress={() => setStep(1)}> |
| 85 | + <Text style={styles.nextTxt}>PROCEED TO ADDRESS</Text> |
| 86 | + <ChevronRight size={18} color={colors.white} /> |
| 87 | + </TouchableOpacity> |
| 88 | + </View> |
| 89 | + )} |
| 90 | + |
| 91 | + {step === 1 && ( |
| 92 | + <View> |
| 93 | + <Text style={styles.sectionTitle}>DELIVERY ADDRESS</Text> |
| 94 | + <View style={styles.formRow}> |
| 95 | + <TextInput style={[styles.input, { flex: 1 }]} placeholder="Full Name *" value={address.name} onChangeText={v => setAddress({ ...address, name: v })} /> |
| 96 | + <TextInput style={[styles.input, { flex: 1 }]} placeholder="Phone *" value={address.phone} onChangeText={v => setAddress({ ...address, phone: v })} keyboardType="phone-pad" /> |
| 97 | + </View> |
| 98 | + <TextInput style={styles.input} placeholder="Street Address *" value={address.street} onChangeText={v => setAddress({ ...address, street: v })} /> |
| 99 | + <View style={styles.formRow}> |
| 100 | + <TextInput style={[styles.input, { flex: 1 }]} placeholder="Pincode *" value={address.pincode} onChangeText={v => setAddress({ ...address, pincode: v })} keyboardType="numeric" /> |
| 101 | + <TextInput style={[styles.input, { flex: 1 }]} placeholder="City *" value={address.city} onChangeText={v => setAddress({ ...address, city: v })} /> |
| 102 | + <TextInput style={[styles.input, { flex: 1 }]} placeholder="State *" value={address.state} onChangeText={v => setAddress({ ...address, state: v })} /> |
| 103 | + </View> |
| 104 | + <View style={styles.btnRow}> |
| 105 | + <TouchableOpacity style={styles.backBtn} onPress={() => setStep(0)}><Text style={styles.backTxt}>BACK</Text></TouchableOpacity> |
| 106 | + <TouchableOpacity style={styles.nextBtn} onPress={() => { |
| 107 | + if (!address.name || !address.phone || !address.street || !address.city || !address.pincode) { |
| 108 | + Alert.alert('Missing Info', 'Please fill all required fields'); return; |
| 109 | + } |
| 110 | + setStep(2); |
| 111 | + }}> |
| 112 | + <Text style={styles.nextTxt}>PROCEED TO PAYMENT</Text> |
| 113 | + <ChevronRight size={18} color={colors.white} /> |
| 114 | + </TouchableOpacity> |
| 115 | + </View> |
| 116 | + </View> |
| 117 | + )} |
| 118 | + |
| 119 | + {step === 2 && ( |
| 120 | + <View> |
| 121 | + <Text style={styles.sectionTitle}>PAYMENT METHOD</Text> |
| 122 | + {PAYMENT_OPTIONS.map(opt => ( |
| 123 | + <TouchableOpacity |
| 124 | + key={opt.id} |
| 125 | + style={[styles.payOption, payment === opt.id && styles.payOptionActive]} |
| 126 | + onPress={() => setPayment(opt.id)} |
| 127 | + > |
| 128 | + <View style={[styles.radio, payment === opt.id && styles.radioActive]}> |
| 129 | + {payment === opt.id && <View style={styles.radioDot} />} |
| 130 | + </View> |
| 131 | + <Text style={styles.payLabel}>{opt.label}</Text> |
| 132 | + </TouchableOpacity> |
| 133 | + ))} |
| 134 | + <View style={styles.billing}> |
| 135 | + <View style={styles.billRow}><Text style={styles.billLabel}>Total</Text><Text style={styles.totalVal}>₹ {(cartTotal + shipping).toLocaleString('en-IN')}</Text></View> |
| 136 | + </View> |
| 137 | + <View style={styles.btnRow}> |
| 138 | + <TouchableOpacity style={styles.backBtn} onPress={() => setStep(1)}><Text style={styles.backTxt}>BACK</Text></TouchableOpacity> |
| 139 | + <TouchableOpacity style={[styles.nextBtn, { flex: 2 }]} onPress={handlePlaceOrder}> |
| 140 | + <Text style={styles.nextTxt}>PLACE ORDER</Text> |
| 141 | + </TouchableOpacity> |
| 142 | + </View> |
| 143 | + </View> |
| 144 | + )} |
| 145 | + |
| 146 | + </ScrollView> |
| 147 | + </SafeAreaView> |
| 148 | + ); |
| 149 | +} |
| 150 | + |
| 151 | +const styles = StyleSheet.create({ |
| 152 | + safe: { flex: 1, backgroundColor: colors.white }, |
| 153 | + topBar: { paddingHorizontal: 20, paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: colors.border }, |
| 154 | + title: { fontSize: 16, fontWeight: '800', letterSpacing: 1.5, color: colors.dark }, |
| 155 | + stepsRow: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 20, paddingVertical: 16 }, |
| 156 | + stepItem: { alignItems: 'center', gap: 4 }, |
| 157 | + stepCircle: { width: 28, height: 28, borderRadius: 14, borderWidth: 2, borderColor: colors.border, alignItems: 'center', justifyContent: 'center' }, |
| 158 | + stepCircleActive: { borderColor: colors.brand }, |
| 159 | + stepCircleDone: { backgroundColor: colors.brand, borderColor: colors.brand }, |
| 160 | + stepNum: { fontSize: 12, fontWeight: '700', color: colors.gray }, |
| 161 | + stepNumActive: { color: colors.brand }, |
| 162 | + stepLabel: { fontSize: 10, fontWeight: '700', color: colors.gray, letterSpacing: 0.5 }, |
| 163 | + stepLabelActive: { color: colors.brand }, |
| 164 | + stepLine: { flex: 1, height: 2, backgroundColor: colors.border, marginBottom: 16 }, |
| 165 | + stepLineDone: { backgroundColor: colors.brand }, |
| 166 | + body: { padding: 20, paddingBottom: 40 }, |
| 167 | + bagItem: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: colors.border }, |
| 168 | + bagInfo: { flex: 1 }, |
| 169 | + bagName: { fontSize: 13, fontWeight: '700', color: colors.dark, marginBottom: 4 }, |
| 170 | + bagMeta: { fontSize: 12, color: colors.gray }, |
| 171 | + bagPrice: { fontSize: 14, fontWeight: '800', color: colors.dark }, |
| 172 | + billing: { backgroundColor: colors.lightGray, borderRadius: 12, padding: 16, marginVertical: 16, gap: 8 }, |
| 173 | + billRow: { flexDirection: 'row', justifyContent: 'space-between' }, |
| 174 | + billLabel: { fontSize: 14, color: colors.gray }, |
| 175 | + billVal: { fontSize: 14, fontWeight: '600', color: colors.dark }, |
| 176 | + totalRow: { paddingTop: 8, borderTopWidth: 1, borderTopColor: colors.border, marginTop: 4 }, |
| 177 | + totalLabel: { fontSize: 15, fontWeight: '800', color: colors.dark }, |
| 178 | + totalVal: { fontSize: 16, fontWeight: '900', color: colors.dark }, |
| 179 | + nextBtn: { flex: 1, backgroundColor: colors.brand, borderRadius: 12, paddingVertical: 14, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 6 }, |
| 180 | + nextTxt: { color: colors.white, fontWeight: '800', fontSize: 13, letterSpacing: 0.5 }, |
| 181 | + btnRow: { flexDirection: 'row', gap: 10, marginTop: 8 }, |
| 182 | + backBtn: { paddingHorizontal: 20, paddingVertical: 14, borderRadius: 12, borderWidth: 1.5, borderColor: colors.border, alignItems: 'center', justifyContent: 'center' }, |
| 183 | + backTxt: { fontSize: 13, fontWeight: '700', color: colors.gray }, |
| 184 | + sectionTitle: { fontSize: 13, fontWeight: '800', letterSpacing: 1, color: colors.dark, marginBottom: 16 }, |
| 185 | + formRow: { flexDirection: 'row', gap: 10 }, |
| 186 | + input: { borderWidth: 1.5, borderColor: colors.border, borderRadius: 10, paddingHorizontal: 14, paddingVertical: 12, fontSize: 14, color: colors.dark, marginBottom: 12 }, |
| 187 | + payOption: { flexDirection: 'row', alignItems: 'center', gap: 12, padding: 14, borderWidth: 1.5, borderColor: colors.border, borderRadius: 12, marginBottom: 10 }, |
| 188 | + payOptionActive: { borderColor: colors.brand, backgroundColor: colors.brandLight }, |
| 189 | + radio: { width: 18, height: 18, borderRadius: 9, borderWidth: 2, borderColor: colors.border, alignItems: 'center', justifyContent: 'center' }, |
| 190 | + radioActive: { borderColor: colors.brand }, |
| 191 | + radioDot: { width: 8, height: 8, borderRadius: 4, backgroundColor: colors.brand }, |
| 192 | + payLabel: { fontSize: 14, fontWeight: '500', color: colors.dark }, |
| 193 | + successBox: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 32, gap: 16 }, |
| 194 | + successIcon: { width: 72, height: 72, borderRadius: 36, backgroundColor: colors.brand, alignItems: 'center', justifyContent: 'center' }, |
| 195 | + successTitle: { fontSize: 24, fontWeight: '800', color: colors.dark }, |
| 196 | + successMsg: { fontSize: 14, color: colors.gray, textAlign: 'center', lineHeight: 22 }, |
| 197 | + continueBtn: { backgroundColor: colors.brand, paddingHorizontal: 32, paddingVertical: 14, borderRadius: 12, marginTop: 8 }, |
| 198 | + continueTxt: { color: colors.white, fontWeight: '800', fontSize: 14, letterSpacing: 1 }, |
| 199 | +}); |
0 commit comments