import React, { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from '../store/store';
import { removeFromCart, updateCartItem } from '../store/actions/cartActions';
import { useNavigate } from 'react-router-dom';

import {
  Typography,
  IconButton,
  Button,
  Divider,
  TableContainer,
  Table,
  TableHead,
  TableRow,
  TableCell,
  TableBody,
  Paper,
  Grid,
  Box,
  Stack,
  Tooltip,
  Checkbox,
} from '@mui/material';
import {
  Delete as DeleteIcon,
  Add as AddIcon,
  Remove as RemoveIcon,
} from '@mui/icons-material';

const CartPage: React.FC = () => {
  const cartItems = useSelector((state: RootState) => state.cart.items);
  const dispatch = useDispatch();
  const navigate = useNavigate();

  // Состояние для выбранных товаров
  const [selectedItems, setSelectedItems] = useState<number[]>([]);

  // Обработка изменения состояния чекбокса
  const handleSelectItem = (productId: number) => {
    setSelectedItems((prevSelected) =>
      prevSelected.includes(productId)
        ? prevSelected.filter((id) => id !== productId)
        : [...prevSelected, productId]
    );
  };

  // Проверка, выбраны ли все товары
  const isAllSelected = cartItems.length > 0 && selectedItems.length === cartItems.length;

  // Обработка выбора всех товаров
  const handleSelectAll = () => {
    if (isAllSelected) {
      setSelectedItems([]);
    } else {
      const allIds = cartItems.map((item) => item.product.id);
      setSelectedItems(allIds);
    }
  };

  // Обработка удаления выбранных товаров
  const handleRemoveSelected = () => {
    selectedItems.forEach((id) => {
      dispatch(removeFromCart(id));
    });
    setSelectedItems([]);
  };

  // Рассчет общей цены выбранных товаров
  const totalPrice = cartItems.reduce((acc, item) => {
    if (selectedItems.includes(item.product.id)) {
      return acc + item.product.price * item.quantity;
    }
    return acc;
  }, 0);

  // Обработка перехода к оформлению с выбранными товарами
  const handleCheckout = () => {
    // Можно передать выбранные товары через состояние или URL
    // Например, используя Redux или контекст
    navigate('/checkout', { state: { selectedItems } });
  };

  // Если корзина изменилась, убедиться, что выбранные товары существуют
  useEffect(() => {
    setSelectedItems((prevSelected) =>
      prevSelected.filter((id) => cartItems.some((item) => item.product.id === id))
    );
  }, [cartItems]);

  return (
    <Box sx={{ mt: 4, px: { xs: 2, md: 4 } }}>
      <Typography variant="h4" gutterBottom>
        Корзина
      </Typography>
      {cartItems.length === 0 ? (
        <Typography variant="h6">Ваша корзина пуста</Typography>
      ) : (
        <Grid container spacing={4}>
          <Grid item xs={12} md={8}>
            <TableContainer component={Paper} sx={{ mb: 4 }}>
              <Table aria-label="cart table">
                <TableHead>
                  <TableRow>
                    <TableCell padding="checkbox">
                      <Checkbox
                        checked={isAllSelected}
                        onChange={handleSelectAll}
                        inputProps={{ 'aria-label': 'Выбрать все товары' }}
                      />
                    </TableCell>
                    <TableCell>Товар</TableCell>
                    <TableCell align="right">Цена</TableCell>
                    <TableCell align="center">Количество</TableCell>
                    <TableCell align="right">Итого</TableCell>
                    <TableCell align="center">Действия</TableCell>
                  </TableRow>
                </TableHead>
                <TableBody>
                  {cartItems.map((item) => {
                    const isItemSelected = selectedItems.includes(item.product.id);
                    return (
                      <TableRow key={item.product.id} hover>
                        <TableCell padding="checkbox">
                          <Checkbox
                            checked={isItemSelected}
                            onChange={() => handleSelectItem(item.product.id)}
                            inputProps={{ 'aria-labelledby': `checkbox-${item.product.id}` }}
                          />
                        </TableCell>
                        <TableCell component="th" scope="row">
                          <Stack direction="row" spacing={2} alignItems="center">
                            <img
                              src={item.product.image}
                              alt={item.product.name}
                              style={{ width: '60px', height: '60px', objectFit: 'cover' }}
                            />
                            <Typography variant="body1">
                              {item.product.name}
                            </Typography>
                          </Stack>
                        </TableCell>
                        <TableCell align="right">
                          {item.product.price.toLocaleString()} ₽
                        </TableCell>
                        <TableCell align="center">
                          <Box
                            sx={{
                              display: 'flex',
                              alignItems: 'center',
                              justifyContent: 'center',
                            }}
                          >
                            <IconButton
                              onClick={() =>
                                handleQuantityChange(
                                  item.product.id,
                                  item.quantity - 1
                                )
                              }
                              disabled={item.quantity <= 1}
                            >
                              <RemoveIcon />
                            </IconButton>
                            <Typography
                              variant="body1"
                              sx={{ mx: 1, minWidth: '24px', textAlign: 'center' }}
                            >
                              {item.quantity}
                            </Typography>
                            <IconButton
                              onClick={() =>
                                handleQuantityChange(
                                  item.product.id,
                                  item.quantity + 1
                                )
                              }
                            >
                              <AddIcon />
                            </IconButton>
                          </Box>
                        </TableCell>
                        <TableCell align="right">
                          {(item.product.price * item.quantity).toLocaleString()} ₽
                        </TableCell>
                        <TableCell align="center">
                          <Tooltip title="Удалить из корзины">
                            <IconButton
                              onClick={() => handleRemoveFromCart(item.product.id)}
                            >
                              <DeleteIcon color="error" />
                            </IconButton>
                          </Tooltip>
                        </TableCell>
                      </TableRow>
                    );
                  })}
                </TableBody>
              </Table>
            </TableContainer>
            {selectedItems.length > 0 && (
              <Button
                variant="outlined"
                color="error"
                onClick={handleRemoveSelected}
                startIcon={<DeleteIcon />}
              >
                Удалить выбранные
              </Button>
            )}
          </Grid>
          <Grid item xs={12} md={4}>
            <Paper sx={{ p: 3 }}>
              <Typography variant="h6" gutterBottom>
                Итого
              </Typography>
              <Divider sx={{ mb: 2 }} />
              <Stack direction="row" justifyContent="space-between" sx={{ mb: 1 }}>
                <Typography variant="body1">Сумма заказа:</Typography>
                <Typography variant="body1">{totalPrice.toLocaleString()} ₽</Typography>
              </Stack>
              {/* Если есть дополнительные сборы или скидки, их можно добавить здесь */}
              <Divider sx={{ mb: 2 }} />
              <Typography variant="h5" sx={{ fontWeight: 'bold', mb: 2 }}>
                Общая сумма: {totalPrice.toLocaleString()} ₽
              </Typography>
              <Button
                variant="contained"
                color="primary"
                fullWidth
                disabled={selectedItems.length === 0}
                onClick={handleCheckout}
              >
                Перейти к оформлению
              </Button>
            </Paper>
          </Grid>
        </Grid>
      )}
    </Box>
  );
};

export default CartPage;// src/components/Home.tsx

import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import products from '../data/products';

import {
  Grid,
  Card,
  CardMedia,
  CardContent,
  Typography,
  Button,
  TextField,
  Select,
  MenuItem,
  InputLabel,
  FormControl,
  Box,
} from '@mui/material';

const Home: React.FC = () => {
  const [searchTerm, setSearchTerm] = useState('');
  const [category, setCategory] = useState('');
  const [sortBy, setSortBy] = useState('');

  // Получаем список уникальных категорий
  const categories = Array.from(new Set(products.map((p) => p.category)));

  // Фильтрация товаров по поисковому запросу и категории
  const filteredProducts = products.filter((product) => {
    const matchesSearch = product.name
      .toLowerCase()
      .includes(searchTerm.toLowerCase());
    const matchesCategory = category ? product.category === category : true;
    return matchesSearch && matchesCategory;
  });

  // Сортировка товаров
  const sortedProducts = filteredProducts.slice().sort((a, b) => {
    if (sortBy === 'price_asc') {
      return a.price - b.price;
    } else if (sortBy === 'price_desc') {
      return b.price - a.price;
    } else if (sortBy === 'name_asc') {
      return a.name.localeCompare(b.name);
    } else if (sortBy === 'name_desc') {
      return b.name.localeCompare(a.name);
    } else {
      return products.indexOf(a) - products.indexOf(b); // Дефолтное расположение
    }
  });

  return (
    <div>
      <Typography variant="h4" gutterBottom>
        Наши товары
      </Typography>

      <Grid container spacing={2} sx={{ marginBottom: 4 }}>
        <Grid item xs={12} sm={8}>
          <TextField
            label="Поиск"
            variant="outlined"
            fullWidth
            value={searchTerm}
            onChange={(e) => setSearchTerm(e.target.value)}
          />
        </Grid>
        <Grid item xs={6} sm={2}>
          <FormControl variant="outlined" fullWidth>
            <InputLabel id="category-label">Категория</InputLabel>
            <Select
              labelId="category-label"
              label="Категория"
              value={category}
              onChange={(e) => setCategory(e.target.value as string)}
            >
              <MenuItem value="">
                <em>Все категории</em>
              </MenuItem>
              {categories.map((cat) => (
                <MenuItem value={cat} key={cat}>
                  {cat}
                </MenuItem>
              ))}
            </Select>
          </FormControl>
        </Grid>
        <Grid item xs={6} sm={2}>
          <FormControl variant="outlined" fullWidth>
            <InputLabel id="sort-label">Сортировать</InputLabel>
            <Select
              labelId="sort-label"
              label="Сортировать"
              value={sortBy}
              onChange={(e) => setSortBy(e.target.value as string)}
            >
              <MenuItem value="">
                <em>По умолчанию</em>
              </MenuItem>
              <MenuItem value="price_asc">Цена: по возрастанию</MenuItem>
              <MenuItem value="price_desc">Цена: по убыванию</MenuItem>
              <MenuItem value="name_asc">Название: А-Я</MenuItem>
              <MenuItem value="name_desc">Название: Я-А</MenuItem>
            </Select>
          </FormControl>
        </Grid>
      </Grid>

      <Grid container spacing={4}>
        {sortedProducts.length > 0 ? (
          sortedProducts.map((product) => (
            <Grid item xs={12} sm={6} md={4} key={product.id}>
              <Card>
                <CardMedia
                  component="img"
                  height="200"
                  image={product.image}
                  alt={product.name}
                />
                <CardContent>
                  <Typography variant="h6">{product.name}</Typography>
                  <Typography variant="body2" color="textSecondary" gutterBottom>
                    {product.description}
                  </Typography>
                  <Typography variant="h6">{product.price} ₽</Typography>
                  <Button
                    variant="contained"
                    color="primary"
                    component={Link}
                    to={`/product/${product.id}`}
                  >
                    Подробнее
                  </Button>
                </CardContent>
              </Card>
            </Grid>
          ))
        ) : (
          <Typography variant="h6">Товары не найдены</Typography>
        )}
      </Grid>
    </div>
  );
};

export default Home;import React, { useState, useContext } from 'react';
import {
  TextField,
  Button,
  Typography,
  Container,
  Grid,
  Paper,
} from '@mui/material';
import { AuthContext } from '../context/AuthContext';
import { useNavigate } from 'react-router-dom';

const Login: React.FC = () => {
  const { login } = useContext(AuthContext);
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');
  const navigate = useNavigate();

  const handleLogin = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault(); // Предотвращаем перезагрузку страницы
    try {
      await login(username, password);
      navigate('/');
    } catch (err: any) {
      setError(err.message);
    }
  };

  return (
    <Container maxWidth="sm">
      <Paper elevation={3} sx={{ padding: 4, marginTop: 8 }}>
        <Typography variant="h4" align="center" gutterBottom>
          Вход
        </Typography>
        {error && <Typography color="error">{error}</Typography>}
        <form onSubmit={handleLogin}>
          <Grid container spacing={2}>
            <Grid item xs={12}>
              <TextField
                label="Имя пользователя"
                variant="outlined"
                fullWidth
                value={username}
                onChange={(e) => setUsername(e.target.value)}
                required
              />
            </Grid>
            <Grid item xs={12}>
              <TextField
                label="Пароль"
                type="password"
                variant="outlined"
                fullWidth
                value={password}
                onChange={(e) => setPassword(e.target.value)}
                required
              />
            </Grid>
            <Grid item xs={12}>
              <Button
                type="submit"
                variant="contained"
                color="primary"
                fullWidth
              >
                Войти
              </Button>
            </Grid>
          </Grid>
        </form>
      </Paper>
    </Container>
  );
};

