Skip to content

Commit db83929

Browse files
authored
Merge pull request #7 from Ayushman8/development
checkout, address, admin and orders screen UI completed
2 parents 35dd8ba + 5122f4e commit db83929

13 files changed

Lines changed: 1635 additions & 0 deletions

File tree

client/app/addresses/index.tsx

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import { Ionicons } from "@expo/vector-icons";
2+
import React, { useEffect, useState } from "react";
3+
import { ScrollView, Text, TouchableOpacity, View, Modal, TextInput, ActivityIndicator } from "react-native";
4+
import { SafeAreaView } from "react-native-safe-area-context";
5+
import Header from "@/components/Header";
6+
import { COLORS } from "@/constants";
7+
import type { Address } from "@/constants/types";
8+
import { dummyAddress } from "@/assets/assets";
9+
10+
export default function Addresses() {
11+
const [addresses, setAddresses] = useState<Address[]>([]);
12+
const [loading, setLoading] = useState(true);
13+
const [modalVisible, setModalVisible] = useState(false);
14+
15+
// Form state
16+
const [type, setType] = useState("Home");
17+
const [street, setStreet] = useState("");
18+
const [city, setCity] = useState("");
19+
const [state, setState] = useState("");
20+
const [zipCode, setZipCode] = useState("");
21+
const [country, setCountry] = useState("");
22+
const [isDefault, setIsDefault] = useState(false);
23+
const [submitting, setSubmitting] = useState(false);
24+
25+
// Edit state
26+
const [isEditing, setIsEditing] = useState(false);
27+
const [editingId, setEditingId] = useState<string | null>(null);
28+
29+
useEffect(() => {
30+
fetchAddresses();
31+
}, []);
32+
33+
const fetchAddresses = async () => {
34+
setAddresses(dummyAddress as any);
35+
setLoading(false);
36+
};
37+
38+
const handleEditSearch = (item: Address) => {
39+
setIsEditing(true);
40+
setEditingId(item._id);
41+
setType(item.type);
42+
setStreet(item.street);
43+
setCity(item.city);
44+
setState(item.state);
45+
setZipCode(item.zipCode);
46+
setCountry(item.country);
47+
setIsDefault(item.isDefault);
48+
setModalVisible(true);
49+
};
50+
51+
const handleSaveAddress = async () => {
52+
setModalVisible(false);
53+
resetForm();
54+
fetchAddresses();
55+
};
56+
57+
const handleDeleteAddress = async (id: string) => {
58+
59+
};
60+
61+
const resetForm = () => {
62+
setStreet("");
63+
setCity("");
64+
setState("");
65+
setZipCode("");
66+
setCountry("");
67+
setType("Home");
68+
setIsDefault(false);
69+
setIsEditing(false);
70+
setEditingId(null);
71+
};
72+
73+
const openAddModal = () => {
74+
resetForm();
75+
setModalVisible(true);
76+
};
77+
78+
return (
79+
<SafeAreaView className="flex-1 bg-surface" edges={['top']}>
80+
<Header title="Shipping Addresses" showBack />
81+
82+
{loading ? (
83+
<View className="flex-1 justify-center items-center">
84+
<ActivityIndicator size="large" color={COLORS.primary} />
85+
</View>
86+
) : (
87+
<ScrollView className="flex-1 px-4 pt-4">
88+
{addresses.length === 0 ? (
89+
<Text className="text-center text-secondary mt-10">No addresses found</Text>
90+
) : (
91+
addresses.map((item) => (
92+
<View key={item._id} className="bg-white p-4 rounded-xl mb-4 shadow-sm">
93+
<View className="flex-row items-center justify-between mb-2">
94+
<View className="flex-row items-center">
95+
<Ionicons
96+
name={item.type === "Home" ? "home-outline" : "briefcase-outline"}
97+
size={20}
98+
color={COLORS.primary}
99+
/>
100+
<Text className="text-base font-bold text-primary ml-2">{item.type}</Text>
101+
{item.isDefault && (
102+
<View className="bg-primary/10 px-2 py-1 rounded ml-2">
103+
<Text className="text-primary text-xs font-bold">Default</Text>
104+
</View>
105+
)}
106+
</View>
107+
<View className="flex-row items-center gap-4">
108+
<TouchableOpacity onPress={() => handleEditSearch(item)}>
109+
<Ionicons name="pencil-outline" size={20} color={COLORS.secondary} />
110+
</TouchableOpacity>
111+
<TouchableOpacity onPress={() => handleDeleteAddress(item._id)}>
112+
<Ionicons name="trash-outline" size={20} color={COLORS.error || '#ff4444'} />
113+
</TouchableOpacity>
114+
</View>
115+
</View>
116+
<Text className="text-secondary leading-5 ml-7">
117+
{item.street}, {item.city}, {item.state} {item.zipCode}, {item.country}
118+
</Text>
119+
</View>
120+
))
121+
)}
122+
123+
<TouchableOpacity className="flex-row items-center justify-center p-4 border border-dashed border-gray-300 rounded-xl mt-2 mb-8" onPress={openAddModal}>
124+
<Ionicons name="add" size={24} color={COLORS.secondary} />
125+
<Text className="text-secondary font-medium ml-2">Add New Address</Text>
126+
</TouchableOpacity>
127+
</ScrollView>
128+
)}
129+
130+
{/* Add Address Modal */}
131+
<Modal animationType="slide" transparent={true} visible={modalVisible} onRequestClose={() => setModalVisible(false)}>
132+
<View className="flex-1 justify-end bg-black/50">
133+
<View className="bg-white rounded-t-3xl p-6 h-[85%]">
134+
<View className="flex-row justify-between items-center mb-6">
135+
<Text className="text-xl font-bold text-primary">{isEditing ? "Edit Address" : "Add New Address"}</Text>
136+
<TouchableOpacity onPress={() => setModalVisible(false)}>
137+
<Ionicons name="close" size={24} color={COLORS.primary} />
138+
</TouchableOpacity>
139+
</View>
140+
141+
<ScrollView showsVerticalScrollIndicator={false}>
142+
<Text className="text-primary font-medium mb-2">Label</Text>
143+
<View className="flex-row gap-3 mb-4">
144+
{["Home", "Work", "Other"].map((t) => (
145+
<TouchableOpacity key={t} onPress={() => setType(t)} className={`px-4 py-2 rounded-full border ${type === t ? 'bg-primary border-primary' : 'bg-white border-gray-300'}`}>
146+
<Text className={type === t ? 'text-white' : 'text-primary'}>{t}</Text>
147+
</TouchableOpacity>
148+
))}
149+
</View>
150+
151+
<Text className="text-primary font-medium mb-2">Street Address</Text>
152+
<TextInput className="bg-surface p-4 rounded-xl text-primary mb-4" placeholder="123 Main St" value={street} onChangeText={setStreet} />
153+
154+
<View className="flex-row gap-4 mb-4">
155+
<View className="flex-1">
156+
<Text className="text-primary font-medium mb-2">City</Text>
157+
<TextInput className="bg-surface p-4 rounded-xl text-primary" placeholder="New York" value={city} onChangeText={setCity} />
158+
</View>
159+
<View className="flex-1">
160+
<Text className="text-primary font-medium mb-2">State</Text>
161+
<TextInput className="bg-surface p-4 rounded-xl text-primary" placeholder="NY" value={state} onChangeText={setState} />
162+
</View>
163+
</View>
164+
165+
<View className="flex-row gap-4 mb-4">
166+
<View className="flex-1">
167+
<Text className="text-primary font-medium mb-2">Zip Code</Text>
168+
<TextInput className="bg-surface p-4 rounded-xl text-primary" placeholder="10001" value={zipCode} onChangeText={setZipCode} keyboardType="numeric" />
169+
</View>
170+
<View className="flex-1">
171+
<Text className="text-primary font-medium mb-2">Country</Text>
172+
<TextInput className="bg-surface p-4 rounded-xl text-primary" placeholder="USA" value={country} onChangeText={setCountry} />
173+
</View>
174+
</View>
175+
176+
<TouchableOpacity className="flex-row items-center mb-8" onPress={() => setIsDefault(!isDefault)}>
177+
<View className={`w-5 h-5 border rounded mr-2 items-center justify-center ${isDefault ? 'bg-primary border-primary' : 'border-gray-300'}`}>
178+
{isDefault && <Ionicons name="checkmark" size={14} color="white" />}
179+
</View>
180+
<Text className="text-primary">Set as default address</Text>
181+
</TouchableOpacity>
182+
183+
<TouchableOpacity className="w-full bg-primary py-4 rounded-full items-center mb-10" onPress={handleSaveAddress} disabled={submitting} >
184+
{submitting ? (
185+
<ActivityIndicator color="white" />
186+
) : (
187+
<Text className="text-white font-bold text-lg">Save Address</Text>
188+
)}
189+
</TouchableOpacity>
190+
</ScrollView>
191+
</View>
192+
</View>
193+
</Modal>
194+
</SafeAreaView>
195+
);
196+
}

