Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion BACKEND/models/returnRequest.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const returnRequestSchema = new mongoose.Schema({
type: [returnItemSchema],
required: [true, "At least one return item is required"],
validate: {
validator: function (v: any[]) {
validator: function (v) {
return v && v.length > 0;
},
message: "Items array cannot be empty",
Expand Down
4 changes: 2 additions & 2 deletions FRONTEND/src/components/ui/QuickAddModal.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
import { useCart } from "../../store/cart";
import { useCartStore } from "../../store/cart";
import {
showSuccessToast,
showWarningToast,
Expand All @@ -25,7 +25,7 @@ import {
const QuickAddModal = ({ product, isOpen, onClose }) => {
const [quantity, setQuantity] = useState(1);

const { addToCart } = useCart();
const { addToCart } = useCartStore();
const toast = useToast();

const isOutOfStock =
Expand Down
111 changes: 89 additions & 22 deletions FRONTEND/src/pages/ProductPage.jsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,53 @@
import React, { useState, useEffect, useMemo } from 'react';
import { useParams, Link as RouterLink } from 'react-router-dom';
import {
Box, Container, Flex, Grid, GridItem, SimpleGrid, Image, Icon, Heading, Text,
Button, Badge, Divider, Checkbox, Select, VStack, HStack, useToast, useColorModeValue,
Box,
Container,
Flex,
Grid,
GridItem,
SimpleGrid,
Image,
Icon,
Heading,
Text,
Button,
IconButton,
Badge,
Divider,
Checkbox,
Select,
Spinner,
Alert,
AlertIcon,
VStack,
HStack,
useToast,
useColorModeValue,
} from '@chakra-ui/react';
import {
FaArrowLeft, FaShoppingCart, FaCheckCircle, FaTruck, FaShieldAlt, FaUndo,
FaInfoCircle, FaGift, FaChevronLeft, FaChevronRight,
import {
FaArrowLeft,
FaShoppingCart,
FaCheckCircle,
FaTruck,
FaShieldAlt,
FaUndo,
FaInfoCircle,
FaGift,
FaChevronLeft,
FaChevronRight,
FaShareAlt,
} from 'react-icons/fa';
import axios from 'axios';
import { getSocket } from '../socket';
import RelatedProducts from '../components/ui/RelatedProducts';
import ProductReviews from '../components/ui/ProductReviews';
import axios from "axios";
import { getSocket } from "../socket";
import Breadcrumbs from "../components/ui/Breadcrumbs";
import RelatedProducts from "../components/ui/RelatedProducts";
import ProductReviews from "../components/ui/ProductReviews";

const ProductPage = () => {
const { id } = useParams();
const toast = useToast();
const [product, setProduct] = useState(null);
const [selectedSize, setSelectedSize] = useState('');
const [selectedColor, setSelectedColor] = useState('');
const [displayPrice, setDisplayPrice] = useState(0);
Expand All @@ -39,10 +71,12 @@ const ProductPage = () => {
const fetchProduct = async () => {
try {
const { data } = await axios.get('/api/products/' + id);
setProduct(data);
if (!data.hasVariants) {
setDisplayPrice(data.basePrice);
setDisplayStock(data.baseStock);

setProduct(data.data);

if (!data.data.hasVariants) {
setDisplayPrice(data.data.basePrice);
setDisplayStock(data.data.baseStock);
}
} catch (err) {
console.error(err);
Expand Down Expand Up @@ -118,6 +152,28 @@ const ProductPage = () => {
}
};

const handleShare = async () => {
try {
await navigator.clipboard.writeText(window.location.href);

toast({
title: "Product link copied!",
description: "Share it with anyone.",
status: "success",
duration: 3000,
isClosable: true,
});
} catch {
toast({
title: "Failed to copy link",
description: "Please try again.",
status: "error",
duration: 3000,
isClosable: true,
});
}
};

const toggleBundleItem = (productId) => {
setSelectedBundleItems((prev) =>
prev.includes(productId)
Expand Down Expand Up @@ -255,15 +311,26 @@ const ProductPage = () => {
</Badge>
)}

<Heading
as="h1"
fontSize={{ base: "3xl", md: "4xl", lg: "5xl" }}
fontWeight="extrabold"
lineHeight="1.2"
mb={2}
>
{product.name}
</Heading>
<HStack spacing={7} align="center" mb={3}>
<Heading
as="h1"
fontSize={{ base: "3xl", md: "4xl", lg: "5xl" }}
fontWeight="extrabold"
lineHeight="1.2"
>
{product.name}
</Heading>

<IconButton
icon={<FaShareAlt />}
aria-label="Share product"
variant="ghost"
size="sm"
mt={1}
onClick={handleShare}
_hover={{ color: "blue.500" }}
/>
</HStack>

{/* Average Rating */}
{product.reviewCount > 0 && (
Expand Down
Loading