export default Login;// src/components/Navbar.tsx

import React, { useContext } from 'react';
import {
  AppBar,
  Toolbar,
  Typography,
  Button,
  IconButton,
  Badge,
  useTheme,
  useMediaQuery,
  Menu,
  MenuItem,
} from '@mui/material';
import {
  ShoppingCart as ShoppingCartIcon,
  Menu as MenuIcon,
  AccountCircle,
} from '@mui/icons-material';
import { Link } from 'react-router-dom';
import { AuthContext } from '../context/AuthContext';
import { useSelector } from 'react-redux';
import { RootState } from '../store/store';

const Navbar: React.FC = () => {
  const { user, logout } = useContext(AuthContext);
  const cartItems = useSelector((state: RootState) => state.cart.items);
  const totalQuantity = cartItems.reduce((acc, item) => acc + item.quantity, 0);

  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down('md'));

  const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);

  const handleMenu = (event: React.MouseEvent<HTMLButtonElement>) => {
    setAnchorEl(event.currentTarget);
  };

  const handleClose = () => {
    setAnchorEl(null);
  };

  return (
    <AppBar position="static" sx={{ backgroundColor: '#1976d2' }}>
      <Toolbar>
        <Typography
          variant="h6"
          component={Link}
          to="/"
          sx={{
            flexGrow: 1,
            textDecoration: 'none',
            color: 'inherit',
            fontWeight: 'bold',
          }}
        >
          MyStore
        </Typography>
        {isMobile ? (
          <>
            <IconButton color="inherit" onClick={handleMenu}>
              <MenuIcon />
            </IconButton>
            <Menu
              anchorEl={anchorEl}
              open={Boolean(anchorEl)}
              onClose={handleClose}
            >
              <MenuItem component={Link} to="/cart" onClick={handleClose}>
                <Badge badgeContent={totalQuantity} color="secondary">
                  Корзина
                </Badge>
              </MenuItem>
              {user ? (
                <>
                  <MenuItem onClick={handleClose}>
                    <Typography variant="body1">Привет, {user}</Typography>
                  </MenuItem>
                  <MenuItem onClick={() => { logout(); handleClose(); }}>
                    Выйти
                  </MenuItem>
                </>
              ) : (
                <>
                  <MenuItem component={Link} to="/login" onClick={handleClose}>
                    Вход
                  </MenuItem>
                  <MenuItem component={Link} to="/register" onClick={handleClose}>
                    Регистрация
                  </MenuItem>
                </>
              )}
            </Menu>
          </>
        ) : (
          <>
            <IconButton
              color="inherit"
              component={Link}
              to="/cart"
              sx={{ marginRight: 2 }}
            >
              <Badge badgeContent={totalQuantity} color="secondary">
                <ShoppingCartIcon />
              </Badge>
            </IconButton>
            {user ? (
              <>
                <Button color="inherit" onClick={logout}>
                  Выйти
                </Button>
              </>
            ) : (
              <>
                <Button
                  color="inherit"
                  component={Link}
                  to="/login"
                  startIcon={<AccountCircle />}
                >
                  Вход
                </Button>
                <Button
                  color="inherit"
                  component={Link}
                  to="/register"
                  startIcon={<AccountCircle />}
                >
                  Регистрация
                </Button>
              </>
            )}
          </>
        )}
      </Toolbar>
    </AppBar>
  );
};

