Skip to content

Commit

Permalink
Merge pull request #332 from reactioncommerce/fix-clear-cart-after-order
Browse files Browse the repository at this point in the history
fix: clear a user's cart after placing an order, and remove MiniCart popper from order completed page.
  • Loading branch information
mikemurray committed Sep 26, 2018
2 parents 572b165 + a8a7729 commit b1ff9af
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 75 deletions.
63 changes: 56 additions & 7 deletions src/components/CheckoutActions/CheckoutActions.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import React, { Component } from "react";
import React, { Fragment, Component } from "react";
import PropTypes from "prop-types";
import { observer } from "mobx-react";
import { inject, observer } from "mobx-react";
import Actions from "@reactioncommerce/components/CheckoutActions/v1";
import ShippingAddressCheckoutAction from "@reactioncommerce/components/ShippingAddressCheckoutAction/v1";
import FulfillmentOptionsCheckoutAction from "@reactioncommerce/components/FulfillmentOptionsCheckoutAction/v1";
import StripePaymentCheckoutAction from "@reactioncommerce/components/StripePaymentCheckoutAction/v1";
import FinalReviewCheckoutAction from "@reactioncommerce/components/FinalReviewCheckoutAction/v1";
import withCart from "containers/cart/withCart";
import withPlaceStripeOrder from "containers/order/withPlaceStripeOrder";
import Dialog from "@material-ui/core/Dialog";
import PageLoading from "components/PageLoading";
import { Router } from "routes";
import {
adaptAddressToFormFields,
isShippingAddressSet
} from "lib/utils/cartUtils";

