dressed_for_succes_store/frontend old/pages/cart.tsx
2025-03-11 22:42:30 +07:00

301 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import Link from 'next/link';
import Image from 'next/image';
import { Trash2, Plus, Minus, ShoppingBag } from 'lucide-react';
import Header from '../components/Header';
import Footer from '../components/Footer';
import cartService, { Cart, CartItem } from '../services/cart';
import authService from '../services/auth';
export default function CartPage() {
const router = useRouter();
const [cart, setCart] = useState<Cart | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Загрузка корзины при монтировании компонента
useEffect(() => {
const fetchCart = async () => {
try {
// Проверяем, авторизован ли пользователь
if (!authService.isAuthenticated()) {
router.push('/login?redirect=/cart');
return;
}
setLoading(true);
const cartData = await cartService.getCart();
setCart(cartData);
setError(null);
} catch (err) {
console.error('Ошибка при загрузке корзины:', err);
setError('Не удалось загрузить корзину. Пожалуйста, попробуйте позже.');
} finally {
setLoading(false);
}
};
fetchCart();
}, [router]);
// Обработчик изменения количества товара
const handleQuantityChange = async (itemId: number, newQuantity: number) => {
if (newQuantity < 1) return;
try {
// Сохраняем текущий порядок элементов
const currentItems = cart?.items || [];
await cartService.updateCartItem(itemId, { quantity: newQuantity });
// Обновляем корзину после изменения, но сохраняем порядок
const updatedCart = await cartService.getCart();
// Сортируем элементы в том же порядке, что и были
if (updatedCart && currentItems.length > 0) {
const itemMap = new Map(currentItems.map(item => [item.id, item]));
const sortedItems = updatedCart.items.sort((a, b) => {
const indexA = currentItems.findIndex(item => item.id === a.id);
const indexB = currentItems.findIndex(item => item.id === b.id);
return indexA - indexB;
});
updatedCart.items = sortedItems;
}
setCart(updatedCart);
} catch (err) {
console.error('Ошибка при обновлении количества:', err);
setError('Не удалось обновить количество товара. Пожалуйста, попробуйте позже.');
}
};
// Обработчик удаления товара из корзины
const handleRemoveItem = async (itemId: number) => {
try {
// Сохраняем текущий порядок элементов
const currentItems = cart?.items.filter(item => item.id !== itemId) || [];
await cartService.removeFromCart(itemId);
// Обновляем корзину после удаления, но сохраняем порядок
const updatedCart = await cartService.getCart();
// Сортируем элементы в том же порядке, что и были
if (updatedCart && currentItems.length > 0) {
const sortedItems = updatedCart.items.sort((a, b) => {
const indexA = currentItems.findIndex(item => item.id === a.id);
const indexB = currentItems.findIndex(item => item.id === b.id);
return indexA - indexB;
});
updatedCart.items = sortedItems;
}
setCart(updatedCart);
} catch (err) {
console.error('Ошибка при удалении товара:', err);
setError('Не удалось удалить товар из корзины. Пожалуйста, попробуйте позже.');
}
};
// Обработчик очистки корзины
const handleClearCart = async () => {
try {
await cartService.clearCart();
// Обновляем корзину после очистки
const updatedCart = await cartService.getCart();
setCart(updatedCart);
} catch (err) {
console.error('Ошибка при очистке корзины:', err);
setError('Не удалось очистить корзину. Пожалуйста, попробуйте позже.');
}
};
// Переход к оформлению заказа
const handleCheckout = () => {
router.push('/checkout');
};
// Функция для корректного отображения URL изображения
const getImageUrl = (imageUrl: string | undefined): string => {
if (!imageUrl) return '/placeholder-image.jpg';
// Проверяем, начинается ли URL с http или https
if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {
return imageUrl;
}
// Формируем базовый URL для изображений
const baseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:8000/api';
const apiBaseUrl = baseUrl.replace(/\/api$/, ''); // Убираем '/api' в конце, если есть
// Если URL начинается с /, добавляем базовый URL API
if (imageUrl.startsWith('/')) {
return `${apiBaseUrl}${imageUrl}`;
}
// В остальных случаях добавляем базовый URL API и /
return `${apiBaseUrl}/${imageUrl}`;
};
return (
<div className="min-h-screen flex flex-col">
<Header />
<main className="flex-grow pt-24 pb-16">
<div className="container mx-auto px-4">
<h1 className="text-2xl md:text-3xl font-bold mb-8 text-center">Корзина</h1>
{loading ? (
<div className="flex justify-center items-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-gray-900"></div>
</div>
) : error ? (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
) : cart && cart.items.length > 0 ? (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Список товаров в корзине */}
<div className="lg:col-span-2">
<div className="bg-white rounded-lg shadow-md overflow-hidden">
<div className="p-6">
<div className="flex justify-between items-center mb-6">
<h2 className="text-lg font-semibold">Товары в корзине ({cart.items_count})</h2>
<button
onClick={handleClearCart}
className="text-sm text-red-600 hover:text-red-800 transition-colors"
>
Очистить корзину
</button>
</div>
<div className="divide-y divide-gray-200">
{cart.items.map((item) => (
<div key={item.id} className="py-6 flex flex-col md:flex-row">
{/* Изображение товара */}
<div className="flex-shrink-0 w-full md:w-24 h-24 mb-4 md:mb-0 relative">
{item.product_image ? (
<Image
src={getImageUrl(item.product_image)}
alt={item.product_name}
fill
className="object-cover rounded-md"
/>
) : (
<div className="w-full h-full bg-gray-200 rounded-md flex items-center justify-center">
<ShoppingBag className="w-8 h-8 text-gray-400" />
</div>
)}
</div>
{/* Информация о товаре */}
<div className="md:ml-6 flex-grow">
<div className="flex flex-col md:flex-row md:justify-between">
<div>
<h3 className="text-base font-medium">
<Link href={`/product/${item.slug}`} className="hover:text-gray-600 transition-colors">
{item.product_name}
</Link>
</h3>
<p className="text-sm text-gray-500 mt-1">Вариант: {item.variant_name}</p>
</div>
<div className="mt-2 md:mt-0 text-right">
<p className="text-base font-medium">
{item.total_price.toLocaleString('ru-RU')}
</p>
<p className="text-sm text-gray-500 mt-1">
{item.product_price.toLocaleString('ru-RU')} за шт.
</p>
</div>
</div>
{/* Управление количеством и удаление */}
<div className="flex justify-between items-center mt-4">
<div className="flex items-center border border-gray-300 rounded-md">
<button
onClick={() => handleQuantityChange(item.id, item.quantity - 1)}
className="px-3 py-1 text-gray-600 hover:bg-gray-100 transition-colors"
disabled={item.quantity <= 1}
>
<Minus className="w-4 h-4" />
</button>
<span className="px-3 py-1 text-center w-10">{item.quantity}</span>
<button
onClick={() => handleQuantityChange(item.id, item.quantity + 1)}
className="px-3 py-1 text-gray-600 hover:bg-gray-100 transition-colors"
>
<Plus className="w-4 h-4" />
</button>
</div>
<button
onClick={() => handleRemoveItem(item.id)}
className="text-red-600 hover:text-red-800 transition-colors"
>
<Trash2 className="w-5 h-5" />
</button>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
{/* Сводка заказа */}
<div className="lg:col-span-1">
<div className="bg-white rounded-lg shadow-md p-6 sticky top-24">
<h2 className="text-lg font-semibold mb-6">Сводка заказа</h2>
<div className="space-y-4 mb-6">
<div className="flex justify-between">
<span className="text-gray-600">Товары ({cart.items_count})</span>
<span>{cart.total_amount.toLocaleString('ru-RU')} </span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Доставка</span>
<span>Бесплатно</span>
</div>
<div className="border-t border-gray-200 pt-4 flex justify-between font-semibold">
<span>Итого</span>
<span>{cart.total_amount.toLocaleString('ru-RU')} </span>
</div>
</div>
<button
onClick={handleCheckout}
className="w-full bg-black text-white py-3 rounded-md hover:bg-gray-800 transition-colors"
>
Оформить заказ
</button>
<div className="mt-6">
<Link href="/all-products" className="text-center block text-gray-600 hover:text-gray-800 transition-colors">
Продолжить покупки
</Link>
</div>
</div>
</div>
</div>
) : (
<div className="text-center py-16">
<div className="flex justify-center mb-6">
<ShoppingBag className="w-16 h-16 text-gray-400" />
</div>
<h2 className="text-xl font-semibold mb-2">Ваша корзина пуста</h2>
<p className="text-gray-600 mb-8">Добавьте товары в корзину, чтобы оформить заказ</p>
<Link href="/all-products" className="bg-black text-white px-6 py-3 rounded-md hover:bg-gray-800 transition-colors">
Перейти к покупкам
</Link>
</div>
)}
</div>
</main>
<Footer />
</div>
);
}