Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/ht7 #89

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions src/components/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,33 @@ import Header from '../header';
import Basket from '../basket';
import { UserProvider } from '../../contexts/user-context';
import { useState } from 'react';
import Error from '../error';
import {
CurrencyProvider,
DEFAULT_CURRENCY,
} from '../../contexts/currency-context';

const App = () => {
const [name, setName] = useState('Andrey');
const [currency, setCurrency] = useState(DEFAULT_CURRENCY);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я говорил о том, что не стоит данные контекста хранить в состоянии нашего приложения. Покажу как это можно сделать удобнее на своей домашке.


return (
<div>
<UserProvider value={{ name, setName }}>
<Header />
<Switch>
<Redirect exact from="/" to="/restaurants" />
<Route path="/checkout" component={Basket} />
<Route path="/restaurants" component={Restaurants} />
<Route path="/error" component={() => <h2>Error Page!</h2>} />
<Route component={() => <h2>404 - Not found :(</h2>} />
</Switch>
<CurrencyProvider value={{ currency, setCurrency }}>
<Header />
<Switch>
<Redirect exact from="/" to="/restaurants" />
<Route path="/checkout" component={Basket} />
<Route path="/restaurants" component={Restaurants} />
<Route path="/error" component={Error} />
<Route
path="/success"
component={() => <h2>Thanks for order!</h2>}
/>
<Route component={() => <h2>404 - Not found :(</h2>} />
</Switch>
</CurrencyProvider>
</UserProvider>
</div>
);
Expand Down
3 changes: 2 additions & 1 deletion src/components/basket/basket-item/basket-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
import { increment, decrement, remove } from '../../../redux/actions';
import Button from '../../button';
import styles from './basket-item.module.css';
import { toCurrency } from '../../../contexts/currency-context';

function BasketItem({
product,
Expand All @@ -25,7 +26,7 @@ function BasketItem({
<span className={styles.count}>{amount}</span>
<Button onClick={increment} icon="plus" secondary small />
</div>
<p className={cn(styles.count, styles.price)}>{subtotal} $</p>
<p className={cn(styles.count, styles.price)}>{toCurrency(subtotal)}</p>
<Button onClick={remove} icon="delete" secondary small />
</div>
</div>
Expand Down
41 changes: 26 additions & 15 deletions src/components/basket/basket.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { CSSTransition, TransitionGroup } from 'react-transition-group';

import styles from './basket.module.css';
import './basket.css';
import itemStyles from './basket-item/basket-item.module.css';
import BasketItem from './basket-item';
import Button from '../button';
import { orderProductsSelector, totalSelector } from '../../redux/selectors';
import { UserConsumer } from '../../contexts/user-context';

function Basket({ title = 'Basket', total, orderProducts }) {
// const { name } = useContext(userContext);
import {
orderLoadingSelector,
orderProductsSelector,
totalSelector,
} from '../../redux/selectors';
import { printUser } from '../../contexts/user-context';
import { checkout } from '../../redux/actions';
import Loader from '../loader/loader';
import { toCurrency } from '../../contexts/currency-context';

function Basket({ title = 'Basket', total, orderProducts, checkout, loading }) {
if (!total) {
return (
<div className={styles.basket}>
Expand All @@ -21,12 +25,16 @@ function Basket({ title = 'Basket', total, orderProducts }) {
);
}

if (loading) {
return <Loader />;
}

return (
<div className={styles.basket}>
<h4 className={styles.title}>
<UserConsumer>{({ name }) => `${name}'s ${title}`}</UserConsumer>
{printUser()}
{`'s ${title}`}
</h4>
{/* <h4 className={styles.title}>{`${name}'s ${title}`}</h4> */}
<TransitionGroup>
{orderProducts.map(({ product, amount, subtotal, restId }) => (
<CSSTransition
Expand All @@ -49,14 +57,12 @@ function Basket({ title = 'Basket', total, orderProducts }) {
<p>Total</p>
</div>
<div className={itemStyles.info}>
<p>{`${total} $`}</p>
<p>{toCurrency(total)}</p>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

это лучше сделать в виде компонента, а то не совсем очевидно, что функция возвращает JSX

<ToCurrency value={total} />

</div>
</div>
<Link to="/checkout">
<Button primary block>
checkout
</Button>
</Link>
<Button primary block onClick={checkout}>
checkout
</Button>
</div>
);
}
Expand All @@ -65,7 +71,12 @@ const mapStateToProps = (state) => {
return {
total: totalSelector(state),
orderProducts: orderProductsSelector(state),
loading: orderLoadingSelector(state),
};
};

export default connect(mapStateToProps)(Basket);
const mapDispatchToProps = {
checkout,
};

export default connect(mapStateToProps, mapDispatchToProps)(Basket);
36 changes: 36 additions & 0 deletions src/components/error/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useContext } from 'react';
import { connect } from 'react-redux';
import {
currencyContext,
DEFAULT_CURRENCY,
toCurrency,
} from '../../contexts/currency-context';
import { orderErrorSelector, orderInfoSelector } from '../../redux/selectors';

function Error({ info, error }) {
const { currency } = useContext(currencyContext);
const currencyInfo =
info && currency !== DEFAULT_CURRENCY ? (
<i>$1 equals to {toCurrency(1)}</i>
) : (
''
);

return (
<>
<h2>Something went wrong!</h2>
<p>{info}</p>
{currencyInfo}
<p>{error}</p>
</>
);
}

const mapStateToProps = (state) => {
return {
info: orderInfoSelector(state),
error: orderErrorSelector(state),
};
};

export default connect(mapStateToProps)(Error);
1 change: 1 addition & 0 deletions src/components/error/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './error';
18 changes: 18 additions & 0 deletions src/components/header/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,33 @@ import { Link } from 'react-router-dom';
import { userContext } from '../../contexts/user-context';
import { ReactComponent as Logo } from '../../icons/logo.svg';
import styles from './header.module.css';
import { currencyContext, CURRENCIES } from '../../contexts/currency-context';
import Button from '../button/button';

const Header = () => {
const { name, setName } = useContext(userContext);
const { currency, setCurrency } = useContext(currencyContext);

const currenciesList = Object.keys(CURRENCIES).map((item) => {
const selected = item === currency;
return (
<Button
secondary={!selected}
primary={selected}
key={item}
onClick={() => setCurrency(item)}
>
{item}
</Button>
);
});

return (
<header className={styles.header} onClick={() => setName('Igor')}>
<Link to="/restaurants">
<Logo />
</Link>
<div style={{ display: 'flex' }}>{currenciesList}</div>
<h2>{name}</h2>
</header>
);
Expand Down
3 changes: 2 additions & 1 deletion src/components/product/product.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import styles from './product.module.css';
import Button from '../button';
import { decrement, increment } from '../../redux/actions';
import { amountSelector, productSelector } from '../../redux/selectors';
import { toCurrency } from '../../contexts/currency-context';

function Product({ product, amount, decrement, increment }) {
return (
Expand All @@ -12,7 +13,7 @@ function Product({ product, amount, decrement, increment }) {
<div>
<h4 className={styles.title}>{product.name}</h4>
<p className={styles.description}>{product.ingredients.join(', ')}</p>
<div className={styles.price}>{product.price} $</div>
<div className={styles.price}>{toCurrency(product.price)}</div>
</div>
<div>
<div className={styles.counter}>
Expand Down
21 changes: 21 additions & 0 deletions src/components/reviews/review/review.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,24 @@
line-height: 20px;
}
}

.review-item-enter {
opacity: 0;
transform: scale(0.9);
}

.review-item-enter-active {
opacity: 1;
transform: translateX(0);
transition: opacity 5000ms, transform 5000ms;
}

.review-item-exit {
opacity: 1;
}

.review-item-exit-active {
opacity: 0;
transform: scale(0.9);
transition: opacity 5000ms, transform 5000ms;
}
23 changes: 20 additions & 3 deletions src/components/reviews/reviews.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import Review from './review';
import Loader from '../loader';
import ReviewForm from './review-form';
import styles from './reviews.module.css';
import reviewStyles from './review/review.module.css';

import { loadReviews, loadUsers } from '../../redux/actions';
import {
reviewsLoadedSelector,
usersLoadedSelector,
} from '../../redux/selectors';
import { CSSTransition, TransitionGroup } from 'react-transition-group';

const Reviews = ({
reviews,
Expand All @@ -27,11 +29,26 @@ const Reviews = ({

if (!usersLoaded || !reviewsLoaded) return <Loader />;

console.log('ff: ', reviewStyles);

return (
<div className={styles.reviews}>
{reviews.map((id) => (
<Review key={id} id={id} />
))}
<TransitionGroup>
{reviews.map((id) => (
<CSSTransition
key={id}
timeout={500}
classNames={{
enter: reviewStyles['review-item-enter'],
enterActive: reviewStyles['review-item-enter-active'],
exit: reviewStyles['review-item-exit'],
exitActive: reviewStyles['review-item-exit-active'],
}}
>
<Review id={id} />
</CSSTransition>
))}
</TransitionGroup>
<ReviewForm restId={restId} />
</div>
);
Expand Down
20 changes: 20 additions & 0 deletions src/contexts/currency-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createContext } from 'react';

export const DEFAULT_CURRENCY = 'USD';
export const CURRENCIES = {
USD: 1,
RUB: 72,
BYN: 2.5,
UAH: 27,
};

export const currencyContext = createContext(DEFAULT_CURRENCY);

export const CurrencyProvider = currencyContext.Provider;
export const CurrencyConsumer = currencyContext.Consumer;

export const toCurrency = (value) => (
<CurrencyConsumer>
{({ currency }) => `${value * CURRENCIES[currency]} ${currency}`}
</CurrencyConsumer>
);
4 changes: 4 additions & 0 deletions src/contexts/user-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ export const userContext = createContext('Default user');

export const UserProvider = userContext.Provider;
export const UserConsumer = userContext.Consumer;

export const printUser = () => (
<UserConsumer>{({ name }) => name}</UserConsumer>
);
42 changes: 41 additions & 1 deletion src/redux/actions.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { replace } from 'connected-react-router';
import { replace, push } from 'connected-react-router';
import {
DECREMENT,
INCREMENT,
Expand All @@ -12,13 +12,17 @@ import {
REQUEST,
SUCCESS,
FAILURE,
SEND_ORDER,
} from './constants';

import {
usersLoadingSelector,
usersLoadedSelector,
reviewsLoadingSelector,
reviewsLoadedSelector,
locationSelector,
orderSelector,
orderLoadedSelector,
} from './selectors';

export const increment = (id) => ({ type: INCREMENT, id });
Expand Down Expand Up @@ -77,3 +81,39 @@ export const loadUsers = () => async (dispatch, getState) => {

dispatch(_loadUsers());
};

export const checkout = () => async (dispatch, getState) => {
const state = getState();
const location = locationSelector(state);

if (location.pathname !== '/checkout') {
return dispatch(push('/checkout'));
}

dispatch({ type: SEND_ORDER + REQUEST });

try {
const order = orderSelector(state);
const orderData = Object.entries(order).map(([id, amount]) => ({
id,
amount: amount,
}));

const result = await fetch('/api/order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(orderData),
});

const info = await result.json();

dispatch({ type: SEND_ORDER + SUCCESS, info, status: result.status });

const loaded = orderLoadedSelector(getState());

dispatch(push(loaded ? '/success' : '/error'));
} catch (error) {
dispatch({ type: SEND_ORDER + FAILURE, error });
dispatch(push('/error'));
}
};
Loading