Commit 38cbf3b1 authored by Pavel Mishakov's avatar Pavel Mishakov

lesson 87 done

parent dab71996
......@@ -11,18 +11,14 @@
"dependencies": {
"@reduxjs/toolkit": "^1.9.3",
"axios": "^1.3.4",
"bootstrap": "^5.2.3",
"react": "^18.2.0",
"react-bootstrap": "^2.7.2",
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
"react-router-dom": "^6.8.2"
},
"devDependencies": {
"@types/axios": "^0.14.0",
"@types/bootstrap": "^5.2.6",
"@types/react": "^18.0.27",
"@types/react-bootstrap": "^0.32.32",
"@types/react-dom": "^18.0.10",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react": "^3.1.0",
......
import { AppDispatch, AppState } from "./store/store"
import { useDispatch, useSelector, shallowEqual } from "react-redux"
import { useEffect, useState } from "react"
import { createProduct, getProductById, getProducts } from "./store/products/products.slice"
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import 'bootstrap/dist/css/bootstrap.min.css';
import { Col, Container, Row } from "react-bootstrap";
import Layout from "./components/Layout/Layout";
import { Routes, Route } from "react-router-dom";
import ProductForm from "./components/ProductForm/ProductForm";
import ProductFormPage from "./containers/ProductFormPage/ProductFormPage";
import ProductsPage from "./containers/ProductsPage/ProductsPage";
const App = () => {
const dispatch: AppDispatch = useDispatch()
const {products, product, messageProducts} = useSelector(
(state: AppState) => state.products, shallowEqual)
useEffect(() => {
// dispatch(getProductById('ea4f2bd1-2d5a-42ea-99e6-50a125bfe82d'))
dispatch(getProducts())
// dispatch(createProduct(
// {
// title: 'Orange',
// price: 999,
// description: 'This is an orange'
// }
// ))
}, [])
return (
<Layout>
<Routes>
<Route path={'/'} element={<h1>HOME PAGE</h1>} />
<Route path={'/add-product'} element={<ProductForm />} />
<Route path={'/products'} element={<ProductsPage />} />
<Route path={'/add-product'} element={<ProductFormPage />} />
</Routes>
</Layout>
)
......
import axios from "axios"
import { EStatuses } from "../enums/EStatuses"
import IProductDto from "../interfaces/IProductDto"
import IProduct from "../interfaces/IProduct"
import IResponse from "../interfaces/IResponse"
import { instance } from "./instance"
class ProductApi {
public getProducts = async(): Promise<IResponse> => {
public getProducts = async(): Promise<IResponse<IProduct[] | undefined>> => {
try {
const response = await instance.get('/products')
return response.data
......@@ -20,7 +22,7 @@ class ProductApi {
}
}
public getProductById = async(id: string): Promise<IResponse> => {
public getProductById = async(id: string): Promise<IResponse<IProduct | undefined>> => {
try {
const response = await instance.get(`/products/${id}`)
return response.data
......@@ -49,6 +51,21 @@ class ProductApi {
return response
}
}
public deleteProductById = async(id: string): Promise<IResponse> => {
try {
const response = await instance.delete(`/products/${id}`)
return response.data
} catch (err: unknown) {
const error = err as Error
const response: IResponse = {
status: EStatuses.NOT_OK,
result: undefined,
message: error.message
}
return response
}
}
}
export const productApi = new ProductApi()
\ No newline at end of file
import IProduct from "../../interfaces/IProduct";
export default interface IProductItemProps {
product: IProduct
}
\ No newline at end of file
.ProductItem {
padding: 20px;
width: 25%;
margin: 10px;
border-radius: 10px;
border: 1px solid black;
}
.ProductItem__imageFrame {
width: 100%;
border-radius: 10px;
overflow: hidden;
height: 200px;
}
.ProductItem__image {
width: 100%;
height: 100%;
object-fit: cover;
}
.ProductItem__btnBlock {
display: flex;
align-items: center;
justify-content: space-between;
}
\ No newline at end of file
import React, { FunctionComponent, ReactElement } from "react";
import IProductItemProps from "./IProductItemProps";
import styles from './ProductItem.module.css'
import defaultImage from '../../assets/images/default.jpg'
import { AppDispatch } from "../../store/store";
import { useDispatch } from "react-redux";
import { deleteProductById } from "../../store/products/products.slice";
const ProductItem: FunctionComponent<IProductItemProps> = (props): ReactElement => {
const dispatch: AppDispatch = useDispatch()
const deleteHandler = (): void => {
dispatch(deleteProductById(props.product._id))
}
return (
<div className={styles.ProductItem}>
<div className={styles.ProductItem__imageFrame}>
<img
className={styles.ProductItem__image}
src={`${import.meta.env.VITE_BASE_URL}uploads/${props.product.image}`}
alt={props.product.product}
onError={(e) => {
e.currentTarget.src = defaultImage
}}
/>
</div>
<h3>{props.product.product}</h3>
<p>{props.product.description}</p>
<p>{props.product.price}</p>
<div className={styles.ProductItem__btnBlock}>
<button>Details</button>
<button>Edit</button>
<button onClick={deleteHandler}>Delete</button>
</div>
</div>
)
}
export default ProductItem
\ No newline at end of file
import IProduct from "../../interfaces/IProduct";
export default interface IProductsListProps {
products: IProduct[]
}
\ No newline at end of file
.ProductsList {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
}
\ No newline at end of file
import React, { FunctionComponent, ReactElement } from "react";
import ProductItem from "../ProductItem/ProductItem";
import IProductListProps from "./IProductsListProps";
import styles from './ProductsList.module.css'
const ProductsList: FunctionComponent<IProductListProps> = (props): ReactElement => {
return (
<div className={styles.ProductsList}>
{props.products.length ?
props.products.map(p => {
return <ProductItem
key={p._id}
product={p}
/>
})
: <h1>No products</h1>}
</div>
)
}
export default ProductsList
\ No newline at end of file
import React, { FunctionComponent, ReactElement } from "react";
import ProductForm from "../../components/ProductForm/ProductForm";
import styles from './ProductFormPage.module.css'
const ProductFormPage: FunctionComponent = (): ReactElement => {
return (
<div className={styles.ProductFormPage}>
<ProductForm />
</div>
)
}
export default ProductFormPage
\ No newline at end of file
import React, { FunctionComponent, ReactElement, useEffect } from "react";
import { shallowEqual, useDispatch, useSelector } from "react-redux";
import ProductsList from "../../components/ProductsList/ProductsList";
import { getProducts } from "../../store/products/products.slice";
import { AppDispatch, AppState } from "../../store/store";
import styles from './ProductsPage.module.css'
const ProductsPage: FunctionComponent = (): ReactElement => {
const dispatch: AppDispatch = useDispatch()
const {products} = useSelector((state: AppState) => state.products, shallowEqual)
useEffect(() => {
dispatch(getProducts())
}, [])
return (
<div className={styles.ProductsPage}>
<ProductsList
products={products}
/>
</div>
)
}
export default ProductsPage
\ No newline at end of file
import ISupplier from "./ISupplier"
export default interface IProduct {
id: string
_id: string
product: string
price: number
category_id: string
brand_id: string
category_id?: string | null
brand_id?: string | null
description: string
suppliers?: ISupplier[]
image: string
......
import { EStatuses } from "../enums/EStatuses";
import IProduct from "./IProduct";
export default interface IResponse {
result: IProduct[] | IProduct | undefined | null
export default interface IResponse<T=undefined> {
result: T | undefined | null
message: string
status: EStatuses
}
\ No newline at end of file
......@@ -2,6 +2,7 @@ import { createSlice } from '@reduxjs/toolkit'
import { productApi } from '../../api/productApi'
import IProduct from '../../interfaces/IProduct'
import IProductDto from '../../interfaces/IProductDto'
import IResponse from '../../interfaces/IResponse'
import { createAppAsyncThunk } from '../createAppAsyncThunk'
import IProductsState from './IProductsState'
......@@ -16,7 +17,7 @@ export const getProducts = createAppAsyncThunk(
export const getProductById = createAppAsyncThunk(
`${namespace}/getProductById`,
async (id: string) => {
async (id: string): Promise<IResponse<IProduct | undefined>> => {
return productApi.getProductById(id)
}
)
......@@ -28,6 +29,13 @@ export const createProduct = createAppAsyncThunk(
}
)
export const deleteProductById = createAppAsyncThunk(
`${namespace}/deleteProductById`,
async (id: string) => {
return productApi.deleteProductById(id)
}
)
export const productsSlice = createSlice({
name: namespace,
initialState: {
......@@ -71,5 +79,15 @@ export const productsSlice = createSlice({
state.loadingProducts = false
state.messageProducts = action.payload.message
})
.addCase(deleteProductById.rejected, (state) => {
state.loadingProducts = false
})
.addCase(deleteProductById.pending, (state) => {
state.loadingProducts = true
})
.addCase(deleteProductById.fulfilled, (state, action) => {
state.loadingProducts = false
state.messageProducts = action.payload.message
})
}
})
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment