richie-education 2.25.0-b2.dev92 → 2.25.0-b2.dev96
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.
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.spec.tsx +20 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +12 -0
- package/js/widgets/Dashboard/components/DashboardItem/_styles.scss +10 -0
- package/js/widgets/Dashboard/components/DashboardItem/index.spec.tsx +43 -0
- package/js/widgets/Dashboard/components/DashboardItem/index.stories.tsx +18 -0
- package/js/widgets/Dashboard/components/DashboardItem/index.tsx +40 -0
- package/package.json +24 -24
|
@@ -15,6 +15,7 @@ import { QueryClientProvider } from '@tanstack/react-query';
|
|
|
15
15
|
import { PropsWithChildren } from 'react';
|
|
16
16
|
import fetchMock from 'fetch-mock';
|
|
17
17
|
import { createMemoryRouter, RouterProvider } from 'react-router-dom';
|
|
18
|
+
import { userEvent } from '@storybook/testing-library';
|
|
18
19
|
import { DEFAULT_DATE_FORMAT } from 'hooks/useDateFormat';
|
|
19
20
|
import {
|
|
20
21
|
CourseStateFactory,
|
|
@@ -166,6 +167,25 @@ describe('<DashboardItemOrder/>', () => {
|
|
|
166
167
|
await expectNoSpinner('Loading certificate ...');
|
|
167
168
|
});
|
|
168
169
|
|
|
170
|
+
it('renders an order with a valid "Go to syllabus" link', async () => {
|
|
171
|
+
const order: CredentialOrder = CredentialOrderFactory().one();
|
|
172
|
+
order.target_courses = [];
|
|
173
|
+
const { product } = mockCourseProductWithOrder(order);
|
|
174
|
+
|
|
175
|
+
render(<DashboardItemOrder order={order} />, { wrapper });
|
|
176
|
+
|
|
177
|
+
await screen.findByRole('heading', { level: 5, name: product.title });
|
|
178
|
+
const moreButton = screen.getByRole('combobox', {
|
|
179
|
+
name: 'See additional options',
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const user = userEvent.setup();
|
|
183
|
+
await user.click(moreButton);
|
|
184
|
+
|
|
185
|
+
const link = screen.getByRole('link', { name: 'Go to syllabus' });
|
|
186
|
+
expect(link.getAttribute('href')).toBe('/redirects/courses/' + order.course.code);
|
|
187
|
+
});
|
|
188
|
+
|
|
169
189
|
/**
|
|
170
190
|
* Non-Writable.
|
|
171
191
|
*/
|
|
@@ -29,6 +29,11 @@ const messages = defineMessages({
|
|
|
29
29
|
description: 'Accessible label displayed while certificate is being fetched on the dashboard.',
|
|
30
30
|
defaultMessage: 'Loading certificate...',
|
|
31
31
|
},
|
|
32
|
+
syllabusLinkLabel: {
|
|
33
|
+
id: 'components.DashboardItemOrder.syllabusLinkLabel',
|
|
34
|
+
description: 'Syllabus link label on order details',
|
|
35
|
+
defaultMessage: 'Go to syllabus',
|
|
36
|
+
},
|
|
32
37
|
});
|
|
33
38
|
|
|
34
39
|
interface DashboardItemOrderProps {
|
|
@@ -109,6 +114,13 @@ export const DashboardItemOrder = ({
|
|
|
109
114
|
title={product?.title ?? ''}
|
|
110
115
|
code={'Ref. ' + course.code}
|
|
111
116
|
imageUrl={course.cover?.src}
|
|
117
|
+
more={
|
|
118
|
+
<li>
|
|
119
|
+
<a className="selector__list__link" href={`/redirects/courses/${course.code}`}>
|
|
120
|
+
<FormattedMessage {...messages.syllabusLinkLabel} />
|
|
121
|
+
</a>
|
|
122
|
+
</li>
|
|
123
|
+
}
|
|
112
124
|
footer={
|
|
113
125
|
<>
|
|
114
126
|
<div className="dashboard-item-order__footer">
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { IntlProvider } from 'react-intl';
|
|
3
|
+
import { userEvent } from '@storybook/testing-library';
|
|
2
4
|
import { DashboardSubItemsList } from './DashboardSubItemsList';
|
|
3
5
|
import { DashboardSubItem } from './DashboardSubItem';
|
|
4
6
|
import { DashboardItem, DEMO_IMAGE_URL } from '.';
|
|
@@ -58,4 +60,45 @@ describe('<DashboardItem />', () => {
|
|
|
58
60
|
screen.getByText('Sub 2');
|
|
59
61
|
screen.getByText('Sub 3');
|
|
60
62
|
});
|
|
63
|
+
|
|
64
|
+
it('renders a DashboardItem with more dropdown', async () => {
|
|
65
|
+
render(
|
|
66
|
+
<IntlProvider locale="en">
|
|
67
|
+
<DashboardItem
|
|
68
|
+
title="Become a React pro"
|
|
69
|
+
code="Ref. 123"
|
|
70
|
+
imageUrl={DEMO_IMAGE_URL}
|
|
71
|
+
more={
|
|
72
|
+
<>
|
|
73
|
+
<li>
|
|
74
|
+
<div className="selector__list__link">Copy</div>
|
|
75
|
+
</li>
|
|
76
|
+
<li>
|
|
77
|
+
<div className="selector__list__link">Duplicate</div>
|
|
78
|
+
</li>
|
|
79
|
+
<li>
|
|
80
|
+
<div className="selector__list__link">Delete</div>
|
|
81
|
+
</li>
|
|
82
|
+
</>
|
|
83
|
+
}
|
|
84
|
+
/>
|
|
85
|
+
,
|
|
86
|
+
</IntlProvider>,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
expect(screen.queryByText('Copy')).not.toBeInTheDocument();
|
|
90
|
+
expect(screen.queryByText('Duplicate')).not.toBeInTheDocument();
|
|
91
|
+
expect(screen.queryByText('Delete')).not.toBeInTheDocument();
|
|
92
|
+
|
|
93
|
+
const moreButton = screen.getByRole('combobox', {
|
|
94
|
+
name: 'See additional options',
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const user = userEvent.setup();
|
|
98
|
+
await user.click(moreButton);
|
|
99
|
+
|
|
100
|
+
screen.getByText('Copy');
|
|
101
|
+
screen.getByText('Duplicate');
|
|
102
|
+
screen.getByText('Delete');
|
|
103
|
+
});
|
|
61
104
|
});
|
|
@@ -22,3 +22,21 @@ export const Default: Story = {};
|
|
|
22
22
|
export const WithImage: Story = {
|
|
23
23
|
args: { imageUrl: DEMO_IMAGE_URL },
|
|
24
24
|
};
|
|
25
|
+
export const WithMore: Story = {
|
|
26
|
+
args: {
|
|
27
|
+
imageUrl: DEMO_IMAGE_URL,
|
|
28
|
+
more: (
|
|
29
|
+
<>
|
|
30
|
+
<li>
|
|
31
|
+
<div className="selector__list__link">Copy</div>
|
|
32
|
+
</li>
|
|
33
|
+
<li>
|
|
34
|
+
<div className="selector__list__link">Duplicate</div>
|
|
35
|
+
</li>
|
|
36
|
+
<li>
|
|
37
|
+
<div className="selector__list__link">Delete</div>
|
|
38
|
+
</li>
|
|
39
|
+
</>
|
|
40
|
+
),
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { PropsWithChildren, ReactNode } from 'react';
|
|
2
|
+
import { Button } from '@openfun/cunningham-react';
|
|
3
|
+
import { useSelect } from 'downshift';
|
|
4
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
2
5
|
import { JoanieFile } from 'types/Joanie';
|
|
3
6
|
import { Nullable, PropsWithTestId } from 'types/utils';
|
|
7
|
+
import { Icon, IconTypeEnum } from 'components/Icon';
|
|
8
|
+
|
|
9
|
+
const messages = defineMessages({
|
|
10
|
+
moreLabel: {
|
|
11
|
+
id: 'components.DashboardItem.more_label',
|
|
12
|
+
description: 'Accessible label for the more button on the dashboard item',
|
|
13
|
+
defaultMessage: 'See additional options',
|
|
14
|
+
},
|
|
15
|
+
});
|
|
4
16
|
|
|
5
17
|
export type DashboardItemProps = PropsWithTestId<{
|
|
6
18
|
title: ReactNode;
|
|
@@ -9,6 +21,7 @@ export type DashboardItemProps = PropsWithTestId<{
|
|
|
9
21
|
imageFile?: Nullable<JoanieFile>;
|
|
10
22
|
footer?: ReactNode;
|
|
11
23
|
mode?: 'full' | 'compact';
|
|
24
|
+
more?: ReactNode;
|
|
12
25
|
}>;
|
|
13
26
|
|
|
14
27
|
// This is temporary due to the fact that backend doesn't give this attribute yet.
|
|
@@ -23,6 +36,7 @@ export const DashboardItem = ({
|
|
|
23
36
|
footer,
|
|
24
37
|
mode = 'full',
|
|
25
38
|
children,
|
|
39
|
+
more,
|
|
26
40
|
...props
|
|
27
41
|
}: PropsWithChildren<DashboardItemProps>) => {
|
|
28
42
|
return (
|
|
@@ -58,6 +72,7 @@ export const DashboardItem = ({
|
|
|
58
72
|
<h5 className="dashboard-item__block__head__title">{title}</h5>
|
|
59
73
|
<div className="dashboard-item__block__head__code">{code}</div>
|
|
60
74
|
</div>
|
|
75
|
+
{more && <DashboardItemMore more={more} />}
|
|
61
76
|
</header>
|
|
62
77
|
)}
|
|
63
78
|
<footer className="dashboard-item__block__footer">{footer}</footer>
|
|
@@ -66,3 +81,28 @@ export const DashboardItem = ({
|
|
|
66
81
|
</div>
|
|
67
82
|
);
|
|
68
83
|
};
|
|
84
|
+
|
|
85
|
+
const DashboardItemMore = ({ more }: { more: ReactNode }) => {
|
|
86
|
+
const intl = useIntl();
|
|
87
|
+
const { isOpen, getToggleButtonProps, getMenuProps } = useSelect({
|
|
88
|
+
items: [],
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className="selector dashboard-item__block__head__more">
|
|
93
|
+
<Button
|
|
94
|
+
icon={<Icon name={IconTypeEnum.MORE} />}
|
|
95
|
+
color="tertiary"
|
|
96
|
+
size="small"
|
|
97
|
+
aria-label={intl.formatMessage(messages.moreLabel)}
|
|
98
|
+
{...getToggleButtonProps()}
|
|
99
|
+
/>
|
|
100
|
+
<ul
|
|
101
|
+
{...getMenuProps()}
|
|
102
|
+
className={`selector__list ${isOpen ? '' : 'selector__list--is-closed'}`}
|
|
103
|
+
>
|
|
104
|
+
{isOpen && more}
|
|
105
|
+
</ul>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "richie-education",
|
|
3
|
-
"version": "2.25.0-b2.
|
|
3
|
+
"version": "2.25.0-b2.dev96",
|
|
4
4
|
"description": "A CMS to build learning portals for Open Education",
|
|
5
5
|
"main": "sandbox/manage.py",
|
|
6
6
|
"scripts": {
|
|
@@ -50,19 +50,19 @@
|
|
|
50
50
|
"@hookform/resolvers": "3.3.4",
|
|
51
51
|
"@openfun/cunningham-react": "2.4.0",
|
|
52
52
|
"@openfun/cunningham-tokens": "2.1.0",
|
|
53
|
-
"@sentry/browser": "7.
|
|
54
|
-
"@sentry/types": "7.
|
|
55
|
-
"@storybook/addon-actions": "7.6.
|
|
56
|
-
"@storybook/addon-essentials": "7.6.
|
|
57
|
-
"@storybook/addon-interactions": "7.6.
|
|
58
|
-
"@storybook/addon-links": "7.6.
|
|
59
|
-
"@storybook/react": "7.6.
|
|
60
|
-
"@storybook/react-webpack5": "7.6.
|
|
53
|
+
"@sentry/browser": "7.101.1",
|
|
54
|
+
"@sentry/types": "7.101.1",
|
|
55
|
+
"@storybook/addon-actions": "7.6.16",
|
|
56
|
+
"@storybook/addon-essentials": "7.6.16",
|
|
57
|
+
"@storybook/addon-interactions": "7.6.16",
|
|
58
|
+
"@storybook/addon-links": "7.6.16",
|
|
59
|
+
"@storybook/react": "7.6.16",
|
|
60
|
+
"@storybook/react-webpack5": "7.6.16",
|
|
61
61
|
"@storybook/testing-library": "0.2.2",
|
|
62
|
-
"@tanstack/query-core": "5.
|
|
63
|
-
"@tanstack/query-sync-storage-persister": "5.
|
|
64
|
-
"@tanstack/react-query": "5.
|
|
65
|
-
"@tanstack/react-query-persist-client": "5.
|
|
62
|
+
"@tanstack/query-core": "5.21.7",
|
|
63
|
+
"@tanstack/query-sync-storage-persister": "5.21.7",
|
|
64
|
+
"@tanstack/react-query": "5.21.7",
|
|
65
|
+
"@tanstack/react-query-persist-client": "5.21.7",
|
|
66
66
|
"@testing-library/dom": "9.3.4",
|
|
67
67
|
"@testing-library/jest-dom": "6.4.2",
|
|
68
68
|
"@testing-library/react": "14.2.1",
|
|
@@ -74,20 +74,20 @@
|
|
|
74
74
|
"@types/lodash-es": "4.17.12",
|
|
75
75
|
"@types/node-fetch": "2.6.11",
|
|
76
76
|
"@types/query-string": "6.3.0",
|
|
77
|
-
"@types/react": "18.2.
|
|
77
|
+
"@types/react": "18.2.56",
|
|
78
78
|
"@types/react-autosuggest": "10.1.11",
|
|
79
79
|
"@types/react-dom": "18.2.19",
|
|
80
80
|
"@types/react-modal": "3.16.3",
|
|
81
81
|
"@types/uuid": "9.0.8",
|
|
82
|
-
"@typescript-eslint/eslint-plugin": "
|
|
83
|
-
"@typescript-eslint/parser": "
|
|
82
|
+
"@typescript-eslint/eslint-plugin": "7.0.1",
|
|
83
|
+
"@typescript-eslint/parser": "7.0.1",
|
|
84
84
|
"babel-jest": "29.7.0",
|
|
85
85
|
"babel-loader": "9.1.3",
|
|
86
86
|
"babel-plugin-react-intl": "8.2.25",
|
|
87
87
|
"bootstrap": ">=4.6.0 <5",
|
|
88
88
|
"classnames": "2.5.1",
|
|
89
89
|
"cljs-merge": "1.1.1",
|
|
90
|
-
"core-js": "3.
|
|
90
|
+
"core-js": "3.36.0",
|
|
91
91
|
"downshift": "8.3.1",
|
|
92
92
|
"eslint": "8.56.0",
|
|
93
93
|
"eslint-config-airbnb": "19.0.4",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"eslint-plugin-prettier": "5.1.3",
|
|
102
102
|
"eslint-plugin-react": "7.33.2",
|
|
103
103
|
"eslint-plugin-react-hooks": "4.6.0",
|
|
104
|
-
"eslint-plugin-storybook": "0.
|
|
104
|
+
"eslint-plugin-storybook": "0.8.0",
|
|
105
105
|
"fetch-mock": "9.11.0",
|
|
106
106
|
"file-loader": "6.2.0",
|
|
107
107
|
"glob": "10.3.10",
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"js-cookie": "3.0.5",
|
|
114
114
|
"lodash-es": "4.17.21",
|
|
115
115
|
"mdn-polyfills": "5.20.0",
|
|
116
|
-
"msw": "2.1
|
|
116
|
+
"msw": "2.2.1",
|
|
117
117
|
"node-fetch": ">2.6.6 <3",
|
|
118
118
|
"nodemon": "3.0.3",
|
|
119
119
|
"prettier": "3.2.5",
|
|
@@ -124,14 +124,14 @@
|
|
|
124
124
|
"react-hook-form": "7.50.1",
|
|
125
125
|
"react-intl": "6.6.2",
|
|
126
126
|
"react-modal": "3.16.1",
|
|
127
|
-
"react-router-dom": "6.22.
|
|
128
|
-
"sass": "1.
|
|
127
|
+
"react-router-dom": "6.22.1",
|
|
128
|
+
"sass": "1.71.0",
|
|
129
129
|
"source-map-loader": "5.0.0",
|
|
130
|
-
"storybook": "7.6.
|
|
130
|
+
"storybook": "7.6.16",
|
|
131
131
|
"tsconfig-paths-webpack-plugin": "4.1.0",
|
|
132
132
|
"typescript": "5.3.3",
|
|
133
133
|
"uuid": "9.0.1",
|
|
134
|
-
"webpack": "5.90.
|
|
134
|
+
"webpack": "5.90.2",
|
|
135
135
|
"webpack-cli": "5.1.4",
|
|
136
136
|
"whatwg-fetch": "3.6.20",
|
|
137
137
|
"xhr-mock": "2.5.1",
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
},
|
|
141
141
|
"resolutions": {
|
|
142
142
|
"@testing-library/dom": "9.3.4",
|
|
143
|
-
"@types/react": "18.2.
|
|
143
|
+
"@types/react": "18.2.56",
|
|
144
144
|
"@types/react-dom": "18.2.19"
|
|
145
145
|
},
|
|
146
146
|
"msw": {
|