Skip to content

Commit

Permalink
Merge pull request #3 from massimoteplovsky/sprint-2/step-1
Browse files Browse the repository at this point in the history
sprint-2/step-1
  • Loading branch information
massimoteplovsky authored Nov 24, 2021
2 parents a759fe4 + 7873986 commit eb5c2df
Show file tree
Hide file tree
Showing 15 changed files with 381 additions and 135 deletions.
57 changes: 32 additions & 25 deletions src/components/app/app.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { useState, useEffect } from 'react';
import { useReducer, useEffect } from 'react';
import s from './app.module.css';
import { ModalType } from '../../utils/constants';
import { AppContext } from '../../services/app-context';
import { ENDPOINT, ApiRoute } from '../../utils/constants';
import appReducer, {
FETCH_INGREDIENTS,
FETCH_INGREDIENTS_ERROR,
} from '../../services/modules/app';

// Components
import AppHeader from '../app-header/app-header';
Expand All @@ -15,18 +20,23 @@ import withModal from '../hocs/with-modal';
const WithModalBurgerConstructor = withModal(BurgerConstructor);
const WithModalBurgerIngredients = withModal(BurgerIngredients);

const ENDPOINT = 'https://norma.nomoreparties.space/api';
const ApiRoute = {
INGREDIENTS: 'ingredients',
const initialAppState = {
ingredients: [],
ingredient: null,
burgerData: {
bunIngredient: null,
mainIngredients: [],
},
orderNumber: null,
totalPrice: 0,
modalType: null,
loading: true,
error: false,
};

const App = () => {
const [state, setState] = useState({
ingredients: [],
loading: true,
error: false,
});
const { ingredients, loading, error } = state;
const [state, dispatch] = useReducer(appReducer, initialAppState);
const { loading, error } = state;

useEffect(() => {
const getData = async () => {
Expand All @@ -35,16 +45,17 @@ const App = () => {

if (response?.ok) {
const { data: ingredients } = await response.json();
return setState((state) => ({
...state,
ingredients,
loading: false,
}));
return dispatch({
type: FETCH_INGREDIENTS,
payload: ingredients,
});
}

throw new Error();
} catch (err) {
setState((state) => ({ ...state, loading: false, error: true }));
dispatch({
type: FETCH_INGREDIENTS_ERROR,
});
}
};
getData();
Expand All @@ -59,14 +70,10 @@ const App = () => {
<Error>Произошла ошибка при загрузке данных...</Error>
) : (
<main className={`${s.container} pb-10`}>
<WithModalBurgerIngredients
ingredients={ingredients}
modalType={ModalType.INGREDIENT}
/>
<WithModalBurgerConstructor
ingredients={ingredients}
modalType={ModalType.ORDER}
/>
<AppContext.Provider value={{ ...state, dispatch }}>
<WithModalBurgerIngredients />
<WithModalBurgerConstructor />
</AppContext.Provider>
</main>
)}
</>
Expand Down
181 changes: 127 additions & 54 deletions src/components/burger-constructor/burger-constructor.jsx
Original file line number Diff line number Diff line change
@@ -1,80 +1,153 @@
import { useMemo } from 'react';
import { useContext, useEffect } from 'react';
import {
Button,
ConstructorElement,
CurrencyIcon,
DragIcon,
BurgerIcon,
} from '@ya.praktikum/react-developer-burger-ui-components';
import pt from 'prop-types';
import s from './burger-constructor.module.css';
import { PropValidator } from '../../utils/prop-validator';
import { AppContext } from '../../services/app-context';
import { ENDPOINT, ApiRoute, ModalType } from '../../utils/constants';
import {
SET_ORDER_NUMBER,
SEND_ORDER_ERROR,
DELETE_INGREDIENT,
SET_TOTAL_PRICE,
} from '../../services/modules/app';

const BurgerConstructor = ({ ingredients, handleSetModalData }) => {
const slicedIngredients = useMemo(() => ingredients.slice(0, 10), [
ingredients,
]);
const totalPrice = slicedIngredients.reduce(
(total, ingredeint) => (total += ingredeint.price),
0
);
// Components
import NoIngredient from '../no-ingredient/no-ingredient';

const BurgerConstructor = () => {
const {
burgerData: { bunIngredient, mainIngredients },
totalPrice,
dispatch,
} = useContext(AppContext);
const isIngredientsExist = bunIngredient || mainIngredients.length > 0;

const handleSendOrder = async () => {
const ingredients = [bunIngredient, ...mainIngredients].map(
({ _id }) => _id
);

const handleSendOrder = () => {
handleSetModalData({ orderNumber: 345679 });
try {
const response = await fetch(`${ENDPOINT}/${ApiRoute.ORDERS}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ingredients }),
});

if (response?.ok) {
const { order } = await response.json();
return dispatch({
type: SET_ORDER_NUMBER,
payload: { orderNumber: order.number, modalType: ModalType.ORDER },
});
}

throw new Error();
} catch (err) {
dispatch({
type: SEND_ORDER_ERROR,
});
}
};

const handleDeleteIngredient = (ingredientIndex) => {
const newMainIngredients = mainIngredients.filter(
(_, index) => index !== ingredientIndex
);
dispatch({ type: DELETE_INGREDIENT, payload: newMainIngredients });
};

useEffect(() => {
const bunPrice = bunIngredient?.price * 2 || 0;

const totalPrice =
mainIngredients.reduce(
(totalPrice, { price }) => (totalPrice += price),
0
) + bunPrice;

dispatch({
type: SET_TOTAL_PRICE,
payload: totalPrice,
});
}, [bunIngredient, mainIngredients, dispatch]);

return (
<section className={`${s.burgerConstructorSection} mr-5 ml-5 pt-25`}>
<div className={`${s.ingredientsContainer} mb-10`}>
<ConstructorElement
type="top"
isLocked={true}
text={slicedIngredients[0].name}
price={slicedIngredients[0].price}
thumbnail={slicedIngredients[0].image}
/>
<ul className={`${s.ingredientsList} pr-4 pl-4`}>
{useMemo(
() =>
slicedIngredients
.slice(1, 9)
.map(({ name, price, image }, index) => (
{!isIngredientsExist ? (
<div className={s.noIngredientsBlock}>
<BurgerIcon type="primary" />
<h2 className="text text_type_main-medium">
Выберите ингредиенты для вашего бургера
</h2>
</div>
) : (
<>
<div className={`${s.ingredientsContainer} mb-10`}>
{bunIngredient ? (
<ConstructorElement
type="top"
isLocked={true}
text={`${bunIngredient.name} (верх)`}
price={bunIngredient.price}
thumbnail={bunIngredient.image_mobile}
/>
) : (
<NoIngredient>Выберите булку (верх)</NoIngredient>
)}
<ul className={`${s.ingredientsList}`}>
{mainIngredients.length ? (
mainIngredients.map(({ name, price, image_mobile }, index) => (
<li key={index} className={s.ingredientItem}>
<DragIcon type="primary" />
<ConstructorElement
isLocked={false}
text={name}
price={price}
thumbnail={image}
thumbnail={image_mobile}
handleClose={() => handleDeleteIngredient(index)}
/>
</li>
)),
[slicedIngredients]
)}
</ul>
<ConstructorElement
type="bottom"
isLocked={true}
text={slicedIngredients[slicedIngredients.length - 1].name}
price={slicedIngredients[slicedIngredients.length - 1].price}
thumbnail={slicedIngredients[slicedIngredients.length - 1].image}
/>
</div>
<div className={`${s.submitSection} mr-6`}>
<p className={`${s.totalPrice} text text_type_digits-medium mr-10`}>
{totalPrice}&nbsp;
<CurrencyIcon clasName="text_type_main-large" type="primary" />
</p>
<Button type="primary" size="large" onClick={handleSendOrder}>
Оформить заказ
</Button>
</div>
))
) : (
<NoIngredient>Выберите основные ингредиенты</NoIngredient>
)}
</ul>
{bunIngredient ? (
<ConstructorElement
type="bottom"
isLocked={true}
text={`${bunIngredient.name} (низ)`}
price={bunIngredient.price}
thumbnail={bunIngredient.image_mobile}
/>
) : (
<NoIngredient>Выберите булку (низ)</NoIngredient>
)}
</div>
<div className={s.submitSection}>
<p className={`${s.totalPrice} text text_type_digits-medium mr-10`}>
{totalPrice}&nbsp;
<CurrencyIcon clasName="text_type_main-large" type="primary" />
</p>
<Button
type="primary"
size="large"
disabled={!bunIngredient}
onClick={handleSendOrder}
>
Оформить заказ
</Button>
</div>
</>
)}
</section>
);
};

BurgerConstructor.propTypes = {
ingredients: pt.arrayOf(PropValidator.INGREDIENT).isRequired,
handleSetModalData: pt.func.isRequired,
};

export default BurgerConstructor;
42 changes: 36 additions & 6 deletions src/components/burger-constructor/burger-constructor.module.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.burgerConstructorSection {
position: relative;
max-width: 600px;
width: 100%;
}
Expand All @@ -10,14 +11,31 @@
gap: 16px;
}

.noIngredientsBlock {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
display: flex;
flex-flow: column;
align-items: center;
}

.noIngredientsBlock svg {
width: 30%;
height: 30%;
margin-bottom: 40px;
}

.ingredientsContainer > div {
margin-left: 17px;
max-width: 536px;
width: 100%;
}

.ingredientsList {
display: flex;
align-items: center;
flex-flow: column;
gap: 16px;
max-height: 465px;
Expand All @@ -39,22 +57,34 @@
.ingredientItem {
display: flex;
align-items: center;
margin-right: 25px;
}

.ingredientItem > div {
max-width: 536px;
.ingredientItem > svg {
margin-right: 5px;
}

.ingredientsContainer > div {
margin-left: 17px;
.ingredientItem > div {
max-width: 536px;
width: 100%;
width: 536px;
}

.submitSection {
display: flex;
justify-content: flex-end;
align-items: center;
margin-right: 30px;
}

.submitSection button {
cursor: pointer;
}

.submitSection button:disabled {
box-shadow: none;
filter: none;
opacity: 0.2;
cursor: default;
}

.totalPrice {
Expand Down
Loading

0 comments on commit eb5c2df

Please sign in to comment.