diff --git a/client/app/addresses/index.tsx b/client/app/addresses/index.tsx
new file mode 100644
index 0000000..9d3d9ff
--- /dev/null
+++ b/client/app/addresses/index.tsx
@@ -0,0 +1,196 @@
+import { Ionicons } from "@expo/vector-icons";
+import React, { useEffect, useState } from "react";
+import { ScrollView, Text, TouchableOpacity, View, Modal, TextInput, ActivityIndicator } from "react-native";
+import { SafeAreaView } from "react-native-safe-area-context";
+import Header from "@/components/Header";
+import { COLORS } from "@/constants";
+import type { Address } from "@/constants/types";
+import { dummyAddress } from "@/assets/assets";
+
+export default function Addresses() {
+ const [addresses, setAddresses] = useState
([]);
+ const [loading, setLoading] = useState(true);
+ const [modalVisible, setModalVisible] = useState(false);
+
+ // Form state
+ const [type, setType] = useState("Home");
+ const [street, setStreet] = useState("");
+ const [city, setCity] = useState("");
+ const [state, setState] = useState("");
+ const [zipCode, setZipCode] = useState("");
+ const [country, setCountry] = useState("");
+ const [isDefault, setIsDefault] = useState(false);
+ const [submitting, setSubmitting] = useState(false);
+
+ // Edit state
+ const [isEditing, setIsEditing] = useState(false);
+ const [editingId, setEditingId] = useState(null);
+
+ useEffect(() => {
+ fetchAddresses();
+ }, []);
+
+ const fetchAddresses = async () => {
+ setAddresses(dummyAddress as any);
+ setLoading(false);
+ };
+
+ const handleEditSearch = (item: Address) => {
+ setIsEditing(true);
+ setEditingId(item._id);
+ setType(item.type);
+ setStreet(item.street);
+ setCity(item.city);
+ setState(item.state);
+ setZipCode(item.zipCode);
+ setCountry(item.country);
+ setIsDefault(item.isDefault);
+ setModalVisible(true);
+ };
+
+ const handleSaveAddress = async () => {
+ setModalVisible(false);
+ resetForm();
+ fetchAddresses();
+ };
+
+ const handleDeleteAddress = async (id: string) => {
+
+ };
+
+ const resetForm = () => {
+ setStreet("");
+ setCity("");
+ setState("");
+ setZipCode("");
+ setCountry("");
+ setType("Home");
+ setIsDefault(false);
+ setIsEditing(false);
+ setEditingId(null);
+ };
+
+ const openAddModal = () => {
+ resetForm();
+ setModalVisible(true);
+ };
+
+ return (
+
+
+
+ {loading ? (
+
+
+
+ ) : (
+
+ {addresses.length === 0 ? (
+ No addresses found
+ ) : (
+ addresses.map((item) => (
+
+
+
+
+ {item.type}
+ {item.isDefault && (
+
+ Default
+
+ )}
+
+
+ handleEditSearch(item)}>
+
+
+ handleDeleteAddress(item._id)}>
+
+
+
+
+
+ {item.street}, {item.city}, {item.state} {item.zipCode}, {item.country}
+
+
+ ))
+ )}
+
+
+
+ Add New Address
+
+
+ )}
+
+ {/* Add Address Modal */}
+ setModalVisible(false)}>
+
+
+
+ {isEditing ? "Edit Address" : "Add New Address"}
+ setModalVisible(false)}>
+
+
+
+
+
+ Label
+
+ {["Home", "Work", "Other"].map((t) => (
+ setType(t)} className={`px-4 py-2 rounded-full border ${type === t ? 'bg-primary border-primary' : 'bg-white border-gray-300'}`}>
+ {t}
+
+ ))}
+
+
+ Street Address
+
+
+
+
+ City
+
+
+
+ State
+
+
+
+
+
+
+ Zip Code
+
+
+
+ Country
+
+
+
+
+ setIsDefault(!isDefault)}>
+
+ {isDefault && }
+
+ Set as default address
+
+
+
+ {submitting ? (
+
+ ) : (
+ Save Address
+ )}
+
+
+
+
+
+
+ );
+}
diff --git a/client/app/admin/_layout.tsx b/client/app/admin/_layout.tsx
new file mode 100644
index 0000000..c23ca10
--- /dev/null
+++ b/client/app/admin/_layout.tsx
@@ -0,0 +1,82 @@
+import { Tabs, useRouter } from "expo-router";
+import { useEffect } from "react";
+import { View, ActivityIndicator, TouchableOpacity, Text } from "react-native";
+import { Ionicons } from "@expo/vector-icons";
+import { COLORS } from "@/constants";
+import { dummyUser } from "@/assets/assets";
+
+export default function AdminLayout() {
+ const { user } = { user: dummyUser }
+ const isLoaded = true;
+ const router = useRouter();
+
+ useEffect(() => {
+ if (isLoaded && (!user || user.publicMetadata?.role !== "admin")) {
+ router.replace("/(tabs)");
+ }
+ }, [isLoaded, user]);
+
+ if (!isLoaded) {
+ return (
+
+
+
+ );
+ }
+
+ if (!user || user.publicMetadata?.role !== "admin") return null;
+
+ return (
+ (
+ router.replace("/(tabs)")}
+ className="mr-4 flex-row items-center"
+ >
+
+ Exit
+
+ ),
+ }}
+ >
+ (
+
+ )
+ }}
+ />
+ (
+
+ )
+ }}
+ />
+ (
+
+ )
+ }}
+ />
+
+ );
+}
diff --git a/client/app/admin/index.tsx b/client/app/admin/index.tsx
new file mode 100644
index 0000000..3503fbd
--- /dev/null
+++ b/client/app/admin/index.tsx
@@ -0,0 +1,107 @@
+import { useRouter } from "expo-router";
+import React, { useEffect, useState } from "react";
+import { ScrollView, Text, View, ActivityIndicator, RefreshControl } from "react-native";
+import { COLORS, getStatusColor } from "@/constants";
+import { dummyAdminStats } from "@/assets/assets";
+
+export default function AdminDashboard() {
+ const router = useRouter();
+ const [loading, setLoading] = useState(true);
+ const [refreshing, setRefreshing] = useState(false);
+ const [stats, setStats] = useState({
+ totalUsers: 0,
+ totalProducts: 0,
+ totalOrders: 0,
+ totalRevenue: 0,
+ recentOrders: []
+ });
+
+ const fetchStats = async () => {
+ setStats(dummyAdminStats as any);
+ setLoading(false);
+ setRefreshing(false);
+ };
+
+ useEffect(() => {
+ fetchStats();
+ }, []);
+
+ const onRefresh = () => {
+ setRefreshing(true);
+ fetchStats();
+ };
+
+ if (loading && !refreshing) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+ }
+ >
+
+ Overview
+
+
+
+
+
+
+
+
+
+ Recent Orders
+ {stats.recentOrders.length === 0 ? (
+
+ No recent orders
+
+ ) : (
+ stats.recentOrders.map((order: any) => (
+
+
+
+ Total Products : {order.items.reduce((acc: number, item: any) => acc + item.quantity, 0)}
+ {new Date(order.createdAt).toLocaleDateString()}
+
+
+ {order.orderStatus}
+
+
+
+ {order.items.map((item: any) => (
+ {item.name} x {item.quantity}
+ ))}
+
+
+
+
+
+
+
+
+ {(order.user?.name || '?').charAt(0).toUpperCase()}
+
+
+ {order.user?.name || 'Unknown User'}
+
+ ${order.totalAmount.toFixed(2)}
+
+
+ ))
+ )}
+
+
+ );
+}
+
+const StatCard = ({ label, value }: { label: string, value: string }) => (
+
+ {value}
+ {label}
+
+);
diff --git a/client/app/admin/orders.tsx b/client/app/admin/orders.tsx
new file mode 100644
index 0000000..76813b9
--- /dev/null
+++ b/client/app/admin/orders.tsx
@@ -0,0 +1,173 @@
+import React, { useEffect, useState } from "react";
+import { ScrollView, Text, TouchableOpacity, View, ActivityIndicator, RefreshControl, Alert, Modal, TouchableWithoutFeedback, FlatList } from "react-native";
+import { COLORS, getStatusColor } from "@/constants";
+import { Ionicons } from "@expo/vector-icons";
+import { dummyOrders, dummyUser } from "@/assets/assets";
+
+export default function AdminOrders() {
+ const [loading, setLoading] = useState(true);
+ const [refreshing, setRefreshing] = useState(false);
+ const [orders, setOrders] = useState([]);
+
+ // Status Modal State
+ const [statusModalVisible, setStatusModalVisible] = useState(false);
+ const [selectedOrder, setSelectedOrder] = useState(null);
+ const [updating, setUpdating] = useState(false);
+
+ const STATUSES = ["placed", "processing", "shipped", "delivered", "cancelled"];
+
+ const fetchOrders = async () => {
+ setOrders(dummyOrders.map((order: any) => ({
+ ...order,
+ user: dummyUser
+ })) as any);
+ setLoading(false);
+ setRefreshing(false);
+ };
+
+ useEffect(() => {
+ fetchOrders();
+ }, []);
+
+ const onRefresh = () => {
+ setRefreshing(true);
+ fetchOrders();
+ };
+
+ const openStatusModal = (order: any) => {
+ setSelectedOrder(order);
+ setStatusModalVisible(true);
+ };
+
+ const updateStatus = async (newStatus: string) => {
+ if (!selectedOrder) return;
+ setOrders(orders.map((order: any) => order._id === selectedOrder._id ? { ...order, orderStatus: newStatus } : order) as any);
+ setStatusModalVisible(false);
+ setUpdating(false);
+ };
+
+ if (loading && !refreshing) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ }
+ >
+ {orders.length === 0 ? (
+
+ No orders found
+
+ ) : (
+ orders.map((order: any) => (
+
+
+ Order ID : #{order._id}
+ {new Date(order.createdAt).toLocaleDateString()}
+
+
+
+ CUSTOMER
+ {order.user?.name || 'Unknown User'}
+ {order.user?.email || 'No email'}
+ {!order.user && ID: {order.user?._id || 'N/A'}}
+
+
+
+ SHIPPING ADDRESS
+
+ {order.shippingAddress?.street}, {order.shippingAddress?.city}
+
+
+ {order.shippingAddress?.state}, {order.shippingAddress?.zipCode}, {order.shippingAddress?.country}
+
+
+
+
+ ITEMS
+ {order.items.map((item: any) => (
+
+
+ {item.quantity}x {item.product?.name || item.name}
+ {(item.size) && (
+
+ {" "}({item.size || '-'})
+
+ )}
+
+
+ ${item.price.toFixed(2)}
+
+
+ ))}
+
+
+
+ ${order.totalAmount.toFixed(2)}
+
+ openStatusModal(order)}
+ className={`flex-row items-center px-4 py-2 rounded-full ${getStatusColor(order.orderStatus)}`}
+ >
+ {order.orderStatus}
+
+
+
+
+ ))
+ )}
+
+
+ {/* STATUS MODAL */}
+
+ setStatusModalVisible(false)}>
+
+
+
+
+ Update Order Status
+
+ setStatusModalVisible(false)}>
+
+
+
+
+ {updating ? (
+
+
+ Updating status...
+
+ ) : (
+ item}
+ renderItem={({ item }) => (
+ updateStatus(item)}
+ >
+
+ {item}
+
+ {selectedOrder?.orderStatus === item && (
+
+ )}
+
+ )}
+ />
+ )}
+
+
+
+
+
+ );
+}
diff --git a/client/app/admin/products/_layout.tsx b/client/app/admin/products/_layout.tsx
new file mode 100644
index 0000000..7c8e126
--- /dev/null
+++ b/client/app/admin/products/_layout.tsx
@@ -0,0 +1,19 @@
+import { Stack } from "expo-router";
+import { COLORS } from "@/constants";
+
+export default function ProductsLayout() {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/client/app/admin/products/add.tsx b/client/app/admin/products/add.tsx
new file mode 100644
index 0000000..a3975ac
--- /dev/null
+++ b/client/app/admin/products/add.tsx
@@ -0,0 +1,225 @@
+import React, { useState } from "react";
+import { ScrollView, Text, TextInput, TouchableOpacity, View, Switch, Image, ActivityIndicator, Modal, FlatList, TouchableWithoutFeedback, Platform, } from "react-native";
+import Toast from 'react-native-toast-message';
+import { COLORS } from "@/constants";
+import { Ionicons } from "@expo/vector-icons";
+import * as ImagePicker from "expo-image-picker";
+import { CATEGORIES } from "@/constants";
+
+export default function AddProduct() {
+
+ const [submitting, setSubmitting] = useState(false);
+ const [modalVisible, setModalVisible] = useState(false);
+
+ // Form state
+ const [name, setName] = useState("");
+ const [description, setDescription] = useState("");
+ const [price, setPrice] = useState("");
+ const [stock, setStock] = useState("");
+ const [category, setCategory] = useState("Men");
+ const [sizes, setSizes] = useState("");
+ const [images, setImages] = useState([]);
+ const [isFeatured, setIsFeatured] = useState(false);
+
+ // PICK MULTIPLE IMAGES (MAX 5)
+ const pickImages = async () => {
+ const result = await ImagePicker.launchImageLibraryAsync({
+ mediaTypes: ImagePicker.MediaTypeOptions.Images,
+ allowsMultipleSelection: true,
+ selectionLimit: 5,
+ quality: 0.8,
+ });
+
+ if (!result.canceled) {
+ const uris = result.assets.map((asset) => asset.uri);
+ setImages(uris.slice(0, 5));
+ }
+ };
+
+ // Add Product
+ const handleSubmit = async () => {
+ if (!name || !price || !category || sizes.length < 1) {
+ Toast.show({
+ type: 'error',
+ text1: 'Missing Fields',
+ text2: 'Please fill in all required fields'
+ });
+ return;
+ }
+ };
+
+ return (
+
+
+ {/* NAME */}
+
+ Product Name *
+
+
+
+ {/* PRICE */}
+
+ Price ($) *
+
+
+
+ {/* CATEGORY */}
+
+ Category
+
+ setModalVisible(true)}
+ className="bg-surface p-3 rounded-lg mb-4 flex-row justify-between items-center"
+ >
+ {category}
+
+
+
+ {/* CATEGORY MODAL */}
+
+ setModalVisible(false)}>
+
+
+
+ Select Category
+
+
+ String(item.id)}
+ renderItem={({ item }) => (
+ {
+ setCategory(item.name);
+ setModalVisible(false);
+ }}
+ >
+
+
+ {item.name}
+
+ {category === item.name && (
+
+ )}
+
+
+ )}
+ />
+
+
+
+
+
+ {/* STOCK */}
+
+ Stock Level
+
+
+
+ {/* SIZES */}
+
+ Sizes (comma separated)
+
+
+
+ {/* IMAGE PICKER */}
+
+ Product Images (max 5)
+
+
+
+ {images.length > 0 ? (
+
+ {images.map((uri, i) => (
+
+ ))}
+
+ ) : (
+
+
+
+ Tap to upload images
+
+
+ )}
+
+
+ {/* DESCRIPTION */}
+
+ Description
+
+
+
+ {/* FEATURED */}
+
+ Featured Product
+
+
+
+ {/* SUBMIT */}
+
+ {submitting ? (
+
+ ) : (
+
+ Create Product
+
+ )}
+
+
+
+ );
+}
diff --git a/client/app/admin/products/edit/[id].tsx b/client/app/admin/products/edit/[id].tsx
new file mode 100644
index 0000000..d95b38d
--- /dev/null
+++ b/client/app/admin/products/edit/[id].tsx
@@ -0,0 +1,291 @@
+import { useLocalSearchParams, useRouter } from "expo-router";
+import React, { useEffect, useState } from "react";
+import { ScrollView, Text, TextInput, TouchableOpacity, View, Switch, Image, ActivityIndicator, Platform, Modal, FlatList, TouchableWithoutFeedback } from "react-native";
+import Toast from 'react-native-toast-message';
+import { COLORS, CATEGORIES } from "@/constants";
+import { Ionicons } from "@expo/vector-icons";
+import * as ImagePicker from "expo-image-picker";
+import { dummyProducts } from "@/assets/assets";
+
+export default function EditProduct() {
+ const { id } = useLocalSearchParams();
+ const router = useRouter();
+
+ const [loading, setLoading] = useState(true);
+ const [submitting, setSubmitting] = useState(false);
+ const [modalVisible, setModalVisible] = useState(false);
+
+ // Form State
+ const [name, setName] = useState("");
+ const [description, setDescription] = useState("");
+ const [price, setPrice] = useState("");
+ const [stock, setStock] = useState("");
+ const [category, setCategory] = useState("");
+ const [sizes, setSizes] = useState("");
+ const [isFeatured, setIsFeatured] = useState(false);
+
+ // Image State
+ const [existingImages, setExistingImages] = useState([]);
+ const [newImages, setNewImages] = useState([]);
+
+ useEffect(() => {
+ const fetchProduct = async () => {
+ try {
+ const product: any = dummyProducts.find((p) => p._id === id);
+ setName(product.name);
+ setDescription(product.description || "");
+ setPrice(product.price.toString());
+ setStock(product.stock.toString());
+ setCategory(typeof product.category === 'object' ? product.category.name : product.category);
+ setIsFeatured(product.isFeatured);
+
+ if (product.sizes) setSizes(Array.isArray(product.sizes) ? product.sizes.join(", ") : product.sizes);
+
+ if (product.images && Array.isArray(product.images)) {
+ setExistingImages(product.images);
+ } else if (product.images) {
+ setExistingImages([product.images]);
+ }
+ } catch (error: any) {
+ console.error("Failed to fetch product:", error);
+ Toast.show({
+ type: 'error',
+ text1: 'Failed to Fetch Product',
+ text2: error.response?.data?.message || "Something went wrong"
+ });
+ router.back();
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ if (id) fetchProduct();
+ }, [id]);
+
+ const pickImages = async () => {
+ const result = await ImagePicker.launchImageLibraryAsync({
+ mediaTypes: ImagePicker.MediaTypeOptions.Images,
+ allowsMultipleSelection: true,
+ selectionLimit: 5 - (existingImages.length + newImages.length),
+ quality: 0.8,
+ });
+
+ if (!result.canceled) {
+ const uris = result.assets.map((asset) => asset.uri);
+ setNewImages([...newImages, ...uris]);
+ }
+ };
+
+ const removeExistingImage = (index: number) => {
+ const updated = [...existingImages];
+ updated.splice(index, 1);
+ setExistingImages(updated);
+ };
+
+ const removeNewImage = (index: number) => {
+ const updated = [...newImages];
+ updated.splice(index, 1);
+ setNewImages(updated);
+ };
+
+ const handleSubmit = async () => {
+ if (!name || !price || sizes.length < 1) {
+ Toast.show({
+ type: 'error',
+ text1: 'Missing Fields',
+ text2: 'Please fill in all required fields'
+ });
+ return;
+ }
+
+ try {
+ setSubmitting(true);
+ const formData = new FormData();
+
+ formData.append("name", name);
+ formData.append("description", description);
+ formData.append("price", price);
+ formData.append("stock", stock);
+ formData.append("category", category);
+ formData.append("isFeatured", String(isFeatured));
+ formData.append("sizes", sizes);
+
+ // Append existing images
+ existingImages.forEach((img) => {
+ formData.append("existingImages", img);
+ });
+
+ // Append new images
+ for (const [i, uri] of newImages.entries()) {
+ const filename = `new-image-${i}.jpg`;
+ if (Platform.OS === "web") {
+ const blob = await (await fetch(uri)).blob();
+ formData.append("images", new File([blob], filename, { type: "image/jpeg" }));
+ } else {
+ formData.append("images", { uri, name: filename, type: "image/jpeg" } as any);
+ }
+ }
+ router.back();
+ } catch (error: any) {
+ console.error("Failed to update product:", error);
+ Toast.show({
+ type: 'error',
+ text1: 'Failed to Update Product',
+ text2: error.response?.data?.message || "Something went wrong"
+ });
+ } finally {
+ setSubmitting(false);
+ }
+ };
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+ Product Name *
+
+
+ Price ($) *
+
+
+ Stock Level
+
+
+ Sizes (comma separated)
+
+
+
+ Category
+
+ setModalVisible(true)}
+ className="bg-surface p-3 rounded-lg mb-4 flex-row justify-between items-center"
+ >
+ {category || "Select Category"}
+
+
+
+
+ setModalVisible(false)}>
+
+
+ Select Category
+ String(item.id)}
+ renderItem={({ item }) => (
+ {
+ setCategory(item.name);
+ setModalVisible(false);
+ }}
+ >
+
+ {item.name}
+ {category === item.name && }
+
+
+ )}
+ />
+
+
+
+
+
+ Images
+
+
+ {existingImages.map((uri, index) => (
+
+
+ removeExistingImage(index)}
+ className="absolute top-1 right-1 bg-black/50 rounded-full p-1"
+ >
+
+
+
+ ))}
+ {newImages.map((uri, index) => (
+
+
+ removeNewImage(index)}
+ className="absolute top-1 right-1 bg-primary rounded-full p-1"
+ >
+
+
+
+ ))}
+ {(existingImages.length + newImages.length) < 5 && (
+
+
+ Add
+
+ )}
+
+
+
+ Description
+
+
+
+ Featured Product
+
+
+
+
+ {submitting ? (
+
+ ) : (
+ Update Product
+ )}
+
+
+
+ );
+}
diff --git a/client/app/admin/products/index.tsx b/client/app/admin/products/index.tsx
new file mode 100644
index 0000000..fb69b8c
--- /dev/null
+++ b/client/app/admin/products/index.tsx
@@ -0,0 +1,114 @@
+import { useRouter } from "expo-router";
+import React, { useEffect, useState } from "react";
+import { ScrollView, Text, TouchableOpacity, View, ActivityIndicator, RefreshControl, Image, Alert } from "react-native";
+import { Ionicons } from "@expo/vector-icons";
+import { COLORS } from "@/constants";
+import { dummyProducts } from "@/assets/assets";
+
+export default function AdminProducts() {
+ const router = useRouter();
+ const [loading, setLoading] = useState(true);
+ const [refreshing, setRefreshing] = useState(false);
+ const [products, setProducts] = useState([]);
+
+ const fetchProducts = async () => {
+ setProducts(dummyProducts as any);
+ setLoading(false);
+ setRefreshing(false);
+ };
+
+ useEffect(() => {
+ fetchProducts();
+ }, []);
+
+ const onRefresh = () => {
+ setRefreshing(true);
+ fetchProducts();
+ };
+
+ const performDelete = async (id: string) => {
+ setProducts(products.filter((product: any) => product._id !== id) as any);
+ };
+
+ const deleteProduct = async (id: string) => {
+ Alert.alert(
+ "Delete Product",
+ "Are you sure you want to delete this product?",
+ [
+ { text: "Cancel", style: "cancel" as const },
+ {
+ text: "Delete",
+ style: "destructive" as const,
+ onPress: () => performDelete(id)
+ }
+ ]
+ );
+ };
+
+ if (loading && !refreshing) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+ Total Products ({products.length})
+ router.push("/admin/products/add")}
+ className="bg-gray-800 px-4 py-2 rounded-full flex-row items-center"
+ >
+
+ Add Product
+
+
+
+ }
+ >
+ {products.length === 0 ? (
+
+ No products found
+
+ ) : (
+ products.map((product: any) => (
+
+ 0 ? product.images[0] : 'https://via.placeholder.com/150' }}
+ className="w-16 h-16 rounded-lg bg-gray-100 mr-3"
+ resizeMode="cover"
+ />
+
+
+ {product.name}
+ Category : {product.category || 'Others'}
+ Stock : {product.stock}
+ Sizes : {product.sizes.join(", ")}
+ ${product.price.toFixed(2)}
+
+
+
+ router.push(`/admin/products/edit/${product._id}`)}
+ className="p-2 bg-slate-50 rounded-full mr-2"
+ >
+
+
+ deleteProduct(product._id)}
+ className="p-2 bg-gray-50 rounded-full"
+ >
+
+
+
+
+ ))
+ )}
+
+
+ );
+}
diff --git a/client/app/checkout.tsx b/client/app/checkout.tsx
new file mode 100644
index 0000000..b7cf2eb
--- /dev/null
+++ b/client/app/checkout.tsx
@@ -0,0 +1,152 @@
+import { View, Text, ActivityIndicator, ScrollView, TouchableOpacity } from 'react-native'
+import React, { useEffect, useState } from 'react'
+import { useCart } from '@/context/CartContext'
+import { useRouter } from 'expo-router'
+import { Address } from '@/constants/types'
+import { dummyAddress } from '@/assets/assets'
+import Toast from 'react-native-toast-message'
+import { SafeAreaView } from 'react-native-safe-area-context'
+import { COLORS } from '@/constants'
+import Header from '@/components/Header'
+import { Ionicons } from '@expo/vector-icons'
+
+export default function Checkout() {
+
+ const { cartTotal } = useCart()
+ const router = useRouter()
+
+ const [loading, setLoading] = useState(false)
+ const [pageLoading, setPageLoading] = useState(true)
+
+ const [selectedAddress, setSelectedAddress] = useState(null)
+ const [payementMethod, setPaymentMethod] = useState<'cash' | 'stripe'>('cash')
+
+ const shipping = 2.0
+ const tax = 0
+ const total = cartTotal + shipping + tax
+
+ const fetchAddress = async () => {
+ const addressList = dummyAddress
+ if (addressList.length > 0) {
+ const def = addressList.find((a: any) => a.isDefault) || addressList[0]
+ setSelectedAddress(def as Address)
+ }
+ setPageLoading(false)
+ }
+
+ const handlePlaceOrder = async () => {
+ if (!selectedAddress) {
+ Toast.show({
+ type: 'error',
+ text1: 'Error',
+ text2: 'Please add a shipping address'
+ })
+ return
+ }
+ if (payementMethod === 'stripe') {
+ return Toast.show({
+ type: 'error',
+ text1: 'Info',
+ text2: 'Stripe not implemented yet'
+ })
+ }
+ router.replace('/orders')
+ }
+
+ useEffect(() => {
+ fetchAddress()
+ }, [])
+
+ if (pageLoading) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+
+
+
+ {/* Address Section */}
+ Shipping Address
+ {selectedAddress ? (
+
+
+ {selectedAddress.type}
+ router.push('/addresses')}>
+ Change
+
+
+
+ {selectedAddress.street}, {selectedAddress.city}
+ {'\n'}
+ {selectedAddress.state}, {selectedAddress.zipCode}
+ {'\n'}
+ {selectedAddress.country}
+
+
+ ) : (
+ router.push('/addresses')} className='bg-white p-6 rounded-xl mb-6 items-center justify-center border-dashed border-2 border-gray-100'>
+ Add Address
+
+ )}
+
+ {/* Payment Section */}
+ Payment Method
+
+ {/* Cash on Delivery Option */}
+ setPaymentMethod('cash')} className={`bg-white p-4 rounded-xl mb-4 shadow-sm flex-row items-center border-2 ${payementMethod === 'cash' ? 'border-primary' : 'border-transparent'}`}>
+
+
+ Cash on Delivery
+ Pay when you recieve the order
+
+ {payementMethod === 'cash' && }
+
+
+ {/* Stripe Option */}
+ setPaymentMethod('stripe')} className={`bg-white p-4 rounded-xl mb-4 shadow-sm flex-row items-center border-2 ${payementMethod === 'stripe' ? 'border-primary' : 'border-transparent'}`}>
+
+
+ Pay with Card
+ Credit or Debit Card
+
+ {payementMethod === 'stripe' && }
+
+
+
+ {/* Order Summary */}
+
+ Order Summary
+
+ {/* Subtotal */}
+
+ Subtotal
+ {cartTotal.toFixed(2)}
+
+ {/* Tax */}
+
+ Tax
+ {tax.toFixed(2)}
+
+ {/* Shipping */}
+
+ Shipping
+ {shipping.toFixed(2)}
+
+ {/* Total */}
+
+ Total
+ {total.toFixed(2)}
+
+ {/* Place Order Button */}
+
+ {loading ? : Place Order}
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/client/app/orders/[id].tsx b/client/app/orders/[id].tsx
new file mode 100644
index 0000000..bde16b9
--- /dev/null
+++ b/client/app/orders/[id].tsx
@@ -0,0 +1,148 @@
+import { Ionicons } from "@expo/vector-icons";
+import { useLocalSearchParams } from "expo-router";
+import React, { useEffect, useState } from "react";
+import { Image, ScrollView, Text, View, ActivityIndicator } from "react-native";
+import { SafeAreaView } from "react-native-safe-area-context";
+import Header from "@/components/Header";
+import { COLORS } from "@/constants";
+import type { Order, Product } from "@/constants/types";
+import { dummyOrders } from "@/assets/assets";
+
+export default function OrderDetails() {
+ const { id } = useLocalSearchParams();
+ const [order, setOrder] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ const fetchOrderDetails = async () => {
+ setOrder(dummyOrders.find((order) => order._id === id) as any);
+ setLoading(false);
+ };
+
+ useEffect(() => {
+ fetchOrderDetails();
+ }, [id]);
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (!order) {
+ return (
+
+ Order not found
+
+ );
+ }
+
+ const formatDate = (dateString: string) => {
+ const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'short', day: 'numeric' };
+ return new Date(dateString).toLocaleDateString(undefined, options);
+ };
+
+ const ORDER_STEPS = [
+ { title: "Order Placed", date: formatDate(order.createdAt), completed: true },
+ { title: "Processing", date: "", completed: ['processing', 'shipped', 'delivered'].includes(order.orderStatus) },
+ { title: "Shipped", date: "", completed: ['shipped', 'delivered'].includes(order.orderStatus) },
+ { title: "Delivered", date: "", completed: order.orderStatus === 'delivered' },
+ ];
+
+ return (
+
+
+
+
+ {/* Order Status */}
+
+ Order Status
+
+ {ORDER_STEPS.map((step, index) => (
+
+
+
+ {index !== ORDER_STEPS.length - 1 && (
+
+ )}
+
+
+ {step.title}
+ {step.date ? {step.date} : null}
+
+
+ ))}
+
+
+ {/* Items */}
+
+ Products
+ {order.items.map((item: any, index: number) => {
+
+ const productData = item.product as Product;
+ const image = productData?.images?.[0];
+
+ return (
+
+ {image && }
+
+ {item.name}
+ Size: {item.size}
+
+ ${item.price}
+ Qty: {item.quantity}
+
+
+
+ )
+ })}
+
+
+ {/* Shipping Details */}
+
+ Shipping Details
+
+
+
+ {order.shippingAddress?.street}, {order.shippingAddress?.city}, {order.shippingAddress?.zipCode}, {order.shippingAddress?.country}
+
+
+
+
+ {/* Payment Summary */}
+
+ Payment Summary
+
+ Payment Method
+ {order.paymentMethod}
+
+
+ Payment Status
+
+ {order.paymentStatus}
+
+
+
+
+ Subtotal
+ ${order.subtotal.toFixed(2)}
+
+
+ Shipping
+ ${order.shippingCost.toFixed(2)}
+
+
+ Tax
+ ${order.tax.toFixed(2)}
+
+
+
+ Total
+ ${order.totalAmount.toFixed(2)}
+
+
+
+
+ );
+}
diff --git a/client/app/orders/index.tsx b/client/app/orders/index.tsx
new file mode 100644
index 0000000..fc24020
--- /dev/null
+++ b/client/app/orders/index.tsx
@@ -0,0 +1,105 @@
+import { useRouter } from "expo-router";
+import React, { useEffect, useState } from "react";
+import { FlatList, Text, TouchableOpacity, View, ActivityIndicator, ScrollView, Image } from "react-native";
+import { Ionicons } from "@expo/vector-icons";
+import { SafeAreaView } from "react-native-safe-area-context";
+import Header from "@/components/Header";
+import { COLORS, getStatusColor } from "@/constants";
+import type { Order } from "@/constants/types";
+import { dummyOrders, formatDate } from "@/assets/assets";
+
+export default function Orders() {
+ const router = useRouter();
+ const [orders, setOrders] = useState([]);
+ const [loading, setLoading] = useState(true);
+
+ const fetchOrders = async () => {
+ setOrders(dummyOrders as any[]);
+ setLoading(false);
+ };
+
+ useEffect(() => {
+ fetchOrders();
+ }, []);
+
+ return (
+
+
+
+ {loading ? (
+
+
+
+ ) : orders.length === 0 ? (
+
+ No orders found
+
+ ) : (
+ item._id}
+ contentContainerStyle={{ padding: 16 }}
+ renderItem={({ item, index }) => (
+ router.push(`/orders/${item._id}`)}
+ >
+
+ Order #{item.orderNumber}
+ {formatDate(item.createdAt)}
+
+
+ {/* Status Badges */}
+
+
+
+ {item.orderStatus}
+
+
+
+
+
+ {item.paymentStatus}
+
+
+
+
+
+ Payment Method: {item.paymentMethod}
+
+
+ {/* Product Images */}
+
+ {item.items.map((prod: any, idx) => {
+ const image = prod.product?.images?.[0];
+ return (
+
+ {image ? (
+
+ ) : (
+
+
+
+ )}
+
+ );
+ })}
+
+
+
+ Items: {item.items.length}
+ ${item.totalAmount.toFixed(2)}
+
+
+ )}
+ />
+ )}
+
+ );
+}
diff --git a/client/package-lock.json b/client/package-lock.json
index 24c3a3c..346e242 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -18,6 +18,7 @@
"expo-font": "~14.0.11",
"expo-haptics": "~15.0.8",
"expo-image": "~3.0.11",
+ "expo-image-picker": "~17.0.11",
"expo-linking": "~8.0.12",
"expo-router": "~6.0.23",
"expo-splash-screen": "~31.0.13",
@@ -6453,6 +6454,27 @@
}
}
},
+ "node_modules/expo-image-loader": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-6.0.0.tgz",
+ "integrity": "sha512-nKs/xnOGw6ACb4g26xceBD57FKLFkSwEUTDXEDF3Gtcu3MqF3ZIYd3YM+sSb1/z9AKV1dYT7rMSGVNgsveXLIQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
+ "node_modules/expo-image-picker": {
+ "version": "17.0.11",
+ "resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-17.0.11.tgz",
+ "integrity": "sha512-/apkoyukDvsCHHb9fzP+F34A1uQqSzUtYH/2P/xJACNEwq+mwEXjXvVU8bzlJq6ih0Qo1+tpVivIa7B9kYSwOQ==",
+ "license": "MIT",
+ "dependencies": {
+ "expo-image-loader": "~6.0.0"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
"node_modules/expo-linking": {
"version": "8.0.12",
"resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-8.0.12.tgz",
diff --git a/client/package.json b/client/package.json
index 5c0a861..e37dc77 100644
--- a/client/package.json
+++ b/client/package.json
@@ -21,6 +21,7 @@
"expo-font": "~14.0.11",
"expo-haptics": "~15.0.8",
"expo-image": "~3.0.11",
+ "expo-image-picker": "~17.0.11",
"expo-linking": "~8.0.12",
"expo-router": "~6.0.23",
"expo-splash-screen": "~31.0.13",