diff --git a/app/[locale]/cart/page.tsx b/app/[locale]/cart/page.tsx index dafee5e..57609ae 100644 --- a/app/[locale]/cart/page.tsx +++ b/app/[locale]/cart/page.tsx @@ -136,7 +136,7 @@ export default function CartPage() { shipping_method: "standart", payment_type_id: paymentType.id, region: selectedRegion, - note: note || undefined, + notes: note || undefined, }, { onSuccess: () => { diff --git a/app/[locale]/globals.css b/app/[locale]/globals.css index ec3033f..fe063b6 100644 --- a/app/[locale]/globals.css +++ b/app/[locale]/globals.css @@ -44,72 +44,72 @@ } :root { - --radius: 0.625rem; - --background: #eff3f6; - --foreground: oklch(0.141 0.005 285.823); - --card: oklch(1 0 0); - --card-foreground: oklch(0.141 0.005 285.823); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.141 0.005 285.823); - --primary: oklch(0.21 0.006 285.885); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.967 0.001 286.375); - --secondary-foreground: oklch(0.21 0.006 285.885); - --muted: oklch(0.967 0.001 286.375); - --muted-foreground: oklch(0.552 0.016 285.938); - --accent: oklch(0.967 0.001 286.375); - --accent-foreground: oklch(0.21 0.006 285.885); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.92 0.004 286.32); - --input: oklch(0.92 0.004 286.32); - --ring: oklch(0.705 0.015 286.067); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.141 0.005 285.823); - --sidebar-primary: oklch(0.21 0.006 285.885); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.967 0.001 286.375); - --sidebar-accent-foreground: oklch(0.21 0.006 285.885); - --sidebar-border: oklch(0.92 0.004 286.32); - --sidebar-ring: oklch(0.705 0.015 286.067); + --radius: 1rem; + --background: #fafafa; + --foreground: #0a0a0a; + --card: #ffffff; + --card-foreground: #0a0a0a; + --popover: #ffffff; + --popover-foreground: #0a0a0a; + --primary: #0a0a0a; + --primary-foreground: #ffffff; + --secondary: #f5f5f5; + --secondary-foreground: #0a0a0a; + --muted: #f5f5f5; + --muted-foreground: #737373; + --accent: #f5f5f5; + --accent-foreground: #0a0a0a; + --destructive: #ef4444; + --border: #e5e5e5; + --input: #e5e5e5; + --ring: #0a0a0a; + --chart-1: #6366f1; + --chart-2: #8b5cf6; + --chart-3: #ec4899; + --chart-4: #f59e0b; + --chart-5: #10b981; + --sidebar: #ffffff; + --sidebar-foreground: #0a0a0a; + --sidebar-primary: #0a0a0a; + --sidebar-primary-foreground: #ffffff; + --sidebar-accent: #f5f5f5; + --sidebar-accent-foreground: #0a0a0a; + --sidebar-border: #e5e5e5; + --sidebar-ring: #0a0a0a; } .dark { - --background: oklch(0.141 0.005 285.823); - --foreground: oklch(0.985 0 0); - --card: oklch(0.21 0.006 285.885); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.21 0.006 285.885); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.92 0.004 286.32); - --primary-foreground: oklch(0.21 0.006 285.885); - --secondary: oklch(0.274 0.006 286.033); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.274 0.006 286.033); - --muted-foreground: oklch(0.705 0.015 286.067); - --accent: oklch(0.274 0.006 286.033); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.552 0.016 285.938); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.21 0.006 285.885); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.274 0.006 286.033); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.552 0.016 285.938); + --background: #0a0a0a; + --foreground: #fafafa; + --card: #171717; + --card-foreground: #fafafa; + --popover: #171717; + --popover-foreground: #fafafa; + --primary: #fafafa; + --primary-foreground: #0a0a0a; + --secondary: #262626; + --secondary-foreground: #fafafa; + --muted: #262626; + --muted-foreground: #a3a3a3; + --accent: #262626; + --accent-foreground: #fafafa; + --destructive: #dc2626; + --border: #262626; + --input: #262626; + --ring: #fafafa; + --chart-1: #818cf8; + --chart-2: #a78bfa; + --chart-3: #f472b6; + --chart-4: #fbbf24; + --chart-5: #34d399; + --sidebar: #171717; + --sidebar-foreground: #fafafa; + --sidebar-primary: #fafafa; + --sidebar-primary-foreground: #0a0a0a; + --sidebar-accent: #262626; + --sidebar-accent-foreground: #fafafa; + --sidebar-border: #262626; + --sidebar-ring: #fafafa; } @layer base { @@ -117,13 +117,52 @@ @apply border-border outline-ring/50; } body { - @apply bg-background text-foreground; + @apply bg-background text-foreground antialiased; } } +/* Toast Customization */ +[data-sonner-toast] { + @apply rounded-2xl shadow-lg border-0; +} + [data-sonner-toast] [data-description] { - color: #000 !important; - opacity: 0.9; + color: #525252 !important; + opacity: 1; +} + +[data-sonner-toast][data-type="success"] { + @apply bg-emerald-50 text-emerald-900; +} + +[data-sonner-toast][data-type="error"] { + @apply bg-red-50 text-red-900; +} + +/* Scrollbar Styling */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + @apply bg-transparent; +} + +::-webkit-scrollbar-thumb { + @apply bg-gray-300 rounded-full; +} + +::-webkit-scrollbar-thumb:hover { + @apply bg-gray-400; +} + +.dark ::-webkit-scrollbar-thumb { + @apply bg-gray-700; +} + +.dark ::-webkit-scrollbar-thumb:hover { + @apply bg-gray-600; } @layer utilities { @@ -133,47 +172,109 @@ .bg-bg { background-color: var(--bg); } + + /* Custom Gradient Utilities */ + .gradient-primary { + background: linear-gradient(135deg, #0a0a0a 0%, #262626 100%); + } + + .gradient-card { + background: linear-gradient(145deg, #fafafa 0%, #f5f5f5 100%); + } + + /* Animation Utilities */ + .animate-fade-in { + animation: fadeIn 0.3s ease-in-out; + } + + .animate-scale-up { + animation: scaleUp 0.2s ease-in-out; + } + + .animate-slide-up { + animation: slideUp 0.3s ease-out; + } + + /* Shopping Cart Animations */ .stroke-primary { - stroke: #005bff; + stroke: #0a0a0a; } .stroke-track { - stroke: hsla(var(--hue), 10%, 10%, 0.1); - transition: stroke var(--trans-dur); + stroke: rgba(10, 10, 10, 0.1); + transition: stroke 0.3s ease; } + @media (prefers-color-scheme: dark) { .stroke-track { - stroke: hsla(var(--hue), 10%, 90%, 0.1); + stroke: rgba(250, 250, 250, 0.1); } } .animate-msg { animation: msg 0.3s 13.7s linear forwards; } + .animate-msgLast { animation: msg 0.3s 14s linear reverse forwards; } + .animate-cartLines { animation: cartLines 2s ease-in-out infinite; } + .animate-cartTop { animation: cartTop 2s ease-in-out infinite; } + .animate-cartWheel1 { animation: cartWheel1 2s ease-in-out infinite; transform: rotate(-0.25turn); transform-origin: 43px 111px; } + .animate-cartWheel2 { animation: cartWheel2 2s ease-in-out infinite; transform: rotate(0.25turn); transform-origin: 102px 111px; } + .animate-cartWheelStroke { animation: cartWheelStroke 2s ease-in-out infinite; } } +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes scaleUp { + from { + transform: scale(0.95); + opacity: 0; + } + to { + transform: scale(1); + opacity: 1; + } +} + +@keyframes slideUp { + from { + transform: translateY(10px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + @keyframes msg { from { opacity: 1; @@ -188,6 +289,7 @@ visibility: hidden; } } + @keyframes cartLines { from, to { @@ -198,6 +300,7 @@ opacity: 1; } } + @keyframes cartTop { from { stroke-dashoffset: -338; @@ -209,6 +312,7 @@ stroke-dashoffset: 338; } } + @keyframes cartWheel1 { from { transform: rotate(-0.25turn); @@ -217,6 +321,7 @@ transform: rotate(2.75turn); } } + @keyframes cartWheel2 { from { transform: rotate(0.25turn); @@ -225,6 +330,7 @@ transform: rotate(3.25turn); } } + @keyframes cartWheelStroke { from, to { @@ -234,3 +340,21 @@ stroke-dashoffset: 40.84; } } + +/* Focus Visible Improvements */ +*:focus-visible { + @apply outline-2 outline-offset-2 outline-gray-900; +} + +.dark *:focus-visible { + @apply outline-gray-100; +} + +/* Selection Styling */ +::selection { + @apply bg-gray-900 text-white; +} + +.dark ::selection { + @apply bg-gray-100 text-gray-900; +} diff --git a/components/layout/Header.tsx b/components/layout/Header.tsx index 6865c59..0216724 100644 --- a/components/layout/Header.tsx +++ b/components/layout/Header.tsx @@ -54,10 +54,14 @@ export default function Header({ locale = "ru" }: HeaderProps) { return ( <> -
-
-
- +
+
+
+ {/* Logo */} +
+ {/* Catalog Button - Desktop */} -
+ {/* Mobile Search & Language */} +
+ {/* Desktop Language Selector */}
+ {/* Desktop Search Bar */} + {/* Action Buttons */} { if (!favoritesData) return 0; return Array.isArray(favoritesData) ? favoritesData.length : 0; }, [favoritesData]); - // Calculate orders count const ordersCount = useMemo(() => { if (!ordersData) return 0; return Array.isArray(ordersData) ? ordersData.length : 0; }, [ordersData]); - const handleLogout = () => { + const handleLogout = () => { logout(undefined, { onSuccess: () => { router.push(`/${locale}`); - router.refresh(); - } + router.refresh(); + }, }); }; @@ -100,50 +93,11 @@ const cartCount = useCartCount() cartCount, cartLoading, t, - ] + ], ); return (
- {/* Profile/Login Button with Dropdown */} - {/* {authLoading ? ( -
- ) : isAuthenticated ? ( - - - - - - router.push(`/${locale}/me`)}> - - {t("profile")} - - - - {isLoggingOut ? t("logging_out") : t("common.logout")} - - - - ) : ( - - )} */} - - {/* Other Action Buttons */} {buttons.map((button, index) => ( ))} @@ -163,25 +117,28 @@ function ActionButton({ ); diff --git a/components/layout/ui/CategoryMenu.tsx b/components/layout/ui/CategoryMenu.tsx index 874892e..55adeae 100644 --- a/components/layout/ui/CategoryMenu.tsx +++ b/components/layout/ui/CategoryMenu.tsx @@ -5,6 +5,7 @@ import { useState, useEffect, useRef } from "react"; import Link from "next/link"; import { useCategories } from "@/lib/hooks"; import { Skeleton } from "@/components/ui/skeleton"; +import { ChevronRight } from "lucide-react"; interface CategoryMenuProps { isOpen: boolean; @@ -16,25 +17,21 @@ export default function CategoryMenu({ isOpen, onClose }: CategoryMenuProps) { const { data: categories, isLoading } = useCategories(); const menuRef = useRef(null); - // Click outside to close useEffect(() => { if (!isOpen) return; const handleClickOutside = (event: MouseEvent) => { const target = event.target as HTMLElement; - if (target.closest("[data-catalog-trigger]")) { return; } - if (menuRef.current && !menuRef.current.contains(target)) { onClose(); } }; - // Add listener after a small delay to prevent immediate closing const timeoutId = setTimeout(() => { document.addEventListener("mousedown", handleClickOutside); }, 100); @@ -45,7 +42,6 @@ export default function CategoryMenu({ isOpen, onClose }: CategoryMenuProps) { }; }, [isOpen, onClose]); - // ESC key to close useEffect(() => { if (!isOpen) return; @@ -67,20 +63,25 @@ export default function CategoryMenu({ isOpen, onClose }: CategoryMenuProps) { return ( <> -
+ {/* Overlay */} +
{/* Menu */}
-
+
{activeCategory?.children && ( @@ -101,6 +102,7 @@ interface CategoryListProps { isLoading: boolean; onCategoryHover: (index: number) => void; onCategoryClick: () => void; + hoveredIndex: number | null; } function CategoryList({ @@ -108,13 +110,16 @@ function CategoryList({ isLoading, onCategoryHover, onCategoryClick, + hoveredIndex, }: CategoryListProps) { return ( -
-
+
+
{isLoading - ? [1, 2, 3, 4, 5].map((i) => ( - + ? Array.from({ length: 8 }).map((_, i) => ( +
+ +
)) : categories.map((category, index) => ( onCategoryHover(index)} - className="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-gray-100 hover:text-primary transition-colors" + className={`flex items-center justify-between gap-3 mx-2 px-4 py-3.5 rounded-lg transition-all duration-200 group ${ + hoveredIndex === index + ? "bg-gray-900 text-white shadow-md" + : "hover:bg-gray-100 text-gray-900" + }`} > - {category.icon_class && ( - +
+ {category.icon_class && ( + + )} + {category.name} +
+ {category.children && category.children.length > 0 && ( + )} - {category.name} ))}
@@ -145,17 +169,24 @@ function SubcategoryList({ onSubcategoryClick, }: SubcategoryListProps) { return ( -
-

{category.name}

-
+
+
+

+ {category.name} +

+
+
+ +
{category.children?.map((subCategory: any) => ( - {subCategory.name} + {subCategory.name} + ))}
diff --git a/components/layout/ui/LanguageSelector.tsx b/components/layout/ui/LanguageSelector.tsx index b55108e..abf2d08 100644 --- a/components/layout/ui/LanguageSelector.tsx +++ b/components/layout/ui/LanguageSelector.tsx @@ -43,17 +43,21 @@ export default function LanguageSelector() { return ( handleSearch(e.target.value)} - className="h-10 rounded-xl focus:border-[#005bff] focus-visible:border-[#005bff] focus-visible:ring-0 active:border-[#005bff]" - autoFocus - /> - {isLoading && ( - - )} +
+ handleSearch(e.target.value)} + className="h-12 rounded-lg pl-12 pr-10 border-gray-200 focus:border-gray-900 focus-visible:border-gray-900 focus-visible:ring-0 transition-colors" + autoFocus + /> + + {isLoading && ( + + )} +
@@ -142,26 +155,32 @@ export default function SearchBar({ } return ( -
+
- handleSearch(e.target.value)} - className="border-[#005bff] w-full rounded-xl border-2 focus-visible:ring-0 bg-white px-2" - /> +
+ handleSearch(e.target.value)} + className="border w-full rounded-lg h-11 border-gray-900 bg-white pl-12 pr-4 focus-visible:ring-2 focus-visible:ring-gray-300 transition-all" + /> + +
{isLoading && ( - + )}
); -} \ No newline at end of file +} diff --git a/features/cart/components/CartItemCard.tsx b/features/cart/components/CartItemCard.tsx index 2927039..f0a2d85 100644 --- a/features/cart/components/CartItemCard.tsx +++ b/features/cart/components/CartItemCard.tsx @@ -20,7 +20,6 @@ interface CartItemCardProps { onUpdate?: () => void; } -// Session Storage Key const PENDING_CART_UPDATES_KEY = "pendingCartUpdates"; interface PendingUpdate { @@ -32,39 +31,33 @@ interface PendingUpdate { export default function CartItemCard({ item, onUpdate }: CartItemCardProps) { const t = useTranslations(); - // Local UI State (Instant feedback) const [localQuantity, setLocalQuantity] = useState(item.quantity); - - // Sync State const [isSyncing, setIsSyncing] = useState(false); const [syncError, setSyncError] = useState(false); - - // Stock limit modal const [showStockModal, setShowStockModal] = useState(false); - // Refs const debounceTimerRef = useRef(undefined); const isRequestInFlightRef = useRef(false); const pendingQuantityRef = useRef(null); const retryCountRef = useRef(0); const retryTimerRef = useRef(undefined); + const isInitializedRef = useRef(false); - // Function refs to solve circular dependency const syncToServerRef = useRef<((quantity: number) => void) | null>(null); const retrySyncRef = useRef<((quantity: number) => void) | null>(null); const { mutate: updateQuantity } = useUpdateCartItemQuantity(); const { mutate: removeItem, isPending: isRemoving } = useRemoveFromCart(); - // Get available stock const availableStock = item.product.stock || 0; - // Initialize from server state useEffect(() => { setLocalQuantity(item.quantity); + if (!isInitializedRef.current) { + isInitializedRef.current = true; + } }, [item.quantity]); - // Save to sessionStorage const savePendingUpdate = useCallback( (quantity: number) => { try { @@ -90,7 +83,6 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) { [item.product_id], ); - // Remove from sessionStorage const clearPendingUpdate = useCallback(() => { try { const stored = sessionStorage.getItem(PENDING_CART_UPDATES_KEY); @@ -112,7 +104,6 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) { } }, [item.product_id]); - // Exponential backoff retry const retrySync = useCallback((quantity: number) => { const maxRetries = 4; const retryCount = retryCountRef.current; @@ -123,7 +114,7 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) { return; } - const delay = Math.min(1000 * Math.pow(2, retryCount), 16000); // Max 16s + const delay = Math.min(1000 * Math.pow(2, retryCount), 16000); retryCountRef.current++; retryTimerRef.current = setTimeout(() => { @@ -131,19 +122,15 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) { }, delay); }, []); - // Update ref retrySyncRef.current = retrySync; - // Sync to server const syncToServer = useCallback( (quantity: number) => { - // If already syncing, queue this update if (isRequestInFlightRef.current) { pendingQuantityRef.current = quantity; return; } - // Mark as syncing isRequestInFlightRef.current = true; setIsSyncing(true); setSyncError(false); @@ -157,7 +144,6 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) { clearPendingUpdate(); onUpdate?.(); - // Process queued update if any if (pendingQuantityRef.current !== null) { const nextQuantity = pendingQuantityRef.current; pendingQuantityRef.current = null; @@ -181,7 +167,6 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) { clearPendingUpdate(); onUpdate?.(); - // Process queued update if any if (pendingQuantityRef.current !== null) { const nextQuantity = pendingQuantityRef.current; pendingQuantityRef.current = null; @@ -192,7 +177,6 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) { console.error("Update failed:", error); isRequestInFlightRef.current = false; - // Rollback on error after retries exhausted if (retryCountRef.current >= 3) { setLocalQuantity(item.quantity); clearPendingUpdate(); @@ -214,55 +198,29 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) { ], ); - // Update ref syncToServerRef.current = syncToServer; - // Load pending updates from sessionStorage on mount useEffect(() => { - const loadPendingUpdates = () => { - try { - const stored = sessionStorage.getItem(PENDING_CART_UPDATES_KEY); - if (stored) { - const pending: Record = JSON.parse(stored); - const productPending = pending[item.product_id]; + if (!isInitializedRef.current) { + return; + } - if (productPending && productPending.quantity !== item.quantity) { - // Apply pending update - setLocalQuantity(productPending.quantity); - pendingQuantityRef.current = productPending.quantity; - retryCountRef.current = productPending.retryCount; - - // Trigger sync after a short delay - setTimeout( - () => syncToServerRef.current?.(productPending.quantity), - 500, - ); - } - } - } catch (error) { - console.error("Failed to load pending updates:", error); - } - }; - - loadPendingUpdates(); - }, [item.product_id, item.quantity]); - - // Debounced sync - useEffect(() => { - // Clear existing timers if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } - // If local quantity matches server, no sync needed if (localQuantity === item.quantity) { return; } - // Save to sessionStorage immediately + if (localQuantity <= 0 && item.quantity > 0) { + // Delete operation + } else if (localQuantity <= 0) { + return; + } + savePendingUpdate(localQuantity); - // Debounce the API call debounceTimerRef.current = setTimeout(() => { syncToServerRef.current?.(localQuantity); }, 800); @@ -274,7 +232,6 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) { }; }, [localQuantity, item.quantity, savePendingUpdate]); - // Cleanup useEffect(() => { return () => { if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current); @@ -286,13 +243,11 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) { e.preventDefault(); e.stopPropagation(); - // Check stock limit if (localQuantity >= availableStock) { setShowStockModal(true); return; } - // Optimistic update (instant UI feedback) setLocalQuantity((prev) => prev + 1); }; @@ -305,7 +260,6 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) { return; } - // Optimistic update (instant UI feedback) setLocalQuantity((prev) => prev - 1); }; @@ -323,49 +277,70 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) { return ( <> - -
+ +
+ {/* Product Image & Info */}
-
+
{item.product.name}
+
-

{item.product.name}

+

+ {item.product.name} +

+ {/*

+ {item.seller?.name || "Store"} +

*/} + + {/* {availableStock <= 5 && ( +
+
+

+ {t("only_left", { count: availableStock })} +

+
+ )} */} +
-
-
-

+ {/* Price & Quantity */} +

+
+

{t("unit_price")}{" "} - {item.price_formatted} + + {item.price_formatted} +

{item.discount_formatted && item.discount_formatted !== "0 TMT" && ( -

+

{t("discount")} {item.discount_formatted}

)} +
- + {t("total_price")} - + {( parseFloat(item.product.price_amount || "0") * localQuantity ).toFixed(2)}{" "} @@ -374,23 +349,31 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) {
-
+ {/* Quantity Controls */} +
-
- {localQuantity} +
+ {isSyncing ? ( +
+
+
+ ) : ( + localQuantity + )} {syncError && ( )} @@ -400,16 +383,16 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) { variant="outline" size="icon" onClick={handleQuantityIncrease} - // disabled={localQuantity >= availableStock} - className={`rounded-lg cursor-pointer bg-blue-50 ${ - isSyncing ? "opacity-70" : "" - } ${ + disabled={isSyncing || localQuantity >= availableStock} + className={`rounded-[10px] h-10 w-10 cursor-pointer border-2 transition-all duration-200 ${ localQuantity >= availableStock - ? "opacity-50 cursor-not-allowed" - : "" - }`} + ? "opacity-30 cursor-not-allowed border-gray-200" + : "border-gray-900 bg-gray-900 hover:bg-gray-800" + } ${isSyncing ? "opacity-50" : ""}`} > - + = availableStock ? "text-gray-400" : "text-white"}`} + />
@@ -418,27 +401,27 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) { {/* Stock Limit Modal */} - +
-
- +
+
- + {t("stock_limit_title")} - + {t("stock_limit_message", { product: item.product.name, stock: availableStock, })} -
+
diff --git a/features/cart/components/DeliveryTypeSelector.tsx b/features/cart/components/DeliveryTypeSelector.tsx index 3e8022c..d96160a 100644 --- a/features/cart/components/DeliveryTypeSelector.tsx +++ b/features/cart/components/DeliveryTypeSelector.tsx @@ -1,52 +1,62 @@ -"use client" -import { Truck, Warehouse } from "lucide-react" -import { Card } from "@/components/ui/card" -import { useTranslations } from "next-intl" -import type { DeliveryType } from "@/lib/types/api" +"use client"; +import { Truck, Warehouse } from "lucide-react"; +import { Card } from "@/components/ui/card"; +import { useTranslations } from "next-intl"; +import type { DeliveryType } from "@/lib/types/api"; interface DeliveryTypeSelectorProps { - selectedType: DeliveryType - onSelect: (type: DeliveryType) => void + selectedType: DeliveryType; + onSelect: (type: DeliveryType) => void; } export default function DeliveryTypeSelector({ selectedType, onSelect, }: DeliveryTypeSelectorProps) { - const t = useTranslations() - + const t = useTranslations(); + const deliveryOptions: { - type: DeliveryType - label: string - icon: typeof Truck + type: DeliveryType; + label: string; + icon: typeof Truck; }[] = [ { type: "SELECTED_DELIVERY", label: t("delivery"), icon: Truck }, { type: "PICK_UP", label: t("pickup"), icon: Warehouse }, - ] + ]; return ( -
-

{t("delivery_type")}

-
+
+

+ {t("delivery_type")} +

+
{deliveryOptions.map(({ type, label, icon: Icon }) => ( onSelect(type)} > -
- +
- + > + +
+ {label}
@@ -54,5 +64,5 @@ export default function DeliveryTypeSelector({ ))}
- ) -} \ No newline at end of file + ); +} diff --git a/features/cart/components/EmptyCart.tsx b/features/cart/components/EmptyCart.tsx index f82b050..f65ae0e 100644 --- a/features/cart/components/EmptyCart.tsx +++ b/features/cart/components/EmptyCart.tsx @@ -4,24 +4,28 @@ import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; export default function EmptyCart() { - const t=useTranslations(); - const router=useRouter(); + const t = useTranslations(); + const router = useRouter(); + return (
-
-
- +
+
+
-

+

{t("cart_empty")}

-

+

{t("cart_empty_message")}

-
diff --git a/features/cart/components/OrderSummary.tsx b/features/cart/components/OrderSummary.tsx index 1c0c2cc..e33e257 100644 --- a/features/cart/components/OrderSummary.tsx +++ b/features/cart/components/OrderSummary.tsx @@ -111,7 +111,6 @@ export default function OrderSummary({ } const digitsOnly = input.substring(prefix.length).replace(/\D/g, ""); - const limitedDigits = digitsOnly.substring(0, 8); let formattedPhone = prefix; @@ -134,15 +133,15 @@ export default function OrderSummary({ }; return ( - + {/* Customer Information */} -
-

+
+

{t("customer_information")}

-
+
-
+
-
{/* Region Selection */} -
-