dce-reactkit 3.12.3 → 4.0.0-beta-date-and-time.1
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/dist/cjs/index.js +5338 -5515
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/LoadingSpinner.d.ts +2 -2
- package/dist/cjs/types/components/SimpleDateChooser.d.ts +3 -1
- package/dist/cjs/types/components/SimpleTimeChooser.d.ts +20 -0
- package/dist/cjs/types/components/Tooltip.d.ts +1 -1
- package/dist/cjs/types/index.d.ts +12 -11
- package/dist/cjs/types/types/ParamType.d.ts +10 -10
- package/dist/cjs/types/types/ReactKitErrorCode.d.ts +2 -14
- package/dist/esm/index.js +5344 -5517
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/LoadingSpinner.d.ts +2 -2
- package/dist/esm/types/components/SimpleDateChooser.d.ts +3 -1
- package/dist/esm/types/components/SimpleTimeChooser.d.ts +20 -0
- package/dist/esm/types/components/Tooltip.d.ts +1 -1
- package/dist/esm/types/index.d.ts +12 -11
- package/dist/esm/types/types/ParamType.d.ts +10 -10
- package/dist/esm/types/types/ReactKitErrorCode.d.ts +2 -14
- package/dist/index.d.ts +183 -354
- package/genEncodedSecret.ts +84 -0
- package/package.json +7 -9
- package/rollup.config.js +2 -0
- package/src/components/AppWrapper.tsx +3 -1
- package/src/components/CheckboxButton.tsx +1 -1
- package/src/components/Dropdown.tsx +1 -1
- package/src/components/LogReviewer.tsx +2 -2
- package/src/components/Modal/index.tsx +1 -1
- package/src/components/RadioButton.tsx +3 -1
- package/src/components/SimpleDateChooser.tsx +62 -26
- package/src/components/SimpleTimeChooser.tsx +195 -0
- package/src/components/Tooltip.tsx +1 -1
- package/src/{client → helpers}/initClient.tsx +5 -1
- package/src/helpers/logClientEvent.tsx +3 -1
- package/src/helpers/visitServerEndpoint.tsx +1 -1
- package/src/index.ts +20 -20
- package/src/types/ParamType.ts +10 -10
- package/src/types/ReactKitErrorCode.tsx +3 -17
- package/dist/cjs/types/components/ItemPicker/index.test.d.ts +0 -1
- package/dist/cjs/types/helpers/addDBEditorEndpoints.d.ts +0 -41
- package/dist/cjs/types/helpers/genRouteHandler.d.ts +0 -76
- package/dist/cjs/types/helpers/handleError.d.ts +0 -18
- package/dist/cjs/types/helpers/handleSuccess.d.ts +0 -8
- package/dist/cjs/types/helpers/parseUserAgent.d.ts +0 -17
- package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/index.d.ts +0 -24
- package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -33
- package/dist/cjs/types/server/initLogCollection.d.ts +0 -8
- package/dist/cjs/types/server/initServer.d.ts +0 -42
- package/dist/esm/types/components/ItemPicker/index.test.d.ts +0 -1
- package/dist/esm/types/helpers/addDBEditorEndpoints.d.ts +0 -41
- package/dist/esm/types/helpers/genRouteHandler.d.ts +0 -76
- package/dist/esm/types/helpers/handleError.d.ts +0 -18
- package/dist/esm/types/helpers/handleSuccess.d.ts +0 -8
- package/dist/esm/types/helpers/parseUserAgent.d.ts +0 -17
- package/dist/esm/types/helpers/visitEndpointOnAnotherServer/index.d.ts +0 -24
- package/dist/esm/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -33
- package/dist/esm/types/server/initLogCollection.d.ts +0 -8
- package/dist/esm/types/server/initServer.d.ts +0 -42
- package/src/components/ItemPicker/index.test.tsx +0 -783
- package/src/helpers/addDBEditorEndpoints.ts +0 -128
- package/src/helpers/genRouteHandler.ts +0 -948
- package/src/helpers/handleError.ts +0 -65
- package/src/helpers/handleSuccess.ts +0 -18
- package/src/helpers/parseUserAgent.ts +0 -108
- package/src/helpers/visitEndpointOnAnotherServer/index.ts +0 -93
- package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +0 -164
- package/src/server/initLogCollection.ts +0 -27
- package/src/server/initServer.ts +0 -287
- /package/dist/cjs/types/{client → helpers}/initClient.d.ts +0 -0
- /package/dist/esm/types/{client → helpers}/initClient.d.ts +0 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Import crypto lib
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
|
|
4
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
5
|
+
|
|
6
|
+
// Get args
|
|
7
|
+
const REACTKIT_CRED_ENCODING_SALT = process.env.npm_config_salt;
|
|
8
|
+
if (!REACTKIT_CRED_ENCODING_SALT) {
|
|
9
|
+
console.log('Encoding salt is required: --salt=...');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Get the key
|
|
14
|
+
let key = process.env.npm_config_key;
|
|
15
|
+
if (!key) {
|
|
16
|
+
console.log('Key is required: --key=...');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Get the description
|
|
21
|
+
let description = process.env.npm_config_description;
|
|
22
|
+
if (!description) {
|
|
23
|
+
console.log('Description is required: --description=...');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Get secret
|
|
28
|
+
let secret = process.env.npm_config_secret;
|
|
29
|
+
if (!secret) {
|
|
30
|
+
// Generate a random secret
|
|
31
|
+
secret = '';
|
|
32
|
+
for (let i = 0; i < 32; i++) {
|
|
33
|
+
secret += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
34
|
+
}
|
|
35
|
+
console.log('Generated a random secret. If you have one in mind, use --secret=...');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Get the host name
|
|
39
|
+
const host = process.env.npm_config_host;
|
|
40
|
+
if (!host) {
|
|
41
|
+
console.log('Host of the receiving server is required: --host=...');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Encryption process based on:
|
|
46
|
+
// https://medium.com/@tony.infisical/guide-to-nodes-crypto-module-for-encryption-decryption-65c077176980
|
|
47
|
+
|
|
48
|
+
// Create a random initialization vector
|
|
49
|
+
const iv = crypto.randomBytes(12).toString('base64');
|
|
50
|
+
|
|
51
|
+
// Create a cipher
|
|
52
|
+
const cipher = crypto.createCipheriv(
|
|
53
|
+
'aes-256-gcm',
|
|
54
|
+
Buffer.from(secret, 'base64'),
|
|
55
|
+
Buffer.from(iv, 'base64'),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Encrypt the string
|
|
59
|
+
let ciphertext = cipher.update(secret, 'utf8', 'base64');
|
|
60
|
+
|
|
61
|
+
// Finalize the encryption
|
|
62
|
+
ciphertext += cipher.final('base64');
|
|
63
|
+
|
|
64
|
+
// Get the authentication tag
|
|
65
|
+
const tag = cipher.getAuthTag();
|
|
66
|
+
|
|
67
|
+
// JSONify the encrypted data
|
|
68
|
+
const encryptionPack = encodeURIComponent(JSON.stringify({
|
|
69
|
+
ciphertext,
|
|
70
|
+
iv,
|
|
71
|
+
tag,
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
// Show the encrypted data
|
|
75
|
+
console.log('\n\n');
|
|
76
|
+
console.log('––––– Done! What\'s Next: –––––');
|
|
77
|
+
console.log('');
|
|
78
|
+
console.log('On the server *sending* the requests, append the following to the REACTKIT_CROSS_SERVER_CREDENTIALS env var:');
|
|
79
|
+
console.log(`|${host}:${key}:${secret}|`);
|
|
80
|
+
console.log('');
|
|
81
|
+
console.log('On the server *receiving* the requests, add an entry to the "CrossServerCredential" collection:');
|
|
82
|
+
console.log(`{ "description": "${description}", "key": "${key}", "encodedeSecret": "${encryptionPack}", "scopes": [] }`);
|
|
83
|
+
console.log('');
|
|
84
|
+
console.log('For all scopes that the server should have access to, add them to the "scopes" array.');
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dce-reactkit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-beta-date-and-time.1",
|
|
4
4
|
"description": "Shared components for Harvard DCE apps",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "rm -rf dist && rollup -c",
|
|
10
|
-
"test": "jest --runInBand"
|
|
10
|
+
"test": "jest --runInBand",
|
|
11
|
+
"gen-reactkit-cross-server-secret": "npx tsx genEncodedSecret.ts"
|
|
11
12
|
},
|
|
12
13
|
"jest": {
|
|
13
14
|
"preset": "ts-jest",
|
|
@@ -24,7 +25,6 @@
|
|
|
24
25
|
},
|
|
25
26
|
"homepage": "https://github.com/harvard-edtech/dce-reactkit#readme",
|
|
26
27
|
"dependencies": {
|
|
27
|
-
"qs": "^6.x.x",
|
|
28
28
|
"react-select": "^5.7.3"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
@@ -33,20 +33,19 @@
|
|
|
33
33
|
"@fortawesome/free-solid-svg-icons": "^x.x.x",
|
|
34
34
|
"@fortawesome/react-fontawesome": "^x.x.x",
|
|
35
35
|
"bootstrap": "^5.x.x",
|
|
36
|
-
"
|
|
37
|
-
"react": "^x.x.x",
|
|
38
|
-
"react-dom": "^x.x.x"
|
|
36
|
+
"react": "^x.x.x"
|
|
39
37
|
},
|
|
40
38
|
"devDependencies": {
|
|
41
39
|
"@rollup/plugin-commonjs": "^22.0.0",
|
|
42
40
|
"@rollup/plugin-json": "^4.1.0",
|
|
43
41
|
"@rollup/plugin-node-resolve": "^13.2.1",
|
|
44
|
-
"@rollup/plugin-typescript": "^
|
|
42
|
+
"@rollup/plugin-typescript": "^10.0.1",
|
|
45
43
|
"@types/bootstrap": "^5.2.8",
|
|
46
44
|
"@types/express": "^4.17.17",
|
|
47
45
|
"@types/jest": "^28.1.7",
|
|
48
46
|
"@types/node": "^20.2.5",
|
|
49
|
-
"@types/react": "^
|
|
47
|
+
"@types/react": "^19.0.10",
|
|
48
|
+
"@types/react-dom": "^19.0.4",
|
|
50
49
|
"@typescript-eslint/eslint-plugin": "^5.59.9",
|
|
51
50
|
"@typescript-eslint/parser": "^5.59.9",
|
|
52
51
|
"eslint": "^8.42.0",
|
|
@@ -58,7 +57,6 @@
|
|
|
58
57
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
59
58
|
"jest": "^28.1.3",
|
|
60
59
|
"jest-environment-jsdom": "^28.1.3",
|
|
61
|
-
"raixa": "^1.0.21",
|
|
62
60
|
"rollup": "^2.70.2",
|
|
63
61
|
"rollup-plugin-dts": "^4.2.1",
|
|
64
62
|
"rollup-plugin-sourcemaps": "^0.6.3",
|
package/rollup.config.js
CHANGED
|
@@ -27,6 +27,7 @@ export default [
|
|
|
27
27
|
typescript({ tsconfig: "./tsconfig.json" }),
|
|
28
28
|
sourcemaps(),
|
|
29
29
|
],
|
|
30
|
+
inlineDynamicImports: true,
|
|
30
31
|
external: [
|
|
31
32
|
...Object.keys(packageJson.dependencies || {}),
|
|
32
33
|
...Object.keys(packageJson.peerDependencies || {}),
|
|
@@ -36,6 +37,7 @@ export default [
|
|
|
36
37
|
input: "dist/esm/types/index.d.ts",
|
|
37
38
|
output: [{ file: "dist/index.d.ts", format: "esm" }],
|
|
38
39
|
plugins: [dts()],
|
|
40
|
+
inlineDynamicImports: true,
|
|
39
41
|
external: [
|
|
40
42
|
...Object.keys(packageJson.dependencies || {}),
|
|
41
43
|
...Object.keys(packageJson.peerDependencies || {}),
|
|
@@ -32,10 +32,12 @@ import ErrorWithCode from '../errors/ErrorWithCode';
|
|
|
32
32
|
// TODO: fix dependency cycle
|
|
33
33
|
// eslint-disable-next-line import/no-cycle
|
|
34
34
|
import logClientEvent from '../helpers/logClientEvent';
|
|
35
|
+
// TODO: fix dependency cycle
|
|
36
|
+
// eslint-disable-next-line import/no-cycle
|
|
35
37
|
import {
|
|
36
38
|
getSessionExpiredMessage,
|
|
37
39
|
isDarkModeOn,
|
|
38
|
-
} from '../
|
|
40
|
+
} from '../helpers/initClient';
|
|
39
41
|
import waitMs from '../helpers/waitMs';
|
|
40
42
|
|
|
41
43
|
// Import constants
|
|
@@ -15,7 +15,7 @@ import { faSquare, faSquareMinus } from '@fortawesome/free-regular-svg-icons';
|
|
|
15
15
|
import Variant from '../types/Variant';
|
|
16
16
|
|
|
17
17
|
// Import shared helpers
|
|
18
|
-
import { isDarkModeOn } from '../
|
|
18
|
+
import { isDarkModeOn } from '../helpers/initClient';
|
|
19
19
|
|
|
20
20
|
/*------------------------------------------------------------------------*/
|
|
21
21
|
/* -------------------------------- Types ------------------------------- */
|
|
@@ -16,7 +16,7 @@ import Variant from '../types/Variant';
|
|
|
16
16
|
import DropdownItemType from '../types/DropdownItemType';
|
|
17
17
|
|
|
18
18
|
// Check dark mode
|
|
19
|
-
import { isDarkModeOn } from '../
|
|
19
|
+
import { isDarkModeOn } from '../helpers/initClient';
|
|
20
20
|
|
|
21
21
|
/*------------------------------------------------------------------------*/
|
|
22
22
|
/* -------------------------------- Types ------------------------------- */
|
|
@@ -1182,7 +1182,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1182
1182
|
year={dateFilterState.startDate.year}
|
|
1183
1183
|
month={dateFilterState.startDate.month}
|
|
1184
1184
|
day={dateFilterState.startDate.day}
|
|
1185
|
-
|
|
1185
|
+
dontAllowFuture
|
|
1186
1186
|
numMonthsToShow={36}
|
|
1187
1187
|
onChange={(month, day, year) => {
|
|
1188
1188
|
dateFilterState.startDate = { month, day, year };
|
|
@@ -1198,7 +1198,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1198
1198
|
year={dateFilterState.endDate.year}
|
|
1199
1199
|
month={dateFilterState.endDate.month}
|
|
1200
1200
|
day={dateFilterState.endDate.day}
|
|
1201
|
-
|
|
1201
|
+
dontAllowFuture
|
|
1202
1202
|
numMonthsToShow={12}
|
|
1203
1203
|
onChange={(month, day, year) => {
|
|
1204
1204
|
if (
|
|
@@ -32,7 +32,7 @@ import NUM_MODAL_PORTALS from '../../constants/NUM_MODAL_PORTALS';
|
|
|
32
32
|
// Import shared helpers
|
|
33
33
|
// TODO: fix dependency cycle
|
|
34
34
|
// eslint-disable-next-line import/no-cycle
|
|
35
|
-
import { isDarkModeOn } from '../../
|
|
35
|
+
import { isDarkModeOn } from '../../helpers/initClient';
|
|
36
36
|
|
|
37
37
|
/*------------------------------------------------------------------------*/
|
|
38
38
|
/* ------------------------------ Constants ----------------------------- */
|
|
@@ -13,7 +13,9 @@ import { faCircle } from '@fortawesome/free-regular-svg-icons';
|
|
|
13
13
|
|
|
14
14
|
// Import shared types
|
|
15
15
|
import Variant from '../types/Variant';
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
// Import shared helpers
|
|
18
|
+
import { isDarkModeOn } from '../helpers/initClient';
|
|
17
19
|
|
|
18
20
|
/*------------------------------------------------------------------------*/
|
|
19
21
|
/* -------------------------------- Types ------------------------------- */
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A very simple, lightweight date chooser
|
|
3
3
|
* @author Gabe Abrams
|
|
4
|
+
* @author Gardeniu Liu
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
// Import React
|
|
7
8
|
import React from 'react';
|
|
8
|
-
import getMonthName from '../helpers/getMonthName';
|
|
9
9
|
|
|
10
10
|
// Import helpers
|
|
11
11
|
import getOrdinal from '../helpers/getOrdinal';
|
|
12
|
+
import getMonthName from '../helpers/getMonthName';
|
|
12
13
|
import getTimeInfoInET from '../helpers/getTimeInfoInET';
|
|
13
14
|
|
|
15
|
+
// Import classes
|
|
16
|
+
import ErrorWithCode from '../errors/ErrorWithCode';
|
|
17
|
+
|
|
18
|
+
// Import types
|
|
19
|
+
import ReactKitErrorCode from '../types/ReactKitErrorCode';
|
|
20
|
+
|
|
14
21
|
/*------------------------------------------------------------------------*/
|
|
15
22
|
/* -------------------------------- Types ------------------------------- */
|
|
16
23
|
/*------------------------------------------------------------------------*/
|
|
@@ -33,12 +40,14 @@ type Props = {
|
|
|
33
40
|
* @param year new full year number
|
|
34
41
|
*/
|
|
35
42
|
onChange: (month: number, day: number, year: number) => void,
|
|
36
|
-
// Number of months to allow the user to choose from
|
|
37
|
-
//
|
|
43
|
+
// Number of months in either the past or future to allow the user to choose from
|
|
44
|
+
// If we aren't allowing the past or the future, we will throw an error
|
|
45
|
+
// (max is 12, default is 6 in the past and 6 in the future)
|
|
38
46
|
numMonthsToShow?: number,
|
|
39
|
-
// If true,
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
// If true, the user isn't allowed to select dates in the past
|
|
48
|
+
dontAllowPast?: boolean,
|
|
49
|
+
// If true, the user isn't allowed to select dates in the future
|
|
50
|
+
dontAllowFuture?: boolean,
|
|
42
51
|
};
|
|
43
52
|
|
|
44
53
|
/*------------------------------------------------------------------------*/
|
|
@@ -56,8 +65,9 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
|
|
|
56
65
|
ariaLabel,
|
|
57
66
|
name,
|
|
58
67
|
onChange,
|
|
59
|
-
chooseFromPast,
|
|
60
68
|
numMonthsToShow = 6,
|
|
69
|
+
dontAllowFuture,
|
|
70
|
+
dontAllowPast,
|
|
61
71
|
} = props;
|
|
62
72
|
|
|
63
73
|
/*------------------------------------------------------------------------*/
|
|
@@ -76,16 +86,41 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
|
|
|
76
86
|
days: number[],
|
|
77
87
|
year: number,
|
|
78
88
|
}[] = [];
|
|
89
|
+
|
|
79
90
|
let startYear = today.year;
|
|
80
91
|
let startMonth = today.month;
|
|
81
|
-
|
|
92
|
+
|
|
93
|
+
// Don't allow past or future dates
|
|
94
|
+
if (dontAllowPast && dontAllowFuture) {
|
|
95
|
+
throw new ErrorWithCode(
|
|
96
|
+
'No past or future dates allowed',
|
|
97
|
+
ReactKitErrorCode.SimpleDateChooserInvalidDateRange,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Require numMonthsToShow to be positive
|
|
102
|
+
if (numMonthsToShow <= 0) {
|
|
103
|
+
throw new ErrorWithCode(
|
|
104
|
+
'numMonthsToShow must be positive',
|
|
105
|
+
ReactKitErrorCode.SimpleDateChooserInvalidNumMonths,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Recalculate startMonth and startYear when allowing past dates
|
|
110
|
+
if (!dontAllowPast) {
|
|
82
111
|
startMonth -= Math.max(0, numMonthsToShow - 1);
|
|
83
112
|
while (startMonth <= 0) {
|
|
84
113
|
startMonth += 12;
|
|
85
114
|
startYear -= 1;
|
|
86
115
|
}
|
|
87
116
|
}
|
|
88
|
-
|
|
117
|
+
// Calculate total number of months to show
|
|
118
|
+
let totalMonthsToShow = numMonthsToShow;
|
|
119
|
+
if (!dontAllowPast && !dontAllowFuture) {
|
|
120
|
+
totalMonthsToShow = totalMonthsToShow * 2 - 1;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (let i = 0; i < totalMonthsToShow; i++) {
|
|
89
124
|
// Get month and year info
|
|
90
125
|
const unmoddedMonth = (startMonth + i);
|
|
91
126
|
let month = unmoddedMonth;
|
|
@@ -105,24 +140,25 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
|
|
|
105
140
|
// Figure out which days are allowed
|
|
106
141
|
const days = [];
|
|
107
142
|
const numDaysInMonth = (new Date(year, month, 0)).getDate();
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
days.push(day);
|
|
143
|
+
|
|
144
|
+
// Current month
|
|
145
|
+
if (month === today.month && year === today.year) {
|
|
146
|
+
// Past selection: add all previous days of the month
|
|
147
|
+
if (!dontAllowPast) {
|
|
148
|
+
for (let day = 1; day < today.day; day++) {
|
|
149
|
+
days.push(day);
|
|
150
|
+
}
|
|
117
151
|
}
|
|
118
|
-
|
|
152
|
+
days.push(today.day); // Add current day
|
|
119
153
|
// Future selection: add all remaining days of the month
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
154
|
+
if (!dontAllowFuture) {
|
|
155
|
+
for (let day = today.day + 1; day <= numDaysInMonth; day++) {
|
|
156
|
+
days.push(day);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} else { // Past or future month
|
|
160
|
+
// Include all days in the month
|
|
161
|
+
for (let day = 1; day <= numDaysInMonth; day++) {
|
|
126
162
|
days.push(day);
|
|
127
163
|
}
|
|
128
164
|
}
|
|
@@ -158,8 +194,8 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
|
|
|
158
194
|
</option>,
|
|
159
195
|
);
|
|
160
196
|
|
|
197
|
+
// This is the currently selected month
|
|
161
198
|
if (month === choice.month) {
|
|
162
|
-
// This is the currently selected month
|
|
163
199
|
// Create day options
|
|
164
200
|
choice.days.forEach((dayChoice) => {
|
|
165
201
|
const ordinal = getOrdinal(dayChoice);
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A very simple, lightweight time chooser
|
|
3
|
+
* @author Gardenia Liu
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Import React
|
|
7
|
+
import React from 'react';
|
|
8
|
+
|
|
9
|
+
// Import helpers
|
|
10
|
+
import padZerosLeft from '../helpers/padZerosLeft';
|
|
11
|
+
|
|
12
|
+
/*------------------------------------------------------------------------*/
|
|
13
|
+
/* -------------------------------- Types ------------------------------- */
|
|
14
|
+
/*------------------------------------------------------------------------*/
|
|
15
|
+
|
|
16
|
+
type Props = {
|
|
17
|
+
// Aria label
|
|
18
|
+
ariaLabel: string,
|
|
19
|
+
// Name of the chooser (machine-readable, hyphenated)
|
|
20
|
+
name: string,
|
|
21
|
+
// Currently selected hour of the day (24hr)
|
|
22
|
+
hour: number,
|
|
23
|
+
// Currently selected minute within the hour
|
|
24
|
+
minute: number,
|
|
25
|
+
/**
|
|
26
|
+
* Handler for when time changes
|
|
27
|
+
* @param hour new 24hr hour number
|
|
28
|
+
* @param minute new minute number
|
|
29
|
+
*/
|
|
30
|
+
onChange: (hour: number, minute: number) => void,
|
|
31
|
+
// Interval in minutes between each choice
|
|
32
|
+
// Allowed options: 15, 30, 60, defaults to 15
|
|
33
|
+
// If an unsupported interval is passed in, it will default to 15
|
|
34
|
+
intervalMin?: number,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/*------------------------------------------------------------------------*/
|
|
38
|
+
/* ------------------------------ Constants ----------------------------- */
|
|
39
|
+
/*------------------------------------------------------------------------*/
|
|
40
|
+
|
|
41
|
+
// Allowed intervals between options
|
|
42
|
+
const ALLOWED_INTERVALS = [15, 30, 60]; // min
|
|
43
|
+
|
|
44
|
+
// Default interval to use if an unsupported interval is passed in
|
|
45
|
+
const DEFAULT_INTERVAL = ALLOWED_INTERVALS[0]; // min
|
|
46
|
+
|
|
47
|
+
/*------------------------------------------------------------------------*/
|
|
48
|
+
/* ------------------------------ Component ----------------------------- */
|
|
49
|
+
/*------------------------------------------------------------------------*/
|
|
50
|
+
|
|
51
|
+
const SimpleTimeChooser: React.FC<Props> = (props) => {
|
|
52
|
+
/*------------------------------------------------------------------------*/
|
|
53
|
+
/* -------------------------------- Setup ------------------------------- */
|
|
54
|
+
/*------------------------------------------------------------------------*/
|
|
55
|
+
|
|
56
|
+
/* -------------- Props ------------- */
|
|
57
|
+
|
|
58
|
+
const {
|
|
59
|
+
ariaLabel,
|
|
60
|
+
name,
|
|
61
|
+
hour,
|
|
62
|
+
minute,
|
|
63
|
+
onChange,
|
|
64
|
+
} = props;
|
|
65
|
+
let {
|
|
66
|
+
intervalMin = DEFAULT_INTERVAL,
|
|
67
|
+
} = props;
|
|
68
|
+
|
|
69
|
+
// Use default interval if not supported
|
|
70
|
+
if (ALLOWED_INTERVALS.includes(intervalMin)) {
|
|
71
|
+
intervalMin = DEFAULT_INTERVAL;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/*------------------------------------------------------------------------*/
|
|
75
|
+
/* ------------------------- Component Functions ------------------------ */
|
|
76
|
+
/*------------------------------------------------------------------------*/
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Convert number of minutes since midnight into 24hour and minute format
|
|
80
|
+
* @author Gabe Abrams
|
|
81
|
+
* @param minSinceMidnight total minutes since midnight
|
|
82
|
+
* @returns hours (24) and minutes
|
|
83
|
+
*/
|
|
84
|
+
const convertMinSinceMidnightToHoursAndMin = (minSinceMidnight: number): {
|
|
85
|
+
hours: number,
|
|
86
|
+
minutes: number,
|
|
87
|
+
} => {
|
|
88
|
+
return {
|
|
89
|
+
hours: Math.floor(minSinceMidnight / 60),
|
|
90
|
+
minutes: minSinceMidnight % 60,
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Convert time in minutes into HH:MM format
|
|
96
|
+
* @author Gardenia Liu
|
|
97
|
+
* @param totalMinutes total minutes since midnight
|
|
98
|
+
* @returns formatted time string
|
|
99
|
+
*/
|
|
100
|
+
const formatTime = (totalMinutes: number): string => {
|
|
101
|
+
// Handle special cases
|
|
102
|
+
if (totalMinutes === 0) {
|
|
103
|
+
return '12:00 Midnight';
|
|
104
|
+
}
|
|
105
|
+
if (totalMinutes === 12 * 60) {
|
|
106
|
+
return '12:00 Noon';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// All normal cases:
|
|
110
|
+
const timeInfo = convertMinSinceMidnightToHoursAndMin(totalMinutes);
|
|
111
|
+
let { hours } = timeInfo;
|
|
112
|
+
const { minutes } = timeInfo;
|
|
113
|
+
|
|
114
|
+
// Process 24hr -> 12hr
|
|
115
|
+
const isAM = (hours < 12);
|
|
116
|
+
if (hours === 0) {
|
|
117
|
+
hours = 12;
|
|
118
|
+
} else if (hours > 12) {
|
|
119
|
+
hours %= 12;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Pad with zeros
|
|
123
|
+
const paddedMinutes = padZerosLeft(minutes, 2);
|
|
124
|
+
|
|
125
|
+
// Assemble time string
|
|
126
|
+
return `${hours}:${paddedMinutes} ${isAM ? 'AM' : 'PM'}`;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/*------------------------------------------------------------------------*/
|
|
130
|
+
/* ------------------------------- Render ------------------------------- */
|
|
131
|
+
/*------------------------------------------------------------------------*/
|
|
132
|
+
|
|
133
|
+
/*----------------------------------------*/
|
|
134
|
+
/* --------------- Main UI -------------- */
|
|
135
|
+
/*----------------------------------------*/
|
|
136
|
+
|
|
137
|
+
// Generate list of time options
|
|
138
|
+
const times: string[] = [];
|
|
139
|
+
for (let time = 0; time < 24 * 60; time += intervalMin) {
|
|
140
|
+
times.push(formatTime(time));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Create choice options
|
|
144
|
+
const selectedTimeMin = hour * 60 + minute; // minutes since midnight
|
|
145
|
+
const timeOptions: React.ReactNode[] = times.map((timeString, timeIndex) => {
|
|
146
|
+
// Create time option
|
|
147
|
+
const numMinutesForChoice = timeIndex * intervalMin;
|
|
148
|
+
|
|
149
|
+
// Render the option
|
|
150
|
+
return (
|
|
151
|
+
<option
|
|
152
|
+
key={numMinutesForChoice}
|
|
153
|
+
value={numMinutesForChoice}
|
|
154
|
+
aria-label={`choose ${timeString}`}
|
|
155
|
+
>
|
|
156
|
+
{timeString}
|
|
157
|
+
</option>
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div
|
|
163
|
+
className="SimpleTimeChooser-container"
|
|
164
|
+
aria-label={`time chooser with selected time: ${formatTime(selectedTimeMin)}`}
|
|
165
|
+
>
|
|
166
|
+
{/* Time Chooser */}
|
|
167
|
+
<select
|
|
168
|
+
aria-label={`time for ${ariaLabel}`}
|
|
169
|
+
className="custom-select d-inline-block"
|
|
170
|
+
style={{ width: 'auto' }}
|
|
171
|
+
id={`SimpleTimeChooser-${name}-time`}
|
|
172
|
+
value={selectedTimeMin}
|
|
173
|
+
onChange={(e) => {
|
|
174
|
+
// Parse selector value (string)
|
|
175
|
+
const newTime = Number.parseInt(e.target.value, 10);
|
|
176
|
+
|
|
177
|
+
// Convert minutes since midnight to hour and minute
|
|
178
|
+
const timeInfo = convertMinSinceMidnightToHoursAndMin(newTime);
|
|
179
|
+
|
|
180
|
+
// Notify parent of change
|
|
181
|
+
onChange(timeInfo.hours, timeInfo.minutes);
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
184
|
+
{timeOptions}
|
|
185
|
+
</select>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/*------------------------------------------------------------------------*/
|
|
191
|
+
/* ------------------------------- Wrap Up ------------------------------ */
|
|
192
|
+
/*------------------------------------------------------------------------*/
|
|
193
|
+
|
|
194
|
+
// Export component
|
|
195
|
+
export default SimpleTimeChooser;
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
// eslint-disable-next-line import/no-cycle
|
|
4
4
|
import { showFatalError } from '../components/AppWrapper';
|
|
5
5
|
|
|
6
|
+
// Import other helpers
|
|
7
|
+
import waitMs from './waitMs';
|
|
8
|
+
|
|
6
9
|
// Import shared types
|
|
7
10
|
import ErrorWithCode from '../errors/ErrorWithCode';
|
|
8
|
-
import waitMs from '../helpers/waitMs';
|
|
9
11
|
import ReactKitErrorCode from '../types/ReactKitErrorCode';
|
|
10
12
|
|
|
11
13
|
/*----------------------------------------*/
|
|
@@ -164,10 +166,12 @@ const initClient = (
|
|
|
164
166
|
storedSendRequest = async () => {
|
|
165
167
|
throw new Error('Cannot send requests because there is no server');
|
|
166
168
|
};
|
|
169
|
+
noServer = true;
|
|
167
170
|
} else {
|
|
168
171
|
// Store values
|
|
169
172
|
storedSendRequest = opts.sendRequest;
|
|
170
173
|
sessionExpiredMessage = opts.sessionExpiredMessage;
|
|
174
|
+
noServer = false;
|
|
171
175
|
}
|
|
172
176
|
|
|
173
177
|
// Handle universal parts
|
|
@@ -5,10 +5,12 @@ import LogFunction from '../types/LogFunction';
|
|
|
5
5
|
import LogLevel from '../types/LogLevel';
|
|
6
6
|
|
|
7
7
|
// Import shared functions
|
|
8
|
+
// TODO: fix dependency cycle
|
|
9
|
+
// eslint-disable-next-line import/no-cycle
|
|
8
10
|
import visitServerEndpoint from './visitServerEndpoint';
|
|
9
11
|
|
|
10
12
|
// Import status
|
|
11
|
-
import { appHasNoServer } from '
|
|
13
|
+
import { appHasNoServer } from './initClient';
|
|
12
14
|
|
|
13
15
|
/* ------- Metadata Populator ------- */
|
|
14
16
|
|
|
@@ -7,7 +7,7 @@ import ReactKitErrorCode from '../types/ReactKitErrorCode';
|
|
|
7
7
|
// Import helpers
|
|
8
8
|
// TODO: fix dependency cycle
|
|
9
9
|
// eslint-disable-next-line import/no-cycle
|
|
10
|
-
import { getSendRequest } from '
|
|
10
|
+
import { getSendRequest } from './initClient';
|
|
11
11
|
import { showSessionExpiredMessage } from '../components/AppWrapper';
|
|
12
12
|
|
|
13
13
|
/*------------------------------------------------------------------------*/
|