From 30705638c281fd679c534f16bb1fedbd6d529d3e Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Mon, 30 Jun 2025 01:52:49 +0300 Subject: [PATCH] Orders page --- .../Dtos/Orders/StatusDto.cs | 6 + ui/src/app/orders/page.tsx | 615 ++++++++++++++++++ 2 files changed, 621 insertions(+) create mode 100644 src/Imprink.Application/Dtos/Orders/StatusDto.cs create mode 100644 ui/src/app/orders/page.tsx diff --git a/src/Imprink.Application/Dtos/Orders/StatusDto.cs b/src/Imprink.Application/Dtos/Orders/StatusDto.cs new file mode 100644 index 0000000..500501c --- /dev/null +++ b/src/Imprink.Application/Dtos/Orders/StatusDto.cs @@ -0,0 +1,6 @@ +namespace Imprink.Application.Dtos; + +public class StatusDto +{ + +} \ No newline at end of file diff --git a/ui/src/app/orders/page.tsx b/ui/src/app/orders/page.tsx new file mode 100644 index 0000000..190fb58 --- /dev/null +++ b/ui/src/app/orders/page.tsx @@ -0,0 +1,615 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { + Box, + Container, + Typography, + Card, + CardContent, + Grid, + Chip, + Avatar, + Skeleton, + Alert, + Paper, + Divider, + Dialog, + DialogContent, + IconButton, + Fade, + Collapse, + Button, +} from '@mui/material'; +import { + ShoppingBag, + LocalShipping, + Close, + ZoomIn, + ExpandMore, + ExpandLess, +} from '@mui/icons-material'; +import clientApi from '@/lib/clientApi'; + +interface OrderStatus { + id: number; + name: string; + description: string; +} + +interface ShippingStatus { + id: number; + name: string; + description: string; +} + +interface Category { + id: string; + name: string; + description: string; + imageUrl: string; + sortOrder: number; + isActive: boolean; + parentCategoryId: string; + createdAt: string; + modifiedAt: string; +} + +interface Product { + id: string; + name: string; + description: string; + basePrice: number; + isCustomizable: boolean; + isActive: boolean; + imageUrl: string; + categoryId: string; + category: Category; + createdAt: string; + modifiedAt: string; +} + +interface ProductVariant { + id: string; + productId: string; + size: string; + color: string; + price: number; + imageUrl: string; + sku: string; + stockQuantity: number; + isActive: boolean; + product: Product; + createdAt: string; + modifiedAt: string; +} + +interface OrderAddress { + id: string; + orderId: string; + addressType: string; + firstName: string; + lastName: string; + company: string; + addressLine1: string; + addressLine2: string; + apartmentNumber: string; + buildingNumber: string; + floor: string; + city: string; + state: string; + postalCode: string; + country: string; + phoneNumber: string; + instructions: string; + createdAt: string; + updatedAt: string; +} + +interface Order { + id: string; + userId: string; + orderDate: string; + amount: number; + quantity: number; + productId: string; + productVariantId: string; + orderStatusId: number; + shippingStatusId: number; + notes: string; + merchantId: string; + customizationImageUrl: string; + originalImageUrls: string[]; + customizationDescription: string; + createdAt: string; + updatedAt: string; + orderStatus: OrderStatus; + shippingStatus: ShippingStatus; + orderAddress: OrderAddress; + product: Product; + productVariant: ProductVariant; +} + +const getStatusColor = (statusName: string) => { + const normalizedStatus = statusName.toLowerCase(); + if (normalizedStatus.includes('pending') || normalizedStatus.includes('processing')) { + return 'warning'; + } + if (normalizedStatus.includes('completed') || normalizedStatus.includes('delivered')) { + return 'success'; + } + if (normalizedStatus.includes('cancelled') || normalizedStatus.includes('failed')) { + return 'error'; + } + return 'default'; +}; + +const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + }).format(amount); +}; + +const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); +}; + +export default function OrdersPage() { + const [orders, setOrders] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [selectedImage, setSelectedImage] = useState(null); + const [expandedOrders, setExpandedOrders] = useState>(new Set()); + + useEffect(() => { + const fetchOrders = async () => { + try { + setLoading(true); + const response = await clientApi.get('/orders/user/me?includeDetails=true'); + console.log("Data", response.data); + setOrders(response.data); + } catch (err: any) { + setError(err.response?.data?.message || err.message || 'Failed to fetch orders'); + } finally { + setLoading(false); + } + }; + + fetchOrders(); + }, []); + + const handleImageClick = (imageUrl: string) => { + setSelectedImage(imageUrl); + }; + + const handleCloseImage = () => { + setSelectedImage(null); + }; + + const toggleOrderExpansion = (orderId: string) => { + const newExpanded = new Set(expandedOrders); + if (newExpanded.has(orderId)) { + newExpanded.delete(orderId); + } else { + newExpanded.add(orderId); + } + setExpandedOrders(newExpanded); + }; + + if (loading) { + return ( + + + My Orders + + + {[...Array(3)].map((_, index) => ( + + + + + + + + + + + + + + + + + + + + ))} + + + ); + } + + if (error) { + return ( + + + {error} + + + ); + } + + if (orders.length === 0) { + return ( + + + My Orders + + + + + No orders found + + + Your orders will appear here once you make a purchase. + + + + ); + } + + return ( + + + My Orders + + + + {orders.map((order) => { + const isExpanded = expandedOrders.has(order.id); + + return ( + + + toggleOrderExpansion(order.id)} + > + + + + + Order #{order.id.slice(-8).toUpperCase()} + + + {formatDate(order.orderDate)} + + + + + } + label={order.orderStatus.name} + color={getStatusColor(order.orderStatus.name) as any} + size="small" + /> + } + label={order.shippingStatus.name} + color={getStatusColor(order.shippingStatus.name) as any} + size="small" + variant="outlined" + /> + + + + + + + {formatCurrency(order.amount)} + + + Qty: {order.quantity} + + + + + {isExpanded ? : } + + + + + + + + + + + + + + {order.product.name} + + {order.product.description} + + + Variant: {order.productVariant.size} - {order.productVariant.color} + + + SKU: {order.productVariant.sku} + + {order.customizationDescription && ( + + Customization: {order.customizationDescription} + + )} + + + + + + + Shipping Address + + + {order.orderAddress.firstName} {order.orderAddress.lastName} +
+ {order.orderAddress.company && ( + <> + {order.orderAddress.company} +
+ + )} + {order.orderAddress.addressLine1} +
+ {order.orderAddress.addressLine2 && ( + <> + {order.orderAddress.addressLine2} +
+ + )} + {order.orderAddress.city}, {order.orderAddress.state} {order.orderAddress.postalCode} +
+ {order.orderAddress.country} +
+
+ + {(order.productVariant.imageUrl || order.customizationImageUrl || order.originalImageUrls.length > 0) && ( + + + + Images + + + + {order.productVariant.imageUrl && ( + + + Product Variant + + handleImageClick(order.productVariant.imageUrl)} + > + + + + + + + )} + + {order.customizationImageUrl && ( + + + Customized + + handleImageClick(order.customizationImageUrl)} + > + + + + + + + )} + + {order.originalImageUrls.length > 0 && ( + + + Original Images ({order.originalImageUrls.length}) + + + {order.originalImageUrls.slice(0, 3).map((imageUrl, index) => ( + handleImageClick(imageUrl)} + > + + {index === 2 && order.originalImageUrls.length > 3 && ( + + +{order.originalImageUrls.length - 3} + + )} + + + + + ))} + + + )} + + + )} + + {order.notes && ( + + + + Notes + + + {order.notes} + + + )} +
+
+
+
+
+
+ ); + })} +
+ + + + + + + {selectedImage && ( + + + + )} + + +
+ ); +} \ No newline at end of file