feat: card banners and test data wireframing
This commit is contained in:
85
package-lock.json
generated
85
package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
22
src/components/Banner.jsx
Normal file
22
src/components/Banner.jsx
Normal file
@@ -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 }) => (
|
||||
<Alert variant={variant} className="mb-0" icon={icon}>
|
||||
{children}
|
||||
</Alert>
|
||||
);
|
||||
Banner.defaultProps = {
|
||||
icon: Info,
|
||||
variant: 'info',
|
||||
};
|
||||
Banner.propTypes = {
|
||||
variant: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default Banner;
|
||||
@@ -1,17 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
// Button,
|
||||
// PageBanner,
|
||||
Alert,
|
||||
} from '@edx/paragon';
|
||||
import {
|
||||
// Program
|
||||
} from '@edx/paragon/icons';
|
||||
|
||||
export const CourseCardFooter = () => (
|
||||
<Alert variant="success">
|
||||
footer
|
||||
</Alert>
|
||||
);
|
||||
|
||||
export default CourseCardFooter;
|
||||
43
src/containers/CourseCard/components/CourseCardActions.jsx
Normal file
43
src/containers/CourseCard/components/CourseCardActions.jsx
Normal file
@@ -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 = (
|
||||
<Button
|
||||
iconBefore={Locked}
|
||||
variant="outline-primary"
|
||||
disabled={!enrollment.canUpgrade}
|
||||
>
|
||||
Upgrade
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
if (courseRun.isPending) {
|
||||
primary = (
|
||||
<Button>
|
||||
Begin Course
|
||||
</Button>
|
||||
);
|
||||
} else if (!courseRun.isEnded) {
|
||||
if (enrollment.isAudit && enrollment.isAuditAccessExpired) {
|
||||
primary = (<Button disabled>Resume</Button>);
|
||||
} else {
|
||||
primary = (<Button>Resume</Button>);
|
||||
}
|
||||
} else {
|
||||
primary = (<Button>View Course</Button>);
|
||||
}
|
||||
return (<>{secondary}{primary}</>);
|
||||
};
|
||||
CourseCardActions.propTypes = {
|
||||
cardData: shapes.courseRunCardData.isRequired,
|
||||
};
|
||||
|
||||
export default CourseCardActions;
|
||||
@@ -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 (
|
||||
<Banner variant="danger">
|
||||
{restrictedMessage}<Hyperlink>info@example.com</Hyperlink>
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Banner variant="danger">
|
||||
{restrictedMessage}<Hyperlink>info@example.com</Hyperlink>
|
||||
If you would like a refund on your Certificate of Achievement, please contact our billing address <Hyperlink>billing@example.com</Hyperlink>
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
if (!grades.isPassing) {
|
||||
if (enrollment.isAudit) {
|
||||
return (
|
||||
<Banner>
|
||||
Grade required to pass the course: {courseRun.minPassingGrade}%
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
if (courseRun.isFinished) {
|
||||
return (
|
||||
<Banner variant="warning">
|
||||
You are not eligible for a certificate.
|
||||
{' '}
|
||||
<Hyperlink>View grades.</Hyperlink>
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Banner variant="warning">
|
||||
Grade required for a certificate: {courseRun.minPassingGrade}%
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
if (certificates.isDownloadable) {
|
||||
if (certificates.downloadUrls.preview) {
|
||||
return (
|
||||
<Banner variant="success" icon={CheckCircle}>
|
||||
Congratulations. Your certificate is ready.
|
||||
{' '}
|
||||
<Hyperlink>View Certificate.</Hyperlink>
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Banner variant="success" icon={CheckCircle}>
|
||||
Congratulations. Your certificate is ready.
|
||||
{' '}
|
||||
<Hyperlink>Download Certificate.</Hyperlink>
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
if (certificates.isEarned && !certificates.isAvailable) {
|
||||
return (
|
||||
<Banner>
|
||||
Your grade and certificate will be ready after {certificates.availableDate}.
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
CertificateBanner.propTypes = {
|
||||
cardData: shapes.courseRunCardData.isRequired,
|
||||
};
|
||||
|
||||
export default CertificateBanner;
|
||||
@@ -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 (
|
||||
<Banner>
|
||||
Your audit access to this course has expired. Upgrade now to access your course again.
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Banner>
|
||||
Your audit access to this course has expired. <Hyperlink>Find another course</Hyperlink>
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
if (isActive && !canUpgrade) {
|
||||
return (
|
||||
<Banner>
|
||||
Your upgrade deadline for this course has passed. To upgrade, enroll in a session that is farther in the future.
|
||||
{' '}
|
||||
<Hyperlink href={cardData.course.website}>Explore course details.</Hyperlink>
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
CourseBanner.propTypes = {
|
||||
cardData: shapes.courseRunCardData.isRequired,
|
||||
};
|
||||
|
||||
export default CourseBanner;
|
||||
@@ -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 (
|
||||
<Banner>You must select a session to access the course.</Banner>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Banner>The deadline to select a session has passed</Banner>
|
||||
);
|
||||
}
|
||||
if (entitlements.canChange) {
|
||||
return (
|
||||
<Banner>
|
||||
You can change sessions until {entitlements.changeDeadline}.
|
||||
{' '}
|
||||
<Hyperlink>Change or leave session</Hyperlink>
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
EntitlementBanner.propTypes = {
|
||||
cardData: shapes.courseRunCardData.isRequired,
|
||||
};
|
||||
|
||||
export default EntitlementBanner;
|
||||
@@ -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 }) => (
|
||||
<>
|
||||
<CourseBanner cardData={cardData} />
|
||||
<CertificateBanner cardData={cardData} />
|
||||
<EntitlementBanner cardData={cardData} />
|
||||
</>
|
||||
);
|
||||
CourseCardBanners.propTypes = {
|
||||
cardData: shapes.courseRunCardData.isRequired,
|
||||
};
|
||||
|
||||
export default CourseCardBanners;
|
||||
@@ -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 (
|
||||
<div>
|
||||
<div className="mb-3">
|
||||
<Card orientation="horizontal">
|
||||
<Card.ImageCap
|
||||
src={imageUrl}
|
||||
@@ -32,23 +36,20 @@ export const CourseCard = ({ courseID }) => {
|
||||
actions={<CourseCardMenu />}
|
||||
/>
|
||||
<Card.Section>
|
||||
{displayOrg} • {displayNumber} • Access expires {accessExpiryDate}
|
||||
{providerName || 'Unkown'} • {courseNumber} • Access expires {accessExpirationDate}
|
||||
</Card.Section>
|
||||
<Card.Footer orientation="vertical" textElement={<RelatedProgram />}>
|
||||
<Button iconBefore={Locked} variant="outline-primary">
|
||||
Upgrade
|
||||
</Button>
|
||||
<Button>Resume</Button>
|
||||
<CourseCardActions cardData={cardData} />
|
||||
</Card.Footer>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
<CourseCardFooter />
|
||||
<CourseCardBanners cardData={cardData} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
CourseCard.propTypes = {
|
||||
courseID: PropTypes.string.isRequired,
|
||||
cardData: shapes.courseRunCardData.isRequired,
|
||||
};
|
||||
|
||||
CourseCard.defaultProps = {};
|
||||
|
||||
@@ -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 }) => (
|
||||
<div className="d-flex flex-column flex-grow-1">
|
||||
{courseIDs.map((id) => (
|
||||
<CourseCard courseID={id} />
|
||||
{courseListData.map((cardData) => (
|
||||
<CourseCard cardData={cardData} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
CourseList.propTypes = {
|
||||
courseIDs: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
courseListData: PropTypes.arrayOf(shapes.courseRunCardData).isRequired,
|
||||
};
|
||||
|
||||
export default CourseList;
|
||||
|
||||
@@ -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 = () => (
|
||||
<div className="d-flex flex-column p-2">
|
||||
{courseIDs.length ? (
|
||||
<>
|
||||
<h2 className="py-2">
|
||||
<FormattedMessage {...messages.myCourse} />
|
||||
</h2>
|
||||
<div className="d-flex">
|
||||
<CourseList courseIDs={courseIDs} />
|
||||
<WidgetSidebar />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<EmptyCourse />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
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 (
|
||||
<div className="d-flex flex-column p-2">
|
||||
{enrollments.length ? (
|
||||
<>
|
||||
<h2 className="py-2">
|
||||
<FormattedMessage {...messages.myCourse} />
|
||||
</h2>
|
||||
<div className="d-flex">
|
||||
<CourseList courseListData={enrollments} />
|
||||
<WidgetSidebar />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<EmptyCourse />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
|
||||
@@ -8,6 +8,7 @@ import './index.scss';
|
||||
export const WidgetSidebar = () => (
|
||||
<div className="widget-sidebar">
|
||||
<div className="d-flex">
|
||||
|
||||
{/* <img src='more-courses-sidewidget.svg' />
|
||||
<div>
|
||||
<h3>
|
||||
@@ -17,6 +18,7 @@ export const WidgetSidebar = () => (
|
||||
<FormattedMessage {...messages.findCoursesButton} />
|
||||
</Hyperlink>
|
||||
</div> */}
|
||||
|
||||
<Card orientation="horizontal" className="mb-4">
|
||||
<Card.ImageCap
|
||||
src="more-courses-sidewidget.svg"
|
||||
|
||||
@@ -2,12 +2,8 @@ import { StrictDict } from 'utils';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
const initialState = {
|
||||
courseMetadata: {
|
||||
name: '',
|
||||
number: '',
|
||||
org: '',
|
||||
courseId: '',
|
||||
},
|
||||
enrollments: [],
|
||||
entitlements: [],
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
@@ -15,7 +11,8 @@ const app = createSlice({
|
||||
name: 'app',
|
||||
initialState,
|
||||
reducers: {
|
||||
loadCourseMetadata: (state, { payload }) => ({ ...state, courseMetadata: payload }),
|
||||
loadEnrollments: (state, { payload }) => ({ ...state, enrollments: payload }),
|
||||
loadEntitlements: (state, { payload }) => ({ ...state, entitlements: payload }),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
70
src/data/services/lms/shapes.js
Normal file
70
src/data/services/lms/shapes.js
Normal file
@@ -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;
|
||||
Reference in New Issue
Block a user