From cf1bd4b86fc0addd673b7482434747cf3380f1a2 Mon Sep 17 00:00:00 2001 From: James Kachel Date: Fri, 27 Sep 2024 16:09:48 -0500 Subject: [PATCH 1/5] Bootstrapping the Unified Ecommerce frontend - Added a "space" for this - EcommercePages - Added a CartPage placeholder (it's the Privacy Policy for now, just needed _something_) - Added a PostHogRoute component to allow us to feature flag the ecomm stuff (but may go a different route here, not sure) - Added routes for the cart page For this to work you'll need to make a feature flag called "enable-ecommerce" and turn it on in PostHog. (And you'll need to fix my code because this actually doesn't work right now.) --- frontends/mit-learn/src/common/urls.ts | 2 + .../components/PostHogRoute/PostHogRoute.tsx | 64 ++++ .../pages/EcommercePages/CartPage.test.tsx | 27 ++ .../src/pages/EcommercePages/CartPage.tsx | 328 ++++++++++++++++++ .../src/pages/EcommercePages/README.md | 9 + frontends/mit-learn/src/routes.tsx | 17 + 6 files changed, 447 insertions(+) create mode 100644 frontends/mit-learn/src/components/PostHogRoute/PostHogRoute.tsx create mode 100644 frontends/mit-learn/src/pages/EcommercePages/CartPage.test.tsx create mode 100644 frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx create mode 100644 frontends/mit-learn/src/pages/EcommercePages/README.md 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/components/PostHogRoute/PostHogRoute.tsx b/frontends/mit-learn/src/components/PostHogRoute/PostHogRoute.tsx new file mode 100644 index 0000000000..6832cd80d8 --- /dev/null +++ b/frontends/mit-learn/src/components/PostHogRoute/PostHogRoute.tsx @@ -0,0 +1,64 @@ +import React from "react" +import { Navigate, Outlet } from "react-router" +import { PostHogFeature } from "posthog-js/react" +import { Permissions } from "@/common/permissions" +import RestrictedRoute from "../RestrictedRoute/RestrictedRoute" + +type PostHogRouteProps = { + children?: React.ReactNode + flag: string + match: boolean | string + requires?: Permissions +} + +/** + * Use `` to feature flag a particular route (or set of routes). + * This can also wrap your output in RestrictedRoute so you can also require + * permissions for your route. + * + * The use of it mirrors the RestrictedRoute. However, there are a couple of + * additional props to control the PostHog part of this: + * - flag: the flag to check for + * - match: either true, or the variant string to match on + * + * These PostHog things aren't supported: + * - fallback: this is really meant more for flagging parts of pages, so instead + * we'll redirect you to the homepage. + * - payload: the PostHog component can provide a payload if you want, but I'm + * not bothering with that just yet. + */ + +const NavigateWrapper: React.FC = () => { + console.log("hit the fallback!") + return +} + +const PostHogRoute: React.FC = ({ + children, + flag, + match, + requires, +}) => { + if (requires) { + console.log("this route is Restricted too!") + return ( + + } + > + {children ? children : } + + + ) + } else { + return ( + }> + {children ? children : } + + ) + } +} + +export default PostHogRoute 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..97830c2a63 --- /dev/null +++ b/frontends/mit-learn/src/pages/EcommercePages/CartPage.test.tsx @@ -0,0 +1,27 @@ +import { + renderTestApp, + screen, + waitFor, + setMockResponse, +} from "../../test-utils" +import { urls } from "api/test-utils" +import * as commonUrls from "@/common/urls" +import { Permissions } from "@/common/permissions" + +describe("CartPage", () => { + test("Renders title", async () => { + setMockResponse.get(urls.userMe.get(), { + [Permissions.Authenticated]: true, + }) + + renderTestApp({ + url: commonUrls.PRIVACY, + }) + await waitFor(() => { + expect(document.title).toBe("Shopping Cart | MIT Learn") + }) + screen.getByRole("heading", { + name: "Shopping Cart", + }) + }) +}) 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..b4828b078f --- /dev/null +++ b/frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx @@ -0,0 +1,328 @@ +import { + Breadcrumbs, + Container, + Typography, + TypographyProps, + styled, +} from "ol-components" +import MetaTags from "@/page-components/MetaTags/MetaTags" +import * as urls from "@/common/urls" +import React from "react" + +const PageContainer = styled.div(({ theme }) => ({ + display: "flex", + flexDirection: "column", + alignItems: "flex-start", + alignSelf: "stretch", + padding: "40px 84px 80px 84px", + [theme.breakpoints.down("md")]: { + padding: "40px 24px 80px 24px", + }, +})) + +const BannerContainer = styled.div({ + display: "flex", + flexDirection: "column", + alignItems: "center", + paddingBottom: "16px", +}) + +const BannerContainerInner = styled.div({ + display: "flex", + flexDirection: "column", + alignItems: "flex-start", + alignSelf: "stretch", + justifyContent: "center", +}) + +const Header = styled(Typography)>( + ({ theme }) => ({ + alignSelf: "stretch", + color: theme.custom.colors.black, + }), +) + +const BodyContainer = styled.div({ + display: "flex", + flexDirection: "column", + alignItems: "center", + alignSelf: "stretch", + gap: "20px", +}) + +const BodyText = styled(Typography)>( + ({ theme }) => ({ + alignSelf: "stretch", + color: theme.custom.colors.black, + }), +) + +const UnorderedList = styled.ul(({ theme }) => ({ + width: "100%", + ...theme.typography.body1, +})) + +const { SITE_NAME, MITOL_SUPPORT_EMAIL } = APP_SETTINGS + +const CartPage: React.FC = () => { + return ( + + + + + + +
+ Shopping Cart +
+
+
+ + + Introduction + + + {SITE_NAME} provides information about MIT courses, programs, and + learning materials to learners from across the world. This Privacy + Statement explains how {SITE_NAME} collects, uses, and processes + personal information about our learners. + + + What personal information we collect + + + We may collect, use, store, and transfer different kinds of personal + information about you, which we have grouped together as follows: + + +
  • + Biographic information – name, email address, education level and + other demographic info +
  • +
  • + Demographics and Interests - Affinity categories, Product Purchase + Interests, and Other Categories of interest +
  • +
  • IP addresses
  • +
  • Course progress and performance
  • +
    + + How we collect personal information about you + + + We collect information, including Personal Information, when you + create and maintain a profile and user account. + + + We also collect certain usage information about learner performance + and patterns of learning. In addition, we track information + indicating, among other things, which pages of our Site were + visited, the order in which they were visited, when they were + visited, and which hyperlinks and other user interface controls were + used. + + + We also collect information when you fill out and submit contact + forms, as well as from marketing data including how many emails you + have received, opened, clicked, and unsubscribed from. + + + We may log the IP address, operating system, page visit behavior, + and browser software used by each user of the Site, and we may be + able to determine from an IP address a user's Internet Service + Provider and the geographic location of his or her point of + connectivity. Various web analytics tools, including Google + Analytics, Google Analytics: Demographics and Interests, and + HubSpot, are used to collect this information. Some of the + information is collected through cookies (small text files placed on + your computer that store information about you, which can be + accessed by the Site). You should be able to control how and whether + cookies will be accepted by your web browser. Most browsers offer + instructions on how to reset the browser to reject cookies in the + "Help" section of the toolbar. If you reject our cookies, many + functions and conveniences of this Site may not work properly. + + + How we use your personal information + + + We collect, use, and process your personal information (1) to + process transactions requested by you and meet our contractual + obligations; (2) to facilitate {SITE_NAME}'s legitimate interests, + and/or (3) with your explicit consent, where applicable. Examples of + the ways in which we use your personal information are as follows: + + +
  • + For the purpose for which you specifically provided the + information, for example, to respond to a specific inquiry or + provide you with access to the specific course content and/or + services you select. +
  • +
  • + To archive this information and/or use it for future + communications with you. +
  • +
  • + To maintain and improve the functioning and security of the Site + and our software, systems, and network. +
  • +
  • + For purposes described elsewhere in this Privacy Policy + (including, e.g., sharing with third parties). +
  • +
  • + As otherwise described to you at the point of collection or + pursuant to your consent. +
  • +
    + + If you have concerns about any of these purposes, or how we + communicate with you, please contact us at {MITOL_SUPPORT_EMAIL}. We + will always respect a request by you to stop processing your + personal information (subject to our legal obligations). + + + When we share your personal information + + + We may share your personal information with departments, labs, and + centers within the MIT Community to provide information which may be + of interest to you. User information may also be shared with + third-party partners to the extent necessary for such third parties + to provide services to us or to users of our services or provide. + Any third parties who receive user information for this purpose are + prohibited from using or sharing user information for any purpose + other than providing services to MIT. + + + We may also provide your information to third parties in + circumstances where we believe that doing so is necessary or + appropriate to satisfy any applicable law, regulation, legal process + or governmental request; to enforce our rights, to detect, prevent + or otherwise address fraud, security or technical issues; or to + protect the rights, property or safety of us, our users or others. + + + How your information is stored and secured + + + {SITE_NAME} is designed to protect Personal Information in its + possession or control. This is done through a variety of privacy and + security policies, processes, and procedures, including + administrative, physical, and technical safeguards that reasonably + and appropriately protect the confidentiality, integrity, and + availability of the Personal Information that it receives, + maintains, or transmits. Nonetheless, no method of transmission over + the Internet or method of electronic storage is 100% secure, and + therefore we do not guarantee its absolute security. + + + All data transferred between systems, from the moment of first + collection, is encrypted using industry-standard TLS protocols with + high-strength private keys. All data at rest is stored on encrypted + media using AWS KMS encryption keys that are only accessible by + infrastructure administrators. All data access is based on a least + privilege model with a default deny policy. Permissions are granted + based on verified business use cases and subject to auditing to + verify appropriate applications. + + + How long we keep your personal information + + + We consider your relationship with the {SITE_NAME} community to be + lifelong. This means that we will maintain a record for you until + such time as you tell us that you no longer wish us to keep in + touch. Requests to delete your account or personal information can + be sent to olprivacy@mit.edu. After such time, we will retain a core + set of information for {SITE_NAME}'s legitimate purposes, such as + archival, scientific and historical research and for the defense of + potential legal claims. + + + Rights for Individuals in the European Economic Area (EEA) or United + Kingdom (UK) + + + You have the right in certain circumstances to (1) access your + personal information; (2) to correct or erase information; (3) + restrict processing; and (4) object to communications, direct + marketing, or profiling. To the extent applicable, the EEA's General + Data Protection Regulation (GDPR) provides further information about + your rights. You also have the right to lodge complaints with your + national or regional data protection authority. + + + If you are inclined to exercise these rights, we request an + opportunity to discuss with you any concerns you may have. To + protect the personal information we hold, we may also request + further information to verify your identity when exercising these + rights. Upon a request to erase information, we will maintain a core + set of personal data to ensure we do not contact you inadvertently + in the future, as well as any information necessary for MIT archival + purposes. We may also need to retain some financial information for + legal purposes, including US IRS compliance. In the event of an + actual or threatened legal claim, we may retain your information for + purposes of establishing, defending against or exercising our rights + with respect to such claim. + + + By providing information directly to MIT, you consent to the + transfer of your personal information outside of the European + Economic Area to the United States. You understand that the current + laws and regulations of the United States may not provide the same + level of protection as the data and privacy laws and regulations of + the EEA. + + + You are under no statutory or contractual obligation to provide any + personal data to us. The controller for your personal information is + MIT. + + + If you are in the EEA or UK and wish to assert any of your + applicable GDPR rights, please contact dataprotection@mit.edu. You + may also contact MIT's representatives listed below: + + + MIT Representative in the European Economic Area + + + PJ-PAL Europe +
    + Email: jpaleurope@povertyactionlab.org +
    + Address: 48 Boulevard Jourdan, 75014 Paris, France +
    + + MIT Representative in the United Kingdom + + + MIT Press UK +
    + Address: 71 Queen Victoria Street, London, EC4V 4BE, United Kingdom +
    + + Additional Information + + + We may change this Privacy Statement from time to time. If we make + any significant changes in the way we treat your personal + information we will make this clear on our MIT websites or by + contacting you directly. + + + This policy was last updated in July 2024. + +
    +
    +
    + ) +} + +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..1209492a19 100644 --- a/frontends/mit-learn/src/routes.tsx +++ b/frontends/mit-learn/src/routes.tsx @@ -3,6 +3,7 @@ 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 PostHogRoute from "./components/PostHogRoute/PostHogRoute" import LearningPathListingPage from "@/pages/LearningPathListingPage/LearningPathListingPage" import ChannelPage from "@/pages/ChannelPage/ChannelPage" import EditChannelPage from "@/pages/ChannelPage/EditChannelPage" @@ -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,21 @@ const routes: RouteObject[] = [ }, ], }, + { + element: ( + + ), + children: [ + { + path: urls.ECOMMERCE_CART, + element: , + }, + ], + }, ], }, ] From 63d115e2b6118074b84102b086e82206249193e5 Mon Sep 17 00:00:00 2001 From: James Kachel Date: Mon, 30 Sep 2024 16:10:01 -0500 Subject: [PATCH 2/5] Finishing this up; should work now (to the extent that it works) - Removed the PostHogRoute component as it didn't really work consistently - Added a EcommerceFeature component to do the feature flag checking in a standardized way - Removed a bunch of stuff from the "Cart" Page (since it was just the Privacy Policy anyway) Not using the PostHogFeature component here - I was trying, but I kept running into issues where it'd throw to the fallback if the flag was enabled. It seems much more stable now with the useFeatureFlagEnabled but I wouldn't be opposed to doing it manually, since this doesn't trigger the Feature Flag View event (and then we can also just.. wait for the flag to come back as something). --- .../components/PostHogRoute/PostHogRoute.tsx | 64 ---- .../EcommerceFeature/EcommerceFeature.tsx | 31 ++ .../src/pages/EcommercePages/CartPage.tsx | 291 ++---------------- frontends/mit-learn/src/routes.tsx | 10 +- 4 files changed, 60 insertions(+), 336 deletions(-) delete mode 100644 frontends/mit-learn/src/components/PostHogRoute/PostHogRoute.tsx create mode 100644 frontends/mit-learn/src/page-components/EcommerceFeature/EcommerceFeature.tsx diff --git a/frontends/mit-learn/src/components/PostHogRoute/PostHogRoute.tsx b/frontends/mit-learn/src/components/PostHogRoute/PostHogRoute.tsx deleted file mode 100644 index 6832cd80d8..0000000000 --- a/frontends/mit-learn/src/components/PostHogRoute/PostHogRoute.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from "react" -import { Navigate, Outlet } from "react-router" -import { PostHogFeature } from "posthog-js/react" -import { Permissions } from "@/common/permissions" -import RestrictedRoute from "../RestrictedRoute/RestrictedRoute" - -type PostHogRouteProps = { - children?: React.ReactNode - flag: string - match: boolean | string - requires?: Permissions -} - -/** - * Use `` to feature flag a particular route (or set of routes). - * This can also wrap your output in RestrictedRoute so you can also require - * permissions for your route. - * - * The use of it mirrors the RestrictedRoute. However, there are a couple of - * additional props to control the PostHog part of this: - * - flag: the flag to check for - * - match: either true, or the variant string to match on - * - * These PostHog things aren't supported: - * - fallback: this is really meant more for flagging parts of pages, so instead - * we'll redirect you to the homepage. - * - payload: the PostHog component can provide a payload if you want, but I'm - * not bothering with that just yet. - */ - -const NavigateWrapper: React.FC = () => { - console.log("hit the fallback!") - return -} - -const PostHogRoute: React.FC = ({ - children, - flag, - match, - requires, -}) => { - if (requires) { - console.log("this route is Restricted too!") - return ( - - } - > - {children ? children : } - - - ) - } else { - return ( - }> - {children ? children : } - - ) - } -} - -export default PostHogRoute 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..cddc7d72de --- /dev/null +++ b/frontends/mit-learn/src/page-components/EcommerceFeature/EcommerceFeature.tsx @@ -0,0 +1,31 @@ +import React from "react" +import { useFeatureFlagEnabled } from "posthog-js/react" +import { ForbiddenError } from "@/common/permissions" + +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. + * + * If the feature flag for this changes, this is where this needs to be set. + */ + +const EcommerceFeature: React.FC = ({ children }) => { + const ecommFlag = useFeatureFlagEnabled("enable-ecommerce") + + 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.tsx b/frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx index b4828b078f..4318812503 100644 --- a/frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx +++ b/frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx @@ -1,3 +1,4 @@ +import React from "react" import { Breadcrumbs, Container, @@ -5,9 +6,9 @@ import { TypographyProps, styled, } from "ol-components" +import EcommerceFeature from "@/page-components/EcommerceFeature/EcommerceFeature" import MetaTags from "@/page-components/MetaTags/MetaTags" import * as urls from "@/common/urls" -import React from "react" const PageContainer = styled.div(({ theme }) => ({ display: "flex", @@ -57,271 +58,33 @@ const BodyText = styled(Typography)>( }), ) -const UnorderedList = styled.ul(({ theme }) => ({ - width: "100%", - ...theme.typography.body1, -})) - -const { SITE_NAME, MITOL_SUPPORT_EMAIL } = APP_SETTINGS - const CartPage: React.FC = () => { return ( - - - - - - -
    - Shopping Cart -
    -
    -
    - - - Introduction - - - {SITE_NAME} provides information about MIT courses, programs, and - learning materials to learners from across the world. This Privacy - Statement explains how {SITE_NAME} collects, uses, and processes - personal information about our learners. - - - What personal information we collect - - - We may collect, use, store, and transfer different kinds of personal - information about you, which we have grouped together as follows: - - -
  • - Biographic information – name, email address, education level and - other demographic info -
  • -
  • - Demographics and Interests - Affinity categories, Product Purchase - Interests, and Other Categories of interest -
  • -
  • IP addresses
  • -
  • Course progress and performance
  • -
    - - How we collect personal information about you - - - We collect information, including Personal Information, when you - create and maintain a profile and user account. - - - We also collect certain usage information about learner performance - and patterns of learning. In addition, we track information - indicating, among other things, which pages of our Site were - visited, the order in which they were visited, when they were - visited, and which hyperlinks and other user interface controls were - used. - - - We also collect information when you fill out and submit contact - forms, as well as from marketing data including how many emails you - have received, opened, clicked, and unsubscribed from. - - - We may log the IP address, operating system, page visit behavior, - and browser software used by each user of the Site, and we may be - able to determine from an IP address a user's Internet Service - Provider and the geographic location of his or her point of - connectivity. Various web analytics tools, including Google - Analytics, Google Analytics: Demographics and Interests, and - HubSpot, are used to collect this information. Some of the - information is collected through cookies (small text files placed on - your computer that store information about you, which can be - accessed by the Site). You should be able to control how and whether - cookies will be accepted by your web browser. Most browsers offer - instructions on how to reset the browser to reject cookies in the - "Help" section of the toolbar. If you reject our cookies, many - functions and conveniences of this Site may not work properly. - - - How we use your personal information - - - We collect, use, and process your personal information (1) to - process transactions requested by you and meet our contractual - obligations; (2) to facilitate {SITE_NAME}'s legitimate interests, - and/or (3) with your explicit consent, where applicable. Examples of - the ways in which we use your personal information are as follows: - - -
  • - For the purpose for which you specifically provided the - information, for example, to respond to a specific inquiry or - provide you with access to the specific course content and/or - services you select. -
  • -
  • - To archive this information and/or use it for future - communications with you. -
  • -
  • - To maintain and improve the functioning and security of the Site - and our software, systems, and network. -
  • -
  • - For purposes described elsewhere in this Privacy Policy - (including, e.g., sharing with third parties). -
  • -
  • - As otherwise described to you at the point of collection or - pursuant to your consent. -
  • -
    - - If you have concerns about any of these purposes, or how we - communicate with you, please contact us at {MITOL_SUPPORT_EMAIL}. We - will always respect a request by you to stop processing your - personal information (subject to our legal obligations). - - - When we share your personal information - - - We may share your personal information with departments, labs, and - centers within the MIT Community to provide information which may be - of interest to you. User information may also be shared with - third-party partners to the extent necessary for such third parties - to provide services to us or to users of our services or provide. - Any third parties who receive user information for this purpose are - prohibited from using or sharing user information for any purpose - other than providing services to MIT. - - - We may also provide your information to third parties in - circumstances where we believe that doing so is necessary or - appropriate to satisfy any applicable law, regulation, legal process - or governmental request; to enforce our rights, to detect, prevent - or otherwise address fraud, security or technical issues; or to - protect the rights, property or safety of us, our users or others. - - - How your information is stored and secured - - - {SITE_NAME} is designed to protect Personal Information in its - possession or control. This is done through a variety of privacy and - security policies, processes, and procedures, including - administrative, physical, and technical safeguards that reasonably - and appropriately protect the confidentiality, integrity, and - availability of the Personal Information that it receives, - maintains, or transmits. Nonetheless, no method of transmission over - the Internet or method of electronic storage is 100% secure, and - therefore we do not guarantee its absolute security. - - - All data transferred between systems, from the moment of first - collection, is encrypted using industry-standard TLS protocols with - high-strength private keys. All data at rest is stored on encrypted - media using AWS KMS encryption keys that are only accessible by - infrastructure administrators. All data access is based on a least - privilege model with a default deny policy. Permissions are granted - based on verified business use cases and subject to auditing to - verify appropriate applications. - - - How long we keep your personal information - - - We consider your relationship with the {SITE_NAME} community to be - lifelong. This means that we will maintain a record for you until - such time as you tell us that you no longer wish us to keep in - touch. Requests to delete your account or personal information can - be sent to olprivacy@mit.edu. After such time, we will retain a core - set of information for {SITE_NAME}'s legitimate purposes, such as - archival, scientific and historical research and for the defense of - potential legal claims. - - - Rights for Individuals in the European Economic Area (EEA) or United - Kingdom (UK) - - - You have the right in certain circumstances to (1) access your - personal information; (2) to correct or erase information; (3) - restrict processing; and (4) object to communications, direct - marketing, or profiling. To the extent applicable, the EEA's General - Data Protection Regulation (GDPR) provides further information about - your rights. You also have the right to lodge complaints with your - national or regional data protection authority. - - - If you are inclined to exercise these rights, we request an - opportunity to discuss with you any concerns you may have. To - protect the personal information we hold, we may also request - further information to verify your identity when exercising these - rights. Upon a request to erase information, we will maintain a core - set of personal data to ensure we do not contact you inadvertently - in the future, as well as any information necessary for MIT archival - purposes. We may also need to retain some financial information for - legal purposes, including US IRS compliance. In the event of an - actual or threatened legal claim, we may retain your information for - purposes of establishing, defending against or exercising our rights - with respect to such claim. - - - By providing information directly to MIT, you consent to the - transfer of your personal information outside of the European - Economic Area to the United States. You understand that the current - laws and regulations of the United States may not provide the same - level of protection as the data and privacy laws and regulations of - the EEA. - - - You are under no statutory or contractual obligation to provide any - personal data to us. The controller for your personal information is - MIT. - - - If you are in the EEA or UK and wish to assert any of your - applicable GDPR rights, please contact dataprotection@mit.edu. You - may also contact MIT's representatives listed below: - - - MIT Representative in the European Economic Area - - - PJ-PAL Europe -
    - Email: jpaleurope@povertyactionlab.org -
    - Address: 48 Boulevard Jourdan, 75014 Paris, France -
    - - MIT Representative in the United Kingdom - - - MIT Press UK -
    - Address: 71 Queen Victoria Street, London, EC4V 4BE, United Kingdom -
    - - Additional Information - - - We may change this Privacy Statement from time to time. If we make - any significant changes in the way we treat your personal - information we will make this clear on our MIT websites or by - contacting you directly. - - - This policy was last updated in July 2024. - -
    -
    -
    + + + + + + + +
    + Shopping Cart +
    +
    +
    + + + The shopping cart layout should go here, if you're allowed to see + this. + + +
    +
    +
    ) } diff --git a/frontends/mit-learn/src/routes.tsx b/frontends/mit-learn/src/routes.tsx index 1209492a19..0cef84e264 100644 --- a/frontends/mit-learn/src/routes.tsx +++ b/frontends/mit-learn/src/routes.tsx @@ -1,9 +1,9 @@ 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 PostHogRoute from "./components/PostHogRoute/PostHogRoute" import LearningPathListingPage from "@/pages/LearningPathListingPage/LearningPathListingPage" import ChannelPage from "@/pages/ChannelPage/ChannelPage" import EditChannelPage from "@/pages/ChannelPage/EditChannelPage" @@ -193,13 +193,7 @@ const routes: RouteObject[] = [ ], }, { - element: ( - - ), + element: , children: [ { path: urls.ECOMMERCE_CART, From fa8253af140152aea811947d3479214126dc8f57 Mon Sep 17 00:00:00 2001 From: James Kachel Date: Tue, 1 Oct 2024 13:50:08 -0500 Subject: [PATCH 3/5] adding tests --- .../pages/EcommercePages/CartPage.test.tsx | 64 +++++++++++++++---- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/frontends/mit-learn/src/pages/EcommercePages/CartPage.test.tsx b/frontends/mit-learn/src/pages/EcommercePages/CartPage.test.tsx index 97830c2a63..08d293f809 100644 --- a/frontends/mit-learn/src/pages/EcommercePages/CartPage.test.tsx +++ b/frontends/mit-learn/src/pages/EcommercePages/CartPage.test.tsx @@ -1,27 +1,65 @@ -import { - renderTestApp, - screen, - waitFor, - setMockResponse, -} from "../../test-utils" +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 mockedUseFatureFlagEnabled = 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", () => { - test("Renders title", async () => { + ;["on", "off"].forEach((testCase: string) => { + test(`Renders when logged in and feature flag is ${testCase}`, async () => { + setMockResponse.get(urls.userMe.get(), { + [Permissions.Authenticated]: true, + }) + mockedUseFatureFlagEnabled.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]: true, + [Permissions.Authenticated]: false, + }) + const expectedUrl = login({ + pathname: "/cart/", }) renderTestApp({ - url: commonUrls.PRIVACY, + url: commonUrls.ECOMMERCE_CART, }) + await waitFor(() => { - expect(document.title).toBe("Shopping Cart | MIT Learn") - }) - screen.getByRole("heading", { - name: "Shopping Cart", + expect(window.location.assign).toHaveBeenCalledWith(expectedUrl) }) }) }) From 956248eb2b166c81f7105c7ee5860552e963a3ba Mon Sep 17 00:00:00 2001 From: James Kachel Date: Thu, 3 Oct 2024 09:55:32 -0500 Subject: [PATCH 4/5] Addressing comments in the PR - Added an enum for feature flags - Updated places where I'd directly referenced the ecommerce flag to use the enum value instead - Stripped out a bunch of styling components from CartPage - this will all go away at some point anyway so why not now? --- .../mit-learn/src/common/feature_flags.ts | 6 ++ .../EcommerceFeature/EcommerceFeature.tsx | 5 +- .../pages/EcommercePages/CartPage.test.tsx | 4 +- .../src/pages/EcommercePages/CartPage.tsx | 92 ++++--------------- 4 files changed, 27 insertions(+), 80 deletions(-) create mode 100644 frontends/mit-learn/src/common/feature_flags.ts 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/page-components/EcommerceFeature/EcommerceFeature.tsx b/frontends/mit-learn/src/page-components/EcommerceFeature/EcommerceFeature.tsx index cddc7d72de..7ee1bed297 100644 --- a/frontends/mit-learn/src/page-components/EcommerceFeature/EcommerceFeature.tsx +++ b/frontends/mit-learn/src/page-components/EcommerceFeature/EcommerceFeature.tsx @@ -1,6 +1,7 @@ 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 @@ -19,13 +20,13 @@ type EcommerceFeatureProps = { */ const EcommerceFeature: React.FC = ({ children }) => { - const ecommFlag = useFeatureFlagEnabled("enable-ecommerce") + const ecommFlag = useFeatureFlagEnabled(FeatureFlags.EnableEcommerce) if (ecommFlag === false) { throw new ForbiddenError("Not enabled.") } - return <>{ecommFlag ? children : null} + 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 index 08d293f809..1a5eb127cc 100644 --- a/frontends/mit-learn/src/pages/EcommercePages/CartPage.test.tsx +++ b/frontends/mit-learn/src/pages/EcommercePages/CartPage.test.tsx @@ -6,7 +6,7 @@ import { login } from "@/common/urls" import { useFeatureFlagEnabled } from "posthog-js/react" jest.mock("posthog-js/react") -const mockedUseFatureFlagEnabled = jest.mocked(useFeatureFlagEnabled) +const mockedUseFeatureFlagEnabled = jest.mocked(useFeatureFlagEnabled) const oldWindowLocation = window.location @@ -33,7 +33,7 @@ describe("CartPage", () => { setMockResponse.get(urls.userMe.get(), { [Permissions.Authenticated]: true, }) - mockedUseFatureFlagEnabled.mockReturnValue(testCase === "on") + mockedUseFeatureFlagEnabled.mockReturnValue(testCase === "on") renderTestApp({ url: commonUrls.ECOMMERCE_CART, diff --git a/frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx b/frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx index 4318812503..59f9b94dd4 100644 --- a/frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx +++ b/frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx @@ -1,88 +1,28 @@ import React from "react" -import { - Breadcrumbs, - Container, - Typography, - TypographyProps, - styled, -} from "ol-components" +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 PageContainer = styled.div(({ theme }) => ({ - display: "flex", - flexDirection: "column", - alignItems: "flex-start", - alignSelf: "stretch", - padding: "40px 84px 80px 84px", - [theme.breakpoints.down("md")]: { - padding: "40px 24px 80px 24px", - }, -})) - -const BannerContainer = styled.div({ - display: "flex", - flexDirection: "column", - alignItems: "center", - paddingBottom: "16px", -}) - -const BannerContainerInner = styled.div({ - display: "flex", - flexDirection: "column", - alignItems: "flex-start", - alignSelf: "stretch", - justifyContent: "center", -}) - -const Header = styled(Typography)>( - ({ theme }) => ({ - alignSelf: "stretch", - color: theme.custom.colors.black, - }), -) - -const BodyContainer = styled.div({ - display: "flex", - flexDirection: "column", - alignItems: "center", - alignSelf: "stretch", - gap: "20px", -}) - -const BodyText = styled(Typography)>( - ({ theme }) => ({ - alignSelf: "stretch", - color: theme.custom.colors.black, - }), -) - const CartPage: React.FC = () => { return ( - - - - - -
    - Shopping Cart -
    -
    -
    - - - The shopping cart layout should go here, if you're allowed to see - this. - - -
    + + + + + Shopping Cart + + + + The shopping cart layout should go here, if you're allowed to see + this. +
    ) From 3a02e22a0d59508ec04db775f8e87b1edb0b9157 Mon Sep 17 00:00:00 2001 From: James Kachel Date: Thu, 3 Oct 2024 10:05:31 -0500 Subject: [PATCH 5/5] fixing docs --- .../src/page-components/EcommerceFeature/EcommerceFeature.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontends/mit-learn/src/page-components/EcommerceFeature/EcommerceFeature.tsx b/frontends/mit-learn/src/page-components/EcommerceFeature/EcommerceFeature.tsx index 7ee1bed297..0cbb0a2edf 100644 --- a/frontends/mit-learn/src/page-components/EcommerceFeature/EcommerceFeature.tsx +++ b/frontends/mit-learn/src/page-components/EcommerceFeature/EcommerceFeature.tsx @@ -16,7 +16,8 @@ type EcommerceFeatureProps = { * because it seemed to be inconsistent - sometimes having the flag enabled * resulted in it tossing to the error page. * - * If the feature flag for this changes, this is where this needs to be set. + * 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 }) => {