Commit 31f76dda authored by Нұрасыл Қайратұлы's avatar Нұрасыл Қайратұлы

Merge branch 'update_front' into 'master'

Update front

See merge request !3
parents 66ec1700 4df12fa3
......@@ -9,11 +9,14 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@types/cors": "^2.8.17",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"cors": "^2.8.5",
"express": "^4.18.2",
"reflect-metadata": "^0.2.2",
"save": "^2.9.0",
"save-dev": "^0.0.1-security",
"ts-node": "^10.9.1",
"tslib": "^2.6.0",
"typescript": "^5.1.6",
......@@ -259,6 +262,14 @@
"@types/node": "*"
}
},
"node_modules/@types/cors": {
"version": "2.8.17",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
"integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/express": {
"version": "4.17.17",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz",
......@@ -940,6 +951,18 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
......@@ -2359,6 +2382,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
......@@ -2887,6 +2918,11 @@
"mingo": "^6.1.0"
}
},
"node_modules/save-dev": {
"version": "0.0.1-security",
"resolved": "https://registry.npmjs.org/save-dev/-/save-dev-0.0.1-security.tgz",
"integrity": "sha512-k6knZTDNK8PKKbIqnvxiOveJinuw2LcQjqDoaorZWP9M5AR2EPsnpDeSbeoZZ0pHr5ze1uoaKdK8NBGQrJ34Uw=="
},
"node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
......
......@@ -13,11 +13,14 @@
"author": "",
"license": "ISC",
"dependencies": {
"@types/cors": "^2.8.17",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"cors": "^2.8.5",
"express": "^4.18.2",
"reflect-metadata": "^0.2.2",
"save": "^2.9.0",
"save-dev": "^0.0.1-security",
"ts-node": "^10.9.1",
"tslib": "^2.6.0",
"typescript": "^5.1.6",
......
[
{
"id": "d2d4a838-64d2-45e1-954b-ac4d744d9497",
"title": "test",
"description": "adsdasdadadsadad",
"price": 500000
},
{
"id": "279e188c-ac70-4186-b3b1-52b9fd721e07",
"title": "asasdasd",
"description": "asdaddasad",
"price": 123123
}
]
\ No newline at end of file
......@@ -2,10 +2,11 @@ import App from './app';
import logger from './middlewares/logger';
import { ArticleRoute } from './routes/article.route';
import { ProductRoute } from './routes/product.route';
import cors from 'cors';
const app = new App({
port: 8000,
middlewares: [logger()],
middlewares: [logger(), cors()],
controllers: [new ArticleRoute(), new ProductRoute()],
});
......
......@@ -3,7 +3,7 @@ import { IRoute } from "@/interfaces/IRoute.interface";
import { ProductController } from "@/controllers/product.controller";
export class ProductRoute implements IRoute {
public path = '/product';
public path = '/products';
public router = Router();
private controller: ProductController;
......
......@@ -151,6 +151,13 @@
dependencies:
"@types/node" "*"
"@types/cors@^2.8.17":
version "2.8.17"
resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz"
integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==
dependencies:
"@types/node" "*"
"@types/express-serve-static-core@^4.17.33":
version "4.17.35"
resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz"
......@@ -560,6 +567,14 @@ cookie@0.5.0:
resolved "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz"
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
cors@^2.8.5:
version "2.8.5"
resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz"
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
dependencies:
object-assign "^4"
vary "^1"
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz"
......@@ -1452,6 +1467,11 @@ npm-run-path@^5.1.0:
dependencies:
path-key "^4.0.0"
object-assign@^4:
version "4.1.1"
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
object-inspect@^1.9.0:
version "1.12.3"
resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz"
......@@ -1695,6 +1715,11 @@ safe-buffer@5.2.1:
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
save-dev@^0.0.1-security:
version "0.0.1-security"
resolved "https://registry.npmjs.org/save-dev/-/save-dev-0.0.1-security.tgz"
integrity sha512-k6knZTDNK8PKKbIqnvxiOveJinuw2LcQjqDoaorZWP9M5AR2EPsnpDeSbeoZZ0pHr5ze1uoaKdK8NBGQrJ34Uw==
save@^2.9.0:
version "2.9.0"
resolved "https://registry.npmjs.org/save/-/save-2.9.0.tgz"
......@@ -2000,7 +2025,7 @@ validator@^13.9.0:
resolved "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz"
integrity sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==
vary@~1.1.2:
vary@^1, vary@~1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
......
{
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"useTabs": false
}
module.exports = {
tabWidth: 2,
singleQuote: true,
trailingComma: 'es5',
printWidth: 100,
useTabs: false,
};
......@@ -10,11 +10,6 @@
"[typescriptreact]": {
"editor.formatOnSave": true
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"typescript.tsdk": "node_modules/typescript/lib",
}
\ No newline at end of file
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
"typescript.tsdk": "node_modules/typescript/lib"
}
......@@ -7,7 +7,8 @@
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
"preview": "vite preview",
"format": "prettier --write './**/*.{js,jsx,ts,tsx,css,md,json}' --config ./.prettierrc"
},
"dependencies": {
"@emotion/react": "^11.11.1",
......
const App = () => (
<>
<header>Navbar will go here</header>
<main>Main content will go here</main>
</>
);
import { Container, CssBaseline } from '@mui/material';
import { Route, Routes } from 'react-router-dom';
import { AppToolbar } from './components/UI/AppToolbar/AppToolbar';
import { Products } from './containers/Products/Products';
import NewProduct from './containers/NewProduct/NewProduct';
function App() {
return (
<>
<CssBaseline />
<header>
<AppToolbar />
</header>
<main>
<Container maxWidth="xl" sx={{ mt: 10 }}>
<Routes>
<Route path="/" element={<Products />} />
<Route path="/products/new" element={<NewProduct />} />
</Routes>
</Container>
</main>
</>
);
}
export default App;
import { useState, ChangeEvent, FormEvent } from 'react';
import { Box, Button, Grid, TextField } from '@mui/material';
import { ProductData } from '@/containers/NewProduct/NewProduct';
interface State {
title: string;
price: string;
description: string;
}
interface Props {
onSubmit: (data: ProductData) => void;
}
const ProductForm = ({onSubmit}: Props) => {
const [state, setState] = useState<State>({
title: '',
price: '',
description: '',
});
const submitFormHandler = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const newProduct: ProductData = {
title: state.title,
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="title"
label="Title"
value={state.title}
onChange={inputChangeHandler}
name="title"
/>
</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
multiline
rows={3}
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 { AppBar, Box, Toolbar, Typography, styled } from '@mui/material';
import { Link } from 'react-router-dom';
const StyledLink = styled(Link)(() => ({
color: 'inherit',
textDecoration: 'none',
['&:hover']: { color: 'inherit' },
}));
export function AppToolbar() {
return (
<>
<AppBar position="fixed">
<Toolbar>
<Typography variant="h6" component={StyledLink} to={'/'}>
Computer parts shop
</Typography>
</Toolbar>
</AppBar>
<Box component={Toolbar} marginBottom={2} />
</>
);
}
import { Typography } from '@mui/material';
import ProductForm from '../../components/ProductForm/ProductForm';
import { useNavigate } from 'react-router-dom';
import { createProduct } from '../../features/productsSlice';
import { useAppDispatch } from '../../store';
export interface ProductData {
title: string;
description: string;
price: number;
}
const NewProduct = () => {
const dispatch = useAppDispatch();
const navigate = useNavigate();
const onProductFormSubmit = async (productData: ProductData) => {
await dispatch(createProduct(productData));
navigate('/');
};
return (
<>
<Typography variant="h4">New product</Typography>
<ProductForm onSubmit={onProductFormSubmit}/>
</>
);
};
export default NewProduct;
import { Link } from 'react-router-dom';
import {
Grid,
Card,
CardHeader,
CardContent,
CardActions,
IconButton,
Typography,
} from '@mui/material';
import { ArrowForward } from '@mui/icons-material';
import { IProduct } from '@/interfaces/IProduct';
interface Props {
product: IProduct;
}
export function ProductItem({ product }: Props) {
const { title, price, id, description } = product;
return (
<Grid item xs={12} sm={12} md={6} lg={4}>
<Card sx={{ minWidth: 275 }}>
<CardHeader title={title} />
<CardContent>
<Typography variant="body2">{description}</Typography>
<strong style={{ marginLeft: '10px' }}>Price: {price} KZT</strong>
</CardContent>
<CardActions>
<IconButton component={Link} to={`/products/${id}`}>
<ArrowForward />
</IconButton>
</CardActions>
</Card>
</Grid>
);
}
import { Link } from 'react-router-dom';
import { Typography, Grid, Button } from '@mui/material';
import { useAppDispatch, useAppSelector } from '../../store';
import { shallowEqual } from 'react-redux';
import { useEffect } from 'react';
import { fetchProducts } from '../../features/productsSlice';
import { ProductItem } from './ProductItem';
export function Products() {
const dispatch = useAppDispatch();
const { products } = useAppSelector((state) => state.products, shallowEqual);
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>
</Grid>
<Grid item container direction="row" spacing={1}>
{products.map((product) => (
<ProductItem key={product.id} product={product} />
))}
</Grid>
</>
);
}
import { createSlice } from "@reduxjs/toolkit";
import { axiosApiClient } from '../helpers/axiosApiClient';
import { IProduct } from '../interfaces/IProduct';
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
const initialState = {};
interface State {
products: IProduct[];
error: Error | null;
loading: boolean;
}
const productsSlice = createSlice(
{
name: 'products',
initialState,
reducers: {},
}
)
const initialState: State = {
products: [],
error: null,
loading: false,
};
export const fetchProducts = createAsyncThunk('fetch/products', async () => {
return await axiosApiClient.get<IProduct[]>('/products').then((res) => res.data);
});
export const createProduct = createAsyncThunk('create/products', async (payload: IProduct) => {
return await axiosApiClient.post<IProduct>('/products', payload).then((res) => res.data);
});
const productsSlice = createSlice({
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.loading = true;
});
},
});
export default productsSlice.reducer;
import axios from 'axios';
export const axiosApiClient = axios.create({
baseURL: 'http://localhost:8000',
});
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
}
export interface IProduct {
id?: string;
title: string;
description: string;
price: number;
}
import { configureStore } from '@reduxjs/toolkit';
import productsReducer from '../features/productsSlice.ts';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
const store = configureStore({
reducer: {
products: productsReducer,
}
})
},
});
type RootState = ReturnType<typeof store.getState>;
type AppDispatch = typeof store.dispatch;
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export default store;
......@@ -2,11 +2,7 @@
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
......@@ -22,17 +18,13 @@
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"paths": {
"@/*": [
"./src/*"
]
"@/*": ["./src/*"]
}
},
"include": [
"src"
],
"include": ["src"],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}
\ No newline at end of file
}
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})
});
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