|
| 1 | +import React, { useState } from 'react'; |
| 2 | +import { View, Text, Image, ScrollView, TouchableOpacity, StyleSheet, Alert, Modal } from 'react-native'; |
| 3 | +import { SafeAreaView } from 'react-native-safe-area-context'; |
| 4 | +import { User, Package, LogOut, ArrowLeft, ChevronRight, Check } from 'lucide-react-native'; |
| 5 | +import { colors } from '../theme/colors'; |
| 6 | +import { useApp } from '../context/AppContext'; |
| 7 | + |
| 8 | +const TRACKING = ['Order Placed', 'Shipped', 'In-Transit', 'Out For Delivery', 'Delivered']; |
| 9 | +const CANCEL_REASONS = ['Changed my mind', 'Ordered by mistake', 'Found a better price elsewhere', 'Delivery time is too long', 'Other']; |
| 10 | + |
| 11 | +const formatDate = (str) => new Date(str).toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' }); |
| 12 | +const estDelivery = (str) => { const d = new Date(str); d.setDate(d.getDate() + 7); return d.toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' }); }; |
| 13 | + |
| 14 | +export default function AccountScreen({ navigation }) { |
| 15 | + const { user, orders, logout, cancelOrder } = useApp(); |
| 16 | + const [view, setView] = useState('main'); |
| 17 | + const [selectedOrder, setSelectedOrder] = useState(null); |
| 18 | + const [cancelReason, setCancelReason] = useState(''); |
| 19 | + const [showConfirm, setShowConfirm] = useState(false); |
| 20 | + |
| 21 | + if (!user) { |
| 22 | + return ( |
| 23 | + <SafeAreaView style={styles.safe}> |
| 24 | + <View style={styles.guestBox}> |
| 25 | + <User size={56} color={colors.border} /> |
| 26 | + <Text style={styles.guestTitle}>You're not logged in</Text> |
| 27 | + <TouchableOpacity style={styles.loginBtn} onPress={() => navigation.navigate('Login')}> |
| 28 | + <Text style={styles.loginBtnTxt}>LOGIN / SIGN UP</Text> |
| 29 | + </TouchableOpacity> |
| 30 | + </View> |
| 31 | + </SafeAreaView> |
| 32 | + ); |
| 33 | + } |
| 34 | + |
| 35 | + const handleCancelConfirm = () => { |
| 36 | + if (!cancelReason) { Alert.alert('Select Reason', 'Please select a reason'); return; } |
| 37 | + setShowConfirm(true); |
| 38 | + }; |
| 39 | + |
| 40 | + const handleCancelDone = () => { |
| 41 | + cancelOrder(selectedOrder.orderId); |
| 42 | + setShowConfirm(false); |
| 43 | + setView('main'); |
| 44 | + setSelectedOrder(null); |
| 45 | + setCancelReason(''); |
| 46 | + }; |
| 47 | + |
| 48 | + return ( |
| 49 | + <SafeAreaView style={styles.safe}> |
| 50 | + <View style={styles.topBar}> |
| 51 | + {view !== 'main' && ( |
| 52 | + <TouchableOpacity onPress={() => setView(view === 'cancel' ? 'detail' : 'main')} style={styles.backBtn}> |
| 53 | + <ArrowLeft size={20} color={colors.dark} /> |
| 54 | + </TouchableOpacity> |
| 55 | + )} |
| 56 | + <Text style={styles.title}> |
| 57 | + {view === 'main' ? 'MY ACCOUNT' : view === 'detail' ? 'ORDER DETAILS' : 'CANCEL ORDER'} |
| 58 | + </Text> |
| 59 | + <View style={{ width: 28 }} /> |
| 60 | + </View> |
| 61 | + |
| 62 | + <ScrollView contentContainerStyle={styles.body}> |
| 63 | + |
| 64 | + {view === 'main' && ( |
| 65 | + <> |
| 66 | + <View style={styles.profileCard}> |
| 67 | + <View style={styles.avatar}> |
| 68 | + {user.picture |
| 69 | + ? <Image source={{ uri: user.picture }} style={styles.avatarImg} /> |
| 70 | + : <Text style={styles.avatarLetter}>{user.name[0]}</Text> |
| 71 | + } |
| 72 | + </View> |
| 73 | + <View> |
| 74 | + <Text style={styles.userName}>{user.name}</Text> |
| 75 | + <Text style={styles.userEmail}>{user.email}</Text> |
| 76 | + </View> |
| 77 | + </View> |
| 78 | + |
| 79 | + <Text style={styles.sectionTitle}><Package size={14} color={colors.gray} /> MY ORDERS</Text> |
| 80 | + |
| 81 | + {orders.length === 0 ? ( |
| 82 | + <View style={styles.emptyOrders}> |
| 83 | + <Package size={36} color={colors.border} /> |
| 84 | + <Text style={styles.emptyTxt}>No orders yet</Text> |
| 85 | + </View> |
| 86 | + ) : ( |
| 87 | + orders.map((order, i) => ( |
| 88 | + <TouchableOpacity key={i} style={styles.orderItem} onPress={() => { setSelectedOrder(order); setView('detail'); }}> |
| 89 | + <Image source={{ uri: order.image }} style={styles.orderImg} /> |
| 90 | + <View style={styles.orderInfo}> |
| 91 | + <Text style={styles.orderName} numberOfLines={1}>{order.name}</Text> |
| 92 | + <Text style={styles.orderMeta}>Size: UK {order.size} · Qty: {order.qty}</Text> |
| 93 | + <Text style={styles.orderPrice}>₹ {(order.price * order.qty).toLocaleString('en-IN')}</Text> |
| 94 | + </View> |
| 95 | + <View style={styles.orderRight}> |
| 96 | + <View style={[styles.statusBadge, order.status === 'Cancelled' && styles.statusCancelled]}> |
| 97 | + <Text style={[styles.statusTxt, order.status === 'Cancelled' && styles.statusTxtCancelled]}>{order.status}</Text> |
| 98 | + </View> |
| 99 | + <ChevronRight size={16} color={colors.gray} /> |
| 100 | + </View> |
| 101 | + </TouchableOpacity> |
| 102 | + )) |
| 103 | + )} |
| 104 | + |
| 105 | + <TouchableOpacity style={styles.logoutBtn} onPress={() => { logout(); }}> |
| 106 | + <LogOut size={16} color={colors.gray} /> |
| 107 | + <Text style={styles.logoutTxt}>LOGOUT</Text> |
| 108 | + </TouchableOpacity> |
| 109 | + </> |
| 110 | + )} |
| 111 | + |
| 112 | + {view === 'detail' && selectedOrder && ( |
| 113 | + <> |
| 114 | + <View style={styles.odHeader}> |
| 115 | + <Text style={styles.odLabel}>Order ID: <Text style={styles.odVal}>{selectedOrder.orderId}</Text></Text> |
| 116 | + <Text style={styles.odLabel}>Date: <Text style={styles.odVal}>{formatDate(selectedOrder.placedAt)}</Text></Text> |
| 117 | + </View> |
| 118 | + <View style={styles.odItem}> |
| 119 | + <Image source={{ uri: selectedOrder.image }} style={styles.odImg} /> |
| 120 | + <View style={styles.odInfo}> |
| 121 | + <Text style={styles.odName}>{selectedOrder.name}</Text> |
| 122 | + <Text style={styles.odMeta}>Size: UK {selectedOrder.size} · Qty: {selectedOrder.qty}</Text> |
| 123 | + <Text style={styles.odPrice}>₹ {(selectedOrder.price * selectedOrder.qty).toLocaleString('en-IN')}</Text> |
| 124 | + {selectedOrder.status !== 'Cancelled' && ( |
| 125 | + <TouchableOpacity style={styles.cancelBtn} onPress={() => setView('cancel')}> |
| 126 | + <Text style={styles.cancelBtnTxt}>Cancel</Text> |
| 127 | + </TouchableOpacity> |
| 128 | + )} |
| 129 | + </View> |
| 130 | + </View> |
| 131 | + |
| 132 | + {selectedOrder.status !== 'Cancelled' ? ( |
| 133 | + <View style={styles.tracking}> |
| 134 | + {TRACKING.map((step, i) => ( |
| 135 | + <View key={step} style={styles.trackStep}> |
| 136 | + <View style={styles.trackLeft}> |
| 137 | + <View style={[styles.trackDot, i === 0 && styles.trackDotActive]} /> |
| 138 | + {i < TRACKING.length - 1 && <View style={styles.trackLine} />} |
| 139 | + </View> |
| 140 | + <View style={styles.trackInfo}> |
| 141 | + <Text style={[styles.trackLabel, i === 0 && styles.trackLabelActive]}>{step}</Text> |
| 142 | + {i === 0 && <Text style={styles.trackDate}>{formatDate(selectedOrder.placedAt)}</Text>} |
| 143 | + {i === TRACKING.length - 1 && <Text style={styles.trackDate}>Est. Delivery by {estDelivery(selectedOrder.placedAt)}</Text>} |
| 144 | + </View> |
| 145 | + </View> |
| 146 | + ))} |
| 147 | + </View> |
| 148 | + ) : ( |
| 149 | + <Text style={styles.cancelledBadge}>● Order Cancelled</Text> |
| 150 | + )} |
| 151 | + </> |
| 152 | + )} |
| 153 | + |
| 154 | + {view === 'cancel' && selectedOrder && ( |
| 155 | + <> |
| 156 | + <View style={styles.odHeader}> |
| 157 | + <Text style={styles.odLabel}>Order ID: <Text style={styles.odVal}>{selectedOrder.orderId}</Text></Text> |
| 158 | + </View> |
| 159 | + <Text style={styles.cancelHeading}>Reason for Cancellation</Text> |
| 160 | + <Text style={styles.cancelSub}>Please tell us the reason for cancellation.</Text> |
| 161 | + {CANCEL_REASONS.map(r => ( |
| 162 | + <TouchableOpacity key={r} style={[styles.reasonOption, cancelReason === r && styles.reasonActive]} onPress={() => setCancelReason(r)}> |
| 163 | + <View style={[styles.radio, cancelReason === r && styles.radioActive]}> |
| 164 | + {cancelReason === r && <View style={styles.radioDot} />} |
| 165 | + </View> |
| 166 | + <Text style={styles.reasonTxt}>{r}</Text> |
| 167 | + </TouchableOpacity> |
| 168 | + ))} |
| 169 | + <View style={styles.cancelBtnRow}> |
| 170 | + <TouchableOpacity style={styles.backBtnSm} onPress={() => setView('detail')}><Text style={styles.backBtnTxt}>BACK</Text></TouchableOpacity> |
| 171 | + <TouchableOpacity style={styles.confirmBtn} onPress={handleCancelConfirm}><Text style={styles.confirmTxt}>CONFIRM</Text></TouchableOpacity> |
| 172 | + </View> |
| 173 | + </> |
| 174 | + )} |
| 175 | + |
| 176 | + </ScrollView> |
| 177 | + |
| 178 | + <Modal visible={showConfirm} transparent animationType="fade"> |
| 179 | + <View style={styles.modalOverlay}> |
| 180 | + <View style={styles.modalBox}> |
| 181 | + <Text style={styles.modalTxt}>Your cancellation request has been received. You will receive an email with further instructions.</Text> |
| 182 | + <View style={styles.modalDivider} /> |
| 183 | + <TouchableOpacity onPress={handleCancelDone} style={styles.modalBtn}> |
| 184 | + <Text style={styles.modalBtnTxt}>Continue</Text> |
| 185 | + </TouchableOpacity> |
| 186 | + </View> |
| 187 | + </View> |
| 188 | + </Modal> |
| 189 | + </SafeAreaView> |
| 190 | + ); |
| 191 | +} |
| 192 | + |
| 193 | +const styles = StyleSheet.create({ |
| 194 | + safe: { flex: 1, backgroundColor: colors.white }, |
| 195 | + topBar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 20, paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: colors.border }, |
| 196 | + backBtn: { padding: 2 }, |
| 197 | + title: { fontSize: 16, fontWeight: '800', letterSpacing: 1.5, color: colors.dark }, |
| 198 | + body: { padding: 20, gap: 16, paddingBottom: 40 }, |
| 199 | + profileCard: { flexDirection: 'row', alignItems: 'center', gap: 14, backgroundColor: colors.lightGray, borderRadius: 16, padding: 16 }, |
| 200 | + avatar: { width: 52, height: 52, borderRadius: 26, backgroundColor: colors.brand, alignItems: 'center', justifyContent: 'center', overflow: 'hidden' }, |
| 201 | + avatarImg: { width: 52, height: 52 }, |
| 202 | + avatarLetter: { fontSize: 20, fontWeight: '800', color: colors.white }, |
| 203 | + userName: { fontSize: 16, fontWeight: '800', color: colors.dark }, |
| 204 | + userEmail: { fontSize: 13, color: colors.gray, marginTop: 2 }, |
| 205 | + sectionTitle: { fontSize: 12, fontWeight: '800', letterSpacing: 1.5, color: colors.gray }, |
| 206 | + emptyOrders: { alignItems: 'center', paddingVertical: 24, gap: 8 }, |
| 207 | + emptyTxt: { fontSize: 14, color: colors.gray }, |
| 208 | + orderItem: { flexDirection: 'row', gap: 12, backgroundColor: colors.white, borderRadius: 14, padding: 12, shadowColor: '#000', shadowOpacity: 0.06, shadowRadius: 6, elevation: 2 }, |
| 209 | + orderImg: { width: 70, height: 70, borderRadius: 10, backgroundColor: colors.lightGray }, |
| 210 | + orderInfo: { flex: 1 }, |
| 211 | + orderName: { fontSize: 13, fontWeight: '700', color: colors.dark, marginBottom: 4 }, |
| 212 | + orderMeta: { fontSize: 12, color: colors.gray, marginBottom: 4 }, |
| 213 | + orderPrice: { fontSize: 14, fontWeight: '800', color: colors.brand }, |
| 214 | + orderRight: { alignItems: 'flex-end', justifyContent: 'space-between' }, |
| 215 | + statusBadge: { backgroundColor: '#f0fdf4', paddingHorizontal: 8, paddingVertical: 3, borderRadius: 20 }, |
| 216 | + statusCancelled: { backgroundColor: '#fef2f2' }, |
| 217 | + statusTxt: { fontSize: 11, fontWeight: '700', color: colors.success }, |
| 218 | + statusTxtCancelled: { color: colors.danger }, |
| 219 | + logoutBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, borderWidth: 1.5, borderColor: colors.border, borderRadius: 12, paddingVertical: 14, marginTop: 8 }, |
| 220 | + logoutTxt: { fontSize: 13, fontWeight: '700', color: colors.gray, letterSpacing: 1 }, |
| 221 | + guestBox: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 16 }, |
| 222 | + guestTitle: { fontSize: 16, fontWeight: '700', color: colors.gray }, |
| 223 | + loginBtn: { backgroundColor: colors.brand, paddingHorizontal: 32, paddingVertical: 14, borderRadius: 12 }, |
| 224 | + loginBtnTxt: { color: colors.white, fontWeight: '800', fontSize: 14, letterSpacing: 1 }, |
| 225 | + odHeader: { backgroundColor: colors.lightGray, borderRadius: 10, padding: 14, gap: 4 }, |
| 226 | + odLabel: { fontSize: 13, color: colors.gray }, |
| 227 | + odVal: { fontWeight: '700', color: colors.dark }, |
| 228 | + odItem: { flexDirection: 'row', gap: 14, borderWidth: 1, borderColor: colors.border, borderRadius: 12, padding: 12 }, |
| 229 | + odImg: { width: 80, height: 80, borderRadius: 10, backgroundColor: colors.lightGray }, |
| 230 | + odInfo: { flex: 1 }, |
| 231 | + odName: { fontSize: 14, fontWeight: '700', color: colors.dark, marginBottom: 4 }, |
| 232 | + odMeta: { fontSize: 12, color: colors.gray, marginBottom: 4 }, |
| 233 | + odPrice: { fontSize: 15, fontWeight: '800', color: colors.dark, marginBottom: 8 }, |
| 234 | + cancelBtn: { borderWidth: 1.5, borderColor: colors.border, borderRadius: 8, paddingHorizontal: 14, paddingVertical: 6, alignSelf: 'flex-start' }, |
| 235 | + cancelBtnTxt: { fontSize: 12, fontWeight: '700', color: colors.gray }, |
| 236 | + tracking: { gap: 0 }, |
| 237 | + trackStep: { flexDirection: 'row', gap: 14 }, |
| 238 | + trackLeft: { alignItems: 'center', width: 18 }, |
| 239 | + trackDot: { width: 16, height: 16, borderRadius: 8, borderWidth: 2, borderColor: colors.border, backgroundColor: colors.white }, |
| 240 | + trackDotActive: { backgroundColor: colors.brand, borderColor: colors.brand }, |
| 241 | + trackLine: { width: 2, height: 36, backgroundColor: colors.border }, |
| 242 | + trackInfo: { flex: 1, paddingBottom: 4 }, |
| 243 | + trackLabel: { fontSize: 14, fontWeight: '600', color: colors.gray }, |
| 244 | + trackLabelActive: { color: colors.dark, fontWeight: '800' }, |
| 245 | + trackDate: { fontSize: 12, color: colors.gray, marginTop: 2 }, |
| 246 | + cancelledBadge: { fontSize: 14, fontWeight: '700', color: colors.danger, paddingVertical: 12 }, |
| 247 | + cancelHeading: { fontSize: 15, fontWeight: '800', color: colors.dark }, |
| 248 | + cancelSub: { fontSize: 13, color: colors.gray }, |
| 249 | + reasonOption: { flexDirection: 'row', alignItems: 'center', gap: 12, padding: 14, borderWidth: 1.5, borderColor: colors.border, borderRadius: 12 }, |
| 250 | + reasonActive: { borderColor: colors.brand, backgroundColor: colors.brandLight }, |
| 251 | + radio: { width: 18, height: 18, borderRadius: 9, borderWidth: 2, borderColor: colors.border, alignItems: 'center', justifyContent: 'center' }, |
| 252 | + radioActive: { borderColor: colors.brand }, |
| 253 | + radioDot: { width: 8, height: 8, borderRadius: 4, backgroundColor: colors.brand }, |
| 254 | + reasonTxt: { fontSize: 14, color: colors.dark }, |
| 255 | + cancelBtnRow: { flexDirection: 'row', gap: 10 }, |
| 256 | + backBtnSm: { flex: 1, paddingVertical: 14, borderRadius: 12, borderWidth: 1.5, borderColor: colors.border, alignItems: 'center' }, |
| 257 | + backBtnTxt: { fontSize: 13, fontWeight: '700', color: colors.gray }, |
| 258 | + confirmBtn: { flex: 2, paddingVertical: 14, borderRadius: 12, backgroundColor: colors.brand, alignItems: 'center' }, |
| 259 | + confirmTxt: { fontSize: 13, fontWeight: '800', color: colors.white, letterSpacing: 0.5 }, |
| 260 | + modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.4)', alignItems: 'center', justifyContent: 'center', padding: 32 }, |
| 261 | + modalBox: { backgroundColor: colors.white, borderRadius: 16, padding: 24 }, |
| 262 | + modalTxt: { fontSize: 14, color: colors.dark, lineHeight: 22, marginBottom: 16 }, |
| 263 | + modalDivider: { height: 1, backgroundColor: colors.border, marginBottom: 12 }, |
| 264 | + modalBtn: { alignSelf: 'flex-end' }, |
| 265 | + modalBtnTxt: { fontSize: 14, fontWeight: '700', color: colors.brand }, |
| 266 | +}); |
0 commit comments