Skip to content

Commit 5f3585f

Browse files
authored
feat: display course credentials on VC page (#469)
1 parent 6816a17 commit 5f3585f

22 files changed

+2242
-3797
lines changed

package-lock.json

+2,041-3,662
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
"@fortawesome/react-fontawesome": "0.2.2",
4747
"@openedx/frontend-plugin-framework": "^1.2.0",
4848
"@openedx/paragon": "^22.2.2",
49+
"ajv": "^8.12.0",
50+
"ajv-keywords": "^5.1.0",
4951
"babel-polyfill": "6.26.0",
5052
"core-js": "3.40.0",
5153
"js-cookie": "3.0.5",
@@ -59,7 +61,8 @@
5961
"react-router": "6.28.2",
6062
"react-router-dom": "6.28.2",
6163
"redux": "4.2.1",
62-
"regenerator-runtime": "0.14.1"
64+
"regenerator-runtime": "0.14.1",
65+
"schema-utils": "^4.2.0"
6366
},
6467
"devDependencies": {
6568
"@edx/browserslist-config": "^1.2.0",

src/components/ProgramCertificate/ProgramCertificate.jsx renamed to src/components/Certificate/Certificate.jsx

+15-11
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import {
99
import { Hyperlink, DropdownButton, Dropdown } from '@openedx/paragon';
1010
import messages from './messages';
1111

12-
function ProgramCertificate({
12+
function Certificate({
1313
intl,
14-
program_title: programTitle,
15-
program_org: programOrg,
14+
type,
15+
credential_title: certificateTitle,
16+
credential_org: certificateOrg,
1617
modified_date: modifiedDate,
1718
uuid,
1819
handleCreate,
@@ -48,15 +49,17 @@ function ProgramCertificate({
4849
<div className="card-body d-flex flex-column">
4950
<div className="card-title">
5051
<p className="small mb-0">
51-
{intl.formatMessage(messages.certificateCardName)}
52+
{type === 'program'
53+
? intl.formatMessage(messages.programCertificateCardName)
54+
: intl.formatMessage(messages.courseCertificateCardName)}
5255
</p>
53-
<h4 className="certificate-title">{programTitle}</h4>
56+
<h4 className="certificate-title">{certificateTitle}</h4>
5457
</div>
55-
<p className="small mb-0">
58+
<p className="small mb-0 mt-auto">
5659
{intl.formatMessage(messages.certificateCardOrgLabel)}
5760
</p>
5861
<p className="h6 mb-4">
59-
{programOrg
62+
{certificateOrg
6063
|| intl.formatMessage(messages.certificateCardNoOrgText)}
6164
</p>
6265
<p className="small mb-2">
@@ -71,10 +74,11 @@ function ProgramCertificate({
7174
);
7275
}
7376

74-
ProgramCertificate.propTypes = {
77+
Certificate.propTypes = {
7578
intl: intlShape.isRequired,
76-
program_title: PropTypes.string.isRequired,
77-
program_org: PropTypes.string.isRequired,
79+
type: PropTypes.oneOf(['program', 'course']),
80+
credential_title: PropTypes.string.isRequired,
81+
credential_org: PropTypes.string.isRequired,
7882
modified_date: PropTypes.string.isRequired,
7983
uuid: PropTypes.string.isRequired,
8084
handleCreate: PropTypes.func.isRequired,
@@ -86,4 +90,4 @@ ProgramCertificate.propTypes = {
8690
).isRequired,
8791
};
8892

89-
export default injectIntl(ProgramCertificate);
93+
export default injectIntl(Certificate);
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
// eslint-disable-next-line no-restricted-exports
2-
export { default } from './ProgramCertificate';
2+
export { default } from './Certificate';

src/components/ProgramCertificate/messages.js renamed to src/components/Certificate/messages.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import { defineMessages } from '@edx/frontend-platform/i18n';
22

33
const messages = defineMessages({
4-
certificateCardName: {
5-
id: 'certificate.card.name',
4+
programCertificateCardName: {
5+
id: 'certificate.program.card.name',
66
defaultMessage: 'Program Certificate',
77
description: 'A title text of the available program certificate item.',
88
},
9+
courseCertificateCardName: {
10+
id: 'certificate.course.card.name',
11+
defaultMessage: 'Course Certificate',
12+
description: 'A title text of the available course certificate item.',
13+
},
914
certificateCardOrgLabel: {
1015
id: 'certificate.card.organization.label',
1116
defaultMessage: 'From',

src/components/ProgramCertificate/test/ProgramCertificate.test.jsx renamed to src/components/Certificate/test/Certificate.test.jsx

+19-10
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import React from 'react';
55
import {
66
render, screen, cleanup, initializeMockApp, fireEvent,
77
} from '../../../setupTest';
8-
import ProgramCertificate from '..';
8+
import Certificate from '../Certificate';
99

1010
describe('program-certificate', () => {
1111
beforeAll(async () => {
@@ -15,30 +15,39 @@ describe('program-certificate', () => {
1515
afterEach(cleanup);
1616

1717
const props = {
18-
program_title: 'Program name',
19-
program_org: 'Test org',
18+
credential_title: 'Certificate title',
19+
credential_org: 'Test org',
2020
modified_date: '2023-02-02',
2121
storages: [{ id: 'storageId', name: 'storageName' }],
2222
handleCreate: jest.fn(),
2323
};
2424

25-
it('renders the component', () => {
26-
render(<ProgramCertificate {...props} />);
25+
it('renders the component with type programm', () => {
26+
render(<Certificate {...props} type="program" />);
2727

2828
expect(screen.getByText('Program Certificate')).toBeTruthy();
29-
expect(screen.getByText(props.program_title)).toBeTruthy();
30-
expect(screen.getByText(props.program_org)).toBeTruthy();
29+
expect(screen.getByText(props.credential_title)).toBeTruthy();
30+
expect(screen.getByText(props.credential_org)).toBeTruthy();
3131
expect(screen.getByText('Awarded on 2/2/2023')).toBeTruthy();
3232
});
3333

34-
it('should display "No organization" if Program Organization wasn\'t set', () => {
35-
render(<ProgramCertificate {...props} program_org="" />);
34+
it('renders the component with type course', () => {
35+
render(<Certificate {...props} type="course" />);
36+
37+
expect(screen.getByText('Course Certificate')).toBeTruthy();
38+
expect(screen.getByText(props.credential_title)).toBeTruthy();
39+
expect(screen.getByText(props.credential_org)).toBeTruthy();
40+
expect(screen.getByText('Awarded on 2/2/2023')).toBeTruthy();
41+
});
42+
43+
it('should display "No organization" if Organization wasn\'t set', () => {
44+
render(<Certificate {...props} credential_org="" />);
3645

3746
expect(screen.getByText('No organization')).toBeTruthy();
3847
});
3948

4049
it('renders modal by clicking on a create button', () => {
41-
render(<ProgramCertificate {...props} />);
50+
render(<Certificate {...props} />);
4251
fireEvent.click(screen.getByText('Create'));
4352

4453
expect(screen.findByTitle('Verifiable credential')).toBeTruthy();

src/components/ProgramCertificateModal/ProgramCertificateModal.jsx renamed to src/components/CertificateModal/CertificateModal.jsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import messages from './messages';
1111
import appStoreImg from '../../assets/images/appStore.png';
1212
import googlePlayImg from '../../assets/images/googleplay.png';
1313

14-
function ProgramCertificateModal({
14+
function CertificateModal({
1515
intl, isOpen, close, data,
1616
}) {
1717
const {
@@ -171,11 +171,11 @@ function ProgramCertificateModal({
171171
);
172172
}
173173

174-
ProgramCertificateModal.propTypes = {
174+
CertificateModal.propTypes = {
175175
intl: intlShape.isRequired,
176176
isOpen: PropTypes.bool.isRequired,
177177
close: PropTypes.func.isRequired,
178178
data: PropTypes.shape.isRequired,
179179
};
180180

181-
export default injectIntl(ProgramCertificateModal);
181+
export default injectIntl(CertificateModal);
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// eslint-disable-next-line no-restricted-exports
2+
export { default } from './CertificateModal';

src/components/ProgramCertificateModal/messages.js renamed to src/components/CertificateModal/messages.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const messages = defineMessages({
2020
id: 'credentials.modal.mobile.title',
2121
defaultMessage:
2222
'To download a verifiable credential to your mobile wallet application, please follow the instructions below.',
23-
description: 'Text for a mobile dialog of the program certificate.',
23+
description: 'Text for a mobile dialog of the certificate.',
2424
},
2525
certificateModalAppStoreBtn: {
2626
id: 'credentials.modal.instruction.appStore.button',
@@ -84,9 +84,9 @@ const messages = defineMessages({
8484
credentialsModalError: {
8585
id: 'credentials.modal.error',
8686
defaultMessage:
87-
'An error occurred attempting to retrieve your program certificate. Please try again later.',
87+
'An error occurred attempting to retrieve your certificate. Please try again later.',
8888
description:
89-
"An error message indicating there is a problem retrieving the user's program certificate data",
89+
"An error message indicating there is a problem retrieving the user's certificate data",
9090
},
9191
});
9292

src/components/ProgramCertificateModal/test/ProgramCertificateModal.test.jsx renamed to src/components/CertificateModal/test/CertificateModal.test.jsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* @jest-environment jsdom
33
*/
44
import React from 'react';
5-
import ProgramCertificateModal from '..';
5+
import CertificateModal from '..';
66
import {
77
render, screen, cleanup, initializeMockApp,
88
} from '../../../setupTest';
@@ -18,15 +18,15 @@ const props = {
1818
},
1919
};
2020

21-
describe('program-certificate-modal', () => {
21+
describe('certificate-modal', () => {
2222
beforeAll(async () => {
2323
await initializeMockApp();
2424
});
2525
beforeEach(() => jest.resetModules);
2626
afterEach(cleanup);
2727

2828
it('renders the component', () => {
29-
render(<ProgramCertificateModal {...props} />);
29+
render(<CertificateModal {...props} />);
3030
expect(screen.getByText('Verifiable credential')).toBeTruthy();
3131
expect(screen.getByText('Close modal window')).toBeTruthy();
3232
});

0 commit comments

Comments
 (0)