dce-reactkit 3.0.1 → 3.0.2
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/README.md +1 -546
- package/dist/cjs/index.js +1782 -0
- package/dist/cjs/index.js.map +1 -0
- package/{lib/esm → dist/cjs}/types/components/AppWrapper.d.ts +26 -1
- package/dist/cjs/types/components/ButtonInputGroup.d.ts +12 -0
- package/dist/cjs/types/components/CheckboxButton.d.ts +20 -0
- package/{lib/cjs/lib → dist/cjs/types}/components/ErrorBox.d.ts +0 -0
- package/{lib/cjs/lib → dist/cjs/types}/components/LoadingSpinner.d.ts +0 -0
- package/{lib/cjs/lib → dist/cjs/types}/components/Modal.d.ts +0 -0
- package/dist/cjs/types/components/RadioButton.d.ts +20 -0
- package/dist/cjs/types/components/SimpleDateChooser.d.ts +22 -0
- package/{lib/esm/lib → dist/cjs/types}/components/TabBox.d.ts +1 -0
- package/{lib/cjs/lib → dist/cjs/types}/errors/ErrorWithCode.d.ts +0 -0
- package/{lib/cjs/lib → dist/cjs/types}/helpers/abbreviate.d.ts +0 -0
- package/{lib/cjs/lib → dist/cjs/types}/helpers/avg.d.ts +0 -0
- package/{lib/cjs/lib → dist/cjs/types}/helpers/ceilToNumDecimals.d.ts +0 -0
- package/{lib/cjs/lib → dist/cjs/types}/helpers/floorToNumDecimals.d.ts +0 -0
- package/{lib/cjs/lib → dist/cjs/types}/helpers/forceNumIntoBounds.d.ts +0 -0
- package/dist/cjs/types/helpers/genRouteHandler.d.ts +32 -0
- package/dist/cjs/types/helpers/getOrdinal.d.ts +8 -0
- package/dist/cjs/types/helpers/getTimeInfoInET.d.ts +17 -0
- package/{lib/cjs/lib → dist/cjs/types}/helpers/handleError.d.ts +5 -1
- package/{lib/cjs/lib → dist/cjs/types}/helpers/handleSuccess.d.ts +0 -0
- package/{lib/cjs/lib → dist/cjs/types}/helpers/padDecimalZeros.d.ts +0 -0
- package/{lib/cjs/lib → dist/cjs/types}/helpers/padZerosLeft.d.ts +0 -0
- package/{lib/cjs/lib → dist/cjs/types}/helpers/roundToNumDecimals.d.ts +0 -0
- package/{lib/cjs/lib → dist/cjs/types}/helpers/sum.d.ts +0 -0
- package/{lib/cjs/lib → dist/cjs/types}/helpers/visitServerEndpoint.d.ts +0 -0
- package/{lib/cjs/lib → dist/cjs/types}/helpers/waitMs.d.ts +0 -0
- package/{lib/cjs/lib → dist/cjs/types}/index.d.ts +13 -6
- package/dist/cjs/types/server/initServer.d.ts +21 -0
- package/{lib/cjs/lib → dist/cjs/types}/types/ModalButtonType.d.ts +0 -0
- package/{lib/cjs/lib → dist/cjs/types}/types/ModalSize.d.ts +0 -0
- package/{lib/cjs/lib → dist/cjs/types}/types/ModalType.d.ts +0 -0
- package/{lib → dist}/cjs/types/types/ParamType.d.ts +1 -1
- package/{lib → dist}/cjs/types/types/ReactKitErrorCode.d.ts +3 -1
- package/{lib/cjs/lib → dist/cjs/types}/types/Variant.d.ts +0 -0
- package/dist/esm/index.js +1739 -0
- package/dist/esm/index.js.map +1 -0
- package/{lib/cjs → dist/esm}/types/components/AppWrapper.d.ts +26 -1
- package/dist/esm/types/components/ButtonInputGroup.d.ts +12 -0
- package/dist/esm/types/components/CheckboxButton.d.ts +20 -0
- package/{lib/cjs → dist/esm}/types/components/ErrorBox.d.ts +0 -0
- package/{lib/cjs → dist/esm}/types/components/LoadingSpinner.d.ts +0 -0
- package/{lib/cjs → dist/esm}/types/components/Modal.d.ts +0 -0
- package/dist/esm/types/components/RadioButton.d.ts +20 -0
- package/dist/esm/types/components/SimpleDateChooser.d.ts +22 -0
- package/{lib/cjs/lib → dist/esm/types}/components/TabBox.d.ts +1 -0
- package/{lib/cjs → dist/esm}/types/errors/ErrorWithCode.d.ts +0 -0
- package/{lib/cjs → dist/esm}/types/helpers/abbreviate.d.ts +0 -0
- package/{lib/cjs → dist/esm}/types/helpers/avg.d.ts +0 -0
- package/{lib/cjs → dist/esm}/types/helpers/ceilToNumDecimals.d.ts +0 -0
- package/{lib/cjs → dist/esm}/types/helpers/floorToNumDecimals.d.ts +0 -0
- package/{lib/cjs → dist/esm}/types/helpers/forceNumIntoBounds.d.ts +0 -0
- package/dist/esm/types/helpers/genRouteHandler.d.ts +32 -0
- package/dist/esm/types/helpers/getOrdinal.d.ts +8 -0
- package/dist/esm/types/helpers/getTimeInfoInET.d.ts +17 -0
- package/{lib/cjs → dist/esm}/types/helpers/handleError.d.ts +5 -1
- package/{lib/cjs → dist/esm}/types/helpers/handleSuccess.d.ts +0 -0
- package/{lib/cjs → dist/esm}/types/helpers/padDecimalZeros.d.ts +0 -0
- package/{lib/cjs → dist/esm}/types/helpers/padZerosLeft.d.ts +0 -0
- package/{lib/cjs → dist/esm}/types/helpers/roundToNumDecimals.d.ts +0 -0
- package/{lib/cjs → dist/esm}/types/helpers/sum.d.ts +0 -0
- package/{lib/cjs → dist/esm}/types/helpers/visitServerEndpoint.d.ts +0 -0
- package/{lib/cjs → dist/esm}/types/helpers/waitMs.d.ts +0 -0
- package/{lib/esm/lib → dist/esm/types}/index.d.ts +13 -6
- package/dist/esm/types/server/initServer.d.ts +21 -0
- package/{lib/cjs → dist/esm}/types/types/ModalButtonType.d.ts +0 -0
- package/{lib/cjs → dist/esm}/types/types/ModalSize.d.ts +0 -0
- package/{lib/cjs → dist/esm}/types/types/ModalType.d.ts +0 -0
- package/{lib/esm/lib → dist/esm/types}/types/ParamType.d.ts +1 -1
- package/{lib/esm/lib → dist/esm/types}/types/ReactKitErrorCode.d.ts +3 -1
- package/{lib/cjs → dist/esm}/types/types/Variant.d.ts +0 -0
- package/{lib → dist}/index.d.ts +232 -11
- package/package.json +18 -29
- package/rollup.config.js +14 -13
- package/src/components/AppWrapper.tsx +74 -12
- package/src/components/ButtonInputGroup.tsx +89 -0
- package/src/components/CheckboxButton.tsx +100 -0
- package/src/components/ErrorBox.tsx +4 -4
- package/src/components/LoadingSpinner.tsx +3 -3
- package/src/components/Modal.tsx +214 -97
- package/src/components/RadioButton.tsx +102 -0
- package/src/components/SimpleDateChooser.tsx +222 -0
- package/src/components/TabBox.tsx +65 -58
- package/src/helpers/genRouteHandler.ts +329 -0
- package/src/helpers/getOrdinal.tsx +13 -0
- package/src/helpers/getTimeInfoInET.tsx +56 -0
- package/src/helpers/handleError.ts +65 -0
- package/src/helpers/handleSuccess.ts +18 -0
- package/src/helpers/visitServerEndpoint.tsx +110 -0
- package/src/index.ts +27 -0
- package/src/server/initServer.ts +53 -0
- package/src/types/ParamType.ts +18 -0
- package/src/types/ReactKitErrorCode.tsx +3 -1
- package/tsconfig.json +9 -7
- package/lib/cjs/index.js +0 -2
- package/lib/cjs/index.js.map +0 -1
- package/lib/cjs/lib/components/AppWrapper.d.ts +0 -35
- package/lib/cjs/lib/helpers/parseRequest.d.ts +0 -21
- package/lib/cjs/lib/types/ParamType.d.ts +0 -17
- package/lib/cjs/lib/types/ReactKitErrorCode.d.ts +0 -13
- package/lib/cjs/types/components/TabBox.d.ts +0 -11
- package/lib/cjs/types/helpers/alert.d.ts +0 -2
- package/lib/cjs/types/helpers/confirm.d.ts +0 -2
- package/lib/cjs/types/helpers/parseRequest.d.ts +0 -21
- package/lib/cjs/types/helpers/showFatalError.d.ts +0 -2
- package/lib/cjs/types/index.d.ts +0 -22
- package/lib/esm/index.js +0 -16
- package/lib/esm/index.js.map +0 -1
- package/lib/esm/lib/components/AppWrapper.d.ts +0 -35
- package/lib/esm/lib/components/ErrorBox.d.ts +0 -12
- package/lib/esm/lib/components/LoadingSpinner.d.ts +0 -7
- package/lib/esm/lib/components/Modal.d.ts +0 -40
- package/lib/esm/lib/errors/ErrorWithCode.d.ts +0 -9
- package/lib/esm/lib/helpers/abbreviate.d.ts +0 -10
- package/lib/esm/lib/helpers/avg.d.ts +0 -8
- package/lib/esm/lib/helpers/ceilToNumDecimals.d.ts +0 -9
- package/lib/esm/lib/helpers/floorToNumDecimals.d.ts +0 -9
- package/lib/esm/lib/helpers/forceNumIntoBounds.d.ts +0 -10
- package/lib/esm/lib/helpers/handleError.d.ts +0 -14
- package/lib/esm/lib/helpers/handleSuccess.d.ts +0 -8
- package/lib/esm/lib/helpers/padDecimalZeros.d.ts +0 -10
- package/lib/esm/lib/helpers/padZerosLeft.d.ts +0 -9
- package/lib/esm/lib/helpers/parseRequest.d.ts +0 -21
- package/lib/esm/lib/helpers/roundToNumDecimals.d.ts +0 -9
- package/lib/esm/lib/helpers/sum.d.ts +0 -8
- package/lib/esm/lib/helpers/visitServerEndpoint.d.ts +0 -23
- package/lib/esm/lib/helpers/waitMs.d.ts +0 -7
- package/lib/esm/lib/types/ModalButtonType.d.ts +0 -17
- package/lib/esm/lib/types/ModalSize.d.ts +0 -11
- package/lib/esm/lib/types/ModalType.d.ts +0 -16
- package/lib/esm/lib/types/Variant.d.ts +0 -15
- package/lib/esm/types/components/ErrorBox.d.ts +0 -12
- package/lib/esm/types/components/LoadingSpinner.d.ts +0 -7
- package/lib/esm/types/components/Modal.d.ts +0 -40
- package/lib/esm/types/components/TabBox.d.ts +0 -11
- package/lib/esm/types/errors/ErrorWithCode.d.ts +0 -9
- package/lib/esm/types/helpers/abbreviate.d.ts +0 -10
- package/lib/esm/types/helpers/alert.d.ts +0 -2
- package/lib/esm/types/helpers/avg.d.ts +0 -8
- package/lib/esm/types/helpers/ceilToNumDecimals.d.ts +0 -9
- package/lib/esm/types/helpers/confirm.d.ts +0 -2
- package/lib/esm/types/helpers/floorToNumDecimals.d.ts +0 -9
- package/lib/esm/types/helpers/forceNumIntoBounds.d.ts +0 -10
- package/lib/esm/types/helpers/handleError.d.ts +0 -14
- package/lib/esm/types/helpers/handleSuccess.d.ts +0 -8
- package/lib/esm/types/helpers/padDecimalZeros.d.ts +0 -10
- package/lib/esm/types/helpers/padZerosLeft.d.ts +0 -9
- package/lib/esm/types/helpers/parseRequest.d.ts +0 -21
- package/lib/esm/types/helpers/roundToNumDecimals.d.ts +0 -9
- package/lib/esm/types/helpers/showFatalError.d.ts +0 -2
- package/lib/esm/types/helpers/sum.d.ts +0 -8
- package/lib/esm/types/helpers/visitServerEndpoint.d.ts +0 -23
- package/lib/esm/types/helpers/waitMs.d.ts +0 -7
- package/lib/esm/types/index.d.ts +0 -22
- package/lib/esm/types/types/ModalButtonType.d.ts +0 -17
- package/lib/esm/types/types/ModalSize.d.ts +0 -11
- package/lib/esm/types/types/ModalType.d.ts +0 -16
- package/lib/esm/types/types/ParamType.d.ts +0 -17
- package/lib/esm/types/types/ReactKitErrorCode.d.ts +0 -13
- package/lib/esm/types/types/Variant.d.ts +0 -15
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A very simple, lightweight date chooser
|
|
3
|
+
* @author Gabe Abrams
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Import React
|
|
7
|
+
import React from 'react';
|
|
8
|
+
|
|
9
|
+
// Import helpers
|
|
10
|
+
import getOrdinal from '../helpers/getOrdinal';
|
|
11
|
+
import getTimeInfoInET from '../helpers/getTimeInfoInET';
|
|
12
|
+
|
|
13
|
+
/*------------------------------------------------------------------------*/
|
|
14
|
+
/* Types */
|
|
15
|
+
/*------------------------------------------------------------------------*/
|
|
16
|
+
|
|
17
|
+
type Props = {
|
|
18
|
+
// Aria label
|
|
19
|
+
ariaLabel: string,
|
|
20
|
+
// Name of the chooser (machine-readable, hyphenated)
|
|
21
|
+
name: string,
|
|
22
|
+
// Currently selected month
|
|
23
|
+
month: number,
|
|
24
|
+
// Currently selected day
|
|
25
|
+
day: number,
|
|
26
|
+
// Currently selected year
|
|
27
|
+
year: number,
|
|
28
|
+
/**
|
|
29
|
+
* Handler for when date changes
|
|
30
|
+
* @param month new 1-indexed month number
|
|
31
|
+
* @param day new 1-indexed day number
|
|
32
|
+
* @param year new full year number
|
|
33
|
+
*/
|
|
34
|
+
onChange: (month: number, day: number, year: number) => void,
|
|
35
|
+
// Number of months in the future to allow the user to schedule into
|
|
36
|
+
// (max is 12)
|
|
37
|
+
numMonthsToShow?: number,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/*------------------------------------------------------------------------*/
|
|
41
|
+
/* Constants */
|
|
42
|
+
/*------------------------------------------------------------------------*/
|
|
43
|
+
|
|
44
|
+
// Constants
|
|
45
|
+
const MONTH_NAMES = [
|
|
46
|
+
'January',
|
|
47
|
+
'February',
|
|
48
|
+
'March',
|
|
49
|
+
'April',
|
|
50
|
+
'May',
|
|
51
|
+
'June',
|
|
52
|
+
'July',
|
|
53
|
+
'August',
|
|
54
|
+
'September',
|
|
55
|
+
'October',
|
|
56
|
+
'November',
|
|
57
|
+
'December',
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
/*------------------------------------------------------------------------*/
|
|
61
|
+
/* Component */
|
|
62
|
+
/*------------------------------------------------------------------------*/
|
|
63
|
+
|
|
64
|
+
const SimpleDateChooser: React.FC<Props> = (props) => {
|
|
65
|
+
/*------------------------------------------------------------------------*/
|
|
66
|
+
/* Setup */
|
|
67
|
+
/*------------------------------------------------------------------------*/
|
|
68
|
+
|
|
69
|
+
/* -------------- Props ------------- */
|
|
70
|
+
|
|
71
|
+
const {
|
|
72
|
+
ariaLabel,
|
|
73
|
+
name,
|
|
74
|
+
month,
|
|
75
|
+
day,
|
|
76
|
+
year,
|
|
77
|
+
onChange,
|
|
78
|
+
} = props;
|
|
79
|
+
const numMonthsToShow = Math.min(props.numMonthsToShow ?? 6, 12);
|
|
80
|
+
|
|
81
|
+
/*------------------------------------------------------------------------*/
|
|
82
|
+
/* Render */
|
|
83
|
+
/*------------------------------------------------------------------------*/
|
|
84
|
+
|
|
85
|
+
/*----------------------------------------*/
|
|
86
|
+
/* Main UI */
|
|
87
|
+
/*----------------------------------------*/
|
|
88
|
+
|
|
89
|
+
// Determine the set of choices allowed
|
|
90
|
+
const today = getTimeInfoInET();
|
|
91
|
+
const choices: {
|
|
92
|
+
choiceName: string,
|
|
93
|
+
month: number,
|
|
94
|
+
days: number[],
|
|
95
|
+
year: number,
|
|
96
|
+
}[] = [];
|
|
97
|
+
for (let i = 0; i < numMonthsToShow; i++) {
|
|
98
|
+
// Get month and year info
|
|
99
|
+
const unmoddedMonth = (today.month + i);
|
|
100
|
+
const month = (
|
|
101
|
+
unmoddedMonth > 12
|
|
102
|
+
? unmoddedMonth - 12
|
|
103
|
+
: unmoddedMonth
|
|
104
|
+
);
|
|
105
|
+
const monthName = MONTH_NAMES[month - 1];
|
|
106
|
+
const year = (
|
|
107
|
+
unmoddedMonth > 12
|
|
108
|
+
? today.year + 1
|
|
109
|
+
: today.year
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// Figure out which days are allowed
|
|
113
|
+
const days = [];
|
|
114
|
+
const numDaysInMonth = (new Date(year, month, 0)).getDate();
|
|
115
|
+
const firstDay = (
|
|
116
|
+
month === today.month
|
|
117
|
+
? today.day // Current month: start at current date
|
|
118
|
+
: 1 // Future month: start at beginning of month
|
|
119
|
+
);
|
|
120
|
+
for (let day = firstDay; day <= numDaysInMonth; day++) {
|
|
121
|
+
days.push(day);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
choices.push({
|
|
125
|
+
choiceName: `${monthName} ${year}`,
|
|
126
|
+
month,
|
|
127
|
+
year,
|
|
128
|
+
days,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Create choice options
|
|
133
|
+
const monthOptions: React.ReactNode[] = [];
|
|
134
|
+
const dayOptions: React.ReactNode[] = [];
|
|
135
|
+
choices.forEach((choice) => {
|
|
136
|
+
// Create month option
|
|
137
|
+
monthOptions.push(
|
|
138
|
+
<option
|
|
139
|
+
key={choice.month}
|
|
140
|
+
value={choice.month}
|
|
141
|
+
aria-label={`choose ${choice.choiceName}`}
|
|
142
|
+
onSelect={() => {
|
|
143
|
+
onChange(choice.month, choice.days[0], choice.year);
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
{choice.choiceName}
|
|
147
|
+
</option>
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
if (month === choice.month) {
|
|
151
|
+
// This is the currently selected month
|
|
152
|
+
// Create day options
|
|
153
|
+
choice.days.forEach((dayChoice) => {
|
|
154
|
+
const ordinal = getOrdinal(dayChoice);
|
|
155
|
+
dayOptions.push(
|
|
156
|
+
<option
|
|
157
|
+
key={dayChoice}
|
|
158
|
+
value={dayChoice}
|
|
159
|
+
aria-label={`choose date ${dayChoice}`}
|
|
160
|
+
>
|
|
161
|
+
{dayChoice}
|
|
162
|
+
{ordinal}
|
|
163
|
+
</option>
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<div
|
|
171
|
+
className="SimpleDateChooser d-inline-block"
|
|
172
|
+
aria-label={`date chooser with selected date: ${month} ${day}, ${year}`}
|
|
173
|
+
>
|
|
174
|
+
{/* Month Chooser */}
|
|
175
|
+
<select
|
|
176
|
+
aria-label={`month for ${ariaLabel}`}
|
|
177
|
+
className="custom-select d-inline-block mr-1"
|
|
178
|
+
style={{ width: 'auto' }}
|
|
179
|
+
id={`SimpleDateChooser-${name}-month`}
|
|
180
|
+
value={month}
|
|
181
|
+
onChange={(e) => {
|
|
182
|
+
const choice = choices[e.target.selectedIndex];
|
|
183
|
+
|
|
184
|
+
// Change day, month, and year
|
|
185
|
+
onChange(
|
|
186
|
+
choice.month,
|
|
187
|
+
choice.days[0],
|
|
188
|
+
choice.year,
|
|
189
|
+
);
|
|
190
|
+
}}
|
|
191
|
+
>
|
|
192
|
+
{monthOptions}
|
|
193
|
+
</select>
|
|
194
|
+
|
|
195
|
+
{/* Day Chooser */}
|
|
196
|
+
<select
|
|
197
|
+
aria-label={`day for ${ariaLabel}`}
|
|
198
|
+
className="custom-select d-inline-block"
|
|
199
|
+
style={{ width: 'auto' }}
|
|
200
|
+
id={`SimpleDateChooser-${name}-day`}
|
|
201
|
+
value={day}
|
|
202
|
+
onChange={(e) => {
|
|
203
|
+
// Only change the day
|
|
204
|
+
onChange(
|
|
205
|
+
month,
|
|
206
|
+
Number.parseInt(e.target.value, 10),
|
|
207
|
+
year,
|
|
208
|
+
);
|
|
209
|
+
}}
|
|
210
|
+
>
|
|
211
|
+
{dayOptions}
|
|
212
|
+
</select>
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/*------------------------------------------------------------------------*/
|
|
218
|
+
/* Wrap Up */
|
|
219
|
+
/*------------------------------------------------------------------------*/
|
|
220
|
+
|
|
221
|
+
// Export component
|
|
222
|
+
export default SimpleDateChooser;
|
|
@@ -15,6 +15,8 @@ type Props = {
|
|
|
15
15
|
title: React.ReactNode,
|
|
16
16
|
// Children/contents inside the box
|
|
17
17
|
children: React.ReactNode,
|
|
18
|
+
// If true, don't add padding below the tab box
|
|
19
|
+
noBottomPadding?: boolean,
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
/*------------------------------------------------------------------------*/
|
|
@@ -22,63 +24,67 @@ type Props = {
|
|
|
22
24
|
/*------------------------------------------------------------------------*/
|
|
23
25
|
|
|
24
26
|
const style = `
|
|
25
|
-
/* Tab Box */
|
|
26
|
-
.TabBox-box {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
/*
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
27
|
+
/* Tab Box */
|
|
28
|
+
.TabBox-box {
|
|
29
|
+
/* Light Border */
|
|
30
|
+
border: 0.15rem solid #dedede;
|
|
31
|
+
|
|
32
|
+
/* Rounded Corners (except top-left) */
|
|
33
|
+
border-bottom-right-radius: 0.3rem;
|
|
34
|
+
border-bottom-left-radius: 0.3rem;
|
|
35
|
+
border-top-right-radius: 0.3rem;
|
|
36
|
+
|
|
37
|
+
/* Very Light Gray Border */
|
|
38
|
+
background: #fdfdfd;
|
|
39
|
+
|
|
40
|
+
/* Align Contents on Left */
|
|
41
|
+
text-align: left;
|
|
42
|
+
|
|
43
|
+
/* Add Text Padding */
|
|
44
|
+
padding-left: 0.3rem;
|
|
45
|
+
padding-right: 0.3rem;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Container for Title */
|
|
49
|
+
.TabBox-title-container {
|
|
50
|
+
/* Place on Left */
|
|
51
|
+
position: relative;
|
|
52
|
+
left: 0;
|
|
53
|
+
text-align: left;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Tab-style Title */
|
|
57
|
+
.TabBox-title {
|
|
58
|
+
/* Place so it Barely Overlaps the Box Border */
|
|
59
|
+
display: inline-block;
|
|
60
|
+
position: relative;
|
|
61
|
+
top: 0.15rem; /* Gives Illusion that Border Doesn't Exist Below Tab */
|
|
62
|
+
|
|
63
|
+
/* Title-sized Font */
|
|
64
|
+
font-size: 1.5rem;
|
|
65
|
+
|
|
66
|
+
/* Add Border on Top and Sides */
|
|
67
|
+
border-top: 0.15rem solid #dedede;
|
|
68
|
+
border-left: 0.15rem solid #dedede;
|
|
69
|
+
border-right: 0.15rem solid #dedede;
|
|
70
|
+
|
|
71
|
+
/* Round the Top Corners */
|
|
72
|
+
border-top-left-radius: 0.3rem;
|
|
73
|
+
border-top-right-radius: 0.3rem;
|
|
74
|
+
|
|
75
|
+
/* Add Text Padding */
|
|
76
|
+
padding-left: 0.5rem;
|
|
77
|
+
padding-right: 0.5rem;
|
|
78
|
+
|
|
79
|
+
/* Match Background Color of Box */
|
|
80
|
+
background: #fdfdfd;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Make the TabBox's Children Appear Above Title if Overlap Occurs */
|
|
84
|
+
.TabBox-children {
|
|
85
|
+
position: relative;
|
|
86
|
+
z-index: 1;
|
|
87
|
+
}
|
|
82
88
|
`;
|
|
83
89
|
|
|
84
90
|
/*------------------------------------------------------------------------*/
|
|
@@ -95,6 +101,7 @@ const TabBox: React.FC<Props> = (props) => {
|
|
|
95
101
|
const {
|
|
96
102
|
title,
|
|
97
103
|
children,
|
|
104
|
+
noBottomPadding,
|
|
98
105
|
} = props;
|
|
99
106
|
|
|
100
107
|
/*------------------------------------------------------------------------*/
|
|
@@ -107,7 +114,7 @@ const TabBox: React.FC<Props> = (props) => {
|
|
|
107
114
|
|
|
108
115
|
// Full UI
|
|
109
116
|
return (
|
|
110
|
-
<div>
|
|
117
|
+
<div className={`TabBox-container ${noBottomPadding ? '' : 'mb-2'}`}>
|
|
111
118
|
{/* Style */}
|
|
112
119
|
<style>{style}</style>
|
|
113
120
|
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
// Import caccl functions
|
|
2
|
+
import { cacclGetLaunchInfo } from '../server/initServer';
|
|
3
|
+
|
|
4
|
+
// Import shared types
|
|
5
|
+
import ReactKitErrorCode from '../types/ReactKitErrorCode';
|
|
6
|
+
import ParamType from '../types/ParamType';
|
|
7
|
+
|
|
8
|
+
// Import helpers
|
|
9
|
+
import handleError from './handleError';
|
|
10
|
+
import handleSuccess from './handleSuccess';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Generate an express API route handler
|
|
14
|
+
* @author Gabe Abrams
|
|
15
|
+
* @param opts object containing all arguments
|
|
16
|
+
* @param opts.paramTypes map containing the types for each parameter that is
|
|
17
|
+
* included in the request (map: param name => type)
|
|
18
|
+
* @param opts.handler function that processes the request
|
|
19
|
+
* @returns express route handler that takes the following arguments:
|
|
20
|
+
* params (map: param name => value), handleSuccess (function for handling
|
|
21
|
+
* successful requests), handleError (function for handling failed requests),
|
|
22
|
+
* req (express request object), res (express response object),
|
|
23
|
+
* next (express next function)
|
|
24
|
+
*/
|
|
25
|
+
const genRouteHandler = (
|
|
26
|
+
opts: {
|
|
27
|
+
paramTypes?: {
|
|
28
|
+
[k: string]: ParamType
|
|
29
|
+
},
|
|
30
|
+
handler: (
|
|
31
|
+
opts: {
|
|
32
|
+
params: {
|
|
33
|
+
[k: string]: any
|
|
34
|
+
},
|
|
35
|
+
handleSuccess: (body: any) => void,
|
|
36
|
+
handleError: (error: any) => void,
|
|
37
|
+
req: any,
|
|
38
|
+
res: any,
|
|
39
|
+
next: () => void,
|
|
40
|
+
},
|
|
41
|
+
) => void,
|
|
42
|
+
},
|
|
43
|
+
) => {
|
|
44
|
+
// Return a route handler
|
|
45
|
+
return async (req: any, res: any, next: () => void) => {
|
|
46
|
+
// Output params
|
|
47
|
+
const output: { [k in string]: any } = {};
|
|
48
|
+
|
|
49
|
+
/*----------------------------------------*/
|
|
50
|
+
/* Parse Params */
|
|
51
|
+
/*----------------------------------------*/
|
|
52
|
+
|
|
53
|
+
// Process items one by one
|
|
54
|
+
const paramList = Object.entries(opts.paramTypes ?? {});
|
|
55
|
+
for (let i = 0; i < paramList.length; i++) {
|
|
56
|
+
const [name, type] = paramList[i];
|
|
57
|
+
|
|
58
|
+
// Find the value as a string
|
|
59
|
+
const value = (
|
|
60
|
+
req.params[name]
|
|
61
|
+
|| req.query[name]
|
|
62
|
+
|| req.body[name]
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Parse
|
|
66
|
+
if (type === ParamType.Boolean || type === ParamType.BooleanOptional) {
|
|
67
|
+
// Boolean
|
|
68
|
+
|
|
69
|
+
// Handle case where value doesn't exist
|
|
70
|
+
if (value === undefined) {
|
|
71
|
+
if (type === ParamType.BooleanOptional) {
|
|
72
|
+
output[name] = undefined;
|
|
73
|
+
} else {
|
|
74
|
+
return handleError(
|
|
75
|
+
res,
|
|
76
|
+
{
|
|
77
|
+
message: `Parameter ${name} is required, but it was not included.`,
|
|
78
|
+
code: ReactKitErrorCode.MissingParameter,
|
|
79
|
+
status: 422,
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
// Value exists
|
|
85
|
+
|
|
86
|
+
// Simplify value
|
|
87
|
+
const simpleVal = (
|
|
88
|
+
String(value)
|
|
89
|
+
.trim()
|
|
90
|
+
.toLowerCase()
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// Parse
|
|
94
|
+
output[name] = (
|
|
95
|
+
[
|
|
96
|
+
'true',
|
|
97
|
+
'yes',
|
|
98
|
+
'y',
|
|
99
|
+
'1',
|
|
100
|
+
't',
|
|
101
|
+
].indexOf(simpleVal) >= 0
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
} else if (type === ParamType.Float || type === ParamType.FloatOptional) {
|
|
105
|
+
// Float
|
|
106
|
+
|
|
107
|
+
// Handle case where value doesn't exist
|
|
108
|
+
if (value === undefined) {
|
|
109
|
+
if (type === ParamType.FloatOptional) {
|
|
110
|
+
output[name] = undefined;
|
|
111
|
+
} else {
|
|
112
|
+
return handleError(
|
|
113
|
+
res,
|
|
114
|
+
{
|
|
115
|
+
message: `Parameter ${name} is required, but it was not included.`,
|
|
116
|
+
code: ReactKitErrorCode.MissingParameter,
|
|
117
|
+
status: 422,
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
} else if (!Number.isNaN(Number.parseFloat(String(value)))) {
|
|
122
|
+
// Value is a number
|
|
123
|
+
output[name] = Number.parseFloat(String(value));
|
|
124
|
+
} else {
|
|
125
|
+
// Issue!
|
|
126
|
+
return handleError(
|
|
127
|
+
res,
|
|
128
|
+
{
|
|
129
|
+
message: `Request data was malformed: ${name} was not a valid float.`,
|
|
130
|
+
code: ReactKitErrorCode.InvalidParameter,
|
|
131
|
+
status: 422,
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
} else if (type === ParamType.Int || type === ParamType.IntOptional) {
|
|
136
|
+
// Int
|
|
137
|
+
|
|
138
|
+
// Handle case where value doesn't exist
|
|
139
|
+
if (value === undefined) {
|
|
140
|
+
if (type === ParamType.IntOptional) {
|
|
141
|
+
output[name] = undefined;
|
|
142
|
+
} else {
|
|
143
|
+
return handleError(
|
|
144
|
+
res,
|
|
145
|
+
{
|
|
146
|
+
message: `Parameter ${name} is required, but it was not included.`,
|
|
147
|
+
code: ReactKitErrorCode.MissingParameter,
|
|
148
|
+
status: 422,
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
} else if (!Number.isNaN(Number.parseInt(String(value), 10))) {
|
|
153
|
+
// Value is a number
|
|
154
|
+
output[name] = Number.parseInt(String(value), 10);
|
|
155
|
+
} else {
|
|
156
|
+
// Issue!
|
|
157
|
+
return handleError(
|
|
158
|
+
res,
|
|
159
|
+
{
|
|
160
|
+
message: `Request data was malformed: ${name} was not a valid int.`,
|
|
161
|
+
code: ReactKitErrorCode.InvalidParameter,
|
|
162
|
+
status: 422,
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
} else if (type === ParamType.JSON || type === ParamType.JSONOptional) {
|
|
167
|
+
// Stringified JSON
|
|
168
|
+
|
|
169
|
+
// Handle case where value doesn't exist
|
|
170
|
+
if (value === undefined) {
|
|
171
|
+
if (type === ParamType.JSONOptional) {
|
|
172
|
+
output[name] = undefined;
|
|
173
|
+
} else {
|
|
174
|
+
return handleError(
|
|
175
|
+
res,
|
|
176
|
+
{
|
|
177
|
+
message: `Parameter ${name} is required, but it was not included.`,
|
|
178
|
+
code: ReactKitErrorCode.MissingParameter,
|
|
179
|
+
status: 422,
|
|
180
|
+
},
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
// Value exists
|
|
185
|
+
|
|
186
|
+
// Parse
|
|
187
|
+
try {
|
|
188
|
+
output[name] = JSON.parse(String(value));
|
|
189
|
+
} catch (err) {
|
|
190
|
+
return handleError(
|
|
191
|
+
res,
|
|
192
|
+
{
|
|
193
|
+
message: `Request data was malformed: ${name} was not a valid JSON payload.`,
|
|
194
|
+
code: ReactKitErrorCode.InvalidParameter,
|
|
195
|
+
status: 422,
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} else if (type === ParamType.String || type === ParamType.StringOptional) {
|
|
201
|
+
// String
|
|
202
|
+
|
|
203
|
+
// Handle case where value doesn't exist
|
|
204
|
+
if (value === undefined) {
|
|
205
|
+
if (type === ParamType.StringOptional) {
|
|
206
|
+
output[name] = undefined;
|
|
207
|
+
} else {
|
|
208
|
+
return handleError(
|
|
209
|
+
res,
|
|
210
|
+
{
|
|
211
|
+
message: `Parameter ${name} is required, but it was not included.`,
|
|
212
|
+
code: ReactKitErrorCode.MissingParameter,
|
|
213
|
+
status: 422,
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
// Value exists
|
|
219
|
+
|
|
220
|
+
// Leave as is
|
|
221
|
+
output[name] = value;
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
// No valid data type
|
|
225
|
+
return handleError(
|
|
226
|
+
res,
|
|
227
|
+
{
|
|
228
|
+
message: `An internal error occurred: we could not determine the type of ${name}.`,
|
|
229
|
+
code: ReactKitErrorCode.InvalidParameter,
|
|
230
|
+
status: 422,
|
|
231
|
+
},
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/*----------------------------------------*/
|
|
237
|
+
/* Launch Info */
|
|
238
|
+
/*----------------------------------------*/
|
|
239
|
+
|
|
240
|
+
// Get launch info
|
|
241
|
+
const { launched, launchInfo } = cacclGetLaunchInfo(req);
|
|
242
|
+
if (!launched || !launchInfo) {
|
|
243
|
+
return handleError(
|
|
244
|
+
res,
|
|
245
|
+
{
|
|
246
|
+
message: 'Your session has expired. Please refresh the page and try again.',
|
|
247
|
+
code: ReactKitErrorCode.SessionExpired,
|
|
248
|
+
status: 440,
|
|
249
|
+
},
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Error if user info cannot be found
|
|
254
|
+
if (
|
|
255
|
+
!launchInfo.userId
|
|
256
|
+
|| !launchInfo.userFirstName
|
|
257
|
+
|| !launchInfo.userLastName
|
|
258
|
+
|| (
|
|
259
|
+
launchInfo.notInCourse
|
|
260
|
+
&& !launchInfo.isAdmin
|
|
261
|
+
)
|
|
262
|
+
|| (
|
|
263
|
+
!launchInfo.isTTM
|
|
264
|
+
&& !launchInfo.isLearner
|
|
265
|
+
&& !launchInfo.isAdmin
|
|
266
|
+
)
|
|
267
|
+
) {
|
|
268
|
+
return handleError(
|
|
269
|
+
res,
|
|
270
|
+
{
|
|
271
|
+
message: 'Your session was invalid. Please refresh the page and try again.',
|
|
272
|
+
code: ReactKitErrorCode.SessionExpired,
|
|
273
|
+
status: 440,
|
|
274
|
+
},
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Add launch info to output
|
|
279
|
+
output.userId = launchInfo.userId;
|
|
280
|
+
output.userFirstName = launchInfo.userFirstName;
|
|
281
|
+
output.userLastName = launchInfo.userLastName;
|
|
282
|
+
output.isLearner = !!launchInfo.isLearner;
|
|
283
|
+
output.isTTM = !!launchInfo.isTTM;
|
|
284
|
+
output.isAdmin = !!launchInfo.isAdmin;
|
|
285
|
+
output.isWatchingInPrivate = !!(req.session.isWatchingInPrivate);
|
|
286
|
+
|
|
287
|
+
/*----------------------------------------*/
|
|
288
|
+
/* Require Course Consistency */
|
|
289
|
+
/*----------------------------------------*/
|
|
290
|
+
|
|
291
|
+
// Make sure the user actually launched from the appropriate course
|
|
292
|
+
if (
|
|
293
|
+
output.courseId
|
|
294
|
+
&& launchInfo.courseId
|
|
295
|
+
&& output.courseId !== launchInfo.courseId
|
|
296
|
+
&& !output.isTTM
|
|
297
|
+
&& !output.isAdmin
|
|
298
|
+
) {
|
|
299
|
+
// Course of interest is not the launch course
|
|
300
|
+
return handleError(
|
|
301
|
+
res,
|
|
302
|
+
{
|
|
303
|
+
message: 'You switched sessions by opening Immersive Classroom in another tab. Please refresh the page and try again.',
|
|
304
|
+
code: ReactKitErrorCode.WrongCourse,
|
|
305
|
+
status: 401,
|
|
306
|
+
},
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/*------------------------------------------------------------------------*/
|
|
311
|
+
/* Call handler */
|
|
312
|
+
/*------------------------------------------------------------------------*/
|
|
313
|
+
|
|
314
|
+
opts.handler({
|
|
315
|
+
params: output,
|
|
316
|
+
handleSuccess: (body: any) => {
|
|
317
|
+
return handleSuccess(res, body);
|
|
318
|
+
},
|
|
319
|
+
handleError: (error: any) => {
|
|
320
|
+
return handleError(res, error);
|
|
321
|
+
},
|
|
322
|
+
req,
|
|
323
|
+
res,
|
|
324
|
+
next,
|
|
325
|
+
});
|
|
326
|
+
};
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
export default genRouteHandler;
|