From c70c3830d62bd030a15696d473dd1b1a552fdb9d Mon Sep 17 00:00:00 2001 From: Ben Warzeski Date: Thu, 2 Jun 2022 14:14:03 -0400 Subject: [PATCH] feat: card banners and test data wireframing --- package-lock.json | 85 +++++----- package.json | 2 +- src/components/Banner.jsx | 22 +++ .../CourseCard/CourseCardFooter.jsx | 17 -- .../components/CourseCardActions.jsx | 43 +++++ .../components/CertificateBanner.jsx | 91 +++++++++++ .../components/CourseBanner.jsx | 49 ++++++ .../components/EntitlementBanner.jsx | 42 +++++ .../components/CourseCardBanners/index.jsx | 20 +++ .../{ => components}/CourseCardMenu.jsx | 0 .../{ => components}/RelatedProgram.jsx | 0 src/containers/CourseCard/index.jsx | 45 +++--- src/containers/CourseList/index.jsx | 10 +- src/containers/Dashboard/index.jsx | 62 +++++--- src/containers/WidgetSidebar/index.jsx | 2 + src/data/redux/app/reducer.js | 11 +- src/data/redux/app/selectors.js | 3 +- src/data/redux/thunkActions/app.js | 14 +- src/data/redux/thunkActions/requests.js | 9 ++ src/data/services/lms/fakeData/courses.js | 148 ++++++++---------- src/data/services/lms/shapes.js | 70 +++++++++ 21 files changed, 539 insertions(+), 206 deletions(-) create mode 100644 src/components/Banner.jsx delete mode 100644 src/containers/CourseCard/CourseCardFooter.jsx create mode 100644 src/containers/CourseCard/components/CourseCardActions.jsx create mode 100644 src/containers/CourseCard/components/CourseCardBanners/components/CertificateBanner.jsx create mode 100644 src/containers/CourseCard/components/CourseCardBanners/components/CourseBanner.jsx create mode 100644 src/containers/CourseCard/components/CourseCardBanners/components/EntitlementBanner.jsx create mode 100644 src/containers/CourseCard/components/CourseCardBanners/index.jsx rename src/containers/CourseCard/{ => components}/CourseCardMenu.jsx (100%) rename src/containers/CourseCard/{ => components}/RelatedProgram.jsx (100%) create mode 100644 src/data/services/lms/shapes.js diff --git a/package-lock.json b/package-lock.json index a35f0f5..aad1534 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@edx/frontend-component-footer": "10.1.6", "@edx/frontend-component-header": "^2.4.6", "@edx/frontend-platform": "^1.15.6", - "@edx/paragon": "16.14.4", + "@edx/paragon": "19.25.0", "@fortawesome/fontawesome-svg-core": "^1.2.36", "@fortawesome/free-brands-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", @@ -3522,36 +3522,34 @@ } }, "node_modules/@edx/paragon": { - "version": "16.14.4", - "resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-16.14.4.tgz", - "integrity": "sha512-vDB8ur2zBvJjHnT0HGmtzRzmeyu9UNjv5fYbACwDnW9iheCnLX4CxP4hPxdM1L0I/FpuoOa3EeKealJzwUhJqQ==", + "version": "19.25.0", + "resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-19.25.0.tgz", + "integrity": "sha512-l6V7KNqTGqUyiAJbDRCtPzWQ0Ec7QAhHUPM/FMFvGQbVm1KWqcA2x0f2KcdtzDc0mqXGFhcuL4oxfZOS6NSuHg==", "dependencies": { - "@fortawesome/fontawesome-svg-core": "^1.2.30", - "@fortawesome/free-solid-svg-icons": "^5.14.0", - "@fortawesome/react-fontawesome": "^0.1.11", - "@popperjs/core": "^2.6.0", - "airbnb-prop-types": "^2.12.0", - "bootstrap": "4.6.0", - "classnames": "^2.2.6", + "@fortawesome/fontawesome-svg-core": "^1.2.36", + "@fortawesome/react-fontawesome": "^0.1.18", + "@popperjs/core": "^2.11.4", + "airbnb-prop-types": "^2.16.0", + "bootstrap": "^4.6.1", + "classnames": "^2.3.1", "email-prop-type": "^3.0.0", "font-awesome": "^4.7.0", "lodash.uniqby": "^4.7.0", "mailto-link": "^1.0.0", - "prop-types": "^15.7.2", - "react-bootstrap": "^1.3.0", - "react-focus-on": "^3.5.0", - "react-popper": "^2.2.4", + "prop-types": "^15.8.1", + "react-bootstrap": "^1.6.4", + "react-focus-on": "^3.5.4", + "react-popper": "^2.2.5", "react-proptype-conditional-require": "^1.0.4", "react-responsive": "^8.2.0", - "react-table": "^7.6.1", - "react-transition-group": "^4.0.0", + "react-table": "^7.7.0", + "react-transition-group": "^4.4.2", "tabbable": "^4.0.0", - "uncontrollable": "7.2.1" + "uncontrollable": "^7.2.1" }, "peerDependencies": { - "prop-types": "^15.7.2", - "react": "^16.8.6", - "react-dom": "^16.8.6" + "react": "^16.8.6 || ^17.0.0", + "react-dom": "^16.8.6 || ^17.0.0" } }, "node_modules/@edx/paragon/node_modules/prop-types": { @@ -9099,9 +9097,9 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, "node_modules/bootstrap": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", - "integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.1.tgz", + "integrity": "sha512-0dj+VgI9Ecom+rvvpNZ4MUZJz8dcX7WCX+eTID9+/8HgOkv3dsRzi8BGeZJCQU6flWQVYxwTQnEZFrmJSEO7og==", "funding": { "type": "opencollective", "url": "https://opencollective.com/bootstrap" @@ -36248,31 +36246,30 @@ } }, "@edx/paragon": { - "version": "16.14.4", - "resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-16.14.4.tgz", - "integrity": "sha512-vDB8ur2zBvJjHnT0HGmtzRzmeyu9UNjv5fYbACwDnW9iheCnLX4CxP4hPxdM1L0I/FpuoOa3EeKealJzwUhJqQ==", + "version": "19.25.0", + "resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-19.25.0.tgz", + "integrity": "sha512-l6V7KNqTGqUyiAJbDRCtPzWQ0Ec7QAhHUPM/FMFvGQbVm1KWqcA2x0f2KcdtzDc0mqXGFhcuL4oxfZOS6NSuHg==", "requires": { - "@fortawesome/fontawesome-svg-core": "^1.2.30", - "@fortawesome/free-solid-svg-icons": "^5.14.0", - "@fortawesome/react-fontawesome": "^0.1.11", - "@popperjs/core": "^2.6.0", - "airbnb-prop-types": "^2.12.0", - "bootstrap": "4.6.0", - "classnames": "^2.2.6", + "@fortawesome/fontawesome-svg-core": "^1.2.36", + "@fortawesome/react-fontawesome": "^0.1.18", + "@popperjs/core": "^2.11.4", + "airbnb-prop-types": "^2.16.0", + "bootstrap": "^4.6.1", + "classnames": "^2.3.1", "email-prop-type": "^3.0.0", "font-awesome": "^4.7.0", "lodash.uniqby": "^4.7.0", "mailto-link": "^1.0.0", - "prop-types": "^15.7.2", - "react-bootstrap": "^1.3.0", - "react-focus-on": "^3.5.0", - "react-popper": "^2.2.4", + "prop-types": "^15.8.1", + "react-bootstrap": "^1.6.4", + "react-focus-on": "^3.5.4", + "react-popper": "^2.2.5", "react-proptype-conditional-require": "^1.0.4", "react-responsive": "^8.2.0", - "react-table": "^7.6.1", - "react-transition-group": "^4.0.0", + "react-table": "^7.7.0", + "react-transition-group": "^4.4.2", "tabbable": "^4.0.0", - "uncontrollable": "7.2.1" + "uncontrollable": "^7.2.1" }, "dependencies": { "prop-types": { @@ -40641,9 +40638,9 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, "bootstrap": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", - "integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.1.tgz", + "integrity": "sha512-0dj+VgI9Ecom+rvvpNZ4MUZJz8dcX7WCX+eTID9+/8HgOkv3dsRzi8BGeZJCQU6flWQVYxwTQnEZFrmJSEO7og==", "requires": {} }, "bottleneck": { diff --git a/package.json b/package.json index ddbfad1..f4bacbf 100755 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@edx/frontend-component-footer": "10.1.6", "@edx/frontend-component-header": "^2.4.6", "@edx/frontend-platform": "^1.15.6", - "@edx/paragon": "16.14.4", + "@edx/paragon": "19.25.0", "@fortawesome/fontawesome-svg-core": "^1.2.36", "@fortawesome/free-brands-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", diff --git a/src/components/Banner.jsx b/src/components/Banner.jsx new file mode 100644 index 0000000..1aeaf63 --- /dev/null +++ b/src/components/Banner.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { Alert } from '@edx/paragon'; +import { Info } from '@edx/paragon/icons'; + +export const Banner = ({ children, variant, icon }) => ( + + {children} + +); +Banner.defaultProps = { + icon: Info, + variant: 'info', +}; +Banner.propTypes = { + variant: PropTypes.string, + icon: PropTypes.string, + children: PropTypes.node.isRequired, +}; + +export default Banner; diff --git a/src/containers/CourseCard/CourseCardFooter.jsx b/src/containers/CourseCard/CourseCardFooter.jsx deleted file mode 100644 index 2155dbc..0000000 --- a/src/containers/CourseCard/CourseCardFooter.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { - // Button, - // PageBanner, - Alert, -} from '@edx/paragon'; -import { -// Program -} from '@edx/paragon/icons'; - -export const CourseCardFooter = () => ( - - footer - -); - -export default CourseCardFooter; diff --git a/src/containers/CourseCard/components/CourseCardActions.jsx b/src/containers/CourseCard/components/CourseCardActions.jsx new file mode 100644 index 0000000..466136a --- /dev/null +++ b/src/containers/CourseCard/components/CourseCardActions.jsx @@ -0,0 +1,43 @@ +import React from 'react'; + +import { Button } from '@edx/paragon'; +import { Locked } from '@edx/paragon/icons'; + +import shapes from 'data/services/lms/shapes'; + +export const CourseCardActions = ({ cardData: { enrollment, courseRun } }) => { + let primary; + let secondary = null; + if (!enrollment.isVerified) { + secondary = ( + + ); + } + if (courseRun.isPending) { + primary = ( + + ); + } else if (!courseRun.isEnded) { + if (enrollment.isAudit && enrollment.isAuditAccessExpired) { + primary = (); + } else { + primary = (); + } + } else { + primary = (); + } + return (<>{secondary}{primary}); +}; +CourseCardActions.propTypes = { + cardData: shapes.courseRunCardData.isRequired, +}; + +export default CourseCardActions; diff --git a/src/containers/CourseCard/components/CourseCardBanners/components/CertificateBanner.jsx b/src/containers/CourseCard/components/CourseCardBanners/components/CertificateBanner.jsx new file mode 100644 index 0000000..5b9dd9d --- /dev/null +++ b/src/containers/CourseCard/components/CourseCardBanners/components/CertificateBanner.jsx @@ -0,0 +1,91 @@ +/* eslint-disable max-len */ +import React from 'react'; + +import { Hyperlink } from '@edx/paragon'; +import { CheckCircle } from '@edx/paragon/icons'; + +import shapes from 'data/services/lms/shapes'; + +import Banner from 'components/Banner'; + +const restrictedMessage = 'Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting '; + +export const CertificateBanner = ({ cardData }) => { + const { + certificates, + courseRun, + enrollment, + grades, + } = cardData; + if (certificates.isRestricted) { + if (enrollment.isAudit) { + return ( + + {restrictedMessage}info@example.com + + ); + } + return ( + + {restrictedMessage}info@example.com + If you would like a refund on your Certificate of Achievement, please contact our billing address billing@example.com + + ); + } + if (!grades.isPassing) { + if (enrollment.isAudit) { + return ( + + Grade required to pass the course: {courseRun.minPassingGrade}% + + ); + } + if (courseRun.isFinished) { + return ( + + You are not eligible for a certificate. + {' '} + View grades. + + ); + } + + return ( + + Grade required for a certificate: {courseRun.minPassingGrade}% + + ); + } + if (certificates.isDownloadable) { + if (certificates.downloadUrls.preview) { + return ( + + Congratulations. Your certificate is ready. + {' '} + View Certificate. + + ); + } + return ( + + Congratulations. Your certificate is ready. + {' '} + Download Certificate. + + ); + } + if (certificates.isEarned && !certificates.isAvailable) { + return ( + + Your grade and certificate will be ready after {certificates.availableDate}. + + ); + } + + return null; +}; +CertificateBanner.propTypes = { + cardData: shapes.courseRunCardData.isRequired, +}; + +export default CertificateBanner; diff --git a/src/containers/CourseCard/components/CourseCardBanners/components/CourseBanner.jsx b/src/containers/CourseCard/components/CourseCardBanners/components/CourseBanner.jsx new file mode 100644 index 0000000..f39b609 --- /dev/null +++ b/src/containers/CourseCard/components/CourseCardBanners/components/CourseBanner.jsx @@ -0,0 +1,49 @@ +/* eslint-disable max-len */ +import React from 'react'; +import { Hyperlink } from '@edx/paragon'; + +import shapes from 'data/services/lms/shapes'; + +import Banner from 'components/Banner'; + +export const CourseBanner = ({ cardData }) => { + const { + // course, + enrollment, + courseRun, + } = cardData; + if (enrollment.isVerified) { + return null; + } + const isActive = courseRun.isStarted && !courseRun.isFinished; + const { canUpgrade, isAuditAccessExpired } = enrollment; + if (isAuditAccessExpired) { + if (canUpgrade) { + return ( + + Your audit access to this course has expired. Upgrade now to access your course again. + + ); + } + return ( + + Your audit access to this course has expired. Find another course + + ); + } + if (isActive && !canUpgrade) { + return ( + + Your upgrade deadline for this course has passed. To upgrade, enroll in a session that is farther in the future. + {' '} + Explore course details. + + ); + } + return null; +}; +CourseBanner.propTypes = { + cardData: shapes.courseRunCardData.isRequired, +}; + +export default CourseBanner; diff --git a/src/containers/CourseCard/components/CourseCardBanners/components/EntitlementBanner.jsx b/src/containers/CourseCard/components/CourseCardBanners/components/EntitlementBanner.jsx new file mode 100644 index 0000000..1767c62 --- /dev/null +++ b/src/containers/CourseCard/components/CourseCardBanners/components/EntitlementBanner.jsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { Hyperlink } from '@edx/paragon'; + +import shapes from 'data/services/lms/shapes'; + +import Banner from 'components/Banner'; + +export const EntitlementBanner = ({ cardData }) => { + const { entitlements } = cardData; + if (!entitlements.isEntitlement) { + return null; + } + if (entitlements.isExpired) { + return null; + } + if (!entitlements.isFulfilled) { + if (entitlements.canChange) { + return ( + You must select a session to access the course. + ); + } + return ( + The deadline to select a session has passed + ); + } + if (entitlements.canChange) { + return ( + + You can change sessions until {entitlements.changeDeadline}. + {' '} + Change or leave session + + ); + } + + return null; +}; +EntitlementBanner.propTypes = { + cardData: shapes.courseRunCardData.isRequired, +}; + +export default EntitlementBanner; diff --git a/src/containers/CourseCard/components/CourseCardBanners/index.jsx b/src/containers/CourseCard/components/CourseCardBanners/index.jsx new file mode 100644 index 0000000..6c8aed2 --- /dev/null +++ b/src/containers/CourseCard/components/CourseCardBanners/index.jsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import shapes from 'data/services/lms/shapes'; + +import CourseBanner from './components/CourseBanner'; +import CertificateBanner from './components/CertificateBanner'; +import EntitlementBanner from './components/EntitlementBanner'; + +export const CourseCardBanners = ({ cardData }) => ( + <> + + + + +); +CourseCardBanners.propTypes = { + cardData: shapes.courseRunCardData.isRequired, +}; + +export default CourseCardBanners; diff --git a/src/containers/CourseCard/CourseCardMenu.jsx b/src/containers/CourseCard/components/CourseCardMenu.jsx similarity index 100% rename from src/containers/CourseCard/CourseCardMenu.jsx rename to src/containers/CourseCard/components/CourseCardMenu.jsx diff --git a/src/containers/CourseCard/RelatedProgram.jsx b/src/containers/CourseCard/components/RelatedProgram.jsx similarity index 100% rename from src/containers/CourseCard/RelatedProgram.jsx rename to src/containers/CourseCard/components/RelatedProgram.jsx diff --git a/src/containers/CourseCard/index.jsx b/src/containers/CourseCard/index.jsx index 3273e6a..cfaf29f 100644 --- a/src/containers/CourseCard/index.jsx +++ b/src/containers/CourseCard/index.jsx @@ -1,24 +1,28 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { Locked } from '@edx/paragon/icons'; -import { Button, Card } from '@edx/paragon'; +// import PropTypes from 'prop-types'; +import { Card } from '@edx/paragon'; -import { courseData } from 'data/services/lms/fakeData/courses'; +import shapes from 'data/services/lms/shapes'; -import RelatedProgram from './RelatedProgram'; -import CourseCardMenu from './CourseCardMenu'; -import CourseCardFooter from './CourseCardFooter'; +import RelatedProgram from './components/RelatedProgram'; +import CourseCardMenu from './components/CourseCardMenu'; +import CourseCardBanners from './components/CourseCardBanners'; +import CourseCardActions from './components/CourseCardActions'; -export const CourseCard = ({ courseID }) => { +export const CourseCard = ({ cardData }) => { const { - title, - imageUrl, - displayNumber, - displayOrg, - accessExpiryDate, - } = courseData[courseID] || {}; + course: { + title, + bannerUrl: imageUrl, + }, + courseRun: { + courseNumber, + accessExpirationDate, + }, + } = cardData; + const providerName = cardData.provider?.name; return ( -
+
{ actions={} /> - {displayOrg} • {displayNumber} • Access expires {accessExpiryDate} + {providerName || 'Unkown'} • {courseNumber} • Access expires {accessExpirationDate} }> - - + - +
); }; CourseCard.propTypes = { - courseID: PropTypes.string.isRequired, + cardData: shapes.courseRunCardData.isRequired, }; CourseCard.defaultProps = {}; diff --git a/src/containers/CourseList/index.jsx b/src/containers/CourseList/index.jsx index 01a0376..29ec03b 100644 --- a/src/containers/CourseList/index.jsx +++ b/src/containers/CourseList/index.jsx @@ -1,17 +1,19 @@ import React from 'react'; import PropTypes from 'prop-types'; +import shapes from 'data/services/lms/shapes'; import CourseCard from 'containers/CourseCard'; -export const CourseList = ({ courseIDs }) => ( +export const CourseList = ({ courseListData }) => (
- {courseIDs.map((id) => ( - + {courseListData.map((cardData) => ( + ))}
); + CourseList.propTypes = { - courseIDs: PropTypes.arrayOf(PropTypes.string).isRequired, + courseListData: PropTypes.arrayOf(shapes.courseRunCardData).isRequired, }; export default CourseList; diff --git a/src/containers/Dashboard/index.jsx b/src/containers/Dashboard/index.jsx index 743d735..b484c20 100644 --- a/src/containers/Dashboard/index.jsx +++ b/src/containers/Dashboard/index.jsx @@ -1,29 +1,51 @@ import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { FormattedMessage } from '@edx/frontend-platform/i18n'; + +import { selectors, thunkActions } from 'data/redux'; import CourseList from 'containers/CourseList'; import WidgetSidebar from 'containers/WidgetSidebar'; -import { courseIDs } from 'data/services/lms/fakeData/courses'; -import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import EmptyCourse from '../EmptyCourse'; +import EmptyCourse from 'containers/EmptyCourse'; import messages from './messages'; +import * as module from '.'; -export const Dashboard = () => ( -
- {courseIDs.length ? ( - <> -

- -

-
- - -
- - ) : ( - - )} -
-); +export const hooks = ({ dispatch }) => ({ + initialize: () => React.useEffect( + () => { dispatch(thunkActions.app.initialize()); }, + [], + ), + enrollments: useSelector(selectors.app.enrollments), + entitlements: useSelector(selectors.app.entitlements), +}); + +export const Dashboard = () => { + const dispatch = useDispatch(); + const { + initialize, + enrollments, + // entitlements, + } = module.hooks({ dispatch }); + initialize(); + return ( +
+ {enrollments.length ? ( + <> +

+ +

+
+ + +
+ + ) : ( + + )} +
+ ); +}; export default Dashboard; diff --git a/src/containers/WidgetSidebar/index.jsx b/src/containers/WidgetSidebar/index.jsx index 6330e62..adaebfa 100644 --- a/src/containers/WidgetSidebar/index.jsx +++ b/src/containers/WidgetSidebar/index.jsx @@ -8,6 +8,7 @@ import './index.scss'; export const WidgetSidebar = () => (
+ {/*

@@ -17,6 +18,7 @@ export const WidgetSidebar = () => (

*/} + ({ ...state, courseMetadata: payload }), + loadEnrollments: (state, { payload }) => ({ ...state, enrollments: payload }), + loadEntitlements: (state, { payload }) => ({ ...state, entitlements: payload }), }, }); diff --git a/src/data/redux/app/selectors.js b/src/data/redux/app/selectors.js index 9ceeabd..2c57c8f 100644 --- a/src/data/redux/app/selectors.js +++ b/src/data/redux/app/selectors.js @@ -10,7 +10,8 @@ const mkSimpleSelector = (cb) => createSelector([module.appSelector], cb); // top-level app data selectors export const simpleSelectors = { - courseMetadata: mkSimpleSelector(app => app.courseMetadata), + enrollments: mkSimpleSelector(app => app.enrollments), + entitlements: mkSimpleSelector(app => app.entitlements), }; export default StrictDict({ diff --git a/src/data/redux/thunkActions/app.js b/src/data/redux/thunkActions/app.js index 537d424..a55ff1b 100644 --- a/src/data/redux/thunkActions/app.js +++ b/src/data/redux/thunkActions/app.js @@ -1,6 +1,8 @@ import { StrictDict } from 'utils'; +import { actions } from 'data/redux'; + +import requests from './requests'; -// import { selectors, actions } from 'data/redux'; // import { locationId } from 'data/constants/app'; // import { } from './requests'; @@ -10,8 +12,14 @@ import { StrictDict } from 'utils'; * initialize the app, loading ora and course metadata from the api, and loading the initial * submission list data. */ -export const initialize = () => () => { -}; +export const initialize = () => (dispatch) => ( + requests.initialize().then( + ({ enrollments, entitlements }) => { + dispatch(actions.app.loadEnrollments(enrollments)); + dispatch(actions.app.loadEntitlements(entitlements)); + }, + ) +); export default StrictDict({ initialize, diff --git a/src/data/redux/thunkActions/requests.js b/src/data/redux/thunkActions/requests.js index c923726..d3a9391 100644 --- a/src/data/redux/thunkActions/requests.js +++ b/src/data/redux/thunkActions/requests.js @@ -2,6 +2,7 @@ import { StrictDict } from 'utils'; // import { RequestKeys } from 'data/constants/requests'; import { actions } from 'data/redux'; +import fakeData from 'data/services/lms/fakeData/courses'; // import api from 'data/services/lms/api'; // import * as module from './requests'; @@ -33,5 +34,13 @@ export const networkRequest = ({ }); }; +export const initialize = () => ( + Promise.resolve({ + enrollments: fakeData.courseRunData, + entitlements: fakeData.entitlementCourses, + }) +); + export default StrictDict({ + initialize, }); diff --git a/src/data/services/lms/fakeData/courses.js b/src/data/services/lms/fakeData/courses.js index 4cb269e..e399aac 100644 --- a/src/data/services/lms/fakeData/courses.js +++ b/src/data/services/lms/fakeData/courses.js @@ -6,7 +6,7 @@ export const providers = StrictDict({ website: 'www.edx.com', email: 'support@edx.com', }, - MIT: { + mit: { name: 'MIT', website: 'www.mit.edu', email: 'support@mit.edu', @@ -38,7 +38,7 @@ export const genCourseRunData = (data = {}) => ({ export const genEnrollmentData = (data = {}) => ({ isAudit: true, isVerified: false, - canUpgrade: data.verified ? null : false, + canUpgrade: data.verified ? null : true, isAuditAccessExpired: data.verified ? null : false, ...data, }); @@ -48,10 +48,8 @@ export const genCertificateData = (data = {}) => ({ isRestricted: false, isAvailable: false, isEarned: false, - isRequesting: false, - isGenerating: false, isDownloadable: false, - downloadUrls: null, // { preview, download, honorCertDownload } + downloadUrls: null, // { preview, download } ...data, }); @@ -68,15 +66,15 @@ export const courseRuns = [ grades: { isPassing: true }, courseRun: { isPending: true }, certificates: genCertificateData(), - entitlement: { isEntitlement: false }, + entitlements: { isEntitlement: false }, }, // audit, started, cannot upgrade, restricted { - enrollment: genEnrollmentData({ isAudit: true }), + enrollment: genEnrollmentData({ isAudit: true, canUpgrade: false }), grades: { isPassing: true }, courseRun: { isStarted: true }, certificates: genCertificateData({ isRestricted: true }), - entitlement: { isEntitlement: false }, + entitlements: { isEntitlement: false }, }, // audit, started, can upgrade { @@ -84,23 +82,35 @@ export const courseRuns = [ grades: { isPassing: true }, courseRun: { isStarted: true }, certificates: genCertificateData(), - entitlement: { isEntitlement: false }, + entitlements: { isEntitlement: false }, }, // audit, started, not passing { enrollment: genEnrollmentData({ isAudit: true, canUpgrade: true }), grades: { isPassing: false }, - courseRun: { isStarted: true, accessExpirationDate: pastDate }, + courseRun: { isStarted: true }, certificates: genCertificateData(), - entitlement: { isEntitlement: false }, + entitlements: { isEntitlement: false }, }, - // audit, started, audit access expired + // audit, started, audit access expired, can upgrade { enrollment: genEnrollmentData({ isAudit: true, isAuditAccessExpired: true }), grades: { isPassing: true }, courseRun: { isStarted: true, accessExpirationDate: pastDate }, certificates: genCertificateData(), - entitlement: { isEntitlement: false }, + entitlements: { isEntitlement: false }, + }, + // audit, started, audit access expired, cannot upgrade + { + enrollment: genEnrollmentData({ + isAudit: true, + isAuditAccessExpired: true, + canUpgrade: false, + }), + grades: { isPassing: true }, + courseRun: { isStarted: true, accessExpirationDate: pastDate }, + certificates: genCertificateData(), + entitlements: { isEntitlement: false }, }, // verified, pending, restricted { @@ -108,7 +118,7 @@ export const courseRuns = [ grades: { isPassing: true }, courseRun: { isPending: true }, certificates: genCertificateData({ isRestricted: true }), - entitlement: { isEntitlement: false }, + entitlements: { isEntitlement: false }, }, // verified, started { @@ -116,7 +126,7 @@ export const courseRuns = [ grades: { isPassing: true }, courseRun: { isStarted: true }, certificates: genCertificateData(), - entitlement: { isEntitlement: false }, + entitlements: { isEntitlement: false }, }, // verified, not passing { @@ -124,7 +134,7 @@ export const courseRuns = [ grades: { isPassing: false }, courseRun: { isStarted: true }, certificates: genCertificateData(), - entitlement: { isEntitlement: false }, + entitlements: { isEntitlement: false }, }, // verified, finished, not passing { @@ -132,7 +142,7 @@ export const courseRuns = [ grades: { isPassing: false }, courseRun: { isFinished: true }, certificates: genCertificateData(), - entitlement: { isEntitlement: false }, + entitlements: { isEntitlement: false }, }, // verified, restricted { @@ -140,7 +150,7 @@ export const courseRuns = [ grades: { isPassing: true }, courseRun: { isStarted: true }, certificates: genCertificateData({ isRestricted: true }), - entitlement: { isEntitlement: false }, + entitlements: { isEntitlement: false }, }, // verified, earned but not available { @@ -151,33 +161,7 @@ export const courseRuns = [ isEarned: true, availableDate: futureDate, }), - entitlement: { isEntitlement: false }, - }, - // verified, earned, requesting - { - enrollment: genEnrollmentData({ isVerified: true }), - grades: { isPassing: true }, - courseRun: { isStarted: true }, - certificates: genCertificateData({ - isEarned: true, - isAvailable: true, - isRequesting: true, - availableDate: pastDate, - }), - entitlement: { isEntitlement: false }, - }, - // verified, earned, generating - { - enrollment: genEnrollmentData({ isVerified: true }), - grades: { isPassing: true }, - courseRun: { isStarted: true }, - certificates: genCertificateData({ - isEarned: true, - isAvailable: true, - isGenerating: true, - availableDate: pastDate, - }), - entitlement: { isEntitlement: false }, + entitlements: { isEntitlement: false }, }, // verified, earned, downloadable (web + link) { @@ -194,7 +178,7 @@ export const courseRuns = [ download: logos.social, }, }), - entitlement: { isEntitlement: false }, + entitlements: { isEntitlement: false }, }, // verified, earned, downloadable (link) { @@ -210,23 +194,7 @@ export const courseRuns = [ download: logos.social, }, }), - entitlement: { isEntitlement: false }, - }, - // verified, earned, downloadable (honor cert) - { - enrollment: genEnrollmentData({ isVerified: true }), - grades: { isPassing: true }, - courseRun: { isStarted: true }, - certificates: genCertificateData({ - isEarned: true, - isAvailable: true, - isDownloadable: true, - availableDate: pastDate, - downloadUrls: { - honorCertDownload: logos.bio, - }, - }), - entitlement: { isEntitlement: false }, + entitlements: { isEntitlement: false }, }, // Entitlement Course Run - Cannot view yet { @@ -234,11 +202,12 @@ export const courseRuns = [ grades: { isPassing: true }, courseRun: { isPending: true }, certificates: genCertificateData(), - entitlement: { + entitlements: { isEntitlement: true, isFulfilled: true, isRefundable: true, canViewCourse: false, + canChange: true, changeDeadline: futureDate, isExpired: false, }, @@ -249,11 +218,12 @@ export const courseRuns = [ grades: { isPassing: true }, courseRun: { isStarted: true }, certificates: genCertificateData(), - entitlement: { + entitlements: { isEntitlement: true, isFulfilled: true, isRefundable: true, canViewCourse: true, + canChange: true, changeDeadline: futureDate, isExpired: false, }, @@ -264,11 +234,12 @@ export const courseRuns = [ grades: { isPassing: true }, courseRun: { isStarted: true }, certificates: genCertificateData(), - entitlement: { + entitlements: { isEntitlement: true, isFulfilled: true, isRefundable: true, canViewCourse: true, + canChange: false, changeDeadline: pastDate, isExpired: false, }, @@ -277,13 +248,14 @@ export const courseRuns = [ { enrollment: genEnrollmentData({ isVerified: true }), grades: { isPassing: true }, - courseRun: { isStarted: true }, + courseRun: { isStarted: true, isFinished: true, isArchived: true }, certificates: genCertificateData(), - entitlement: { + entitlements: { isEntitlement: true, isFulfilled: true, - isRefundable: true, + isRefundable: false, canViewCourse: true, + canChange: false, changeDeadline: pastDate, isExpired: true, }, @@ -294,31 +266,37 @@ export const entitlementCourses = [ { course: { title: genCourseTitle(100) }, entitlements: { + isEntitlement: true, availableSessions, isRefundable: true, isFulfilled: false, canViewCourse: false, changeDeadline: futureDate, + canChange: true, isExpired: false, }, }, { course: { title: genCourseTitle(101) }, entitlements: { + isEntitlement: true, availableSessions, isRefundable: true, isFulfilled: false, canViewCourse: false, changeDeadline: pastDate, + canChange: false, isExpired: false, }, }, { course: { title: genCourseTitle(102) }, entitlements: { + isEntitlement: true, availableSessions, isRefundable: true, isFulfilled: false, canViewCourse: false, changeDeadline: pastDate, + canChange: false, isExpired: true, }, }, @@ -328,30 +306,26 @@ export const entitlementCourses = [ // Entitlement Course - cannot view yet // Entitlement Course - can view and change // Entitlement Course - expired -export const courseRunData = courseRuns.reduce( - (obj, curr, index) => { - const out = { ...curr }; +export const courseRunData = courseRuns.map( + (data, index) => { + const title = genCourseTitle(index); + const courseNumber = genCourseID(index); const providerIndex = index % 3; const iteratedData = [ - { - provider: providers.edx, - course: { title: genCourseTitle(index), bannerUrl: logos.edx }, - }, - { - provider: providers.mit, - course: { title: genCourseTitle(index), bannerUrl: logos.bio }, - }, - { - provider: null, - course: { title: genCourseTitle(index), bannerUrl: logos.social }, - }, + { provider: providers.edx, course: { title, bannerUrl: logos.edx } }, + { provider: providers.mit, course: { title, bannerUrl: logos.science } }, + { provider: null, course: { title, bannerUrl: logos.social } }, ]; - out.courseRun.courseNumber = genCourseID(index); return { - ...out, + ...data, + courseRun: genCourseRunData({ ...data.courseRun, courseNumber }), ...iteratedData[providerIndex], credit: { isPurchased: false, requestStatus: null }, }; }, - {}, ); + +export default { + courseRunData, + entitlementCourses, +}; diff --git a/src/data/services/lms/shapes.js b/src/data/services/lms/shapes.js new file mode 100644 index 0000000..b148a01 --- /dev/null +++ b/src/data/services/lms/shapes.js @@ -0,0 +1,70 @@ +import PropTypes from 'prop-types'; +import { StrictDict } from 'utils'; + +export const shapes = StrictDict({ + course: PropTypes.shape({ + bannerUrl: PropTypes.string, + title: PropTypes.string, + website: PropTypes.string, + }), + provider: PropTypes.shape({ + email: PropTypes.string, + name: PropTypes.string, + website: PropTypes.string, + }), + courseRun: PropTypes.shape({ + accessExpirationDate: PropTypes.string, + courseNumber: PropTypes.string, + isArchived: PropTypes.bool, + isFinished: PropTypes.bool, + isPending: PropTypes.bool, + isStarted: PropTypes.bool, + minPassingGrade: PropTypes.number, + }), + credit: PropTypes.shape({ + isPurchased: PropTypes.bool, + requestStatus: PropTypes.string, + }), + certificates: PropTypes.shape({ + availableDate: PropTypes.string, + downloadUrls: PropTypes.shape({ + preview: PropTypes.string, + download: PropTypes.string, + }), + isAvailable: PropTypes.bool, + isDownloadable: PropTypes.bool, + isEarned: PropTypes.bool, + isRestricted: PropTypes.bool, + }), + enrollment: PropTypes.shape({ + canUpgrade: PropTypes.bool, + isAudit: PropTypes.bool, + isAuditAccessExpired: PropTypes.bol, + isVerified: PropTypes.bool, + }), + entitlement: PropTypes.shape({ + isEntitlement: PropTypes.bool, + canChange: PropTypes.bool, + isFulfilled: PropTypes.bool, + isRefundable: PropTypes.bool, + changeDeadline: PropTypes.string, + isExpired: PropTypes.bool, + canViewCourse: PropTypes.bool, + }), + grades: PropTypes.shape({ + isPassing: PropTypes.bool, + }), +}); + +shapes.courseRunCardData = PropTypes.shape({ + course: shapes.course, + provider: shapes.provider, + courseRun: shapes.courseRun, + credit: shapes.credit, + certificates: shapes.certificates, + enrollment: shapes.enrollment, + entitlement: shapes.entitlement, + grades: shapes.grades, +}); + +export default shapes;