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

HT-7: redirect to restaurant & submit order & currencies & review animation #82

Open
wants to merge 4 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
9 changes: 5 additions & 4 deletions src/components/app/app.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import React, { useState } from 'react';
import { Route, Switch } from 'react-router-dom';
import { Route, Switch, Redirect } from 'react-router-dom';
import Header from '../header';
import Basket from '../basket';
import RestaurantsPage from '../../pages/restaurants-page';
import BasketPage from '../../pages/basket-page';
import { UserProvider } from '../../contexts/user-context';

const App = () => {
const [name, setName] = useState('Igor');

return (
<div>
<UserProvider value={{ name, setName }}>
<Header />
<Switch>
<Route path="/checkout" component={Basket} />
<Route path="/checkout" component={BasketPage} />
<Route path="/restaurants" component={RestaurantsPage} />
<Route path="/error" component={() => <h1>Error Page</h1>} />
<Route path="/" component={() => '404 - not found'} />
<Redirect exact from="/" to="/restaurants" />
Copy link
Owner

Choose a reason for hiding this comment

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

Redirect с рута лучше ставить самым первым, так мы сразу понимаем, что происходит при первом заходе на /

</Switch>
</UserProvider>
</div>
Expand Down
6 changes: 4 additions & 2 deletions src/components/basket/basket-item/basket-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import cn from 'classnames';
import { increment, decrement, remove } from '../../../redux/actions';
import Button from '../../button';
import styles from './basket-item.module.css';
import {withCurrency} from "../../../contexts/currency-context";

function BasketItem({
product,
Expand All @@ -14,6 +15,7 @@ function BasketItem({
increment,
decrement,
remove,
convertCurrency
}) {
return (
<div className={styles.basketItem}>
Expand All @@ -38,7 +40,7 @@ function BasketItem({
small
/>
</div>
<p className={cn(styles.count, styles.price)}>{subtotal} $</p>
<p className={cn(styles.count, styles.price)}>{convertCurrency(subtotal)}</p>
<Button
onClick={() => remove(product.id)}
icon="delete"
Expand All @@ -50,4 +52,4 @@ function BasketItem({
);
}

export default connect(null, { increment, decrement, remove })(BasketItem);
export default connect(null, { increment, decrement, remove })(withCurrency(BasketItem));
31 changes: 21 additions & 10 deletions src/components/basket/basket.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@ import './basket.css';
import BasketRow from './basket-row';
import BasketItem from './basket-item';
import Button from '../button';
import { orderProductsSelector, totalSelector } from '../../redux/selectors';
import Loader from '../loader';
import { orderProductsSelector, totalSelector, orderProcessingSelector} from '../../redux/selectors';
import { UserConsumer } from '../../contexts/user-context';
import { submitOrder } from '../../redux/actions';
import { withCurrency } from '../../contexts/currency-context';

function Basket({ title = 'Basket', total, orderProducts }) {
function Basket({ title = 'Basket', total, orderProducts, canCheckout, submitOrder, processing, convertCurrency }) {
// const { name } = useContext(userContext);

if (processing) {
return <Loader />
}

if (!total) {
return (
<div className={styles.basket}>
Expand All @@ -24,6 +31,12 @@ function Basket({ title = 'Basket', total, orderProducts }) {
);
}

const checkoutButton = canCheckout
? <Button onClick={submitOrder} primary block>checkout</Button>
: <Link to="/checkout">
<Button primary block>checkout</Button>
</Link>;

return (
<div className={styles.basket}>
{/* <h4 className={styles.title}>{`${name}'s ${title}`}</h4> */}
Expand All @@ -47,21 +60,19 @@ function Basket({ title = 'Basket', total, orderProducts }) {
))}
</TransitionGroup>
<hr className={styles.hr} />
<BasketRow label="Sub-total" content={`${total} $`} />
<BasketRow label="Sub-total" content={`${convertCurrency(total)}`} />
<BasketRow label="Delivery costs:" content="FREE" />
<BasketRow label="total" content={`${total} $`} bold />
<Link to="/checkout">
<Button primary block>
checkout
</Button>
</Link>
<BasketRow label="total" content={`${convertCurrency(total)}`} bold />

{checkoutButton}
</div>
);
}

const mapStateToProps = createStructuredSelector({
total: totalSelector,
orderProducts: orderProductsSelector,
processing: orderProcessingSelector
});

export default connect(mapStateToProps)(Basket);
export default connect(mapStateToProps, { submitOrder })(withCurrency(Basket));
19 changes: 19 additions & 0 deletions src/components/currency/currency-picker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import cn from 'classnames';
import React, { useMemo } from 'react';
import { withCurrency } from '../../contexts/currency-context';

import styles from './currency-picker.module.css';

const CurrencyPicker = ({ currencies, activeCurrency, setActiveCurrency }) => {
const currencyList = useMemo(() => Object.keys(currencies), [currencies]);

return currencyList.map((code) => (
<span key={code}
onClick={() => setActiveCurrency(code)}
className={cn(styles.currency, { [styles.active]: code === activeCurrency })}>
{code}
</span>
));
};

export default withCurrency(CurrencyPicker);
8 changes: 8 additions & 0 deletions src/components/currency/currency-picker.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.currency {
padding: 0 10px;
cursor: pointer;
}

.active {
font-weight: bold;
}
1 change: 1 addition & 0 deletions src/components/currency/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './currency-picker';
4 changes: 4 additions & 0 deletions src/components/header/header.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useContext } from 'react';

import Logo from './logo';
import CurrencyPicker from '../currency';
import styles from './header.module.css';
import userContext from '../../contexts/user-context';

Expand All @@ -9,6 +10,9 @@ const Header = () => {

return (
<header className={styles.header} onClick={() => setName('Ivan')}>
<div className={styles.currencies}>
<CurrencyPicker />
</div>
<Logo />
<h2>{name}</h2>
</header>
Expand Down
6 changes: 6 additions & 0 deletions src/components/header/header.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@
right: 20px;
top: 0;
}

.currencies {
position: absolute;
left: 20px;
color: var(--white);
}
7 changes: 4 additions & 3 deletions src/components/product/product.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { decrement, increment } from '../../redux/actions';

import Button from '../button';
import { productAmountSelector, productSelector } from '../../redux/selectors';
import { withCurrency } from '../../contexts/currency-context';

const Product = ({ product, amount = 0, increment, decrement }) => {
const Product = ({ product, amount = 0, increment, decrement, convertCurrency }) => {
if (!product) return null;

return (
Expand All @@ -18,7 +19,7 @@ const Product = ({ product, amount = 0, increment, decrement }) => {
<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}>{convertCurrency(product.price)}</div>
</div>
<div>
<div className={styles.counter}>
Expand Down Expand Up @@ -58,4 +59,4 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
decrement: () => dispatch(decrement(ownProps.id)),
});

export default connect(mapStateToProps, mapDispatchToProps)(Product);
export default connect(mapStateToProps, mapDispatchToProps)(withCurrency(Product));
11 changes: 8 additions & 3 deletions src/components/reviews/reviews.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useEffect } from 'react';
import { createStructuredSelector } from 'reselect';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import PropTypes from 'prop-types';
import Review from './review';
import ReviewForm from './review-form';
Expand Down Expand Up @@ -31,9 +32,13 @@ const Reviews = ({

return (
<div className={styles.reviews}>
{reviews.map((id) => (
<Review key={id} id={id} />
))}
<TransitionGroup>
{reviews.map((id) => (
<CSSTransition key={id} timeout={500} classNames="review-animation">
<Review id={id} />
</CSSTransition>
))}
</TransitionGroup>
<ReviewForm restaurantId={restaurantId} />
</div>
);
Expand Down
11 changes: 11 additions & 0 deletions src/components/reviews/reviews.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,14 @@
max-width: 884px;
width: 100%;
}

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

.review-animation-enter-active {
opacity: 1;
transform: translateX(0);
transition: opacity 500ms, transform 500ms;
}
39 changes: 39 additions & 0 deletions src/contexts/currency-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, { createContext, useState, useContext } from 'react';

const initialValue = {
currencies: {},
activeCurrency: null,
setActiveCurrency: () => {}
};

const context = createContext(initialValue);

export const CurrencyProvider = ({ currencies, defaultCurrency, children }) => {
const [activeCurrency, setActiveCurrency] = useState(defaultCurrency);
const providerValue = { currencies, activeCurrency, setActiveCurrency };

return (
<context.Provider value={providerValue}>
{children}
</context.Provider>
);
};

const convert = (context) => (value) => {
const { currencies, activeCurrency } = context;
const { rate, sign } = currencies[activeCurrency];
const convertedValue = Math.floor(value * rate * 10) / 10;

return `${convertedValue} ${sign}`;
};

export const withCurrency = (WrappedComponent) => (props) => {
Copy link
Owner

Choose a reason for hiding this comment

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

Хорошее решение по контексту валюты.

const currencyContext = useContext(context);
const convertCurrency = convert(currencyContext);
const currencyProps = {...currencyContext, convertCurrency};

return <WrappedComponent {...props} {...currencyProps} />
};

export default context;

19 changes: 19 additions & 0 deletions src/fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -381,10 +381,29 @@ const normalizedUsers = [
},
];

const normalizedCurrencies = [
{
code: 'USD',
sign: '$',
rate: 1
},
{
code: 'UAH',
sign: '₴',
rate: 28
},
{
code: 'EUR',
sign: '€',
rate: 0.8
}
];

export {
restaurants,
normalizedProducts,
normalizedRestaurants,
normalizedReviews,
normalizedUsers,
normalizedCurrencies
};
17 changes: 12 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,21 @@ import App from './components/app';
import store from './redux/store';
import history from './history';

import { CurrencyProvider } from './contexts/currency-context';
import { normalizedCurrencies } from './fixtures';

const currencies = normalizedCurrencies.reduce((acc, currency) => ({...acc, [currency.code]: currency }), {});

// DEV ONLY!!!
window.store = store;

ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
<CurrencyProvider currencies={currencies} defaultCurrency="USD">
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>
</CurrencyProvider>,
document.getElementById('root')
);
31 changes: 31 additions & 0 deletions src/pages/basket-page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { connect } from 'react-redux';
import { Route, Switch } from 'react-router-dom';
import { createStructuredSelector } from 'reselect';

import styles from './basket-page.module.css';

import Basket from '../components/basket';
import { orderErrorSelector } from '../redux/selectors';

const BasketPage = ({ match, orderError }) => {
const { path } = match;

return (
<Switch>
<Route exact path={path}>
<Basket canCheckout />
</Route>
<Route path={`${path}/error`}>
<h2 className={styles.message}>{orderError}</h2>
</Route>
<Route path={`${path}/success`}>
<h2 className={styles.message}>Thanks for your order!</h2>
</Route>
</Switch>
)
};

export default connect(() => createStructuredSelector({
orderError: orderErrorSelector
}))(BasketPage);
4 changes: 4 additions & 0 deletions src/pages/basket-page.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.message {
margin-top: 100px;
text-align: center;
}
Loading