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.
Files changed (69) hide show
  1. package/dist/cjs/index.js +5338 -5515
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/LoadingSpinner.d.ts +2 -2
  4. package/dist/cjs/types/components/SimpleDateChooser.d.ts +3 -1
  5. package/dist/cjs/types/components/SimpleTimeChooser.d.ts +20 -0
  6. package/dist/cjs/types/components/Tooltip.d.ts +1 -1
  7. package/dist/cjs/types/index.d.ts +12 -11
  8. package/dist/cjs/types/types/ParamType.d.ts +10 -10
  9. package/dist/cjs/types/types/ReactKitErrorCode.d.ts +2 -14
  10. package/dist/esm/index.js +5344 -5517
  11. package/dist/esm/index.js.map +1 -1
  12. package/dist/esm/types/components/LoadingSpinner.d.ts +2 -2
  13. package/dist/esm/types/components/SimpleDateChooser.d.ts +3 -1
  14. package/dist/esm/types/components/SimpleTimeChooser.d.ts +20 -0
  15. package/dist/esm/types/components/Tooltip.d.ts +1 -1
  16. package/dist/esm/types/index.d.ts +12 -11
  17. package/dist/esm/types/types/ParamType.d.ts +10 -10
  18. package/dist/esm/types/types/ReactKitErrorCode.d.ts +2 -14
  19. package/dist/index.d.ts +183 -354
  20. package/genEncodedSecret.ts +84 -0
  21. package/package.json +7 -9
  22. package/rollup.config.js +2 -0
  23. package/src/components/AppWrapper.tsx +3 -1
  24. package/src/components/CheckboxButton.tsx +1 -1
  25. package/src/components/Dropdown.tsx +1 -1
  26. package/src/components/LogReviewer.tsx +2 -2
  27. package/src/components/Modal/index.tsx +1 -1
  28. package/src/components/RadioButton.tsx +3 -1
  29. package/src/components/SimpleDateChooser.tsx +62 -26
  30. package/src/components/SimpleTimeChooser.tsx +195 -0
  31. package/src/components/Tooltip.tsx +1 -1
  32. package/src/{client → helpers}/initClient.tsx +5 -1
  33. package/src/helpers/logClientEvent.tsx +3 -1
  34. package/src/helpers/visitServerEndpoint.tsx +1 -1
  35. package/src/index.ts +20 -20
  36. package/src/types/ParamType.ts +10 -10
  37. package/src/types/ReactKitErrorCode.tsx +3 -17
  38. package/dist/cjs/types/components/ItemPicker/index.test.d.ts +0 -1
  39. package/dist/cjs/types/helpers/addDBEditorEndpoints.d.ts +0 -41
  40. package/dist/cjs/types/helpers/genRouteHandler.d.ts +0 -76
  41. package/dist/cjs/types/helpers/handleError.d.ts +0 -18
  42. package/dist/cjs/types/helpers/handleSuccess.d.ts +0 -8
  43. package/dist/cjs/types/helpers/parseUserAgent.d.ts +0 -17
  44. package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/index.d.ts +0 -24
  45. package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -33
  46. package/dist/cjs/types/server/initLogCollection.d.ts +0 -8
  47. package/dist/cjs/types/server/initServer.d.ts +0 -42
  48. package/dist/esm/types/components/ItemPicker/index.test.d.ts +0 -1
  49. package/dist/esm/types/helpers/addDBEditorEndpoints.d.ts +0 -41
  50. package/dist/esm/types/helpers/genRouteHandler.d.ts +0 -76
  51. package/dist/esm/types/helpers/handleError.d.ts +0 -18
  52. package/dist/esm/types/helpers/handleSuccess.d.ts +0 -8
  53. package/dist/esm/types/helpers/parseUserAgent.d.ts +0 -17
  54. package/dist/esm/types/helpers/visitEndpointOnAnotherServer/index.d.ts +0 -24
  55. package/dist/esm/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -33
  56. package/dist/esm/types/server/initLogCollection.d.ts +0 -8
  57. package/dist/esm/types/server/initServer.d.ts +0 -42
  58. package/src/components/ItemPicker/index.test.tsx +0 -783
  59. package/src/helpers/addDBEditorEndpoints.ts +0 -128
  60. package/src/helpers/genRouteHandler.ts +0 -948
  61. package/src/helpers/handleError.ts +0 -65
  62. package/src/helpers/handleSuccess.ts +0 -18
  63. package/src/helpers/parseUserAgent.ts +0 -108
  64. package/src/helpers/visitEndpointOnAnotherServer/index.ts +0 -93
  65. package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +0 -164
  66. package/src/server/initLogCollection.ts +0 -27
  67. package/src/server/initServer.ts +0 -287
  68. /package/dist/cjs/types/{client → helpers}/initClient.d.ts +0 -0
  69. /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.12.3",
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
- "express": "^x.x.x",
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": "^8.3.2",
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": "^17.0.7",
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 '../client/initClient';
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 '../client/initClient';
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 '../client/initClient';
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
- chooseFromPast
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
- chooseFromPast
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 '../../client/initClient';
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
- import { isDarkModeOn } from '../client/initClient';
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
- // (max is 12, default is 6)
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, instead of showing numMonthsToShow months into the future,
40
- // show numMonthsToShow months into the past
41
- chooseFromPast?: boolean,
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
- if (chooseFromPast) {
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
- for (let i = 0; i < numMonthsToShow; i++) {
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
- if (chooseFromPast) {
109
- // Past selection
110
- const numDaysToAdd = (
111
- (month === today.month)
112
- ? today.day // Current month, only add up to today
113
- : numDaysInMonth // Past month, add all days
114
- );
115
- for (let day = 1; day <= numDaysToAdd; day++) {
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
- } else {
152
+ days.push(today.day); // Add current day
119
153
  // Future selection: add all remaining days of the month
120
- const firstDay = (
121
- month === today.month
122
- ? today.day // Current month: start at current date
123
- : 1 // Future month: start at beginning of month
124
- );
125
- for (let day = firstDay; day <= numDaysInMonth; day++) {
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;
@@ -15,7 +15,7 @@ type Props = {
15
15
  // Text of the tooltip
16
16
  text: string,
17
17
  // Children to wrap in the tooltip
18
- children: JSX.Element,
18
+ children: any,
19
19
  };
20
20
 
21
21
  /*------------------------------------------------------------------------*/
@@ -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 '../client/initClient';
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 '../client/initClient';
10
+ import { getSendRequest } from './initClient';
11
11
  import { showSessionExpiredMessage } from '../components/AppWrapper';
12
12
 
13
13
  /*------------------------------------------------------------------------*/