export default Navbar;import React, { useContext } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import products from '../data/products';
import { useDispatch, useSelector } from 'react-redux';
import { addToCart } from '../store/actions/cartActions';
import { AuthContext } from '../context/AuthContext';
import { RootState } from '../store/store';

import {
  Grid,
  Typography,
  Button,
  CardMedia,
  Paper,
  Rating,
} from '@mui/material';

const ProductPage: React.FC = () => {
  const { id } = useParams<{ id: string }>();
  const product = products.find((p) => p.id === Number(id));
  const dispatch = useDispatch();
  const { user } = useContext(AuthContext);
  const navigate = useNavigate();

  const cartItems = useSelector((state: RootState) => state.cart.items);
  const cartItem = cartItems.find((item) => item.product.id === product?.id);

  if (!product) return <Typography variant="h5">Товар не найден</Typography>;

  const handleAddToCart = () => {
    if (!user) {
      navigate('/login');
    } else {
      dispatch(addToCart(product));
    }
  };

  return (
    <Paper sx={{ padding: 4, marginTop: 2 }}>
      <Grid container spacing={4}>
        <Grid item xs={12} md={6}>
          <CardMedia
            component="img"
            height="400"
            image={product.image}
            alt={product.name}
            sx={{ objectFit: 'contain' }}
          />
        </Grid>
        <Grid item xs={12} md={6}>
          <Typography variant="h4" gutterBottom>
            {product.name}
          </Typography>
          <Rating value={4} readOnly sx={{ marginBottom: 2 }} />
          <Typography variant="h6" color="textSecondary" gutterBottom>
            Цена: {product.price} ₽
          </Typography>
          <Typography variant="body1" paragraph>
            {product.description}
          </Typography>
          <Typography variant="body2" color="textSecondary" paragraph>
            Категория: {product.category}
          </Typography>
          {cartItem ? (
            <Typography variant="h6" color="primary">
              В корзине: {cartItem.quantity} шт.
            </Typography>
          ) : null}
          <Button
            variant="contained"
            color="primary"
            onClick={handleAddToCart}
            sx={{ marginTop: 2 }}
          >
            {cartItem ? 'Добавить еще' : 'Добавить в корзину'}
          </Button>
        </Grid>
      </Grid>
    </Paper>
  );
};

