diff --git a/frontends/mit-learn/src/common/feature_flags.ts b/frontends/mit-learn/src/common/feature_flags.ts new file mode 100644 index 0000000000..8d6e479d1e --- /dev/null +++ b/frontends/mit-learn/src/common/feature_flags.ts @@ -0,0 +1,6 @@ +// Feature flags for the app. These should correspond to the flag that's set up +// in PostHog. + +export enum FeatureFlags { + EnableEcommerce = "enable-ecommerce", +} diff --git a/frontends/mit-learn/src/common/urls.ts b/frontends/mit-learn/src/common/urls.ts index 8dad6174e8..363bced253 100644 --- a/frontends/mit-learn/src/common/urls.ts +++ b/frontends/mit-learn/src/common/urls.ts @@ -127,3 +127,5 @@ export const SEARCH_PROGRAM = querifiedSearchUrl({ export const SEARCH_LEARNING_MATERIAL = querifiedSearchUrl({ resource_category: "learning_material", }) + +export const ECOMMERCE_CART = "/cart/" as const diff --git a/frontends/mit-learn/src/page-components/EcommerceFeature/EcommerceFeature.tsx b/frontends/mit-learn/src/page-components/EcommerceFeature/EcommerceFeature.tsx new file mode 100644 index 0000000000..0cbb0a2edf --- /dev/null +++ b/frontends/mit-learn/src/page-components/EcommerceFeature/EcommerceFeature.tsx @@ -0,0 +1,33 @@ +import React from "react" +import { useFeatureFlagEnabled } from "posthog-js/react" +import { ForbiddenError } from "@/common/permissions" +import { FeatureFlags } from "@/common/feature_flags" + +type EcommerceFeatureProps = { + children: React.ReactNode +} + +/** + * Simple wrapper to standardize the feature flag check for ecommerce UI pages. + * If the flag is enabled, display the children; if not, throw a ForbiddenError + * like you'd get for an unauthenticated route. + * + * There's a PostHogFeature component that is provided but went this route + * because it seemed to be inconsistent - sometimes having the flag enabled + * resulted in it tossing to the error page. + * + * Set the feature flag here using the enum, and then make sure it's also + * defined in commmon/feature_flags too. + */ + +const EcommerceFeature: React.FC = ({ children }) => { + const ecommFlag = useFeatureFlagEnabled(FeatureFlags.EnableEcommerce) + + if (ecommFlag === false) { + throw new ForbiddenError("Not enabled.") + } + + return ecommFlag ? children : null +} + +export default EcommerceFeature diff --git a/frontends/mit-learn/src/pages/EcommercePages/CartPage.test.tsx b/frontends/mit-learn/src/pages/EcommercePages/CartPage.test.tsx new file mode 100644 index 0000000000..1a5eb127cc --- /dev/null +++ b/frontends/mit-learn/src/pages/EcommercePages/CartPage.test.tsx @@ -0,0 +1,65 @@ +import { renderTestApp, waitFor, setMockResponse } from "../../test-utils" +import { urls } from "api/test-utils" +import * as commonUrls from "@/common/urls" +import { Permissions } from "@/common/permissions" +import { login } from "@/common/urls" +import { useFeatureFlagEnabled } from "posthog-js/react" + +jest.mock("posthog-js/react") +const mockedUseFeatureFlagEnabled = jest.mocked(useFeatureFlagEnabled) + +const oldWindowLocation = window.location + +beforeAll(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + delete (window as any).location + + window.location = Object.defineProperties({} as Location, { + ...Object.getOwnPropertyDescriptors(oldWindowLocation), + assign: { + configurable: true, + value: jest.fn(), + }, + }) +}) + +afterAll(() => { + window.location = oldWindowLocation +}) + +describe("CartPage", () => { + ;["on", "off"].forEach((testCase: string) => { + test(`Renders when logged in and feature flag is ${testCase}`, async () => { + setMockResponse.get(urls.userMe.get(), { + [Permissions.Authenticated]: true, + }) + mockedUseFeatureFlagEnabled.mockReturnValue(testCase === "on") + + renderTestApp({ + url: commonUrls.ECOMMERCE_CART, + }) + await waitFor(() => { + testCase === "on" + ? expect(document.title).toBe("Shopping Cart | MIT Learn") + : expect(document.title).not.toBe("Shopping Cart | MIT Learn") + }) + }) + }) + + test("Sends to login page when logged out", async () => { + setMockResponse.get(urls.userMe.get(), { + [Permissions.Authenticated]: false, + }) + const expectedUrl = login({ + pathname: "/cart/", + }) + + renderTestApp({ + url: commonUrls.ECOMMERCE_CART, + }) + + await waitFor(() => { + expect(window.location.assign).toHaveBeenCalledWith(expectedUrl) + }) + }) +}) diff --git a/frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx b/frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx new file mode 100644 index 0000000000..59f9b94dd4 --- /dev/null +++ b/frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx @@ -0,0 +1,31 @@ +import React from "react" +import { Breadcrumbs, Container, Typography } from "ol-components" +import EcommerceFeature from "@/page-components/EcommerceFeature/EcommerceFeature" +import MetaTags from "@/page-components/MetaTags/MetaTags" +import * as urls from "@/common/urls" + +const CartPage: React.FC = () => { + return ( + + + + + + + Shopping Cart + + + + The shopping cart layout should go here, if you're allowed to see + this. + + + + ) +} + +export default CartPage diff --git a/frontends/mit-learn/src/pages/EcommercePages/README.md b/frontends/mit-learn/src/pages/EcommercePages/README.md new file mode 100644 index 0000000000..878d3f0887 --- /dev/null +++ b/frontends/mit-learn/src/pages/EcommercePages/README.md @@ -0,0 +1,9 @@ +# Unified Ecommerce in MIT Learn + +The front end for the Unified Ecommerce system lives in MIT Learn. So, pages that exist here are designed to talk to Unified Ecommerce rather than to the Learn system. + +There's a few functional pieces here: + +- **Cart** - Displays the user's cart, and provides some additional functionality for that (item management, discount application, etc.) +- **Receipts** - Allows the user to display their order history and view receipts from their purchases, including historical ones from other systems. +- **Financial Assistance** - For learning resources that support it, the learner side of the financial assistance request system lives here. (Approvals do not.) diff --git a/frontends/mit-learn/src/routes.tsx b/frontends/mit-learn/src/routes.tsx index fb5b338066..0cef84e264 100644 --- a/frontends/mit-learn/src/routes.tsx +++ b/frontends/mit-learn/src/routes.tsx @@ -1,6 +1,7 @@ import React from "react" import { RouteObject, Outlet } from "react-router" import { ScrollRestoration } from "react-router-dom" + import HomePage from "@/pages/HomePage/HomePage" import RestrictedRoute from "@/components/RestrictedRoute/RestrictedRoute" import LearningPathListingPage from "@/pages/LearningPathListingPage/LearningPathListingPage" @@ -26,6 +27,7 @@ import DepartmentListingPage from "./pages/DepartmentListingPage/DepartmentListi import TopicsListingPage from "./pages/TopicListingPage/TopicsListingPage" import UnitsListingPage from "./pages/UnitsListingPage/UnitsListingPage" import OnboardingPage from "./pages/OnboardingPage/OnboardingPage" +import CartPage from "./pages/EcommercePages/CartPage" import { styled } from "ol-components" @@ -190,6 +192,15 @@ const routes: RouteObject[] = [ }, ], }, + { + element: , + children: [ + { + path: urls.ECOMMERCE_CART, + element: , + }, + ], + }, ], }, ]