Commit c6a0358f authored by Kulpybaev Ilyas's avatar Kulpybaev Ilyas

Урок 89

parent 7d5d1079
import axios from "axios"; import axios from "axios";
import { apiURL } from "./constants.ts";
const axiosApi = axios.create({ const axiosApi = axios.create({
baseURL: "http://localhost:8000", baseURL: apiURL,
}); });
export default axiosApi; export default axiosApi;
import { ChangeEvent, FormEvent, useState } from "react"; import { ChangeEvent, FormEvent, useState } from "react";
import { Box, Button, Grid, TextField } from "@mui/material"; import { Box, Button, Grid, TextField } from "@mui/material";
import { CreateProductType } from "../../interfaces/IProduct.ts"; import FileInput from "../UI/Form/FileInput/FileInput.tsx";
interface IState { interface IState {
name: string; title: string;
price: string; price: string;
description: string; description: string;
image: string;
} }
interface IProps { interface IProps {
onSubmit: (data: CreateProductType) => void; onSubmit: (data: FormData) => void;
} }
const ProductForm = ({ onSubmit }: IProps) => { const ProductForm = ({ onSubmit }: IProps) => {
const [state, setState] = useState<IState>({ const [state, setState] = useState<IState>({
name: "", title: "",
price: "", price: "",
description: "", description: "",
image: "",
}); });
const submitFormHandler = (e: FormEvent<HTMLFormElement>) => { const submitFormHandler = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
const newProduct: CreateProductType = { const formData: FormData = new FormData();
name: state.name,
description: state.description, Object.keys(state).forEach((key) => {
price: parseInt(state.price), formData.append(key, state[key as keyof IState]);
}; });
onSubmit(newProduct); onSubmit(formData);
}; };
const inputChangeHandler = (e: ChangeEvent<HTMLInputElement>) => { const inputChangeHandler = (e: ChangeEvent<HTMLInputElement>) => {
...@@ -37,6 +39,18 @@ const ProductForm = ({ onSubmit }: IProps) => { ...@@ -37,6 +39,18 @@ const ProductForm = ({ onSubmit }: IProps) => {
}); });
}; };
const fileChangeHandler = (e: ChangeEvent<HTMLInputElement>) => {
const name = e.target.name;
if (e.target.files) {
const file = e.target.files[0];
setState((prevState) => ({
...prevState,
[name]: file,
}));
}
};
return ( return (
<Box <Box
component="form" component="form"
...@@ -49,11 +63,11 @@ const ProductForm = ({ onSubmit }: IProps) => { ...@@ -49,11 +63,11 @@ const ProductForm = ({ onSubmit }: IProps) => {
<TextField <TextField
fullWidth fullWidth
variant="outlined" variant="outlined"
id="name" id="title"
label="Title" label="Title"
value={state.name} value={state.title}
onChange={inputChangeHandler} onChange={inputChangeHandler}
name="name" name="title"
/> />
</Grid> </Grid>
...@@ -81,6 +95,10 @@ const ProductForm = ({ onSubmit }: IProps) => { ...@@ -81,6 +95,10 @@ const ProductForm = ({ onSubmit }: IProps) => {
/> />
</Grid> </Grid>
<Grid item xs>
<FileInput onChange={fileChangeHandler} name="image" label="Image" />
</Grid>
<Grid item xs> <Grid item xs>
<Button type="submit" color="primary" variant="contained"> <Button type="submit" color="primary" variant="contained">
Create Create
......
...@@ -7,17 +7,32 @@ import { ...@@ -7,17 +7,32 @@ import {
CardHeader, CardHeader,
Grid, Grid,
IconButton, IconButton,
CardMedia,
} from "@mui/material"; } from "@mui/material";
import imageNotAvailable from "../../assets/images/Image_not_available.png";
import { apiURL } from "../../constants.ts";
interface IProps { interface IProps {
title: string; title: string;
price: number; price: number;
id: string; id: string;
image?: string;
} }
const ProductItem = ({ title, id, price }: IProps) => { const ProductItem = ({ title, id, price, image }: IProps) => {
let cardImage = imageNotAvailable;
if (image) {
cardImage = apiURL + "/uploads/" + image;
}
return ( return (
<Grid item xs={12} sm={12} md={6} lg={4}> <Grid item xs={12} sm={6} md={6} lg={4}>
<Card> <Card sx={{ minWidth: 275 }}>
<CardHeader title={title} /> <CardHeader title={title} />
<CardMedia
image={cardImage}
title={title}
sx={{ height: 200, paddingTop: "56.25%" }}
/>
<CardContent> <CardContent>
<strong style={{ marginLeft: 10 }}>Price: {price} KZT</strong> <strong style={{ marginLeft: 10 }}>Price: {price} KZT</strong>
</CardContent> </CardContent>
......
import { ChangeEvent, ChangeEventHandler, useRef, useState } from "react";
import { Button, Grid, TextField, styled } from "@mui/material";
interface IProps {
onChange: ChangeEventHandler<HTMLInputElement>;
name: string;
label: string;
}
const HiddenInputFile = styled("input")({
display: "none",
});
const FileInput = ({ onChange, name, label }: IProps) => {
const inputRef = useRef<HTMLInputElement>(null);
const [fileName, setFileName] = useState("");
const onFileChange = (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
setFileName(e.target.files[0].name);
} else {
setFileName("");
}
onChange(e);
};
const activeInput = () => {
if (inputRef.current) {
inputRef.current.click();
}
};
return (
<>
<HiddenInputFile
type="file"
name={name}
onChange={onFileChange}
ref={inputRef}
/>
<Grid container direction="row" spacing={2} alignItems="center">
<Grid item xs>
<TextField
variant="outlined"
disabled
fullWidth
label={label}
value={fileName}
onClick={activeInput}
/>
</Grid>
<Grid item>
<Button variant="contained" onClick={activeInput}>
Browse
</Button>
</Grid>
</Grid>
</>
);
};
export default FileInput;
export const apiURL = "http://localhost:8000";
...@@ -2,14 +2,13 @@ import { Typography } from "@mui/material"; ...@@ -2,14 +2,13 @@ import { Typography } from "@mui/material";
import ProductForm from "../../components/ProductForm/ProductForm.tsx"; import ProductForm from "../../components/ProductForm/ProductForm.tsx";
import { useAppDispatch } from "../../store/hooks.ts"; import { useAppDispatch } from "../../store/hooks.ts";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { CreateProductType } from "../../interfaces/IProduct.ts";
import { createProduct } from "../../features/productsSlice.ts"; import { createProduct } from "../../features/productsSlice.ts";
const NewProduct = () => { const NewProduct = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const onProductFormSubmit = async (productData: CreateProductType) => { const onProductFormSubmit = async (productData: FormData) => {
await dispatch(createProduct(productData)); await dispatch(createProduct(productData));
navigate("/"); navigate("/");
}; };
......
...@@ -31,15 +31,18 @@ const Products = () => { ...@@ -31,15 +31,18 @@ const Products = () => {
</Button> </Button>
</Grid> </Grid>
</Grid> </Grid>
<Grid container item spacing={2}>
{products.map((product) => ( {products.map((product) => (
<ProductItem <ProductItem
key={product.id} key={product.id}
title={product.name} title={product.title}
price={product.price} price={product.price}
id={product.id} id={product.id}
image={product.image}
/> />
))} ))}
</Grid> </Grid>
</Grid>
); );
}; };
......
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { CreateProductType, IProduct } from "../interfaces/IProduct.ts"; import { IProduct } from "../interfaces/IProduct.ts";
import axiosApi from "../axiosApi.ts"; import axiosApi from "../axiosApi.ts";
interface IState { interface IState {
...@@ -19,7 +19,7 @@ export const fetchProducts = createAsyncThunk("fetch/products", async () => { ...@@ -19,7 +19,7 @@ export const fetchProducts = createAsyncThunk("fetch/products", async () => {
export const createProduct = createAsyncThunk( export const createProduct = createAsyncThunk(
"create/products", "create/products",
async (payload: CreateProductType) => { async (payload: FormData) => {
return await axiosApi.post("/products", payload).then((res) => res.data); return await axiosApi.post("/products", payload).then((res) => res.data);
}, },
); );
......
export interface IProduct { export interface IProduct {
id: string; id: string;
name: string; title: string;
description: string; description: string;
price: number; price: number;
image?: string;
} }
export type CreateProductType = Omit<IProduct, "id">; export type CreateProductType = Omit<IProduct, "id">;
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