export default ProductPage;import React, { useState, useContext } from 'react';
import {
  TextField,
  Button,
  Typography,
  Container,
  Grid,
  Paper,
} from '@mui/material';
import { AuthContext } from '../context/AuthContext';
import { useNavigate } from 'react-router-dom';

const Register: React.FC = () => {
  const { register } = useContext(AuthContext);
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');
  const navigate = useNavigate();

  const handleRegister = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault(); // Предотвращаем перезагрузку страницы
    try {
      await register(username, password);
      navigate('/');
    } catch (err: any) {
      setError(err.message);
    }
  };

  return (
    <Container maxWidth="sm">
      <Paper elevation={3} sx={{ padding: 4, marginTop: 8 }}>
        <Typography variant="h4" align="center" gutterBottom>
          Регистрация
        </Typography>
        {error && <Typography color="error">{error}</Typography>}
        <form onSubmit={handleRegister}>
          <Grid container spacing={2}>
            <Grid item xs={12}>
              <TextField
                label="Имя пользователя"
                variant="outlined"
                fullWidth
                value={username}
                onChange={(e) => setUsername(e.target.value)}
                required
              />
            </Grid>
            <Grid item xs={12}>
              <TextField
                label="Пароль"
                type="password"
                variant="outlined"
                fullWidth
                value={password}
                onChange={(e) => setPassword(e.target.value)}
                required
              />
            </Grid>
            <Grid item xs={12}>
              <Button
                type="submit"
                variant="contained"
                color="primary"
                fullWidth
              >
                Зарегистрироваться
              </Button>
            </Grid>
          </Grid>
        </form>
      </Paper>
    </Container>
  );
};

