diff --git a/frontends/mit-learn/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx b/frontends/mit-learn/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx
index 87704724bd..bbfb594b2c 100644
--- a/frontends/mit-learn/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx
+++ b/frontends/mit-learn/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx
@@ -5,6 +5,7 @@ import {
renderWithProviders,
screen,
waitFor,
+ within,
} from "@/test-utils"
import LearningResourceDrawer, {
useOpenLearningResourceDrawer,
@@ -12,6 +13,8 @@ import LearningResourceDrawer, {
import { urls, factories, setMockResponse } from "api/test-utils"
import { LearningResourceExpanded } from "ol-components"
import { RESOURCE_DRAWER_QUERY_PARAM } from "@/common/urls"
+import { ResourceTypeEnum } from "api"
+import invariant from "tiny-invariant"
jest.mock("ol-components", () => {
const actual = jest.requireActual("ol-components")
@@ -40,6 +43,7 @@ describe("LearningResourceDrawer", () => {
])(
"Renders drawer content when resource=id is in the URL and captures the view if PostHog $descriptor",
async ({ enablePostHog }) => {
+ setMockResponse.get(urls.userMe.get(), {})
APP_SETTINGS.POSTHOG = {
api_key: enablePostHog ? "test1234" : "", // pragma: allowlist secret
}
@@ -95,4 +99,84 @@ describe("LearningResourceDrawer", () => {
dog: "woof",
})
})
+
+ test.each([
+ {
+ isLearningPathEditor: true,
+ isAuthenticated: true,
+ expectAddToLearningPathButton: true,
+ expectAddToUserListButton: true,
+ },
+ {
+ isLearningPathEditor: false,
+ isAuthenticated: true,
+ expectAddToLearningPathButton: false,
+ expectAddToUserListButton: true,
+ },
+ {
+ isLearningPathEditor: false,
+ isAuthenticated: false,
+ expectAddToLearningPathButton: false,
+ expectAddToUserListButton: false,
+ },
+ ])(
+ "Renders info section list buttons correctly",
+ async ({
+ isLearningPathEditor,
+ isAuthenticated,
+ expectAddToLearningPathButton,
+ expectAddToUserListButton,
+ }) => {
+ const resource = factories.learningResources.resource({
+ resource_type: ResourceTypeEnum.Course,
+ runs: [
+ factories.learningResources.run({
+ languages: ["en-us", "es-es", "fr-fr"],
+ }),
+ ],
+ })
+ setMockResponse.get(
+ urls.learningResources.details({ id: resource.id }),
+ resource,
+ )
+
+ renderWithProviders(, {
+ url: `?resource=${resource.id}`,
+ user: {
+ is_learning_path_editor: isLearningPathEditor,
+ is_authenticated: isAuthenticated,
+ },
+ })
+
+ expect(LearningResourceExpanded).toHaveBeenCalled()
+
+ await waitFor(() => {
+ expectProps(LearningResourceExpanded, { resource })
+ })
+
+ const section = screen
+ .getByRole("heading", { name: "Info" })
+ .closest("section")
+ invariant(section)
+
+ if (!isAuthenticated) {
+ const buttons = within(section).queryAllByRole("button")
+ expect(buttons).toHaveLength(0)
+ return
+ } else {
+ const buttons = within(section).getAllByRole("button")
+ const expectedButtons =
+ expectAddToLearningPathButton && expectAddToUserListButton ? 2 : 1
+ expect(buttons).toHaveLength(expectedButtons)
+ expect(
+ !!within(section).queryByRole("button", {
+ name: "Add to Learning Path",
+ }),
+ ).toBe(expectAddToLearningPathButton)
+ expect(
+ !!within(section).queryByRole("button", { name: "Add to User List" }),
+ ).toBe(expectAddToUserListButton)
+ }
+ },
+ )
})
diff --git a/frontends/mit-learn/src/page-components/LearningResourceDrawer/LearningResourceDrawer.tsx b/frontends/mit-learn/src/page-components/LearningResourceDrawer/LearningResourceDrawer.tsx
index 754b43957b..cc762d7b83 100644
--- a/frontends/mit-learn/src/page-components/LearningResourceDrawer/LearningResourceDrawer.tsx
+++ b/frontends/mit-learn/src/page-components/LearningResourceDrawer/LearningResourceDrawer.tsx
@@ -1,15 +1,24 @@
-import React, { useEffect, useCallback } from "react"
+import React, { useEffect, useCallback, useMemo } from "react"
import {
RoutedDrawer,
LearningResourceExpanded,
imgConfigs,
} from "ol-components"
-import type { RoutedDrawerProps } from "ol-components"
+import type {
+ LearningResourceCardProps,
+ RoutedDrawerProps,
+} from "ol-components"
import { useLearningResourcesDetail } from "api/hooks/learningResources"
import { useSearchParams, useLocation } from "react-router-dom"
import { RESOURCE_DRAWER_QUERY_PARAM } from "@/common/urls"
import { usePostHog } from "posthog-js/react"
import MetaTags from "@/page-components/MetaTags/MetaTags"
+import { useUserMe } from "api/hooks/user"
+import NiceModal from "@ebay/nice-modal-react"
+import {
+ AddToLearningPathDialog,
+ AddToUserListDialog,
+} from "../Dialogs/AddToListDialog"
const RESOURCE_DRAWER_PARAMS = [RESOURCE_DRAWER_QUERY_PARAM] as const
@@ -56,6 +65,25 @@ const DrawerContent: React.FC<{
resourceId: number
}> = ({ resourceId }) => {
const resource = useLearningResourcesDetail(Number(resourceId))
+ const { data: user } = useUserMe()
+ const handleAddToLearningPathClick: LearningResourceCardProps["onAddToLearningPathClick"] =
+ useMemo(() => {
+ if (user?.is_learning_path_editor) {
+ return (event, resourceId: number) => {
+ NiceModal.show(AddToLearningPathDialog, { resourceId })
+ }
+ }
+ return null
+ }, [user])
+ const handleAddToUserListClick: LearningResourceCardProps["onAddToUserListClick"] =
+ useMemo(() => {
+ if (user?.is_authenticated) {
+ return (event, resourceId: number) => {
+ NiceModal.show(AddToUserListDialog, { resourceId })
+ }
+ }
+ return null
+ }, [user])
useCapturePageView(Number(resourceId))
return (
@@ -70,6 +98,9 @@ const DrawerContent: React.FC<{
>
)
diff --git a/frontends/ol-ckeditor/src/types/settings.d.ts b/frontends/ol-ckeditor/src/types/settings.d.ts
index fbb37f3706..237e6ca039 100644
--- a/frontends/ol-ckeditor/src/types/settings.d.ts
+++ b/frontends/ol-ckeditor/src/types/settings.d.ts
@@ -8,5 +8,6 @@ export declare global {
MITOL_API_BASE_URL: string
PUBLIC_URL: string
SITE_NAME: string
+ CSRF_COOKIE_NAME: string
}
}
diff --git a/frontends/ol-components/src/components/LearningResourceExpanded/InfoSection.tsx b/frontends/ol-components/src/components/LearningResourceExpanded/InfoSection.tsx
index 315f2a3e62..67c767fb42 100644
--- a/frontends/ol-components/src/components/LearningResourceExpanded/InfoSection.tsx
+++ b/frontends/ol-components/src/components/LearningResourceExpanded/InfoSection.tsx
@@ -13,6 +13,8 @@ import {
RiTranslate2,
RiAwardLine,
RiPresentationLine,
+ RiMenuAddLine,
+ RiBookmarkLine,
} from "@remixicon/react"
import { LearningResource, LearningResourceRun, ResourceTypeEnum } from "api"
import {
@@ -21,6 +23,9 @@ import {
} from "ol-utilities"
import { theme } from "../ThemeProvider/ThemeProvider"
import Typography from "@mui/material/Typography"
+import type { User } from "api/hooks/user"
+import { CardActionButton } from "../LearningResourceCard/LearningResourceListCard"
+import { LearningResourceCardProps } from "../LearningResourceCard/LearningResourceCard"
const InfoItems = styled.section`
display: flex;
@@ -28,6 +33,17 @@ const InfoItems = styled.section`
gap: 16px;
`
+const InfoHeader = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+`
+
+const ListButtonContainer = styled.div`
+ display: flex;
+ gap: 8px;
+`
+
const InfoItemContainer = styled.div`
display: flex;
align-items: flex-start;
@@ -223,14 +239,23 @@ const InfoItem = ({ label, Icon, value }: InfoItemProps) => {
const InfoSection = ({
resource,
run,
+ user,
+ onAddToLearningPathClick,
+ onAddToUserListClick,
}: {
resource?: LearningResource
run?: LearningResourceRun
+ user?: User
+ onAddToLearningPathClick?: LearningResourceCardProps["onAddToLearningPathClick"]
+ onAddToUserListClick?: LearningResourceCardProps["onAddToUserListClick"]
}) => {
if (!resource) {
return null
}
+ const inUserList = !!resource?.user_list_parents?.length
+ const inLearningPath = !!resource?.learning_path_parents?.length
+
const infoItems = INFO_ITEMS.map(({ label, Icon, selector }) => ({
label,
Icon,
@@ -243,9 +268,39 @@ const InfoSection = ({
return (
-
- Info
-
+
+
+ Info
+
+
+ {user?.is_learning_path_editor && (
+
+ onAddToLearningPathClick
+ ? onAddToLearningPathClick(event, resource.id)
+ : null
+ }
+ >
+
+
+ )}
+ {user?.is_authenticated && (
+
+ onAddToUserListClick
+ ? onAddToUserListClick(event, resource.id)
+ : null
+ }
+ >
+
+
+ )}
+
+
{infoItems.map((props, index) => (
))}
diff --git a/frontends/ol-components/src/components/LearningResourceExpanded/LearningResourceExpanded.tsx b/frontends/ol-components/src/components/LearningResourceExpanded/LearningResourceExpanded.tsx
index a76f386725..8949a85719 100644
--- a/frontends/ol-components/src/components/LearningResourceExpanded/LearningResourceExpanded.tsx
+++ b/frontends/ol-components/src/components/LearningResourceExpanded/LearningResourceExpanded.tsx
@@ -20,6 +20,8 @@ import type { SimpleSelectProps } from "../SimpleSelect/SimpleSelect"
import { EmbedlyCard } from "../EmbedlyCard/EmbedlyCard"
import { PlatformLogo, PLATFORMS } from "../Logo/Logo"
import InfoSection from "./InfoSection"
+import type { User } from "api/hooks/user"
+import { LearningResourceCardProps } from "../LearningResourceCard/LearningResourceCard"
const Container = styled.div<{ padTop?: boolean }>`
display: flex;
@@ -138,7 +140,10 @@ const OnPlatform = styled.span`
type LearningResourceExpandedProps = {
resource?: LearningResource
+ user?: User
imgConfig: EmbedlyConfig
+ onAddToLearningPathClick?: LearningResourceCardProps["onAddToLearningPathClick"]
+ onAddToUserListClick?: LearningResourceCardProps["onAddToUserListClick"]
}
const ImageSection: React.FC<{
@@ -314,7 +319,10 @@ const formatRunDate = (
const LearningResourceExpanded: React.FC = ({
resource,
+ user,
imgConfig,
+ onAddToLearningPathClick,
+ onAddToUserListClick,
}) => {
const [selectedRun, setSelectedRun] = useState(resource?.runs?.[0])
@@ -411,7 +419,13 @@ const LearningResourceExpanded: React.FC = ({
-
+
)
}