diff --git a/ecommerce/views/v0/__init__.py b/ecommerce/views/v0/__init__.py index f41fa312e2..35c691fa53 100644 --- a/ecommerce/views/v0/__init__.py +++ b/ecommerce/views/v0/__init__.py @@ -559,6 +559,17 @@ def cart(self, request): return Response(BasketWithProductSerializer(basket).data) + @action( + detail=False, + methods=["get"], + name="Basket Items Count", + url_name="basket_items_count", + ) + def basket_items_count(self, request): + basket, _ = Basket.objects.get_or_create(user=request.user) + + return Response(basket.basket_items.count()) + @method_decorator(csrf_exempt, name="dispatch") class CheckoutCallbackView(View): diff --git a/frontend/public/src/components/Header.js b/frontend/public/src/components/Header.js index 4d763ef38a..2181d0cda8 100644 --- a/frontend/public/src/components/Header.js +++ b/frontend/public/src/components/Header.js @@ -10,10 +10,11 @@ import TopBar from "./TopBar" type Props = { currentUser: CurrentUser, + cartItemsCount: number, location: ?Location } -const Header = ({ currentUser, location }: Props) => { +const Header = ({ currentUser, cartItemsCount, location }: Props) => { if (currentUser && currentUser.is_authenticated) { Sentry.getCurrentScope().setUser({ id: currentUser.id, @@ -30,7 +31,11 @@ const Header = ({ currentUser, location }: Props) => { } return ( - + ) } diff --git a/frontend/public/src/components/TopBar.js b/frontend/public/src/components/TopBar.js index fe6990ca59..71eb585f3f 100644 --- a/frontend/public/src/components/TopBar.js +++ b/frontend/public/src/components/TopBar.js @@ -14,10 +14,11 @@ import { checkFeatureFlag } from "../lib/util" type Props = { currentUser: CurrentUser, + cartItemsCount: number, location: ?Location } -const TopBar = ({ currentUser }: Props) => { +const TopBar = ({ currentUser, cartItemsCount }: Props) => { // Delay any alert displayed on page-load by 500ms in order to // ensure the alert is read by screen readers. const [showComponent, setShowComponent] = useState(false) @@ -35,7 +36,6 @@ const TopBar = ({ currentUser }: Props) => { currentUser.id : "anonymousUser" ) - const cartItemCount = 0 return (
{showComponent ? ( @@ -80,9 +80,9 @@ const TopBar = ({ currentUser }: Props) => { onClick={() => (window.location = routes.cart)} aria-label="Cart" /> - {cartItemCount ? ( + {cartItemsCount ? ( - {cartItemCount} + {cartItemsCount} ) : null} { describe("for anonymous users", () => { const user = makeAnonymousUser() + const cartItemsCount = 0 it("has an AnonymousMenu component", () => { assert.isOk( - shallow() + shallow( + + ) .find("AnonymousMenu") .exists() ) @@ -21,9 +28,16 @@ describe("TopBar component", () => { describe("for logged in users", () => { const user = makeUser() + const cartItemsCount = 3 it("has a UserMenu component", () => { assert.isOk( - shallow() + shallow( + + ) .find("UserMenu") .exists() ) diff --git a/frontend/public/src/containers/App.js b/frontend/public/src/containers/App.js index 1b5496df62..0189997d6a 100644 --- a/frontend/public/src/containers/App.js +++ b/frontend/public/src/containers/App.js @@ -32,11 +32,16 @@ import CatalogPage from "./pages/CatalogPage" import type { Match, Location } from "react-router" import type { CurrentUser } from "../flow/authTypes" +import { + cartItemsCountQuery, + cartItemsCountSelector +} from "../lib/queries/cart" type Props = { match: Match, location: Location, currentUser: ?CurrentUser, + cartItemsCount: number, addUserNotification: Function } @@ -59,7 +64,7 @@ export class App extends React.Component { } render() { - const { match, currentUser, location } = this.props + const { match, currentUser, cartItemsCount, location } = this.props if (!currentUser) { // application is still loading return
@@ -67,7 +72,11 @@ export class App extends React.Component { return (
-
+
{ } const mapStateToProps = createStructuredSelector({ - currentUser: currentUserSelector + currentUser: currentUserSelector, + cartItemsCount: cartItemsCountSelector }) const mapDispatchToProps = { addUserNotification } -const mapPropsToConfig = () => [users.currentUserQuery()] - +const mapPropsToConfig = () => [cartItemsCountQuery(), users.currentUserQuery()] export default compose( connect(mapStateToProps, mapDispatchToProps), connectRequest(mapPropsToConfig) diff --git a/frontend/public/src/containers/HeaderApp.js b/frontend/public/src/containers/HeaderApp.js index fc3fb44be1..7a3256503a 100644 --- a/frontend/public/src/containers/HeaderApp.js +++ b/frontend/public/src/containers/HeaderApp.js @@ -15,9 +15,14 @@ import { import type { Store } from "redux" import type { CurrentUser } from "../flow/authTypes" +import { + cartItemsCountQuery, + cartItemsCountSelector +} from "../lib/queries/cart" type Props = { currentUser: ?CurrentUser, + cartItemsCount: number, store: Store<*, *>, addUserNotification: Function } @@ -41,22 +46,29 @@ export class HeaderApp extends React.Component { } render() { - const { currentUser } = this.props + const { currentUser, cartItemsCount } = this.props if (!currentUser) { // application is still loading return
} - return
+ return ( +
+ ) } } const mapStateToProps = createStructuredSelector({ - currentUser: currentUserSelector + currentUser: currentUserSelector, + cartItemsCount: cartItemsCountSelector }) -const mapPropsToConfig = () => [users.currentUserQuery()] +const mapPropsToConfig = () => [cartItemsCountQuery(), users.currentUserQuery()] const mapDispatchToProps = { addUserNotification diff --git a/frontend/public/src/lib/queries/cart.js b/frontend/public/src/lib/queries/cart.js index 2cf56a0b18..eb5c75c224 100644 --- a/frontend/public/src/lib/queries/cart.js +++ b/frontend/public/src/lib/queries/cart.js @@ -5,6 +5,10 @@ import { getCsrfOptions, nextState } from "./util" export const cartSelector = pathOr(null, ["entities", "cartItems"]) export const totalPriceSelector = pathOr(null, ["entities", "totalPrice"]) export const orderHistorySelector = pathOr(null, ["entities", "orderHistory"]) +export const cartItemsCountSelector = pathOr(null, [ + "entities", + "cartItemsCount" +]) export const discountedPriceSelector = pathOr(null, [ "entities", @@ -115,3 +119,14 @@ export const applyCartMutation = (productId: string) => ({ }, update: {} }) + +export const cartItemsCountQuery = () => ({ + queryKey: "cartItemsCount", + url: `/api/checkout/basket_items_count/`, + transform: json => ({ + cartItemsCount: json + }), + update: { + cartItemsCount: nextState + } +})