export default Register;// src/context/AuthContext.tsx

import React, { createContext, useState, useEffect, ReactNode } from 'react';
import {mockApi} from "../api/mockApi.ts";

interface AuthContextType {
  user: string | null;
  login: (username: string, password: string) => Promise<void>;
  register: (username: string, password: string) => Promise<void>;
  logout: () => void;
}

export const AuthContext = createContext<AuthContextType>({
  user: null,
  login: async () => {},
  register: async () => {},
  logout: () => {},
});

export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
  const [user, setUser] = useState<string | null>(null);

  useEffect(() => {
    const storedUser = localStorage.getItem('user');
    if (storedUser) {
      setUser(storedUser);
    }
  }, []);

  // Функции для работы с пользователем
  const login = async (username: string, password: string) => {
    try {
      // Имитация API запроса
      const response = await mockApi.login(username, password);
      setUser(response.username);
      localStorage.setItem('user', response.username);
    } catch (error: any) {
      throw new Error(error.message);
    }
  };

  const register = async (username: string, password: string) => {
    try {
      await mockApi.register(username, password);
      setUser(username);
      localStorage.setItem('user', username);
    } catch (error: any) {
      throw new Error(error.message);
    }
  };

  const logout = () => {
    setUser(null);
    localStorage.removeItem('user');
  };

  return (
    <AuthContext.Provider value={{ user, login, register, logout }}>
      {children}
    </AuthContext.Provider>
  );
};// src/store/actions/cartActions.ts

