richie-education 2.25.0-b2.dev32 → 2.25.0-b2.dev34

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.
Files changed (22) hide show
  1. package/js/api/joanie.ts +26 -3
  2. package/js/hooks/useContractArchive/index.download.spec.tsx +119 -0
  3. package/js/hooks/useContractArchive/index.spec.tsx +91 -0
  4. package/js/hooks/useContractArchive/index.ts +83 -0
  5. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.spec.tsx +136 -0
  6. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.timer.spec.tsx +144 -0
  7. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.tsx +73 -0
  8. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +166 -0
  9. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.tsx +23 -8
  10. package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +74 -0
  11. package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.spec.tsx +124 -0
  12. package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.tsx +73 -0
  13. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.spec.ts +85 -0
  14. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.ts +50 -0
  15. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.spec.tsx +266 -0
  16. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.tsx +153 -0
  17. package/js/pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.spec.tsx +100 -0
  18. package/js/pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.tsx +23 -0
  19. package/js/settings.ts +7 -0
  20. package/js/types/Joanie.ts +5 -0
  21. package/js/utils/errors/HttpError.ts +1 -0
  22. package/package.json +1 -1
@@ -0,0 +1,266 @@
1
+ import { faker } from '@faker-js/faker';
2
+ import { act, renderHook, waitFor } from '@testing-library/react';
3
+ import { IntlProvider } from 'react-intl';
4
+ import { PropsWithChildren } from 'react';
5
+ import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
6
+ import JoanieApiProvider from 'contexts/JoanieApiContext';
7
+ import { CONTRACT_DOWNLOAD_SETTINGS } from 'settings';
8
+ import {
9
+ getStoredContractArchiveId,
10
+ storeContractArchiveId,
11
+ unstoreContractArchiveId,
12
+ } from './contractArchiveLocalStorage';
13
+ import useDownloadContractArchive, { ContractDownloadStatus } from '.';
14
+
15
+ jest.mock('settings', () => ({
16
+ ...jest.requireActual('settings'),
17
+ CONTRACT_DOWNLOAD_SETTINGS: {
18
+ ...jest.requireActual('settings').CONTRACT_DOWNLOAD_SETTINGS,
19
+ pollInterval: 100,
20
+ },
21
+ }));
22
+
23
+ jest.mock('utils/context', () => ({
24
+ __esModule: true,
25
+ default: mockRichieContextFactory({
26
+ authentication: { backend: 'fonzie', endpoint: 'https://auth.test' },
27
+ joanie_backend: { endpoint: 'https://joanie.test' },
28
+ }).one(),
29
+ }));
30
+
31
+ const mockCheckArchive = jest.fn();
32
+ const mockCreateArchive = jest.fn();
33
+ const mockGetArchive = jest.fn();
34
+ jest.mock('hooks/useContractArchive', () => ({
35
+ __esModule: true,
36
+ default: () => ({
37
+ methods: { get: mockGetArchive, create: mockCreateArchive, check: mockCheckArchive },
38
+ }),
39
+ }));
40
+
41
+ let mockHasContractToDownload: boolean;
42
+ jest.mock('pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.tsx', () => ({
43
+ __esModule: true,
44
+ default: () => mockHasContractToDownload,
45
+ }));
46
+
47
+ describe('useDownloadContractArchive', () => {
48
+ let organizationId: string;
49
+ const Wrapper = ({ children }: PropsWithChildren) => {
50
+ return (
51
+ <IntlProvider locale="en">
52
+ <JoanieApiProvider>{children}</JoanieApiProvider>
53
+ </IntlProvider>
54
+ );
55
+ };
56
+
57
+ afterEach(() => {
58
+ jest.resetAllMocks();
59
+ unstoreContractArchiveId();
60
+ });
61
+
62
+ describe('with no contract available for download', () => {
63
+ beforeEach(() => {
64
+ organizationId = faker.string.uuid();
65
+ mockHasContractToDownload = false;
66
+ });
67
+
68
+ it('should return IDLE status when no contractArchiveId is stored', async () => {
69
+ const { result } = renderHook(() => useDownloadContractArchive({ organizationId }), {
70
+ wrapper: Wrapper,
71
+ });
72
+ expect(result.current.status).toBe(ContractDownloadStatus.IDLE);
73
+
74
+ // no api call should have been done.
75
+ expect(mockCheckArchive).not.toHaveBeenCalled();
76
+ expect(mockGetArchive).not.toHaveBeenCalled();
77
+ expect(mockCreateArchive).not.toHaveBeenCalled();
78
+ });
79
+
80
+ it('should return IDLE status when contractArchiveId is stored', async () => {
81
+ const contractArchiveId = faker.string.uuid();
82
+ storeContractArchiveId(contractArchiveId);
83
+
84
+ const { result } = renderHook(() => useDownloadContractArchive({ organizationId }), {
85
+ wrapper: Wrapper,
86
+ });
87
+
88
+ expect(result.current.status).toBe(ContractDownloadStatus.IDLE);
89
+
90
+ // no api call should have been done.
91
+ expect(mockCheckArchive).not.toHaveBeenCalled();
92
+ expect(mockGetArchive).not.toHaveBeenCalled();
93
+ expect(mockCreateArchive).not.toHaveBeenCalled();
94
+ });
95
+ });
96
+
97
+ describe('with contracts available for download', () => {
98
+ beforeEach(() => {
99
+ organizationId = faker.string.uuid();
100
+ mockHasContractToDownload = true;
101
+ });
102
+
103
+ it('should return IDLE status when no contractArchiveId is stored', async () => {
104
+ const { result } = renderHook(() => useDownloadContractArchive({ organizationId }), {
105
+ wrapper: Wrapper,
106
+ });
107
+
108
+ // ensure that initial effects are done
109
+ expect(result.current.status).toBe(ContractDownloadStatus.IDLE);
110
+
111
+ // no api call should have been done.
112
+ expect(mockCheckArchive).not.toHaveBeenCalled();
113
+ expect(mockGetArchive).not.toHaveBeenCalled();
114
+ expect(mockCreateArchive).not.toHaveBeenCalled();
115
+ });
116
+
117
+ it('should return PENDING status when a contractArchiveId is stored', async () => {
118
+ const contractArchiveId = faker.string.uuid();
119
+ storeContractArchiveId(contractArchiveId);
120
+ mockCheckArchive.mockResolvedValue(false);
121
+
122
+ const { result } = renderHook(() => useDownloadContractArchive({ organizationId }), {
123
+ wrapper: Wrapper,
124
+ });
125
+
126
+ await waitFor(() => {
127
+ expect(result.current.status).toBe(ContractDownloadStatus.PENDING);
128
+ });
129
+
130
+ expect(mockCheckArchive).toHaveBeenCalledTimes(1);
131
+ expect(mockGetArchive).not.toHaveBeenCalled();
132
+ expect(mockCreateArchive).not.toHaveBeenCalled();
133
+ });
134
+
135
+ it('should return READY status when a contractArchiveId is stored and it exists on the server', async () => {
136
+ const contractArchiveId = faker.string.uuid();
137
+ storeContractArchiveId(contractArchiveId);
138
+ mockCheckArchive.mockResolvedValue(true);
139
+
140
+ const { result } = renderHook(() => useDownloadContractArchive({ organizationId }), {
141
+ wrapper: Wrapper,
142
+ });
143
+
144
+ // ensure that initial effects are done
145
+ await waitFor(() => {
146
+ expect(result.current.status).toBe(ContractDownloadStatus.READY);
147
+ });
148
+
149
+ expect(mockCheckArchive).toHaveBeenCalledTimes(1);
150
+ expect(mockGetArchive).not.toHaveBeenCalled();
151
+ expect(mockCreateArchive).not.toHaveBeenCalled();
152
+ });
153
+
154
+ it("doDownloadArchive should call 'getArchive' if the archive is ready for download", async () => {
155
+ const contractArchiveId = faker.string.uuid();
156
+ storeContractArchiveId(contractArchiveId);
157
+ mockCheckArchive.mockResolvedValue(true);
158
+
159
+ const { result } = renderHook(() => useDownloadContractArchive({ organizationId }), {
160
+ wrapper: Wrapper,
161
+ });
162
+
163
+ // ensure that initial effects are done
164
+ await waitFor(() => {
165
+ expect(result.current.status).toBe(ContractDownloadStatus.READY);
166
+ });
167
+ expect(mockCheckArchive).toHaveBeenCalledTimes(1);
168
+
169
+ act(() => {
170
+ result.current.downloadContractArchive();
171
+ });
172
+
173
+ await waitFor(() => {
174
+ expect(result.current.status).toBe(ContractDownloadStatus.IDLE);
175
+ });
176
+
177
+ expect(mockCheckArchive).toHaveBeenCalledTimes(1);
178
+ // backend is called to download the archive
179
+ expect(mockGetArchive).toHaveBeenCalledTimes(1);
180
+
181
+ // but not to generate the archive
182
+ expect(mockCreateArchive).not.toHaveBeenCalled();
183
+ });
184
+
185
+ it("doDownloadArchive should call 'createArchive' if no contractArchiveId is stored", async () => {
186
+ const { result } = renderHook(() => useDownloadContractArchive({ organizationId }), {
187
+ wrapper: Wrapper,
188
+ });
189
+
190
+ mockCheckArchive.mockResolvedValue(true);
191
+ mockCreateArchive.mockResolvedValue(faker.string.uuid());
192
+ act(() => {
193
+ result.current.downloadContractArchive();
194
+ });
195
+
196
+ await waitFor(() => {
197
+ expect(result.current.status).toBe(ContractDownloadStatus.IDLE);
198
+ });
199
+
200
+ expect(mockCreateArchive).toHaveBeenCalledTimes(1);
201
+ expect(mockCheckArchive).toHaveBeenCalledTimes(1);
202
+ expect(mockGetArchive).toHaveBeenCalledTimes(1);
203
+ });
204
+ });
205
+
206
+ describe('with contracts available for download and expired contractArchiveId', () => {
207
+ let contractArchiveId: string;
208
+ beforeEach(() => {
209
+ organizationId = faker.string.uuid();
210
+ mockHasContractToDownload = true;
211
+
212
+ const now = Date.now();
213
+ const unvalidCreationTime =
214
+ now - CONTRACT_DOWNLOAD_SETTINGS.contractArchiveLocalVaklidityDurationMs * 2;
215
+
216
+ jest.useFakeTimers();
217
+ jest.setSystemTime(new Date(unvalidCreationTime));
218
+ contractArchiveId = faker.string.uuid();
219
+ storeContractArchiveId(contractArchiveId);
220
+ jest.setSystemTime(new Date(now));
221
+ });
222
+
223
+ afterEach(() => {
224
+ jest.runOnlyPendingTimers();
225
+ jest.useRealTimers();
226
+ });
227
+
228
+ it('should return READY status when archive exists on the server', async () => {
229
+ mockCheckArchive.mockResolvedValue(true);
230
+
231
+ const { result } = renderHook(() => useDownloadContractArchive({ organizationId }), {
232
+ wrapper: Wrapper,
233
+ });
234
+
235
+ // ensure that initial effects are done
236
+ await waitFor(() => {
237
+ expect(result.current.status).toBe(ContractDownloadStatus.READY);
238
+ });
239
+
240
+ expect(getStoredContractArchiveId()).toBe(contractArchiveId);
241
+
242
+ expect(mockCheckArchive).toHaveBeenCalledTimes(1);
243
+ expect(mockGetArchive).not.toHaveBeenCalled();
244
+ expect(mockCreateArchive).not.toHaveBeenCalled();
245
+ });
246
+
247
+ it("should return IDLE status and clear stored id when archive doesn't exists on the server", async () => {
248
+ mockCheckArchive.mockResolvedValue(false);
249
+
250
+ const { result } = renderHook(() => useDownloadContractArchive({ organizationId }), {
251
+ wrapper: Wrapper,
252
+ });
253
+
254
+ // ensure that initial effects are done
255
+ await waitFor(() => {
256
+ expect(result.current.status).toBe(ContractDownloadStatus.IDLE);
257
+ });
258
+
259
+ expect(getStoredContractArchiveId()).toBe(null);
260
+
261
+ expect(mockCheckArchive).toHaveBeenCalledTimes(1);
262
+ expect(mockGetArchive).not.toHaveBeenCalled();
263
+ expect(mockCreateArchive).not.toHaveBeenCalled();
264
+ });
265
+ });
266
+ });
@@ -0,0 +1,153 @@
1
+ import { useEffect, useMemo, useState } from 'react';
2
+ import useContractArchive from 'hooks/useContractArchive';
3
+ import useCheckContractArchiveExists from '../useCheckContractArchiveExists';
4
+ import useHasContractToDownload from '../useHasContractToDownload';
5
+ import {
6
+ storeContractArchiveId,
7
+ unstoreContractArchiveId,
8
+ getStoredContractArchiveId,
9
+ isStoredContractArchiveIdExpired,
10
+ } from './contractArchiveLocalStorage';
11
+
12
+ export enum ContractDownloadStatus {
13
+ INITIALIZING = 'initializing',
14
+ IDLE = 'idle',
15
+ PENDING = 'pending',
16
+ READY = 'ready',
17
+ }
18
+
19
+ interface UseTeacherContractsBulkDownloadProps {
20
+ organizationId: string;
21
+ }
22
+
23
+ const useDownloadContractArchive = ({ organizationId }: UseTeacherContractsBulkDownloadProps) => {
24
+ // Contract's archive api interface
25
+ const {
26
+ methods: { get: getArchive, create: createArchive },
27
+ } = useContractArchive();
28
+
29
+ // Simple hook that verifiy if current user have some fully signed contracts to download.
30
+ const hasContractToDownload = useHasContractToDownload(organizationId);
31
+
32
+ // Component state of the localstorage contract's archive id
33
+ const [contractArchiveId, setContractArchiveId] = useState<string | null>(
34
+ getStoredContractArchiveId(),
35
+ );
36
+
37
+ // Hook that handle contract archive existence check and recursive polling of it.
38
+ const { isPolling, isContractArchiveExists, checkArchiveExists } = useCheckContractArchiveExists({
39
+ enable: !!hasContractToDownload,
40
+ });
41
+
42
+ // Stored contract's archive id have to be cleanup at some point.
43
+ // this expired logic is handle by ./contractArchiveLocalStorage and
44
+ // this isContractArchiveIdExpired track it at a component level.
45
+ const [isContractArchiveIdExpired, setIsContractArchiveIdExpired] = useState<boolean | null>(
46
+ null,
47
+ );
48
+
49
+ // This state track if the user have request the download or not.
50
+ // if he have click the download button, it will be true.
51
+ // if he just load the page, it will be false.
52
+ const [isDownloadRequest, setIsDownloadRequest] = useState(false);
53
+
54
+ // Here we compute the download status.
55
+ // It stay as INITIALIZING until all needed data get fetched.
56
+ // Then it become either:
57
+ // * READY: the archive is ready to be download from the server
58
+ // * PENDING: the archive is generating on the server
59
+ // * IDLE: A fresh new download process
60
+ const contractDownloadStatus = useMemo(() => {
61
+ if (
62
+ [hasContractToDownload, isContractArchiveExists, isContractArchiveIdExpired].includes(null)
63
+ ) {
64
+ return ContractDownloadStatus.INITIALIZING;
65
+ }
66
+
67
+ if (hasContractToDownload && contractArchiveId) {
68
+ if (isContractArchiveExists) {
69
+ return ContractDownloadStatus.READY;
70
+ }
71
+
72
+ return ContractDownloadStatus.PENDING;
73
+ }
74
+
75
+ return ContractDownloadStatus.IDLE;
76
+ }, [
77
+ hasContractToDownload,
78
+ contractArchiveId,
79
+ isContractArchiveExists,
80
+ isContractArchiveIdExpired,
81
+ ]);
82
+
83
+ const clearContractArchive = () => {
84
+ setIsDownloadRequest(false);
85
+ setContractArchiveId(null);
86
+ unstoreContractArchiveId();
87
+ };
88
+
89
+ // Either download the archive
90
+ // or flag the download as requested and request the archive's creation
91
+ const downloadContractArchive = async () => {
92
+ if (isContractArchiveExists && contractArchiveId !== null) {
93
+ await getArchive(contractArchiveId);
94
+ clearContractArchive();
95
+ } else {
96
+ // When archive is ready and download is requested
97
+ // a useEffect will trigger the download.
98
+ setIsDownloadRequest(true);
99
+ createContractArchive();
100
+ }
101
+ };
102
+
103
+ // Request the archive's creation if needed
104
+ // then can start archive's polling (if option.polling === true)
105
+ const createContractArchive = async () => {
106
+ let newContractArchiveId;
107
+ if (contractArchiveId === null) {
108
+ newContractArchiveId = await createArchive(organizationId);
109
+ setContractArchiveId(newContractArchiveId);
110
+ storeContractArchiveId(newContractArchiveId);
111
+ } else {
112
+ newContractArchiveId = contractArchiveId;
113
+ }
114
+
115
+ if (!isPolling) {
116
+ checkArchiveExists(newContractArchiveId, { polling: true });
117
+ }
118
+ };
119
+
120
+ // Here we check the validity of stored id timestamp
121
+ // if an id is expired, we'll either:
122
+ // * when it exists on the server: switch expired state but nothing else
123
+ // * when didn't exists on the server: switch expired state and clear download data.
124
+ useEffect(() => {
125
+ if (isContractArchiveExists === null) {
126
+ return;
127
+ }
128
+
129
+ if (isStoredContractArchiveIdExpired() && isContractArchiveExists === false) {
130
+ setIsContractArchiveIdExpired(true);
131
+ clearContractArchive();
132
+ } else {
133
+ setIsContractArchiveIdExpired(false);
134
+ }
135
+ }, [contractDownloadStatus, isContractArchiveExists]);
136
+
137
+ // When the archive become ready for download
138
+ // this effect will trigger the download
139
+ // if it have been previously requested by the user
140
+ useEffect(() => {
141
+ if (isDownloadRequest && isContractArchiveExists) {
142
+ downloadContractArchive();
143
+ }
144
+ }, [isDownloadRequest, isContractArchiveExists]);
145
+
146
+ return {
147
+ status: contractDownloadStatus,
148
+ downloadContractArchive,
149
+ createContractArchive,
150
+ };
151
+ };
152
+
153
+ export default useDownloadContractArchive;
@@ -0,0 +1,100 @@
1
+ import fetchMock from 'fetch-mock';
2
+ import { faker } from '@faker-js/faker';
3
+ import { renderHook, waitFor } from '@testing-library/react';
4
+ import { IntlProvider } from 'react-intl';
5
+ import { QueryClientProvider } from '@tanstack/react-query';
6
+ import { PropsWithChildren } from 'react';
7
+ import { PER_PAGE } from 'settings';
8
+ import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
9
+ import { ContractState } from 'types/Joanie';
10
+ import { ContractFactory } from 'utils/test/factories/joanie';
11
+ import { createTestQueryClient } from 'utils/test/createTestQueryClient';
12
+ import JoanieSessionProvider from 'contexts/SessionContext/JoanieSessionProvider';
13
+ import useHasContractToDownload from '.';
14
+
15
+ jest.mock('utils/context', () => ({
16
+ __esModule: true,
17
+ default: mockRichieContextFactory({
18
+ authentication: { backend: 'fonzie', endpoint: 'https://auth.test' },
19
+ joanie_backend: { endpoint: 'https://joanie.test' },
20
+ }).one(),
21
+ }));
22
+
23
+ describe('hooks/useHasContractToDownload', () => {
24
+ const Wrapper = ({ children }: PropsWithChildren) => {
25
+ return (
26
+ <IntlProvider locale="en">
27
+ <QueryClientProvider client={createTestQueryClient({ user: true })}>
28
+ <JoanieSessionProvider>{children}</JoanieSessionProvider>
29
+ </QueryClientProvider>
30
+ </IntlProvider>
31
+ );
32
+ };
33
+
34
+ beforeEach(() => {
35
+ // Joanie providers calls
36
+ fetchMock.get('https://joanie.test/api/v1.0/orders/', []);
37
+ fetchMock.get('https://joanie.test/api/v1.0/credit-cards/', []);
38
+ fetchMock.get('https://joanie.test/api/v1.0/addresses/', []);
39
+ });
40
+
41
+ afterEach(() => {
42
+ fetchMock.restore();
43
+ });
44
+
45
+ it('should return null when joanie request is pending', async () => {
46
+ const organizationId = faker.string.uuid();
47
+ const contractListUrl = `https://joanie.test/api/v1.0/organizations/${organizationId}/contracts/?signature_state=${ContractState.SIGNED}&page=1&page_size=${PER_PAGE.teacherContractList}`;
48
+ fetchMock.get(contractListUrl, {
49
+ count: 1,
50
+ next: null,
51
+ previous: null,
52
+ results: [ContractFactory().one()],
53
+ });
54
+
55
+ const { result } = renderHook(() => useHasContractToDownload(organizationId), {
56
+ wrapper: Wrapper,
57
+ });
58
+
59
+ expect(result.current).toBeNull();
60
+ });
61
+
62
+ it('should return true when joanie return some signed contracts', async () => {
63
+ const organizationId = faker.string.uuid();
64
+ const contractListUrl = `https://joanie.test/api/v1.0/organizations/${organizationId}/contracts/?signature_state=${ContractState.SIGNED}&page=1&page_size=${PER_PAGE.teacherContractList}`;
65
+ fetchMock.get(contractListUrl, {
66
+ count: 1,
67
+ next: null,
68
+ previous: null,
69
+ results: [ContractFactory().one()],
70
+ });
71
+
72
+ const { result } = renderHook(() => useHasContractToDownload(organizationId), {
73
+ wrapper: Wrapper,
74
+ });
75
+
76
+ await waitFor(() => {
77
+ expect(fetchMock.calls().map((call) => call[0])).toContain(contractListUrl);
78
+ expect(result.current).toBe(true);
79
+ });
80
+ });
81
+
82
+ it("should return false when joanie doesn't return any signed contracts", async () => {
83
+ const organizationId = faker.string.uuid();
84
+ const contractListUrl = `https://joanie.test/api/v1.0/organizations/${organizationId}/contracts/?signature_state=${ContractState.SIGNED}&page=1&page_size=${PER_PAGE.teacherContractList}`;
85
+ fetchMock.get(contractListUrl, {
86
+ count: 0,
87
+ next: null,
88
+ previous: null,
89
+ results: [],
90
+ });
91
+
92
+ const { result } = renderHook(() => useHasContractToDownload(organizationId), {
93
+ wrapper: Wrapper,
94
+ });
95
+ await waitFor(() => {
96
+ expect(fetchMock.calls().map((call) => call[0])).toContain(contractListUrl);
97
+ expect(result.current).toBe(false);
98
+ });
99
+ });
100
+ });
@@ -0,0 +1,23 @@
1
+ import { useOrganizationContracts } from 'hooks/useContracts';
2
+ import { PER_PAGE } from 'settings';
3
+ import { ContractState, Organization } from 'types/Joanie';
4
+
5
+ const useHasContractToDownload = (organizationId: Organization['id']) => {
6
+ const {
7
+ items: contracts,
8
+ states: { isFetched },
9
+ } = useOrganizationContracts({
10
+ organization_id: organizationId,
11
+ signature_state: ContractState.SIGNED,
12
+ page: 1,
13
+ page_size: PER_PAGE.teacherContractList,
14
+ });
15
+
16
+ if (!isFetched) {
17
+ return null;
18
+ }
19
+
20
+ return contracts.length > 0;
21
+ };
22
+
23
+ export default useHasContractToDownload;
package/js/settings.ts CHANGED
@@ -46,6 +46,13 @@ export const CONTRACT_SETTINGS = {
46
46
  dummySignatureSignTimeout: 2000,
47
47
  };