client/app/admin/_layout.tsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { Tabs, useRouter } from "expo-router";
2+
import { useEffect } from "react";
3+
import { View, ActivityIndicator, TouchableOpacity, Text } from "react-native";
4+
import { Ionicons } from "@expo/vector-icons";
5+
import { COLORS } from "@/constants";
6+
import { dummyUser } from "@/assets/assets";
7+
8+
export default function AdminLayout() {
9+
const { user } = { user: dummyUser }
10+
const isLoaded = true;
11+
const router = useRouter();
12+
13+
useEffect(() => {
14+
if (isLoaded && (!user || user.publicMetadata?.role !== "admin")) {
15+
router.replace("/(tabs)");
16+
}
17+
}, [isLoaded, user]);
18+
19+
if (!isLoaded) {
20+
return (
21+
<View className="flex-1 justify-center items-center bg-surface">
22+
<ActivityIndicator size="large" color={COLORS.primary} />
23+
</View>
24+
);
25+
}
26+
27+
if (!user || user.publicMetadata?.role !== "admin") return null;
28+
29+
return (
30+
<Tabs
31+
screenOptions={{
32+
headerStyle: {
33+
backgroundColor: "#fff",
34+
},
35+
headerTintColor: COLORS.primary,
36+
headerTitleStyle: {
37+
fontWeight: "bold",
38+
},
39+
headerShadowVisible: false,
40+
tabBarActiveTintColor: COLORS.primary,
41+
tabBarInactiveTintColor: "gray",
42+
headerRight: () => (
43+
<TouchableOpacity
44+
onPress={() => router.replace("/(tabs)")}
45+
className="mr-4 flex-row items-center"
46+
>
47+
<Ionicons name="log-out-outline" size={24} color={COLORS.primary} />
48+
<Text className="ml-1 text-primary font-medium">Exit</Text>
49+
</TouchableOpacity>
50+
),
51+
}}
52+
>
53+
<Tabs.Screen
54+
name="index"
55+
options={{
56+
title: "Dashboard",
57+
tabBarIcon: ({ color, size }) => (
58+
<Ionicons name="grid-outline" size={size} color={color} />
59+
)
60+
}}
61+
/>
62+
<Tabs.Screen
63+
name="products"
64+
options={{
65+
title: "Products",
66+
tabBarIcon: ({ color, size }) => (
67+
<Ionicons name="cube-outline" size={size} color={color} />
68+
)
69+
}}
70+
/>
71+
<Tabs.Screen
72+
name="orders"
73+
options={{
74+
title: "Orders",
75+
tabBarIcon: ({ color, size }) => (
76+
<Ionicons name="receipt-outline" size={size} color={color} />
77+
)
78+
}}
79+
/>
80+
</Tabs>
81+
);
82+
}