import { Product } from '../../types';

export const ADD_TO_CART = 'ADD_TO_CART';
export const REMOVE_FROM_CART = 'REMOVE_FROM_CART';
export const UPDATE_CART_ITEM = 'UPDATE_CART_ITEM';
export const CLEAR_CART = 'CLEAR_CART'; // Убедитесь, что здесь написано CLEAR_CART, а не CLEAR_CAR

export const addToCart = (product: Product) => {
  return {
    type: ADD_TO_CART,
    payload: product,
  };
};

export const removeFromCart = (productId: number) => {
  return {
    type: REMOVE_FROM_CART,
    payload: productId,
  };
};

export const updateCartItem = (productId: number, quantity: number) => {
  return {
    type: UPDATE_CART_ITEM,
    payload: { productId, quantity },
  };
};

export const clearCart = () => {
  return {
    type: CLEAR_CART,
  };
}; import {
  ADD_TO_CART,
  REMOVE_FROM_CART,
  UPDATE_CART_ITEM,
  CLEAR_CART,
} from '../actions/cartActions';
import { CartItem, Product } from '../../types';

interface CartState {
  items: CartItem[];
}

const initialState: CartState = {
  items: [],
};

type Action =
  | { type: typeof ADD_TO_CART; payload: Product }
  | { type: typeof REMOVE_FROM_CART; payload: number }
  | { type: typeof UPDATE_CART_ITEM; payload: { productId: number; quantity: number } }
  | { type: typeof CLEAR_CART };

export const cartReducer = (
  state = initialState,
  action: Action
): CartState => {
  switch (action.type) {
    case ADD_TO_CART: {
      const product = action.payload;
      const existingItemIndex = state.items.findIndex(
        (item) => item.product.id === product.id
      );

      if (existingItemIndex >= 0) {
        // Товар уже есть в корзине, увеличиваем количество
        const updatedItems = state.items.map((item, index) =>
          index === existingItemIndex
            ? { ...item, quantity: item.quantity + 1 }
            : item
        );
        return { items: updatedItems };
      } else {
        // Товара еще нет в корзине, добавляем новый
        return {
          items: [...state.items, { product, quantity: 1 }],
        };
      }
    }

    case REMOVE_FROM_CART:
      return {
        items: state.items.filter((item) => item.product.id !== action.payload),
      };
    // В обработчике UPDATE_CART_ITEM
    case UPDATE_CART_ITEM: {
      const { productId, quantity } = action.payload;
      if (quantity <= 0) {
        // Если количество 0 или меньше, удаляем товар из корзины
        return {
          items: state.items.filter((item) => item.product.id !== productId),
        };
      } else {
        // Обновляем количество товара
        const updatedItems = state.items.map((item) =>
          item.product.id === productId ? { ...item, quantity } : item
        );
        return { items: updatedItems };
      }
    }

    case CLEAR_CART:
      return {
        items: [],
      };

    default:
      return state;
  }
}; напиши сервер для этого приложения на java spring