From 8a86d3c938e68d9fb96d306006c65662922bd8ff Mon Sep 17 00:00:00 2001 From: Anna Date: Thu, 23 Jan 2025 14:37:53 -0500 Subject: [PATCH 1/5] Adding cart items info to the header --- ecommerce/views/v0/__init__.py | 21 +++++++++++++++++++ frontend/public/src/components/TopBar.js | 2 +- frontend/public/src/containers/App.js | 17 ++++++++++++--- frontend/public/src/lib/queries/courseRuns.js | 13 ++++++++++++ 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/ecommerce/views/v0/__init__.py b/ecommerce/views/v0/__init__.py index f41fa312e2..457985e9eb 100644 --- a/ecommerce/views/v0/__init__.py +++ b/ecommerce/views/v0/__init__.py @@ -205,6 +205,27 @@ def get_object(self, username=None): # noqa: ARG002 def get_queryset(self): return Basket.objects.filter(user=self.request.user).all() + @action( + detail=False, + methods=["get"], + name="Basket Items Count", + url_name="basket_items_count", + ) + def get_items_in_basket(self): + basket, _ = Basket.objects.get_or_create(user=self.request.user) + print(basket.get_products()) + print(basket.get_products().count()) + + print(basket.get_basket_items()) + print(basket.products.all()) + return Response( + { + "message": "Discount applied", + "cartItemsCount": 5, + } + ) + + class BasketItemViewSet( NestedViewSetMixin, ListCreateAPIView, mixins.DestroyModelMixin, GenericViewSet diff --git a/frontend/public/src/components/TopBar.js b/frontend/public/src/components/TopBar.js index fe6990ca59..3658061823 100644 --- a/frontend/public/src/components/TopBar.js +++ b/frontend/public/src/components/TopBar.js @@ -35,7 +35,7 @@ const TopBar = ({ currentUser }: Props) => { currentUser.id : "anonymousUser" ) - const cartItemCount = 0 + const cartItemCount = 1 return (
{showComponent ? ( diff --git a/frontend/public/src/containers/App.js b/frontend/public/src/containers/App.js index 1b5496df62..f932095b74 100644 --- a/frontend/public/src/containers/App.js +++ b/frontend/public/src/containers/App.js @@ -32,11 +32,19 @@ import CatalogPage from "./pages/CatalogPage" import type { Match, Location } from "react-router" import type { CurrentUser } from "../flow/authTypes" +import {pathOr} from "ramda" +import { + cartItemsCountQuery, + cartItemsCountQueryKey, + cartItemsCountSelector, + coursesQuery +} from "../lib/queries/courseRuns" type Props = { match: Match, location: Location, currentUser: ?CurrentUser, + cartItemsCount: number, addUserNotification: Function } @@ -135,15 +143,18 @@ export class App extends React.Component { } const mapStateToProps = createStructuredSelector({ - currentUser: currentUserSelector + currentUser: currentUserSelector, + cartItemsCount: cartItemsCountSelector, }) const mapDispatchToProps = { addUserNotification } -const mapPropsToConfig = () => [users.currentUserQuery()] - +const mapPropsToConfig = props => [ + cartItemsCountQuery(props.courseId), + users.currentUserQuery() +] export default compose( connect(mapStateToProps, mapDispatchToProps), connectRequest(mapPropsToConfig) diff --git a/frontend/public/src/lib/queries/courseRuns.js b/frontend/public/src/lib/queries/courseRuns.js index d3e56d9b9f..1d5d11613a 100644 --- a/frontend/public/src/lib/queries/courseRuns.js +++ b/frontend/public/src/lib/queries/courseRuns.js @@ -4,6 +4,7 @@ import { nextState } from "./util" export const courseRunsSelector = pathOr(null, ["entities", "courseRuns"]) export const coursesSelector = pathOr(null, ["entities", "courses"]) +export const cartItemsCountSelector = pathOr(null, ["entities", "cartItemsCount"]) export const programsSelector = pathOr(null, ["entities", "programs"]) export const courseRunsQueryKey = "courseRuns" @@ -32,6 +33,18 @@ export const coursesQuery = (courseKey: string = "") => ({ } }) +export const cartItemsCountQuery = () => ({ + queryKey: "cartItemsCount", + url: `/api/baskets/basket_items_count/`, + transform: json => ({ + cartItemsCount: json + }), + update: { + cartItemsCount: nextState + } +}) + + // This will need to be updated to v2 once we get the courses endpoint to allow for multiple ID query export const programsQuery = (programKey: string = "") => ({ queryKey: programsQueryKey, From c814ecab5245aee59cc4d3968201f48346caa276 Mon Sep 17 00:00:00 2001 From: Anna Date: Fri, 24 Jan 2025 09:38:20 -0500 Subject: [PATCH 2/5] ready --- ecommerce/views/v0/__init__.py | 32 +++++++------------ frontend/public/src/components/Header.js | 5 +-- frontend/public/src/components/TopBar.js | 9 +++--- frontend/public/src/containers/App.js | 13 +++----- frontend/public/src/lib/queries/cart.js | 13 ++++++++ frontend/public/src/lib/queries/courseRuns.js | 12 ------- 6 files changed, 37 insertions(+), 47 deletions(-) diff --git a/ecommerce/views/v0/__init__.py b/ecommerce/views/v0/__init__.py index 457985e9eb..f2fd717184 100644 --- a/ecommerce/views/v0/__init__.py +++ b/ecommerce/views/v0/__init__.py @@ -205,27 +205,6 @@ def get_object(self, username=None): # noqa: ARG002 def get_queryset(self): return Basket.objects.filter(user=self.request.user).all() - @action( - detail=False, - methods=["get"], - name="Basket Items Count", - url_name="basket_items_count", - ) - def get_items_in_basket(self): - basket, _ = Basket.objects.get_or_create(user=self.request.user) - print(basket.get_products()) - print(basket.get_products().count()) - - print(basket.get_basket_items()) - print(basket.products.all()) - return Response( - { - "message": "Discount applied", - "cartItemsCount": 5, - } - ) - - class BasketItemViewSet( NestedViewSetMixin, ListCreateAPIView, mixins.DestroyModelMixin, GenericViewSet @@ -580,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=self.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..13a2b4d4dc 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,7 @@ const Header = ({ currentUser, location }: Props) => { } return ( - + ) } diff --git a/frontend/public/src/components/TopBar.js b/frontend/public/src/components/TopBar.js index 3658061823..6c14c93f16 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,7 @@ const TopBar = ({ currentUser }: Props) => { currentUser.id : "anonymousUser" ) - const cartItemCount = 1 + console.log(cartItemsCount) return (
{showComponent ? ( @@ -80,9 +81,9 @@ const TopBar = ({ currentUser }: Props) => { onClick={() => (window.location = routes.cart)} aria-label="Cart" /> - {cartItemCount ? ( + {cartItemsCount ? ( - {cartItemCount} + {cartItemsCount} ) : null} { } render() { - const { match, currentUser, location } = this.props + const { match, currentUser, cartItemsCount, location } = this.props if (!currentUser) { // application is still loading return
@@ -75,7 +72,7 @@ export class App extends React.Component { return (
-
+
[ - cartItemsCountQuery(props.courseId), +const mapPropsToConfig = () => [ + cartItemsCountQuery(), users.currentUserQuery() ] export default compose( diff --git a/frontend/public/src/lib/queries/cart.js b/frontend/public/src/lib/queries/cart.js index 2cf56a0b18..a0ce8ababc 100644 --- a/frontend/public/src/lib/queries/cart.js +++ b/frontend/public/src/lib/queries/cart.js @@ -5,6 +5,8 @@ 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 +117,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 + } +}) diff --git a/frontend/public/src/lib/queries/courseRuns.js b/frontend/public/src/lib/queries/courseRuns.js index 1d5d11613a..e928ce5d84 100644 --- a/frontend/public/src/lib/queries/courseRuns.js +++ b/frontend/public/src/lib/queries/courseRuns.js @@ -4,7 +4,6 @@ import { nextState } from "./util" export const courseRunsSelector = pathOr(null, ["entities", "courseRuns"]) export const coursesSelector = pathOr(null, ["entities", "courses"]) -export const cartItemsCountSelector = pathOr(null, ["entities", "cartItemsCount"]) export const programsSelector = pathOr(null, ["entities", "programs"]) export const courseRunsQueryKey = "courseRuns" @@ -33,17 +32,6 @@ export const coursesQuery = (courseKey: string = "") => ({ } }) -export const cartItemsCountQuery = () => ({ - queryKey: "cartItemsCount", - url: `/api/baskets/basket_items_count/`, - transform: json => ({ - cartItemsCount: json - }), - update: { - cartItemsCount: nextState - } -}) - // This will need to be updated to v2 once we get the courses endpoint to allow for multiple ID query export const programsQuery = (programKey: string = "") => ({ From 6402d3950fbf1695dc298170eb2b206fe8e35ec8 Mon Sep 17 00:00:00 2001 From: Anna Date: Fri, 24 Jan 2025 12:13:46 -0500 Subject: [PATCH 3/5] tests --- frontend/public/src/components/Header.js | 6 +++++- frontend/public/src/components/TopBar.js | 3 +-- frontend/public/src/components/TopBar_test.js | 6 ++++-- frontend/public/src/containers/App.js | 15 ++++++++------- frontend/public/src/containers/HeaderApp.js | 11 +++++++---- frontend/public/src/lib/queries/cart.js | 6 ++++-- frontend/public/src/lib/queries/courseRuns.js | 1 - 7 files changed, 29 insertions(+), 19 deletions(-) diff --git a/frontend/public/src/components/Header.js b/frontend/public/src/components/Header.js index 13a2b4d4dc..2181d0cda8 100644 --- a/frontend/public/src/components/Header.js +++ b/frontend/public/src/components/Header.js @@ -31,7 +31,11 @@ const Header = ({ currentUser, cartItemsCount, location }: Props) => { } return ( - + ) } diff --git a/frontend/public/src/components/TopBar.js b/frontend/public/src/components/TopBar.js index 6c14c93f16..71eb585f3f 100644 --- a/frontend/public/src/components/TopBar.js +++ b/frontend/public/src/components/TopBar.js @@ -18,7 +18,7 @@ type Props = { location: ?Location } -const TopBar = ({ currentUser, cartItemsCount }: 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) @@ -36,7 +36,6 @@ const TopBar = ({ currentUser, cartItemsCount }: Props) => { currentUser.id : "anonymousUser" ) - console.log(cartItemsCount) return (
{showComponent ? ( diff --git a/frontend/public/src/components/TopBar_test.js b/frontend/public/src/components/TopBar_test.js index 549e5fb414..472b4238bb 100644 --- a/frontend/public/src/components/TopBar_test.js +++ b/frontend/public/src/components/TopBar_test.js @@ -9,10 +9,11 @@ import { makeUser, makeAnonymousUser } from "../factories/user" describe("TopBar component", () => { describe("for anonymous users", () => { const user = makeAnonymousUser() + const cartItemsCount = 0 it("has an AnonymousMenu component", () => { assert.isOk( - shallow() + shallow() .find("AnonymousMenu") .exists() ) @@ -21,9 +22,10 @@ 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 1b38cd7752..0189997d6a 100644 --- a/frontend/public/src/containers/App.js +++ b/frontend/public/src/containers/App.js @@ -34,7 +34,7 @@ import type { Match, Location } from "react-router" import type { CurrentUser } from "../flow/authTypes" import { cartItemsCountQuery, - cartItemsCountSelector, + cartItemsCountSelector } from "../lib/queries/cart" type Props = { @@ -72,7 +72,11 @@ export class App extends React.Component { return (
-
+
{ const mapStateToProps = createStructuredSelector({ currentUser: currentUserSelector, - cartItemsCount: cartItemsCountSelector, + cartItemsCount: cartItemsCountSelector }) const mapDispatchToProps = { addUserNotification } -const mapPropsToConfig = () => [ - cartItemsCountQuery(), - 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..baf26133ce 100644 --- a/frontend/public/src/containers/HeaderApp.js +++ b/frontend/public/src/containers/HeaderApp.js @@ -15,9 +15,11 @@ 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 +43,23 @@ 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 a0ce8ababc..eb5c75c224 100644 --- a/frontend/public/src/lib/queries/cart.js +++ b/frontend/public/src/lib/queries/cart.js @@ -5,8 +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 cartItemsCountSelector = pathOr(null, [ + "entities", + "cartItemsCount" +]) export const discountedPriceSelector = pathOr(null, [ "entities", diff --git a/frontend/public/src/lib/queries/courseRuns.js b/frontend/public/src/lib/queries/courseRuns.js index e928ce5d84..d3e56d9b9f 100644 --- a/frontend/public/src/lib/queries/courseRuns.js +++ b/frontend/public/src/lib/queries/courseRuns.js @@ -32,7 +32,6 @@ export const coursesQuery = (courseKey: string = "") => ({ } }) - // This will need to be updated to v2 once we get the courses endpoint to allow for multiple ID query export const programsQuery = (programKey: string = "") => ({ queryKey: programsQueryKey, From 458e03a3146cb5beae6d7ed60f552ef194f74159 Mon Sep 17 00:00:00 2001 From: Anna Date: Fri, 24 Jan 2025 14:56:03 -0500 Subject: [PATCH 4/5] format --- frontend/public/src/components/TopBar_test.js | 16 ++++++++++++++-- frontend/public/src/containers/HeaderApp.js | 13 +++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/frontend/public/src/components/TopBar_test.js b/frontend/public/src/components/TopBar_test.js index 472b4238bb..00159f9b37 100644 --- a/frontend/public/src/components/TopBar_test.js +++ b/frontend/public/src/components/TopBar_test.js @@ -13,7 +13,13 @@ describe("TopBar component", () => { it("has an AnonymousMenu component", () => { assert.isOk( - shallow() + shallow( + + ) .find("AnonymousMenu") .exists() ) @@ -25,7 +31,13 @@ describe("TopBar component", () => { const cartItemsCount = 3 it("has a UserMenu component", () => { assert.isOk( - shallow() + shallow( + + ) .find("UserMenu") .exists() ) diff --git a/frontend/public/src/containers/HeaderApp.js b/frontend/public/src/containers/HeaderApp.js index baf26133ce..7a3256503a 100644 --- a/frontend/public/src/containers/HeaderApp.js +++ b/frontend/public/src/containers/HeaderApp.js @@ -15,7 +15,10 @@ import { import type { Store } from "redux" import type { CurrentUser } from "../flow/authTypes" -import {cartItemsCountQuery, cartItemsCountSelector} from "../lib/queries/cart"; +import { + cartItemsCountQuery, + cartItemsCountSelector +} from "../lib/queries/cart" type Props = { currentUser: ?CurrentUser, @@ -50,7 +53,13 @@ export class HeaderApp extends React.Component { return
} - return
+ return ( +
+ ) } } From cc9cff8f2076e5bc1d3ccbea3f4e54f9b7fae4ce Mon Sep 17 00:00:00 2001 From: Anna Date: Fri, 24 Jan 2025 15:03:14 -0500 Subject: [PATCH 5/5] request --- ecommerce/views/v0/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ecommerce/views/v0/__init__.py b/ecommerce/views/v0/__init__.py index f2fd717184..35c691fa53 100644 --- a/ecommerce/views/v0/__init__.py +++ b/ecommerce/views/v0/__init__.py @@ -566,7 +566,7 @@ def cart(self, request): url_name="basket_items_count", ) def basket_items_count(self, request): - basket, _ = Basket.objects.get_or_create(user=self.request.user) + basket, _ = Basket.objects.get_or_create(user=request.user) return Response(basket.basket_items.count())