@edx/frontend-app-subscription-learner-dashboard 1.67.1 → 1.67.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,4 @@
1
1
  import { FC } from 'react';
2
2
  import { PopularProgramsProps } from './data/types';
3
- export declare const getProductSlugFromMarketingUrl: (marketingUrl: string | null | undefined) => string | null;
4
- export declare const buildEnrollPath: (marketingUrl: string | null | undefined) => string;
5
3
  export declare const PopularPrograms: FC<PopularProgramsProps>;
6
4
  export default PopularPrograms;
@@ -1,27 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { ProductCard } from '../../ProductCard';
3
- export const getProductSlugFromMarketingUrl = (marketingUrl) => {
4
- if (!marketingUrl) {
5
- return null;
6
- }
7
- try {
8
- const pathname = new URL(marketingUrl).pathname;
9
- const slug = pathname.split('/').filter(Boolean).at(-1);
10
- return slug !== null && slug !== void 0 ? slug : null;
11
- }
12
- catch (_a) {
13
- return null;
14
- }
15
- };
16
- export const buildEnrollPath = (marketingUrl) => {
17
- const slug = getProductSlugFromMarketingUrl(marketingUrl);
18
- if (!slug) {
19
- return '';
20
- }
21
- return `/program/${encodeURIComponent(slug)}`;
22
- };
23
3
  export const PopularPrograms = ({ items }) => {
24
- return (_jsx("div", { "data-testid": "popular-programs-items", children: _jsx("div", { className: "row g-3 flex-nowrap flex-md-wrap overflow-auto", children: items.map((item) => (_jsx("div", { className: "col-12 col-md-6 col-xl-3 d-flex mb-2", children: _jsx(ProductCard, { item: Object.assign(Object.assign({}, item), { marketing_url: buildEnrollPath(item.marketing_url) }) }) }, item.objectID))) }) }));
4
+ return (_jsx("div", { "data-testid": "popular-programs-items", children: _jsx("div", { className: "row g-3 flex-nowrap flex-md-wrap overflow-auto", children: items.map((item) => (_jsx("div", { className: "col-12 col-md-6 col-xl-3 d-flex mb-2", children: _jsx(ProductCard, { item: item }) }, item.objectID))) }) }));
25
5
  };
26
6
  export default PopularPrograms;
27
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/containers/PopularProgramsPanel/PopularPrograms/index.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAC5C,YAAuC,EACxB,EAAE;IACjB,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC;QAChD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACxD,OAAO,IAAI,aAAJ,IAAI,cAAJ,IAAI,GAAI,IAAI,CAAC;IACtB,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,YAAuC,EAC/B,EAAE;IACV,MAAM,IAAI,GAAG,8BAA8B,CAAC,YAAY,CAAC,CAAC;IAE1D,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,YAAY,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;AAChD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAA6B,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE;IACrE,OAAO,CACL,6BAAiB,wBAAwB,YACvC,cAAK,SAAS,EAAC,gDAAgD,YAC5D,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACnB,cAAyB,SAAS,EAAC,sCAAsC,YACvE,KAAC,WAAW,IACV,IAAI,kCACC,IAAI,KACP,aAAa,EAAE,eAAe,CAAC,IAAI,CAAC,aAAa,CAAC,MAEpD,IANM,IAAI,CAAC,QAAQ,CAOjB,CACP,CAAC,GACE,GACF,CACP,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,eAAe,CAAC","sourcesContent":["import { FC } from 'react';\n\nimport { ProductCard } from '../../ProductCard';\nimport { PopularProgramsProps } from './data/types';\n\nexport const getProductSlugFromMarketingUrl = (\n marketingUrl: string | null | undefined,\n): string | null => {\n if (!marketingUrl) {\n return null;\n }\n\n try {\n const pathname = new URL(marketingUrl).pathname;\n const slug = pathname.split('/').filter(Boolean).at(-1);\n return slug ?? null;\n } catch {\n return null;\n }\n};\n\nexport const buildEnrollPath = (\n marketingUrl: string | null | undefined,\n): string => {\n const slug = getProductSlugFromMarketingUrl(marketingUrl);\n\n if (!slug) {\n return '';\n }\n\n return `/program/${encodeURIComponent(slug)}`;\n};\n\nexport const PopularPrograms: FC<PopularProgramsProps> = ({ items }) => {\n return (\n <div data-testid=\"popular-programs-items\">\n <div className=\"row g-3 flex-nowrap flex-md-wrap overflow-auto\">\n {items.map((item) => (\n <div key={item.objectID} className=\"col-12 col-md-6 col-xl-3 d-flex mb-2\">\n <ProductCard\n item={{\n ...item,\n marketing_url: buildEnrollPath(item.marketing_url),\n }}\n />\n </div>\n ))}\n </div>\n </div>\n );\n};\n\nexport default PopularPrograms;\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/containers/PopularProgramsPanel/PopularPrograms/index.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD,MAAM,CAAC,MAAM,eAAe,GAA6B,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE;IACrE,OAAO,CACL,6BAAiB,wBAAwB,YACvC,cAAK,SAAS,EAAC,gDAAgD,YAC5D,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACnB,cAAyB,SAAS,EAAC,sCAAsC,YACvE,KAAC,WAAW,IACV,IAAI,EAAE,IAAI,GACV,IAHM,IAAI,CAAC,QAAQ,CAIjB,CACP,CAAC,GACE,GACF,CACP,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,eAAe,CAAC","sourcesContent":["import { FC } from 'react';\n\nimport { ProductCard } from '../../ProductCard';\nimport { PopularProgramsProps } from './data/types';\n\nexport const PopularPrograms: FC<PopularProgramsProps> = ({ items }) => {\n return (\n <div data-testid=\"popular-programs-items\">\n <div className=\"row g-3 flex-nowrap flex-md-wrap overflow-auto\">\n {items.map((item) => (\n <div key={item.objectID} className=\"col-12 col-md-6 col-xl-3 d-flex mb-2\">\n <ProductCard\n item={item}\n />\n </div>\n ))}\n </div>\n </div>\n );\n};\n\nexport default PopularPrograms;\n"]}
@@ -1,30 +1,39 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useIntl } from '@openedx/frontend-base';
3
- import { Card, Hyperlink, Icon } from '@openedx/paragon';
4
- import { Timelapse, Speed, FormatListNumbered } from '@openedx/paragon/icons';
3
+ import { Card, Icon } from '@openedx/paragon';
4
+ import { Speed, FormatListNumbered, Timer } from '@openedx/paragon/icons';
5
5
  import './index.scss';
