Skip to content

Commit 598fac2

Browse files
authored
Adds base infra for the Unified Ecommerce frontend (#1634)
1 parent 3d4b4e9 commit 598fac2

File tree

7 files changed

+157
-0
lines changed

7 files changed

+157
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Feature flags for the app. These should correspond to the flag that's set up
2+
// in PostHog.
3+
4+
export enum FeatureFlags {
5+
EnableEcommerce = "enable-ecommerce",
6+
}

frontends/mit-learn/src/common/urls.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,5 @@ export const SEARCH_PROGRAM = querifiedSearchUrl({
127127
export const SEARCH_LEARNING_MATERIAL = querifiedSearchUrl({
128128
resource_category: "learning_material",
129129
})
130+
131+
export const ECOMMERCE_CART = "/cart/" as const
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from "react"
2+
import { useFeatureFlagEnabled } from "posthog-js/react"
3+
import { ForbiddenError } from "@/common/permissions"
4+
import { FeatureFlags } from "@/common/feature_flags"
5+
6+
type EcommerceFeatureProps = {
7+
children: React.ReactNode
8+
}
9+
10+
/**
11+
* Simple wrapper to standardize the feature flag check for ecommerce UI pages.
12+
* If the flag is enabled, display the children; if not, throw a ForbiddenError
13+
* like you'd get for an unauthenticated route.
14+
*
15+
* There's a PostHogFeature component that is provided but went this route
16+
* because it seemed to be inconsistent - sometimes having the flag enabled
17+
* resulted in it tossing to the error page.
18+
*
19+
* Set the feature flag here using the enum, and then make sure it's also
20+
* defined in commmon/feature_flags too.
21+
*/
22+
23+
const EcommerceFeature: React.FC<EcommerceFeatureProps> = ({ children }) => {
24+
const ecommFlag = useFeatureFlagEnabled(FeatureFlags.EnableEcommerce)
25+
26+
if (ecommFlag === false) {
27+
throw new ForbiddenError("Not enabled.")
28+
}
29+
30+
return ecommFlag ? children : null
31+
}
32+
33+
export default EcommerceFeature
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { renderTestApp, waitFor, setMockResponse } from "../../test-utils"
2+
import { urls } from "api/test-utils"
3+
import * as commonUrls from "@/common/urls"
4+
import { Permissions } from "@/common/permissions"
5+
import { login } from "@/common/urls"
6+
import { useFeatureFlagEnabled } from "posthog-js/react"
7+
8+
jest.mock("posthog-js/react")
9+
const mockedUseFeatureFlagEnabled = jest.mocked(useFeatureFlagEnabled)
10+
11+
const oldWindowLocation = window.location
12+
13+
beforeAll(() => {
14+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
15+
delete (window as any).location
16+
17+
window.location = Object.defineProperties({} as Location, {
18+
...Object.getOwnPropertyDescriptors(oldWindowLocation),
19+
assign: {
20+
configurable: true,
21+
value: jest.fn(),
22+
},
23+
})
24+
})
25+
26+
afterAll(() => {
27+
window.location = oldWindowLocation
28+
})
29+
30+
describe("CartPage", () => {
31+
;["on", "off"].forEach((testCase: string) => {
32+
test(`Renders when logged in and feature flag is ${testCase}`, async () => {
33+
setMockResponse.get(urls.userMe.get(), {
34+
[Permissions.Authenticated]: true,
35+
})
36+
mockedUseFeatureFlagEnabled.mockReturnValue(testCase === "on")
37+
38+
renderTestApp({
39+
url: commonUrls.ECOMMERCE_CART,
40+
})
41+
await waitFor(() => {
42+
testCase === "on"
43+
? expect(document.title).toBe("Shopping Cart | MIT Learn")
44+
: expect(document.title).not.toBe("Shopping Cart | MIT Learn")
45+
})
46+
})
47+
})
48+
49+
test("Sends to login page when logged out", async () => {
50+
setMockResponse.get(urls.userMe.get(), {
51+
[Permissions.Authenticated]: false,
52+
})
53+
const expectedUrl = login({
54+
pathname: "/cart/",
55+
})
56+
57+
renderTestApp({
58+
url: commonUrls.ECOMMERCE_CART,
59+
})
60+
61+
await waitFor(() => {
62+
expect(window.location.assign).toHaveBeenCalledWith(expectedUrl)
63+
})
64+
})
65+
})
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from "react"
2+
import { Breadcrumbs, Container, Typography } from "ol-components"
3+
import EcommerceFeature from "@/page-components/EcommerceFeature/EcommerceFeature"
4+
import MetaTags from "@/page-components/MetaTags/MetaTags"
5+
import * as urls from "@/common/urls"
6+
7+
const CartPage: React.FC = () => {
8+
return (
9+
<EcommerceFeature>
10+
<Container>
11+
<MetaTags title="Shopping Cart" />
12+
<Breadcrumbs
13+
variant="light"
14+
ancestors={[{ href: urls.HOME, label: "Home" }]}
15+
current="Shopping Cart"
16+
/>
17+
18+
<Typography component="h1" variant="h3">
19+
Shopping Cart
20+
</Typography>
21+
22+
<Typography>
23+
The shopping cart layout should go here, if you're allowed to see
24+
this.
25+
</Typography>
26+
</Container>
27+
</EcommerceFeature>
28+
)
29+
}
30+
31+
export default CartPage
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Unified Ecommerce in MIT Learn
2+
3+
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.
4+
5+
There's a few functional pieces here:
6+
7+
- **Cart** - Displays the user's cart, and provides some additional functionality for that (item management, discount application, etc.)
8+
- **Receipts** - Allows the user to display their order history and view receipts from their purchases, including historical ones from other systems.
9+
- **Financial Assistance** - For learning resources that support it, the learner side of the financial assistance request system lives here. (Approvals do not.)

frontends/mit-learn/src/routes.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from "react"
22
import { RouteObject, Outlet } from "react-router"
33
import { ScrollRestoration } from "react-router-dom"
4+
45
import HomePage from "@/pages/HomePage/HomePage"
56
import RestrictedRoute from "@/components/RestrictedRoute/RestrictedRoute"
67
import LearningPathListingPage from "@/pages/LearningPathListingPage/LearningPathListingPage"
@@ -26,6 +27,7 @@ import DepartmentListingPage from "./pages/DepartmentListingPage/DepartmentListi
2627
import TopicsListingPage from "./pages/TopicListingPage/TopicsListingPage"
2728
import UnitsListingPage from "./pages/UnitsListingPage/UnitsListingPage"
2829
import OnboardingPage from "./pages/OnboardingPage/OnboardingPage"
30+
import CartPage from "./pages/EcommercePages/CartPage"
2931

3032
import { styled } from "ol-components"
3133

@@ -190,6 +192,15 @@ const routes: RouteObject[] = [
190192
},
191193
],
192194
},
195+
{
196+
element: <RestrictedRoute requires={Permissions.Authenticated} />,
197+
children: [
198+
{
199+
path: urls.ECOMMERCE_CART,
200+
element: <CartPage />,
201+
},
202+
],
203+
},
193204
],
194205
},
195206
]

0 commit comments

Comments
 (0)