Skip to content

Commit 69d4a87

Browse files
committed
add Account screen with orders,
1 parent 46377ff commit 69d4a87

1 file changed

Lines changed: 266 additions & 0 deletions

File tree

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
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

Comments
 (0)