6
6
  import cardFallbackImg from '@openedx/brand-openedx/paragon/images/card-imagecap-fallback.png';
7
7
  import messages from './messages';
8
8
  import { eventNames, trackSubscriptionEvent } from '../../tracking/trackers/subscription';
9
- const getIdentifyingUrlPath = (marketingUrl) => {
10
- if (!marketingUrl) {
11
- return null;
12
- }
13
- try {
14
- const path = new URL(marketingUrl, window.location.origin).pathname.replace(/^\//, '');
15
- return path || null;
16
- }
17
- catch (_a) {
18
- return null;
19
- }
20
- };
21
9
  export const ProductCard = ({ item, isLoading }) => {
22
10
  const { formatMessage } = useIntl();
23
- const identifyingUrlPath = getIdentifyingUrlPath(item.marketing_url);
24
11
  const totalProgramCourses = item.product === 'program' && item.subject && item.subject.length ? item.subject.length : 0;
12
+ const durationWeeks = item.weeks_to_complete != null && typeof item.weeks_to_complete === 'number' ? item.weeks_to_complete : '';
13
+ const level = item.level || '';
14
+ const linkProps = item.marketing_url
15
+ ? { as: 'a', href: item.marketing_url }
16
+ : {};
25
17
  const handleClick = () => {
26
18
  trackSubscriptionEvent(eventNames.cardTileSelected, {});
27
19
  };
28
- return (_jsxs(Card, { className: "shadow-sm d-flex flex-column rounded w-100 overflow-hidden align-items-stretch card-container", variant: "light", "data-testid": "product-card", isLoading: isLoading, as: identifyingUrlPath ? Hyperlink : 'div', onClick: identifyingUrlPath && !isLoading ? handleClick : undefined, destination: identifyingUrlPath ? `/subscription-enroll/${identifyingUrlPath}` : undefined, children: [_jsx(Card.Header, { title: item.product === 'program' ? `${item.content_type}` : 'Course', className: "pb-2 pt-2 bg-light-500 text-primary-500 content-type", size: "xs" }), _jsx("div", { className: "position-relative image-container", children: _jsx(Card.ImageCap, { src: item.url || cardFallbackImg, srcAlt: `${item.title} main-image`, "data-testid": "product-card-image", logoSrc: item.thumbnail || '', logoAlt: `${item.title} thumbnail-image` }) }), _jsx(Card.Header, { title: item.title, subtitle: item.partner, size: "sm", className: "product-card-title" }), _jsxs(Card.Section, { className: "d-flex flex-column flex-grow-1 overflow-hidden mt-5", children: [totalProgramCourses > 0 ? (_jsxs("span", { className: "d-flex align-items-center mb-2", children: [_jsx(Icon, { src: FormatListNumbered, className: "mr-2" }), _jsx("span", { children: formatMessage(messages.courses, { count: totalProgramCourses }) })] })) : null, typeof item.weeks_to_complete === 'number' && (_jsxs("span", { className: "d-flex align-items-center mb-2", children: [_jsx(Icon, { src: Timelapse, className: "mr-2" }), _jsx("span", { children: formatMessage(messages.weeksToComplete, { count: item.weeks_to_complete }) })] })), item.level ? (_jsxs("span", { className: `d-flex align-items-center${typeof item.weeks_to_complete === 'number' ? ' mt-3' : ''}`, children: [_jsx(Icon, { src: Speed, className: "mr-2" }), _jsx("span", { children: formatMessage(messages.level, { level: item.level }) })] })) : null] })] }));
20
+ const metadataItems = [
21
+ totalProgramCourses > 0 && {
22
+ key: 'course-count',
23
+ icon: FormatListNumbered,
24
+ label: formatMessage(messages.courses, { count: totalProgramCourses }),
25
+ },
26
+ durationWeeks && {
27
+ key: 'duration',
28
+ icon: Timer,
29
+ label: formatMessage(messages.weeksToComplete, { count: item.weeks_to_complete }),
30
+ },
31
+ level && {
32
+ key: 'level',
33
+ icon: Speed,
34
+ label: formatMessage(messages.level, { level }),
35
+ },
36
+ ].filter(Boolean);
37
+ return (_jsxs(Card, Object.assign({ isClickable: Boolean(item.marketing_url), className: "product-card course-card h-100 d-flex flex-column overflow-hidden", variant: "light", "data-testid": "product-card", isLoading: isLoading, onClick: handleClick }, linkProps, { children: [_jsx(Card.Header, { className: "product-card__header", size: "sm", title: (_jsx("span", { className: "product-card__header-label", children: item.product === 'program' ? `${item.content_type}` : 'Course' })) }), _jsx(Card.ImageCap, { src: item.url || cardFallbackImg, srcAlt: `${item.title} main-image`, "data-testid": "product-card-image", logoSrc: item.thumbnail || '', logoAlt: `${item.title} thumbnail-image`, fallbackLogoSrc: cardFallbackImg }), _jsxs(Card.Section, { className: "pt-4\\.5 flex-grow-1", children: [item.title && (_jsx("h4", { className: "h6 m-0 product-card__title", children: item.title })), item.partner && (_jsx("p", { className: "mt-1 mb-0 product-card__institution", children: item.partner }))] }), _jsx(Card.Section, { className: "product-card__metadata pt-0 mt-auto", children: metadataItems.map((mitem) => (_jsxs("div", { className: "product-card__metadata-item", children: [_jsx(Icon, { src: mitem.icon, size: "sm", className: "product-card__metadata-icon" }), _jsx("span", { children: mitem.label })] }, mitem.key))) })] })));
29
38
  };
30
39
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/containers/ProductCard/index.tsx"],"names":[],"mappings":"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","sourcesContent":["import { FC } from 'react';\nimport { useIntl } from '@openedx/frontend-base';\nimport { Card, Hyperlink, Icon } from '@openedx/paragon';\nimport { Timelapse, Speed, FormatListNumbered } from '@openedx/paragon/icons';\nimport './index.scss';\nimport cardFallbackImg from '@openedx/brand-openedx/paragon/images/card-imagecap-fallback.png';\nimport { ProductCardProps } from './data/types';\nimport messages from './messages';\nimport { eventNames, trackSubscriptionEvent } from '@src/tracking/trackers/subscription';\n\nconst getIdentifyingUrlPath = (marketingUrl: string): string | null => {\n if (!marketingUrl) {\n return null;\n }\n try {\n const path = new URL(marketingUrl, window.location.origin).pathname.replace(/^\\//, '');\n return path || null;\n } catch {\n return null;\n }\n};\n\nexport const ProductCard: FC<ProductCardProps> = ({ item, isLoading }) => {\n const { formatMessage } = useIntl();\n const identifyingUrlPath = getIdentifyingUrlPath(item.marketing_url);\n const totalProgramCourses = item.product === 'program' && item.subject && item.subject.length ? item.subject.length : 0;\n\n const handleClick = () => {\n trackSubscriptionEvent(eventNames.cardTileSelected, {});\n };\n\n return (\n <Card\n className=\"shadow-sm d-flex flex-column rounded w-100 overflow-hidden align-items-stretch card-container\"\n variant=\"light\"\n data-testid=\"product-card\"\n isLoading={isLoading}\n as={identifyingUrlPath ? Hyperlink : 'div'}\n onClick={identifyingUrlPath && !isLoading ? handleClick : undefined}\n destination={identifyingUrlPath ? `/subscription-enroll/${identifyingUrlPath}` : undefined}\n >\n <Card.Header title={item.product === 'program' ? `${item.content_type}` : 'Course'} className=\"pb-2 pt-2 bg-light-500 text-primary-500 content-type\" size=\"xs\" />\n\n <div className=\"position-relative image-container\">\n <Card.ImageCap\n src={item.url || cardFallbackImg}\n srcAlt={`${item.title} main-image`}\n data-testid=\"product-card-image\"\n logoSrc={item.thumbnail || ''}\n logoAlt={`${item.title} thumbnail-image`}\n />\n </div>\n\n <Card.Header title={item.title} subtitle={item.partner} size=\"sm\" className=\"product-card-title\" />\n\n <Card.Section className=\"d-flex flex-column flex-grow-1 overflow-hidden mt-5\">\n {totalProgramCourses > 0 ? (\n <span className=\"d-flex align-items-center mb-2\">\n <Icon src={FormatListNumbered} className=\"mr-2\" />\n <span>{formatMessage(messages.courses, { count: totalProgramCourses })}</span>\n </span>\n ) : null}\n {typeof item.weeks_to_complete === 'number' && (\n <span className=\"d-flex align-items-center mb-2\">\n <Icon src={Timelapse} className=\"mr-2\" />\n <span>{formatMessage(messages.weeksToComplete, { count: item.weeks_to_complete })}</span>\n </span>\n )}\n {item.level ? (\n <span className={`d-flex align-items-center${typeof item.weeks_to_complete === 'number' ? ' mt-3' : ''}`}>\n <Icon src={Speed} className=\"mr-2\" />\n <span>{formatMessage(messages.level, { level: item.level })}</span>\n </span>\n ) : null}\n </Card.Section>\n\n </Card>\n );\n};\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/containers/ProductCard/index.tsx"],"names":[],"mappings":"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","sourcesContent":["import { FC } from 'react';\nimport { useIntl } from '@openedx/frontend-base';\nimport { Card, Icon } from '@openedx/paragon';\nimport { Speed, FormatListNumbered, Timer } from '@openedx/paragon/icons';\nimport './index.scss';\nimport cardFallbackImg from '@openedx/brand-openedx/paragon/images/card-imagecap-fallback.png';\nimport { ProductCardProps } from './data/types';\nimport messages from './messages';\nimport { eventNames, trackSubscriptionEvent } from '@src/tracking/trackers/subscription';\n\nexport const ProductCard: FC<ProductCardProps> = ({ item, isLoading }) => {\n const { formatMessage } = useIntl();\n const totalProgramCourses = item.product === 'program' && item.subject && item.subject.length ? item.subject.length : 0;\n const durationWeeks = item.weeks_to_complete != null && typeof item.weeks_to_complete === 'number' ? item.weeks_to_complete : '';\n const level = item.level || '';\n const linkProps = item.marketing_url\n ? { as: 'a' as const, href: item.marketing_url }\n : {};\n\n const handleClick = () => {\n trackSubscriptionEvent(eventNames.cardTileSelected, {});\n };\n\n const metadataItems = [\n totalProgramCourses > 0 && {\n key: 'course-count',\n icon: FormatListNumbered,\n label: formatMessage(messages.courses, { count: totalProgramCourses }),\n },\n durationWeeks && {\n key: 'duration',\n icon: Timer,\n label: formatMessage(messages.weeksToComplete, { count: item.weeks_to_complete }),\n },\n level && {\n key: 'level',\n icon: Speed,\n label: formatMessage(messages.level, { level }),\n },\n ].filter(Boolean) as {\n key: string,\n icon: React.ComponentType,\n label: string,\n }[];\n\n return (\n <Card\n isClickable={Boolean(item.marketing_url)}\n className=\"product-card course-card h-100 d-flex flex-column overflow-hidden\"\n variant=\"light\"\n data-testid=\"product-card\"\n isLoading={isLoading}\n onClick={handleClick}\n {...linkProps}\n >\n <Card.Header\n className=\"product-card__header\"\n size=\"sm\"\n title={(\n <span className=\"product-card__header-label\">\n {item.product === 'program' ? `${item.content_type}` : 'Course'}\n </span>\n )}\n />\n\n <Card.ImageCap\n src={item.url || cardFallbackImg}\n srcAlt={`${item.title} main-image`}\n data-testid=\"product-card-image\"\n logoSrc={item.thumbnail || ''}\n logoAlt={`${item.title} thumbnail-image`}\n fallbackLogoSrc={cardFallbackImg}\n />\n\n <Card.Section className=\"pt-4\\.5 flex-grow-1\">\n {item.title && (\n <h4 className=\"h6 m-0 product-card__title\">\n {item.title}\n </h4>\n )}\n {item.partner && (\n <p className=\"mt-1 mb-0 product-card__institution\">\n {item.partner}\n </p>\n )}\n </Card.Section>\n\n <Card.Section className=\"product-card__metadata pt-0 mt-auto\">\n {metadataItems.map((mitem) => (\n <div key={mitem.key} className=\"product-card__metadata-item\">\n <Icon\n src={mitem.icon}\n size=\"sm\"\n className=\"product-card__metadata-icon\"\n />\n <span>{mitem.label}</span>\n </div>\n ))}\n </Card.Section>\n\n </Card>\n );\n};\n"]}
@@ -1,17 +1,77 @@
1
- .product-card-title {
2
- min-height: 6.4rem;
3
- }
1
+ .product-card {
2
+ overflow: hidden;
3
+ text-decoration: none;
4
+ color: inherit;
4
5
 
5
- .card-container > .content-type > .pgn__card-header-content {
6
- margin-top: 0;
7
- }
6
+ &:hover,
7
+ &:focus {
8
+ text-decoration: none;
9
+ color: inherit;
10
+ }
8
11
 
9
- .card-container .pgn__card-logo-cap {
10
- position: absolute;
11
- top: 50%;
12
- transform: translateY(-50%);
13
- }
12
+ .product-card__header.pgn__card-header {
13
+ background-color: var(--pgn-color-light-500);
14
+
15
+ .pgn__card-header-content {
16
+ margin: 0;
17
+ overflow: hidden;
18
+ padding: 0.6875rem 0;
19
+ }
20
+
21
+ .product-card__header-label {
22
+ color: var(--pgn-color-primary-base);
23
+ font-size: 0.875rem;
24
+ font-weight: 700;
25
+ display: block;
26
+ line-height: 1;
27
+ }
28
+ }
29
+
30
+ .pgn__card-wrapper-image-cap.vertical {
31
+ background-color: var(--pgn-color-gray-200);
32
+ height: 6.5rem;
33
+ max-height: 6.5rem;
34
+ }
35
+
36
+ .pgn__card-image-cap {
37
+ border-top-left-radius: 0;
38
+ border-top-right-radius: 0;
39
+ }
40
+
41
+ .pgn__card-logo-cap.show {
42
+ bottom: 50%;
43
+ transform: translateY(50%);
44
+ }
45
+
46
+ .product-card__title {
47
+ color: var(--pgn-color-gray-900);
48
+ display: -webkit-box;
49
+ -webkit-box-orient: vertical;
50
+ -webkit-line-clamp: 2;
51
+ overflow: hidden;
52
+ }
53
+
54
+ .product-card__institution {
55
+ color: var(--pgn-color-gray-500);
56
+ }
57
+
58
+ .product-card__metadata {
59
+ .product-card__metadata-item {
60
+ align-items: center;
61
+ color: var(--pgn-color-gray-500);
62
+ display: flex;
63
+ font-size: 0.875rem;
64
+ gap: 0.5rem;
65
+ line-height: 1.25rem;
66
+
67
+ &:not(:last-child) {
68
+ margin-bottom: 0.5rem;
69
+ }
70
+ }
14
71
 
15
- .card-container .pgn__card-image-cap {
16
- border-radius: 0 !important;
72
+ .product-card__metadata-icon {
73
+ color: var(--pgn-color-gray-500);
74
+ flex-shrink: 0;
75
+ }
76
+ }
17
77
  }
@@ -8,4 +8,6 @@ export declare const searchKeys: {
8
8
  courses: (query: string, tags: string[], page: number) => readonly ["search", "courses", string, string[], number];
9
9
  popular: (productType: ProductType, limit: number, skill?: Skill) => readonly ["search", "popular", ProductType, number, string | null];
10
10
  };
11
+ export declare const getProductSlugFromMarketingUrl: (marketingUrl: string | null | undefined) => string | null;
12
+ export declare const buildEnrollPdpPath: (productType: string | null | undefined, marketingUrl: string | null | undefined) => string | null;
11
13
  export declare const usePopularCoursesAndProgramsData: (searchClient: SearchClient | null, enabled: boolean, productType?: ProductType, skill?: Skill) => UseQueryResult<ProductCardItem[]>;
@@ -23,6 +23,31 @@ const hitPartner = (hit) => {
23
23
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
24
24
  return (Array.isArray(hit.partner) ? hit.partner[0] : (_l = (_k = (_g = (_d = (_a = hit.partner) !== null && _a !== void 0 ? _a : (_c = (_b = hit.owners) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.name) !== null && _d !== void 0 ? _d : (_f = (_e = hit.authoring_organizations) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.name) !== null && _g !== void 0 ? _g : (_j = (_h = hit.partners) === null || _h === void 0 ? void 0 : _h[0]) === null || _j === void 0 ? void 0 : _j.name) !== null && _k !== void 0 ? _k : hit.institution) !== null && _l !== void 0 ? _l : '');
25
25
  };
26
+ export const getProductSlugFromMarketingUrl = (marketingUrl) => {
27
+ var _a, _b;
28
+ if (!marketingUrl) {
29
+ return null;
30
+ }
31
+ try {
32
+ const pathname = new URL(marketingUrl).pathname;
33
+ const parts = pathname.split('/').filter(Boolean);
34
+ return (_a = parts[parts.length - 1]) !== null && _a !== void 0 ? _a : null;
35
+ }
36
+ catch (_c) {
37
+ const parts = marketingUrl.split('/').filter(Boolean);
38
+ return (_b = parts[parts.length - 1]) !== null && _b !== void 0 ? _b : null;
39
+ }
40
+ };
41
+ export const buildEnrollPdpPath = (productType, marketingUrl) => {
42
+ const slug = getProductSlugFromMarketingUrl(marketingUrl);
43
+ if (!slug) {
44
+ return null;
45
+ }
46
+ const kind = (productType !== null && productType !== void 0 ? productType : '').toLowerCase() === 'program'
47
+ ? 'program'
48
+ : 'course';
49
+ return `/subscription-enroll/${kind}/${encodeURIComponent(slug)}`;
50
+ };
26
51
  export const usePopularCoursesAndProgramsData = (searchClient, enabled, productType = PRODUCT_TYPE_COURSE, skill) => useQuery({
27
52
  queryKey: searchKeys.popular(productType, 4, skill),
28
53
  queryFn: () => __awaiter(void 0, void 0, void 0, function* () {
@@ -35,7 +60,7 @@ export const usePopularCoursesAndProgramsData = (searchClient, enabled, productT
35
60
  title: (_b = (_a = hit.title) !== null && _a !== void 0 ? _a : hit.name) !== null && _b !== void 0 ? _b : 'Untitled',
36
61
  primary_description: (_e = (_d = (_c = hitDetails.primary_description) !== null && _c !== void 0 ? _c : hitDetails.short_description) !== null && _d !== void 0 ? _d : hitDetails.description) !== null && _e !== void 0 ? _e : '',
37
62
  url: (_g = (_f = hitDetails.card_image_url) !== null && _f !== void 0 ? _f : hitDetails.image_url) !== null && _g !== void 0 ? _g : '',
38
- marketing_url: (_h = hitDetails.marketing_url) !== null && _h !== void 0 ? _h : '',
63
+ marketing_url: buildEnrollPdpPath((_h = hitDetails.product) !== null && _h !== void 0 ? _h : 'Course', hitDetails.marketing_url) || '',
39
64
  thumbnail: (_p = (_o = (_m = (_l = (_k = (_j = hitDetails.owners) === null || _j === void 0 ? void 0 : _j[0]) === null || _k === void 0 ? void 0 : _k.logoImageUrl) !== null && _l !== void 0 ? _l : hitDetails.organization_logo_override) !== null && _m !== void 0 ? _m : hitDetails.organization_logo_url) !== null && _o !== void 0 ? _o : hitDetails.partner_logo_url) !== null && _p !== void 0 ? _p : '',
40
65
  content_type: (_r = (_q = hitDetails.program_type) !== null && _q !== void 0 ? _q : hitDetails.product) !== null && _r !== void 0 ? _r : 'Course',
41
66
  product: productType,
@@ -1 +1 @@
1
- {"version":3,"file":"usePopularCoursesAndPrograms.js","sourceRoot":"","sources":["../../src/hooks/usePopularCoursesAndPrograms.ts"],"names":[],"mappings":"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","sourcesContent":["import { useQuery, UseQueryResult } from '@tanstack/react-query';\nimport { SearchClient } from 'algoliasearch';\nimport { ProductType, Skill } from '@src/types/types';\nimport { PRODUCT_TYPE_COURSE } from '@src/constants';\nimport { fetchPopularProducts } from '@src/utils/algoliaUtils';\nimport { ProductCardItem } from '@src/containers/ProductCard/data/types';\nimport { firstOf } from './utils';\n\n// ─── Query key factories ──────────────────────────────────────────────────────\n// Centralised so cache invalidation is never a typo\n\nexport const searchKeys = {\n all: ['search'] as const,\n programs: (query: string, tags: string[]) =>\n [...searchKeys.all, 'programs', query, tags] as const,\n courses: (query: string, tags: string[], page: number) =>\n [...searchKeys.all, 'courses', query, tags, page] as const,\n popular: (productType: ProductType, limit: number, skill?: Skill) =>\n [...searchKeys.all, 'popular', productType, limit, skill ?? null] as const,\n};\n\nconst hitPartner = (hit: any): string => (\n Array.isArray(hit.partner) ? hit.partner[0] : hit.partner\n ?? hit.owners?.[0]?.name\n ?? hit.authoring_organizations?.[0]?.name\n ?? hit.partners?.[0]?.name\n ?? hit.institution\n ?? ''\n);\n\nexport const usePopularCoursesAndProgramsData = (\n searchClient: SearchClient | null,\n enabled: boolean,\n productType: ProductType = PRODUCT_TYPE_COURSE,\n skill?: Skill,\n): UseQueryResult<ProductCardItem[]> =>\n useQuery({\n queryKey: searchKeys.popular(productType, 4, skill),\n queryFn: async () => {\n const hits = await fetchPopularProducts(searchClient!, productType, 4, skill);\n return hits.map((hit): ProductCardItem => {\n const hitDetails = hit as {\n short_description?: string,\n primary_description?: string,\n description?: string,\n marketing_url?: string,\n image_url?: string,\n card_image_url?: string,\n organization_logo_override?: string,\n content_type?: string,\n program_type?: string,\n product?: string,\n partner?: string,\n weeks_to_complete?: number,\n level?: string,\n subject?: string[],\n owners?: { logoImageUrl?: string }[],\n organization_logo_url?: string,\n partner_logo_url?: string,\n level_type?: string,\n authoring_organizations?: [],\n partners?: [],\n institution?: string,\n course_titles?: string[],\n };\n\n return {\n objectID: hit.objectID,\n title: hit.title ?? hit.name ?? 'Untitled',\n primary_description: hitDetails.primary_description ?? hitDetails.short_description ?? hitDetails.description ?? '',\n url: hitDetails.card_image_url ?? hitDetails.image_url ?? '',\n marketing_url: hitDetails.marketing_url ?? '',\n thumbnail: hitDetails.owners?.[0]?.logoImageUrl ?? hitDetails.organization_logo_override ?? hitDetails.organization_logo_url ?? hitDetails.partner_logo_url ?? '',\n content_type: hitDetails.program_type ?? hitDetails.product ?? 'Course',\n product: productType,\n partner: hitPartner(hit) ?? '',\n weeks_to_complete: hitDetails.weeks_to_complete ?? undefined,\n level: firstOf(hitDetails.level) ?? hitDetails.level_type ?? null,\n subject: hitDetails?.course_titles ? hitDetails?.course_titles : undefined,\n };\n });\n },\n enabled: !!searchClient && enabled,\n staleTime: 1000 * 60 * 10,\n });\n"]}
1
+ {"version":3,"file":"usePopularCoursesAndPrograms.js","sourceRoot":"","sources":["../../src/hooks/usePopularCoursesAndPrograms.ts"],"names":[],"mappings":"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","sourcesContent":["import { useQuery, UseQueryResult } from '@tanstack/react-query';\nimport { SearchClient } from 'algoliasearch';\nimport { ProductType, Skill } from '@src/types/types';\nimport { PRODUCT_TYPE_COURSE } from '@src/constants';\nimport { fetchPopularProducts } from '@src/utils/algoliaUtils';\nimport { ProductCardItem } from '@src/containers/ProductCard/data/types';\nimport { firstOf } from './utils';\n\n// ─── Query key factories ──────────────────────────────────────────────────────\n// Centralised so cache invalidation is never a typo\n\nexport const searchKeys = {\n all: ['search'] as const,\n programs: (query: string, tags: string[]) =>\n [...searchKeys.all, 'programs', query, tags] as const,\n courses: (query: string, tags: string[], page: number) =>\n [...searchKeys.all, 'courses', query, tags, page] as const,\n popular: (productType: ProductType, limit: number, skill?: Skill) =>\n [...searchKeys.all, 'popular', productType, limit, skill ?? null] as const,\n};\n\nconst hitPartner = (hit: any): string => (\n Array.isArray(hit.partner) ? hit.partner[0] : hit.partner\n ?? hit.owners?.[0]?.name\n ?? hit.authoring_organizations?.[0]?.name\n ?? hit.partners?.[0]?.name\n ?? hit.institution\n ?? ''\n);\n\nexport const getProductSlugFromMarketingUrl = (\n marketingUrl: string | null | undefined,\n): string | null => {\n if (!marketingUrl) {\n return null;\n }\n\n try {\n const pathname = new URL(marketingUrl).pathname;\n const parts = pathname.split('/').filter(Boolean);\n return parts[parts.length - 1] ?? null;\n } catch {\n const parts = marketingUrl.split('/').filter(Boolean);\n return parts[parts.length - 1] ?? null;\n }\n};\n\nexport const buildEnrollPdpPath = (\n productType: string | null | undefined,\n marketingUrl: string | null | undefined,\n): string | null => {\n const slug = getProductSlugFromMarketingUrl(marketingUrl);\n if (!slug) {\n return null;\n }\n\n const kind = (productType ?? '').toLowerCase() === 'program'\n ? 'program'\n : 'course';\n\n return `/subscription-enroll/${kind}/${encodeURIComponent(slug)}`;\n};\n\nexport const usePopularCoursesAndProgramsData = (\n searchClient: SearchClient | null,\n enabled: boolean,\n productType: ProductType = PRODUCT_TYPE_COURSE,\n skill?: Skill,\n): UseQueryResult<ProductCardItem[]> =>\n useQuery({\n queryKey: searchKeys.popular(productType, 4, skill),\n queryFn: async () => {\n const hits = await fetchPopularProducts(searchClient!, productType, 4, skill);\n return hits.map((hit): ProductCardItem => {\n const hitDetails = hit as {\n short_description?: string,\n primary_description?: string,\n description?: string,\n marketing_url?: string,\n image_url?: string,\n card_image_url?: string,\n organization_logo_override?: string,\n content_type?: string,\n program_type?: string,\n product?: string,\n partner?: string,\n weeks_to_complete?: number,\n level?: string,\n subject?: string[],\n owners?: { logoImageUrl?: string }[],\n organization_logo_url?: string,\n partner_logo_url?: string,\n level_type?: string,\n authoring_organizations?: [],\n partners?: [],\n institution?: string,\n course_titles?: string[],\n };\n\n return {\n objectID: hit.objectID,\n title: hit.title ?? hit.name ?? 'Untitled',\n primary_description: hitDetails.primary_description ?? hitDetails.short_description ?? hitDetails.description ?? '',\n url: hitDetails.card_image_url ?? hitDetails.image_url ?? '',\n marketing_url: buildEnrollPdpPath(hitDetails.product ?? 'Course', hitDetails.marketing_url) || '',\n thumbnail: hitDetails.owners?.[0]?.logoImageUrl ?? hitDetails.organization_logo_override ?? hitDetails.organization_logo_url ?? hitDetails.partner_logo_url ?? '',\n content_type: hitDetails.program_type ?? hitDetails.product ?? 'Course',\n product: productType,\n partner: hitPartner(hit) ?? '',\n weeks_to_complete: hitDetails.weeks_to_complete ?? undefined,\n level: firstOf(hitDetails.level) ?? hitDetails.level_type ?? null,\n subject: hitDetails?.course_titles ? hitDetails?.course_titles : undefined,\n };\n });\n },\n enabled: !!searchClient && enabled,\n staleTime: 1000 * 60 * 10,\n });\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edx/frontend-app-subscription-learner-dashboard",
3
- "version": "1.67.1",
3
+ "version": "1.67.2",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",