removed some unnecessary ui elements
This commit is contained in:
@@ -19,14 +19,13 @@ import EmptyCart from "@/features/cart/components/EmptyCart";
|
||||
import ErrorPage from "@/components/ErrorPage";
|
||||
|
||||
export default function CartPage() {
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
const [paymentType, setPaymentType] = useState<PaymentType | null>(null);
|
||||
const [deliveryType, setDeliveryType] =
|
||||
useState<DeliveryType>("SELECTED_DELIVERY");
|
||||
const [selectedRegion, setSelectedRegion] = useState<string>("");
|
||||
const [selectedProvince, setSelectedProvince] = useState<number | null>(null);
|
||||
const [note, setNote] = useState<string>("");
|
||||
const [phone, setPhone] = useState<string>("+993 ");
|
||||
const [phone, setPhone] = useState<string>("+993 ");
|
||||
const [name, setName] = useState<string>("");
|
||||
const [lastName, setLastName] = useState<string>("");
|
||||
const router = useRouter();
|
||||
@@ -42,38 +41,52 @@ export default function CartPage() {
|
||||
const isLoading = cartLoading || provincesLoading || paymentTypesLoading;
|
||||
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
if (paymentTypes.length > 0) {
|
||||
const defaultType = paymentTypes.find((t) => t.id === 1);
|
||||
if (defaultType) {
|
||||
setPaymentType(defaultType);
|
||||
}
|
||||
}
|
||||
}, [paymentTypes]);
|
||||
|
||||
const regionGroups = useMemo(() => {
|
||||
return provinces.reduce((acc, province) => {
|
||||
if (!acc[province.region]) {
|
||||
acc[province.region] = [];
|
||||
}
|
||||
acc[province.region].push(province);
|
||||
return acc;
|
||||
}, {} as Record<string, typeof provinces>);
|
||||
return provinces.reduce(
|
||||
(acc, province) => {
|
||||
if (!acc[province.region]) {
|
||||
acc[province.region] = [];
|
||||
}
|
||||
acc[province.region].push(province);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, typeof provinces>,
|
||||
);
|
||||
}, [provinces]);
|
||||
|
||||
const availableRegions = useMemo(
|
||||
() => Object.keys(regionGroups),
|
||||
[regionGroups]
|
||||
[regionGroups],
|
||||
);
|
||||
|
||||
const itemsBySeller = useMemo(() => {
|
||||
return cartItems.reduce((acc, item) => {
|
||||
const sellerId = item.product.channel?.[0]?.id || 0;
|
||||
const sellerName = item.product.channel?.[0]?.name || "Unknown Seller";
|
||||
return cartItems.reduce(
|
||||
(acc, item) => {
|
||||
const sellerId = item.product.channel?.[0]?.id || 0;
|
||||
const sellerName = item.product.channel?.[0]?.name || "Unknown Seller";
|
||||
|
||||
if (!acc[sellerId]) {
|
||||
acc[sellerId] = {
|
||||
seller: { id: sellerId, name: sellerName },
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
acc[sellerId].items.push(item);
|
||||
return acc;
|
||||
}, {} as Record<number, { seller: { id: number; name: string }; items: typeof cartItems }>);
|
||||
if (!acc[sellerId]) {
|
||||
acc[sellerId] = {
|
||||
seller: { id: sellerId, name: sellerName },
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
acc[sellerId].items.push(item);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<
|
||||
number,
|
||||
{ seller: { id: number; name: string }; items: typeof cartItems }
|
||||
>,
|
||||
);
|
||||
}, [cartItems]);
|
||||
|
||||
const totalAmount = useMemo(() => {
|
||||
@@ -88,46 +101,50 @@ export default function CartPage() {
|
||||
setSelectedProvince(null);
|
||||
};
|
||||
|
||||
|
||||
const formatPhoneForBackend = (phoneNumber: string): string => {
|
||||
|
||||
return phoneNumber.replace(/^\+993\s*/, "").replace(/\s+/g, "");
|
||||
};
|
||||
|
||||
const handleCompleteOrder = () => {
|
||||
if (!selectedRegion || !selectedProvince || !paymentType || !phone || !name) {
|
||||
console.warn("Missing required fields for order");
|
||||
return;
|
||||
}
|
||||
|
||||
const phoneDigits = formatPhoneForBackend(phone);
|
||||
if (phoneDigits.length !== 8) {
|
||||
console.warn("Phone number must be exactly 8 digits");
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedProvinceData = provinces.find((p) => p.id === selectedProvince);
|
||||
if (!selectedProvinceData) return;
|
||||
|
||||
createOrder(
|
||||
{
|
||||
customer_name: `${name} ${lastName}`.trim(),
|
||||
customer_phone: parseInt(phoneDigits, 10),
|
||||
customer_address: selectedProvinceData.name,
|
||||
shipping_method: "standart",
|
||||
payment_type_id: paymentType.id,
|
||||
region: selectedRegion,
|
||||
note: note || undefined,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
router.push(`/orders`);
|
||||
},
|
||||
if (
|
||||
!selectedRegion ||
|
||||
!selectedProvince ||
|
||||
!paymentType ||
|
||||
!phone ||
|
||||
!name
|
||||
) {
|
||||
console.warn("Missing required fields for order");
|
||||
return;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
if (!isClient) return null;
|
||||
const phoneDigits = formatPhoneForBackend(phone);
|
||||
if (phoneDigits.length !== 8) {
|
||||
console.warn("Phone number must be exactly 8 digits");
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedProvinceData = provinces.find(
|
||||
(p) => p.id === selectedProvince,
|
||||
);
|
||||
if (!selectedProvinceData) return;
|
||||
|
||||
createOrder(
|
||||
{
|
||||
customer_name: `${name} ${lastName}`.trim(),
|
||||
customer_phone: parseInt(phoneDigits, 10),
|
||||
customer_address: selectedProvinceData.name,
|
||||
shipping_method: "standart",
|
||||
payment_type_id: paymentType.id,
|
||||
region: selectedRegion,
|
||||
note: note || undefined,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
router.push(`/orders`);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -151,8 +168,8 @@ export default function CartPage() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError ) {
|
||||
|
||||
if (isError) {
|
||||
return <ErrorPage />;
|
||||
}
|
||||
if (cartItems.length === 0) {
|
||||
@@ -171,12 +188,10 @@ export default function CartPage() {
|
||||
{Object.entries(itemsBySeller).map(
|
||||
([sellerId, { seller, items }]) => (
|
||||
<div key={sellerId} className="mb-6">
|
||||
<p className="text-base font-semibold mb-3">{seller.name}</p>
|
||||
{/* <p className="text-base font-semibold mb-3">{seller.name}</p> */}
|
||||
<div className="space-y-4">
|
||||
{items.map((item) => {
|
||||
const price = parseFloat(
|
||||
item.product.price_amount || "0"
|
||||
);
|
||||
const price = parseInt(item.product.price_amount || "0");
|
||||
const quantity = item.product_quantity;
|
||||
const total = price * quantity;
|
||||
|
||||
@@ -200,7 +215,7 @@ export default function CartPage() {
|
||||
item.product.media?.[0]?.thumbnail,
|
||||
images:
|
||||
item.product.media?.map(
|
||||
(m) => m.images_800x800 || m.thumbnail
|
||||
(m) => m.images_800x800 || m.thumbnail,
|
||||
) || [],
|
||||
},
|
||||
}}
|
||||
@@ -212,7 +227,7 @@ export default function CartPage() {
|
||||
<Separator className="mt-4" />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
),
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
@@ -258,4 +273,4 @@ export default function CartPage() {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,8 +80,9 @@ export default function FavoritesPage() {
|
||||
price_color="#0059ff"
|
||||
height={360}
|
||||
width={250}
|
||||
button={false}
|
||||
button={true}
|
||||
stock={product.stock}
|
||||
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -11,12 +11,12 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||
const { locale, slug } = await params;
|
||||
|
||||
return {
|
||||
title: `Product ${slug} | E-Commerce`,
|
||||
title: `Product ${slug} | Smart-Electronics`,
|
||||
description: `View details for product ${slug}`,
|
||||
openGraph: {
|
||||
locale,
|
||||
type: "website",
|
||||
title: `Product ${slug} | E-Commerce`,
|
||||
title: `Product ${slug} | Smart-Electronics`,
|
||||
description: `View details for product ${slug}`,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,15 +3,8 @@ import { useRouter } from "next/navigation";
|
||||
import { useMemo } from "react";
|
||||
import type React from "react";
|
||||
import Link from "next/link";
|
||||
import { User, Truck, Heart, Store, LogOut } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useCart, useFavorites, useOrders, useCartCount } from "@/lib/hooks";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useTranslations } from "next-intl";
|
||||
@@ -20,7 +13,6 @@ import {
|
||||
CartIcon,
|
||||
FavoriteIcon,
|
||||
OrderIcon,
|
||||
ProfileIcon,
|
||||
} from "@/components/icons";
|
||||
|
||||
interface ActionButtonsProps {
|
||||
@@ -78,11 +70,6 @@ const cartCount = useCartCount()
|
||||
|
||||
const buttons: ActionButtonData[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
icon: <Store />,
|
||||
label: t("common.openStore"),
|
||||
href: "/openStore",
|
||||
},
|
||||
{
|
||||
icon: <OrderIcon />,
|
||||
label: t("common.orders"),
|
||||
@@ -119,7 +106,7 @@ const cartCount = useCartCount()
|
||||
return (
|
||||
<div className="hidden items-center gap-1 lg:flex">
|
||||
{/* Profile/Login Button with Dropdown */}
|
||||
{authLoading ? (
|
||||
{/* {authLoading ? (
|
||||
<div className="h-10 w-24 animate-pulse bg-gray-200 rounded" />
|
||||
) : isAuthenticated ? (
|
||||
<DropdownMenu>
|
||||
@@ -154,7 +141,7 @@ const cartCount = useCartCount()
|
||||
<ProfileIcon />
|
||||
<span className="text-xs text-gray-700">{t("common.login")}</span>
|
||||
</Button>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
{/* Other Action Buttons */}
|
||||
{buttons.map((button, index) => (
|
||||
|
||||
@@ -81,13 +81,13 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) {
|
||||
|
||||
sessionStorage.setItem(
|
||||
PENDING_CART_UPDATES_KEY,
|
||||
JSON.stringify(pending)
|
||||
JSON.stringify(pending),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Failed to save pending update:", error);
|
||||
}
|
||||
},
|
||||
[item.product_id]
|
||||
[item.product_id],
|
||||
);
|
||||
|
||||
// Remove from sessionStorage
|
||||
@@ -103,7 +103,7 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) {
|
||||
} else {
|
||||
sessionStorage.setItem(
|
||||
PENDING_CART_UPDATES_KEY,
|
||||
JSON.stringify(pending)
|
||||
JSON.stringify(pending),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -200,7 +200,7 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) {
|
||||
|
||||
retrySyncRef.current?.(quantity);
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -211,7 +211,7 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) {
|
||||
removeItem,
|
||||
onUpdate,
|
||||
clearPendingUpdate,
|
||||
]
|
||||
],
|
||||
);
|
||||
|
||||
// Update ref
|
||||
@@ -235,7 +235,7 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) {
|
||||
// Trigger sync after a short delay
|
||||
setTimeout(
|
||||
() => syncToServerRef.current?.(productPending.quantity),
|
||||
500
|
||||
500,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -336,14 +336,6 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) {
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="font-semibold text-base">{item.product.name}</h3>
|
||||
<p className="text-sm text-gray-600">
|
||||
{item.seller?.name || "Store"}
|
||||
</p>
|
||||
{availableStock <= 5 && (
|
||||
<p className="text-xs text-orange-600 font-medium">
|
||||
{t("only_left", { count: availableStock })}
|
||||
</p>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
|
||||
@@ -189,47 +189,12 @@ export default function OrderSummary({
|
||||
}`}
|
||||
/>
|
||||
{showValidation && !isPhoneValid && (
|
||||
<p className="text-xs text-red-500 mt-1">
|
||||
{t("requiredField")}
|
||||
</p>
|
||||
<p className="text-xs text-red-500 mt-1">{t("requiredField")}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Payment Type */}
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-semibold mb-3">{t("payment_type")}</h3>
|
||||
<div className="flex gap-2">
|
||||
{paymentTypes.map((type) => (
|
||||
<Card
|
||||
key={type.id}
|
||||
className={`flex-1 cursor-pointer transition-all ${
|
||||
paymentType?.id === type.id
|
||||
? "border-2 border-[#005bff] bg-blue-50"
|
||||
: showValidation && !paymentType
|
||||
? "border-2 border-red-500"
|
||||
: "border-2 border-gray-200"
|
||||
}`}
|
||||
onClick={() => onPaymentTypeChange(type)}
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center p-4 gap-2">
|
||||
<span
|
||||
className={`text-xs font-medium ${
|
||||
paymentType?.id === type.id ? "text-[#005bff]" : ""
|
||||
}`}
|
||||
>
|
||||
{type.name}
|
||||
</span>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
{showValidation && !paymentType && (
|
||||
<p className="text-xs text-red-500 mt-1">{t("requiredField")}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Region Selection */}
|
||||
<div className="mb-6">
|
||||
<Label className="text-lg font-semibold mb-3 block">
|
||||
|
||||
@@ -299,21 +299,7 @@ export default function ProductCard({
|
||||
)}
|
||||
</Carousel>
|
||||
|
||||
<button
|
||||
onClick={handleFavorite}
|
||||
disabled={isFavoriteToggling || isFavoriteLoading}
|
||||
className="absolute top-3 cursor-pointer right-3 z-10 rounded-full bg-white/80 p-2 hover:bg-white transition-all disabled:opacity-50"
|
||||
>
|
||||
{isFavoriteLoading ? (
|
||||
<div className="w-5 h-5 border-2 border-gray-300 border-t-gray-600 rounded-full animate-spin" />
|
||||
) : (
|
||||
<Heart
|
||||
className={`w-5 h-5 ${
|
||||
isFavorite ? "text-[#005bff] fill-[#005bff]" : "text-gray-700"
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
|
||||
|
||||
{hasMultipleImages && (
|
||||
<div className="absolute bottom-2 left-1/2 -translate-x-1/2 z-10 flex gap-1.5">
|
||||
@@ -365,8 +351,24 @@ export default function ProductCard({
|
||||
</p>
|
||||
</CardContent>
|
||||
|
||||
<div className="flex">
|
||||
<button
|
||||
onClick={handleFavorite}
|
||||
disabled={isFavoriteToggling || isFavoriteLoading}
|
||||
className=" cursor-pointer z-10 rounded-full bg-white/80 p-2 hover:bg-white transition-all disabled:opacity-50"
|
||||
>
|
||||
{isFavoriteLoading ? (
|
||||
<div className="w-5 h-5 border-2 border-gray-300 border-t-gray-600 rounded-full animate-spin" />
|
||||
) : (
|
||||
<Heart
|
||||
className={`w-5 h-5 ${
|
||||
isFavorite ? "text-[#005bff] fill-[#005bff]" : "text-gray-700"
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
{button && !isOutOfStock && (
|
||||
<div className="px-1">
|
||||
<div className="px-1 w-full">
|
||||
{!isInCart ? (
|
||||
<Button
|
||||
onClick={handleAddToCart}
|
||||
@@ -413,6 +415,7 @@ export default function ProductCard({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ export default function CollectionSection({ collection, locale }: Props) {
|
||||
price_color="#0059ff"
|
||||
height={360}
|
||||
width={250}
|
||||
button={true}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -47,31 +47,8 @@ export function ProductInfoCard({
|
||||
</>
|
||||
)}
|
||||
|
||||
{stock !== undefined && (
|
||||
<>
|
||||
<div className="flex justify-between items-center py-2">
|
||||
<span className="text-gray-500">{t("stock")}</span>
|
||||
<span
|
||||
className={`font-medium ${
|
||||
stock === 0
|
||||
? "text-red-500"
|
||||
: stock <= 5
|
||||
? "text-orange-600"
|
||||
: "text-green-600"
|
||||
}`}
|
||||
>
|
||||
{stock === 0
|
||||
? t("out_of_stock")
|
||||
: stock <= 5
|
||||
? `${t("only_left", { count: stock })}`
|
||||
: stock}
|
||||
</span>
|
||||
</div>
|
||||
<Separator />
|
||||
</>
|
||||
)}
|
||||
|
||||
{barcode && (
|
||||
{/* {barcode && (
|
||||
<>
|
||||
<div className="flex justify-between items-center py-2">
|
||||
<span className="text-gray-500">{t("barcode")}</span>
|
||||
@@ -79,7 +56,7 @@ export function ProductInfoCard({
|
||||
</div>
|
||||
<Separator />
|
||||
</>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
{colour && (
|
||||
<>
|
||||
|
||||
@@ -39,10 +39,6 @@ interface PendingUpdate {
|
||||
retryCount: number;
|
||||
}
|
||||
|
||||
// const DEBUG = true
|
||||
// const log = (...args: any[]) => {
|
||||
// if (DEBUG) console.log("[ProductPage]", ...args)
|
||||
// }
|
||||
|
||||
export default function ProductPageContent({ slug }: ProductDetailProps) {
|
||||
const [localQuantity, setLocalQuantity] = useState(1);
|
||||
@@ -524,14 +520,14 @@ export default function ProductPageContent({ slug }: ProductDetailProps) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ProductReviewsSection
|
||||
{/* <ProductReviewsSection
|
||||
reviews={reviews}
|
||||
averageRating={averageRating}
|
||||
isLoading={false}
|
||||
onWriteReview={() => setShowReviewModal(true)}
|
||||
/>
|
||||
|
||||
<RelatedProductsSection products={transformedRelatedProducts} />
|
||||
<RelatedProductsSection products={transformedRelatedProducts} /> */}
|
||||
</div>
|
||||
|
||||
<StockLimitModal
|
||||
|
||||
@@ -2,7 +2,6 @@ import Link from "next/link";
|
||||
import { Minus, Plus, Heart, ShoppingCart, Store } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||
|
||||
interface ProductPurchaseCardProps {
|
||||
price: string;
|
||||
@@ -163,28 +162,7 @@ export function ProductPurchaseCard({
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{channelName && (
|
||||
<Card className="p-6 rounded-xl">
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<Avatar className="w-14 h-14 bg-primary/10">
|
||||
<AvatarFallback className="bg-transparent">
|
||||
<Store className="h-6 w-6 text-primary" />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">{t("store")}</p>
|
||||
<h4 className="text-lg font-bold">{channelName}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="w-full cursor-pointer rounded-lg"
|
||||
>
|
||||
{t("write_to_store")}
|
||||
</Button>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "ecommerce",
|
||||
"name": "Smart-Electronics",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
Reference in New Issue
Block a user