client/app/admin/index.tsx

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { useRouter } from "expo-router";
2+
import React, { useEffect, useState } from "react";
3+
import { ScrollView, Text, View, ActivityIndicator, RefreshControl } from "react-native";
4+
import { COLORS, getStatusColor } from "@/constants";
5+
import { dummyAdminStats } from "@/assets/assets";
6+
7+
export default function AdminDashboard() {
8+
const router = useRouter();
9+
const [loading, setLoading] = useState(true);
10+
const [refreshing, setRefreshing] = useState(false);
11+
const [stats, setStats] = useState({
12+
totalUsers: 0,
13+
totalProducts: 0,
14+
totalOrders: 0,
15+
totalRevenue: 0,
16+
recentOrders: []
17+
});
18+
19+
const fetchStats = async () => {
20+
setStats(dummyAdminStats as any);
21+
setLoading(false);
22+
setRefreshing(false);
23+
};
24+
25+
useEffect(() => {
26+
fetchStats();
27+
}, []);
28+
29+
const onRefresh = () => {
30+
setRefreshing(true);
31+
fetchStats();
32+
};
33+
34+
if (loading && !refreshing) {
35+
return (
36+
<View className="flex-1 justify-center items-center bg-surface">
37+
<ActivityIndicator size="large" color={COLORS.primary} />
38+
</View>
39+
);
40+
}
41+
42+
return (
43+
<ScrollView
44+
className="flex-1 bg-surface p-4"
45+
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
46+
>
47+
<View className="mb-8">
48+
<Text className="text-primary font-bold text-2xl mb-4 tracking-tight">Overview</Text>
49+
<View className="flex-row flex-wrap justify-between">
50+
<StatCard label="Total Revenue" value={`$${stats.totalRevenue.toFixed(2)}`} />
51+
<StatCard label="Total Orders" value={stats.totalOrders.toString()} />
52+
<StatCard label="Products" value={stats.totalProducts.toString()} />
53+
<StatCard label="Users" value={stats.totalUsers.toString()} />
54+
</View>
55+
</View>
56+
57+
<View className="mb-6">
58+
<Text className="text-primary font-bold text-2xl mb-4 tracking-tight">Recent Orders</Text>
59+
{stats.recentOrders.length === 0 ? (
60+
<View className="bg-white p-6 rounded-2xl border border-gray-100 items-center">
61+
<Text className="text-secondary">No recent orders</Text>
62+
</View>
63+
) : (
64+
stats.recentOrders.map((order: any) => (
65+
<View key={order._id} className="bg-white p-5 rounded-2xl border border-gray-100 mb-3">
66+
<View className="flex-row justify-between items-center mb-3">
67+
<View>
68+
<Text className="font-bold text-primary text-base">Total Products : {order.items.reduce((acc: number, item: any) => acc + item.quantity, 0)}</Text>
69+
<Text className="text-secondary text-xs mt-1">{new Date(order.createdAt).toLocaleDateString()}</Text>
70+
</View>
71+
<View className={`px-3 py-1.5 rounded-full ${getStatusColor(order.orderStatus)}`}>
72+
<Text className="text-[10px] font-bold uppercase">{order.orderStatus}</Text>
73+
</View>
74+
</View>
75+
<View className="pb-2">
76+
{order.items.map((item: any) => (
77+
<Text key={item._id} className="text-secondary text-xs mt-1">{item.name} x {item.quantity}</Text>
78+
))}
79+
</View>
80+
81+
<View className="h-[1px] bg-gray-100 mb-3" />
82+
83+
<View className="flex-row justify-between items-center">
84+
<View className="flex-row items-center">
85+
<View className="w-8 h-8 rounded-full bg-gray-100 items-center justify-center mr-2">
86+
<Text className="text-primary font-bold text-xs">
87+
{(order.user?.name || '?').charAt(0).toUpperCase()}
88+
</Text>
89+
</View>
90+
<Text className="text-secondary text-sm">{order.user?.name || 'Unknown User'}</Text>
91+
</View>
92+
<Text className="text-primary font-bold text-lg">${order.totalAmount.toFixed(2)}</Text>
93+
</View>
94+
</View>
95+
))
96+
)}
97+
</View>
98+
</ScrollView>
99+
);
100+
}
101+
102+
const StatCard = ({ label, value }: { label: string, value: string }) => (
103+
<View className="bg-white p-5 rounded-2xl border border-gray-100 w-[48%] mb-4 justify-center">
104+
<Text className="text-xl font-bold text-primary mb-1">{value}</Text>
105+
<Text className="text-secondary text-xs font-medium uppercase tracking-wide">{label}</Text>
106+
</View>
107+
);

0 commit comments

Comments
 (0)