@withCart
@withPlaceStripeOrder
@inject("authStore")
@observer
export default class CheckoutActions extends Component {
static propTypes = {
Expand All @@ -35,6 +39,10 @@ export default class CheckoutActions extends Component {
placeOrderWithStripeCard: PropTypes.func.isRequired
};

state = {
isPlacingOrder: false
}

setShippingAddress = (address) => {
const { checkoutMutations: { onSetShippingAddress } } = this.props;

Expand Down Expand Up @@ -65,8 +73,8 @@ export default class CheckoutActions extends Component {
cartStore.setStripeToken(stripeToken);
}

placeOrder = () => {
const { cart, cartStore, placeOrderWithStripeCard } = this.props;
buildOrder = async () => {
const { cart, cartStore } = this.props;
const cartId = cartStore.hasAccountCart ? cartStore.accountCartId : cartStore.anonymousCartId;
const { checkout, email, shop } = cart;
const fulfillmentGroups = checkout.fulfillmentGroups.map((group) => {
Expand Down Expand Up @@ -98,7 +106,45 @@ export default class CheckoutActions extends Component {
shopId: shop._id
};

return placeOrderWithStripeCard(order);
return this.setState({ isPlacingOrder: true }, () => this.placeOrder(order));
}

placeOrder = async (order) => {
const { authStore, cartStore, placeOrderWithStripeCard } = this.props;
const { data, error } = await placeOrderWithStripeCard(order);

// If success
if (data && !error) {
const { placeOrderWithStripeCardPayment: { orders, token } } = data;

// Clear anonymous cart
if (!authStore.isAuthenticated) {
cartStore.clearAnonymousCartCredentials();
}

// Send user to order confirmation page
Router.pushRoute("checkoutComplete", { orderId: orders[0]._id, token });
}

// TODO: if an error occurred, notify user
}

handleClose = () => {
// TODO: if an error occurs, then close dialog
}

renderPlacingOrderOverlay = () => {
const { isPlacingOrder } = this.state;

return (
<Dialog
fullScreen
open={isPlacingOrder}
onClose={this.handleClose}
>
<PageLoading delay={0} message="Placing your order..."/>
</Dialog>
);
}

render() {
Expand Down Expand Up @@ -176,14 +222,17 @@ export default class CheckoutActions extends Component {
label: "Review and place order",
status: "incomplete",
component: FinalReviewCheckoutAction,
onSubmit: this.placeOrder,
onSubmit: this.buildOrder,
props: {
checkoutSummary
}
}
];
return (
<Actions actions={actions} />
<Fragment>
{this.renderPlacingOrderOverlay()}
<Actions actions={actions} />
</Fragment>
);
}
}
8 changes: 6 additions & 2 deletions src/components/MiniCart/MiniCart.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,15 @@ export default class MiniCart extends Component {
}

handleClick = () => Router.pushRoute("/");
handleCheckoutButtonClick = () => Router.pushRoute("/cart/checkout");

handleCheckoutButtonClick = () => {
this.handleLeavePopper();
Router.pushRoute("/cart/checkout");
}

handlePopperClose = () => {
const { closeCart } = this.props.uiStore;
closeCart();
closeCart(0);
}

handleEnterPopper = () => {
Expand Down
31 changes: 29 additions & 2 deletions src/components/PageLoading/PageLoading.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { withStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";

const styles = () => ({
svg: {
Expand All @@ -10,27 +11,40 @@ const styles = () => ({
alignItems: "center",
display: "flex",
height: "75vh",
justifyContent: "center"
justifyContent: "center",
flexDirection: "column"
},
message: {
marginTop: "20px"
}
});

@withStyles(styles, { withTheme: true })
class PageLoading extends Component {
static propTypes = {
classes: PropTypes.object,
delay: PropTypes.number,
message: PropTypes.string,
theme: PropTypes.object
};

static defaultProps = {
delay: 800,
message: null
}

state = {
delayIsDone: false
}

componentDidMount() {
const { delay } = this.props;

this.timeout = setTimeout(() => {
this.setState({
delayIsDone: true
});
}, 800);
}, delay);
}

componentWillUnmount() {
Expand Down Expand Up @@ -100,13 +114,26 @@ class PageLoading extends Component {
);
}

renderMessage = () => {
const { classes, message } = this.props;

if (!message) {
return null;
}

return (
<Typography variant="body1" className={classes.message}>{message}</Typography>
);
}

render() {
const { classes } = this.props;
const { delayIsDone } = this.state;

return (
<div className={classes.wrapper}>
{!!delayIsDone && this.renderSpinner()}
{!!delayIsDone && this.renderMessage()}
</div>
);
}
Expand Down
12 changes: 3 additions & 9 deletions src/components/ProductDetailAddToCart/ProductDetailAddToCart.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ export default class ProductDetailAddToCart extends Component {
classes: PropTypes.object,
onClick: PropTypes.func,
uiStore: PropTypes.shape({
closeCartPopover: PropTypes.func,
openCartPopover: PropTypes.func
openCartWithTimeout: PropTypes.func
}).isRequired
};

Expand All @@ -135,13 +134,8 @@ export default class ProductDetailAddToCart extends Component {
// Reset cart quantity to 1 after items are added to cart
this.setState({ addToCartQuantity: 1 });

// Open cart popover on addToCart
uiStore.openCartPopover();

// Close cart popover after 3 seconds
setTimeout(() => {
uiStore.closeCartPopover();
}, 3000);
// Open cart popper on addToCart
uiStore.openCartWithTimeout();
}

handleQuantityInputChange = (event) => {
Expand Down
28 changes: 28 additions & 0 deletions src/containers/cart/withCart.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,33 @@ export default function withCart(Component) {
cartStore.setAnonymousCartCredentialsFromLocalStorage();
}

/**
* Clears an authenticated user's cart, this method is
* called after successfully placing on order.
* @name clearAuthenticatedUsersCart
* @summary Called when an authenticated user's cart needs to be cleared.
* @private
* @returns {undefined} No return
*/
clearAuthenticatedUsersCart = () => {
const {
authStore,
client: { cache },
shop
} = this.props;

if (authStore.isAuthenticated) {
cache.writeQuery({
query: accountCartByAccountIdQuery,
data: { cart: null },
variables: {
accountId: authStore.accountId,
shopId: shop._id
}
});
}
}

/**
* Reconcile an anonymous and account cart when an anonymous user signs in
* and they have an anonymous cart.
Expand Down Expand Up @@ -447,6 +474,7 @@ export default function withCart(Component) {
}}
onChangeCartItemsQuantity={this.handleChangeCartItemsQuantity}
onRemoveCartItems={this.handleRemoveCartItems}
clearAuthenticatedUsersCart={this.clearAuthenticatedUsersCart}
refetchCart={refetchCart}
setEmailOnAnonymousCart={this.handleSetEmailOnAnonymousCart}
/>
Expand Down
18 changes: 1 addition & 17 deletions src/containers/order/withPlaceStripeOrder.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from "react";
import PropTypes from "prop-types";
import { withApollo } from "react-apollo";
import { inject, observer } from "mobx-react";
import { Router } from "routes";
import { placeOrderWithStripeCardPayment } from "./mutations.gql";

/**
Expand Down Expand Up @@ -50,7 +49,7 @@ export default (Component) => (
stripeTokenId: cartStore.stripeToken.token.id
};

const { data, error } = await apolloClient.mutate({
return apolloClient.mutate({
mutation: placeOrderWithStripeCardPayment,
variables: {
input: {
Expand All @@ -59,21 +58,6 @@ export default (Component) => (
}
}
});

// If success
if (data && !error) {
const { placeOrderWithStripeCardPayment: { orders, token } } = data;

// Clear anonymous cart
if (!authStore.isAuthenticated) {
cartStore.clearAnonymousCartCredentials();
}

// Send user to order confirmation page
Router.pushRoute("checkoutComplete", { orderId: orders[0]._id, token });
}

// TODO: if an error occurred, notify user
}

render() {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/stores/CartStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class CartStore {
@observable accountCartId = null;

/**
* Is the cart currently in the process of being reconcoled
* Is the cart currently in the process of being reconciled
*
* @type Boolean
*/
Expand Down
16 changes: 0 additions & 16 deletions src/lib/stores/UIStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,6 @@ class UIStore {
*/
@observable isCartOpen = false;

/**
* Is the cart popover open or closed
*
* @type Boolean
* @default false
*/
@observable isCartPopoverOpen = false;

/**
* Is the menu drawer open or closed
*
Expand Down Expand Up @@ -148,14 +140,6 @@ class UIStore {
this.isCartOpen = !this.isCartOpen;
}

@action openCartPopover() {
this.isCartPopoverOpen = true;
}

@action closeCartPopover() {
this.isCartPopoverOpen = false;
}

@action closeMenuDrawer() {
this.isMenuDrawerOpen = false;
}
Expand Down
Loading

0 comments on commit b1ff9af

Please sign in to comment.