Commit 7d5d1079 authored by Kulpybaev Ilyas's avatar Kulpybaev Ilyas

Урок 83

parent 4882a76d
This source diff could not be displayed because it is too large. You can view the blob instead.
import { Container, CssBaseline } from "@mui/material";
import AppToolbar from "./components/UI/AppToolbar/AppToolbar.tsx";
import { Route, Routes } from "react-router-dom";
import Products from "./containers/Products/Products.tsx";
import NewProduct from "./containers/Products/NewProduct.tsx";
const App = () => (
<>
<header>Navbar will go here</header>
<main>Main content will go here</main>
<CssBaseline />
<header>
<AppToolbar />
</header>
<main>
<Container maxWidth="xl">
<Routes>
<Route path="/" element={<Products />} />
<Route path="/products/new" element={<NewProduct />} />
</Routes>
</Container>
</main>
</>
);
......
import axios from "axios";
const axiosApi = axios.create({
baseURL: "http://localhost:8000",
});
export default axiosApi;
import { ChangeEvent, FormEvent, useState } from "react";
import { Box, Button, Grid, TextField } from "@mui/material";
import { CreateProductType } from "../../interfaces/IProduct.ts";
interface IState {
name: string;
price: string;
description: string;
}
interface IProps {
onSubmit: (data: CreateProductType) => void;
}
const ProductForm = ({ onSubmit }: IProps) => {
const [state, setState] = useState<IState>({
name: "",
price: "",
description: "",
});
const submitFormHandler = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const newProduct: CreateProductType = {
name: state.name,
description: state.description,
price: parseInt(state.price),
};
onSubmit(newProduct);
};
const inputChangeHandler = (e: ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setState((prevState) => {
return { ...prevState, [name]: value };
});
};
return (
<Box
component="form"
autoComplete="off"
onSubmit={submitFormHandler}
paddingY={2}
>
<Grid container direction="column" spacing={2}>
<Grid item xs>
<TextField
fullWidth
variant="outlined"
id="name"
label="Title"
value={state.name}
onChange={inputChangeHandler}
name="name"
/>
</Grid>
<Grid item xs>
<TextField
fullWidth
variant="outlined"
id="price"
label="Price"
value={state.price}
onChange={inputChangeHandler}
name="price"
/>
</Grid>
<Grid item xs>
<TextField
fullWidth
variant="outlined"
id="description"
label="Description"
value={state.description}
onChange={inputChangeHandler}
name="description"
/>
</Grid>
<Grid item xs>
<Button type="submit" color="primary" variant="contained">
Create
</Button>
</Grid>
</Grid>
</Box>
);
};
export default ProductForm;
import { Link } from "react-router-dom";
import { ArrowForward } from "@mui/icons-material";
import {
Card,
CardActions,
CardContent,
CardHeader,
Grid,
IconButton,
} from "@mui/material";
interface IProps {
title: string;
price: number;
id: string;
}
const ProductItem = ({ title, id, price }: IProps) => {
return (
<Grid item xs={12} sm={12} md={6} lg={4}>
<Card>
<CardHeader title={title} />
<CardContent>
<strong style={{ marginLeft: 10 }}>Price: {price} KZT</strong>
</CardContent>
<CardActions>
<IconButton component={Link} to={`/products/${id}`}>
<ArrowForward />
</IconButton>
</CardActions>
</Card>
</Grid>
);
};
export default ProductItem;
import { AppBar, Toolbar, Typography, styled, Box } from "@mui/material";
import { Link } from "react-router-dom";
const StyledLink = styled(Link)(() => ({
color: "inherit",
textDecoration: "none",
["&:hover"]: { color: "inherit" },
}));
const AppToolbar = () => {
return (
<>
<AppBar position="fixed">
<Toolbar>
<Typography variant="h6" component={StyledLink} to="/">
Computer parts shop
</Typography>
</Toolbar>
</AppBar>
<Box component={Toolbar} marginBottom={2} />
</>
);
};
export default AppToolbar;
import { Typography } from "@mui/material";
import ProductForm from "../../components/ProductForm/ProductForm.tsx";
import { useAppDispatch } from "../../store/hooks.ts";
import { useNavigate } from "react-router-dom";
import { CreateProductType } from "../../interfaces/IProduct.ts";
import { createProduct } from "../../features/productsSlice.ts";
const NewProduct = () => {
const dispatch = useAppDispatch();
const navigate = useNavigate();
const onProductFormSubmit = async (productData: CreateProductType) => {
await dispatch(createProduct(productData));
navigate("/");
};
return (
<>
<Typography variant="h4">New product</Typography>
<ProductForm onSubmit={onProductFormSubmit} />
</>
);
};
export default NewProduct;
import { Button, Grid, Typography } from "@mui/material";
import { Link } from "react-router-dom";
import { useAppDispatch, useAppSelector } from "../../store/hooks.ts";
import { useEffect } from "react";
import { fetchProducts } from "../../features/productsSlice.ts";
import ProductItem from "../../components/ProductItem/ProductItem.tsx";
const Products = () => {
const dispatch = useAppDispatch();
const { products } = useAppSelector((state) => state.products);
useEffect(() => {
dispatch(fetchProducts());
}, [dispatch]);
return (
<Grid container direction="column" spacing={2}>
<Grid
item
container
direction="row"
justifyContent="space-between"
alignItems="center"
>
<Grid item>
<Typography variant="h4">Products</Typography>
</Grid>
<Grid item>
<Button color="primary" component={Link} to="/products/new">
Add product
</Button>
</Grid>
</Grid>
{products.map((product) => (
<ProductItem
key={product.id}
title={product.name}
price={product.price}
id={product.id}
/>
))}
</Grid>
);
};
export default Products;
import {createSlice} from "@reduxjs/toolkit";
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { CreateProductType, IProduct } from "../interfaces/IProduct.ts";
import axiosApi from "../axiosApi.ts";
const initialState = {};
interface IState {
products: IProduct[];
error: Error | null;
loading: boolean;
}
const initialState: IState = {
products: [],
error: null,
loading: false,
};
export const fetchProducts = createAsyncThunk("fetch/products", async () => {
return axiosApi.get<IProduct[]>("/products").then((res) => res.data);
});
export const createProduct = createAsyncThunk(
"create/products",
async (payload: CreateProductType) => {
return await axiosApi.post("/products", payload).then((res) => res.data);
},
);
const productsSlice = createSlice({
name: 'products',
name: "products",
initialState,
reducers: {},
})
extraReducers: (builder) => {
builder
.addCase(fetchProducts.fulfilled, (state, action) => {
state.products = action.payload;
state.loading = false;
})
.addCase(fetchProducts.rejected, (state, action) => {
state.error = action.error as Error;
state.loading = false;
})
.addCase(fetchProducts.pending, (state) => {
state.error = null;
state.loading = true;
});
},
});
export default productsSlice.reducer;
export interface IProduct {
id: string;
name: string;
description: string;
price: number;
}
export type CreateProductType = Omit<IProduct, "id">;
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import { AppDispatch, RootState } from "./index.ts";
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
import {configureStore} from '@reduxjs/toolkit';
import productsReducer from '../features/productsSlice.ts';
import { configureStore } from "@reduxjs/toolkit";
import productsReducer from "../features/productsSlice.ts";
const store = configureStore({
reducer: {
products: productsReducer,
}
})
},
});
export default store;
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
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