48
48
 
49
+ export const CONTRACT_DOWNLOAD_SETTINGS = {
50
+ // Interval in ms to poll the related contract's archive.
51
+ pollInterval: 1000,
52
+ contractArchiveLocalStorageKey: 'RICHIE_CONTRACT_ARCHIVE',
53
+ contractArchiveLocalVaklidityDurationMs: 10 * 60 * 60 * 1000, // 10min
54
+ };
55
+
49
56
  const DEFAULT_PER_PAGE = 50;
50
57
  export const PER_PAGE = {
51
58
  teacherContractList: 25,
@@ -520,6 +520,11 @@ interface APIUser {
520
520
  ? Promise<Nullable<Contract>>
521
521
  : Promise<PaginatedResponse<Contract>>;
522
522
  download(id: string): Promise<File>;
523
+ zip_archive: {
524
+ check: (id: string) => Promise<Response>;
525
+ create: ({ organization_id }: { organization_id: string }) => Promise<{ url: string }>;
526
+ get: (id: string) => Promise<File>;
527
+ };
523
528
  };
524
529
  }
525
530
 
@@ -25,6 +25,7 @@ export function isHttpError(error: any): error is HttpError {
25
25
 
26
26
  export enum HttpStatusCode {
27
27
  OK = 200,
28
+ NO_CONTENT = 204,
28
29
  UNAUTHORIZED = 401,
29
30
  BAD_REQUEST = 400,
30
31
  FORBIDDEN = 403,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "richie-education",
3
- "version": "2.25.0-b2.dev32",
3
+ "version": "2.25.0-b2.dev34",
4
4
  "description": "A CMS to build learning portals for Open Education",
5
5
  "main": "sandbox/manage.py",
6
6
  "scripts": {