@terreno/ui 0.7.1 → 0.8.0
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/BooleanField.js +23 -23
- package/dist/BooleanField.js.map +1 -1
- package/dist/ConsentFormScreen.d.ts +14 -0
- package/dist/ConsentFormScreen.js +93 -0
- package/dist/ConsentFormScreen.js.map +1 -0
- package/dist/ConsentHistory.d.ts +8 -0
- package/dist/ConsentHistory.js +70 -0
- package/dist/ConsentHistory.js.map +1 -0
- package/dist/ConsentNavigator.d.ts +9 -0
- package/dist/ConsentNavigator.js +72 -0
- package/dist/ConsentNavigator.js.map +1 -0
- package/dist/DataTable.js +1 -1
- package/dist/DataTable.js.map +1 -1
- package/dist/DateTimeActionSheet.js +24 -8
- package/dist/DateTimeActionSheet.js.map +1 -1
- package/dist/DateTimeField.d.ts +22 -0
- package/dist/DateTimeField.js +187 -67
- package/dist/DateTimeField.js.map +1 -1
- package/dist/DraggableList.d.ts +66 -0
- package/dist/DraggableList.js +241 -0
- package/dist/DraggableList.js.map +1 -0
- package/dist/Link.js +1 -1
- package/dist/Link.js.map +1 -1
- package/dist/MarkdownEditor.d.ts +12 -0
- package/dist/MarkdownEditor.js +12 -0
- package/dist/MarkdownEditor.js.map +1 -0
- package/dist/MarkdownEditorField.d.ts +1 -0
- package/dist/MarkdownEditorField.js +16 -16
- package/dist/MarkdownEditorField.js.map +1 -1
- package/dist/Modal.js +11 -1
- package/dist/Modal.js.map +1 -1
- package/dist/PickerSelect.js +10 -0
- package/dist/PickerSelect.js.map +1 -1
- package/dist/Slider.js +2 -8
- package/dist/Slider.js.map +1 -1
- package/dist/TerrenoProvider.js +10 -1
- package/dist/TerrenoProvider.js.map +1 -1
- package/dist/UpgradeRequiredScreen.d.ts +8 -0
- package/dist/UpgradeRequiredScreen.js +10 -0
- package/dist/UpgradeRequiredScreen.js.map +1 -0
- package/dist/generateConsentHistoryPdf.d.ts +2 -0
- package/dist/generateConsentHistoryPdf.js +185 -0
- package/dist/generateConsentHistoryPdf.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/useConsentForms.d.ts +29 -0
- package/dist/useConsentForms.js +50 -0
- package/dist/useConsentForms.js.map +1 -0
- package/dist/useConsentHistory.d.ts +31 -0
- package/dist/useConsentHistory.js +17 -0
- package/dist/useConsentHistory.js.map +1 -0
- package/dist/useSubmitConsent.d.ts +12 -0
- package/dist/useSubmitConsent.js +23 -0
- package/dist/useSubmitConsent.js.map +1 -0
- package/package.json +4 -2
- package/src/BooleanField.test.tsx +3 -5
- package/src/BooleanField.tsx +33 -31
- package/src/ConsentFormScreen.tsx +216 -0
- package/src/ConsentHistory.tsx +249 -0
- package/src/ConsentNavigator.test.tsx +111 -0
- package/src/ConsentNavigator.tsx +128 -0
- package/src/DataTable.tsx +1 -1
- package/src/DateTimeActionSheet.tsx +21 -8
- package/src/DateTimeField.tsx +416 -133
- package/src/DraggableList.tsx +424 -0
- package/src/Link.tsx +1 -1
- package/src/MarkdownEditor.tsx +66 -0
- package/src/MarkdownEditorField.tsx +32 -28
- package/src/Modal.tsx +19 -1
- package/src/PickerSelect.tsx +11 -0
- package/src/Slider.tsx +2 -1
- package/src/TerrenoProvider.tsx +10 -1
- package/src/TimezonePicker.test.tsx +9 -1
- package/src/UpgradeRequiredScreen.tsx +52 -0
- package/src/__snapshots__/BooleanField.test.tsx.snap +167 -203
- package/src/__snapshots__/DataTable.test.tsx.snap +0 -114
- package/src/__snapshots__/Field.test.tsx.snap +53 -69
- package/src/__snapshots__/Link.test.tsx.snap +14 -21
- package/src/__snapshots__/Slider.test.tsx.snap +0 -7
- package/src/__snapshots__/TimezonePicker.test.tsx.snap +0 -4710
- package/src/generateConsentHistoryPdf.ts +211 -0
- package/src/index.tsx +9 -1
- package/src/useConsentForms.ts +70 -0
- package/src/useConsentHistory.ts +40 -0
- package/src/useSubmitConsent.ts +35 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import React, {useState} from "react";
|
|
2
|
+
|
|
3
|
+
import {Box} from "./Box";
|
|
4
|
+
import {Button} from "./Button";
|
|
5
|
+
import {ConsentFormScreen} from "./ConsentFormScreen";
|
|
6
|
+
import {Spinner} from "./Spinner";
|
|
7
|
+
import {Text} from "./Text";
|
|
8
|
+
import {detectLocale, useConsentForms} from "./useConsentForms";
|
|
9
|
+
import type {SubmitConsentBody} from "./useSubmitConsent";
|
|
10
|
+
import {useSubmitConsent} from "./useSubmitConsent";
|
|
11
|
+
|
|
12
|
+
interface ConsentNavigatorProps {
|
|
13
|
+
api: any;
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
onError?: (error: any) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const ConsentNavigator: React.FC<ConsentNavigatorProps> = ({
|
|
20
|
+
api,
|
|
21
|
+
baseUrl,
|
|
22
|
+
children,
|
|
23
|
+
onError,
|
|
24
|
+
}) => {
|
|
25
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
26
|
+
const {forms, isLoading, error, refetch} = useConsentForms(api, baseUrl);
|
|
27
|
+
const {submit, isSubmitting} = useSubmitConsent(api, baseUrl);
|
|
28
|
+
const locale = detectLocale();
|
|
29
|
+
|
|
30
|
+
if (isLoading) {
|
|
31
|
+
console.debug("[ConsentNavigator] Loading pending consents...");
|
|
32
|
+
return (
|
|
33
|
+
<Box
|
|
34
|
+
alignItems="center"
|
|
35
|
+
flex="grow"
|
|
36
|
+
justifyContent="center"
|
|
37
|
+
testID="consent-navigator-loading"
|
|
38
|
+
>
|
|
39
|
+
<Spinner />
|
|
40
|
+
</Box>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (error) {
|
|
45
|
+
const status = (error as any)?.status ?? (error as any)?.originalStatus;
|
|
46
|
+
console.warn("[ConsentNavigator] Error fetching pending consents:", {error, status});
|
|
47
|
+
// On auth errors, pass through to let the app handle re-authentication
|
|
48
|
+
if (status === 401 || status === 403) {
|
|
49
|
+
return <>{children}</>;
|
|
50
|
+
}
|
|
51
|
+
onError?.(error);
|
|
52
|
+
return (
|
|
53
|
+
<Box
|
|
54
|
+
alignItems="center"
|
|
55
|
+
direction="column"
|
|
56
|
+
flex="grow"
|
|
57
|
+
gap={3}
|
|
58
|
+
justifyContent="center"
|
|
59
|
+
padding={6}
|
|
60
|
+
testID="consent-navigator-error"
|
|
61
|
+
>
|
|
62
|
+
<Text align="center" color="error" size="lg">
|
|
63
|
+
Failed to load consent forms
|
|
64
|
+
</Text>
|
|
65
|
+
<Button onClick={refetch} text="Retry" />
|
|
66
|
+
</Box>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (forms.length === 0 || currentIndex >= forms.length) {
|
|
71
|
+
console.debug("[ConsentNavigator] No pending consents, showing app");
|
|
72
|
+
return <>{children}</>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.info(
|
|
76
|
+
`[ConsentNavigator] Showing consent form ${currentIndex + 1}/${forms.length}: ${forms[currentIndex]?.title}`
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const currentForm = forms[currentIndex];
|
|
80
|
+
|
|
81
|
+
const handleAgree = async (data: {
|
|
82
|
+
checkboxValues: Record<string, boolean>;
|
|
83
|
+
signature?: string;
|
|
84
|
+
}) => {
|
|
85
|
+
const body: SubmitConsentBody = {
|
|
86
|
+
agreed: true,
|
|
87
|
+
checkboxValues: data.checkboxValues,
|
|
88
|
+
consentFormId: currentForm.id,
|
|
89
|
+
locale,
|
|
90
|
+
signature: data.signature,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
await submit(body);
|
|
95
|
+
// Always refetch and reset so we pick up the updated pending list.
|
|
96
|
+
// Advancing currentIndex is racy because invalidatesTags shrinks
|
|
97
|
+
// the forms array in the background.
|
|
98
|
+
setCurrentIndex(0);
|
|
99
|
+
await refetch();
|
|
100
|
+
} catch (err) {
|
|
101
|
+
onError?.(err);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const handleDecline = async () => {
|
|
106
|
+
try {
|
|
107
|
+
await submit({
|
|
108
|
+
agreed: false,
|
|
109
|
+
consentFormId: currentForm.id,
|
|
110
|
+
locale,
|
|
111
|
+
});
|
|
112
|
+
setCurrentIndex(0);
|
|
113
|
+
await refetch();
|
|
114
|
+
} catch (err) {
|
|
115
|
+
onError?.(err);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<ConsentFormScreen
|
|
121
|
+
form={currentForm}
|
|
122
|
+
isSubmitting={isSubmitting}
|
|
123
|
+
locale={locale}
|
|
124
|
+
onAgree={handleAgree}
|
|
125
|
+
onDecline={currentForm.required ? undefined : handleDecline}
|
|
126
|
+
/>
|
|
127
|
+
);
|
|
128
|
+
};
|
package/src/DataTable.tsx
CHANGED
|
@@ -36,7 +36,7 @@ const TextCell: FC<{
|
|
|
36
36
|
column: DataTableColumn;
|
|
37
37
|
}> = ({cellData}) => {
|
|
38
38
|
return (
|
|
39
|
-
<Box flex="grow" justifyContent="center"
|
|
39
|
+
<Box flex="grow" justifyContent="center">
|
|
40
40
|
<Text size={cellData.textSize || "md"}>{cellData.value}</Text>
|
|
41
41
|
</Box>
|
|
42
42
|
);
|
|
@@ -222,7 +222,7 @@ const MobileTime = ({
|
|
|
222
222
|
}}
|
|
223
223
|
>
|
|
224
224
|
{hours.map((n) => (
|
|
225
|
-
<Picker.Item key={String(n)} label={String(n)} value={
|
|
225
|
+
<Picker.Item key={String(n)} label={String(n)} value={Number(n)} />
|
|
226
226
|
))}
|
|
227
227
|
</Picker>
|
|
228
228
|
</Box>
|
|
@@ -239,7 +239,7 @@ const MobileTime = ({
|
|
|
239
239
|
}}
|
|
240
240
|
>
|
|
241
241
|
{minutes.map((n) => (
|
|
242
|
-
<Picker.Item key={String(n)} label={String(n)} value={
|
|
242
|
+
<Picker.Item key={String(n)} label={String(n)} value={Number(n)} />
|
|
243
243
|
))}
|
|
244
244
|
</Picker>
|
|
245
245
|
</Box>
|
|
@@ -352,9 +352,14 @@ const DateCalendar = ({
|
|
|
352
352
|
}
|
|
353
353
|
|
|
354
354
|
if (date) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
355
|
+
let displayDate: string;
|
|
356
|
+
if (type === "date") {
|
|
357
|
+
displayDate = DateTime.fromISO(dateString).toUTC().toFormat("yyyy-MM-dd");
|
|
358
|
+
} else if (timezone) {
|
|
359
|
+
displayDate = DateTime.fromISO(dateString).setZone(timezone).toFormat("yyyy-MM-dd");
|
|
360
|
+
} else {
|
|
361
|
+
displayDate = DateTime.fromISO(dateString).toFormat("yyyy-MM-dd");
|
|
362
|
+
}
|
|
358
363
|
markedDates[displayDate] = {
|
|
359
364
|
customStyles: {
|
|
360
365
|
container: {
|
|
@@ -437,7 +442,13 @@ export const DateTimeActionSheet = ({
|
|
|
437
442
|
useEffect(() => {
|
|
438
443
|
let datetime;
|
|
439
444
|
if (value) {
|
|
440
|
-
|
|
445
|
+
if (type === "date") {
|
|
446
|
+
datetime = DateTime.fromISO(value).toUTC().set({millisecond: 0, second: 0});
|
|
447
|
+
} else {
|
|
448
|
+
datetime = DateTime.fromISO(value)
|
|
449
|
+
.setZone(originalTimezone)
|
|
450
|
+
.set({millisecond: 0, second: 0});
|
|
451
|
+
}
|
|
441
452
|
} else {
|
|
442
453
|
datetime = DateTime.now().setZone(originalTimezone).set({millisecond: 0, second: 0});
|
|
443
454
|
}
|
|
@@ -456,7 +467,7 @@ export const DateTimeActionSheet = ({
|
|
|
456
467
|
setDate(datetime.toISO());
|
|
457
468
|
// Reset timezone when the sent date changes.
|
|
458
469
|
setTimezone(originalTimezone);
|
|
459
|
-
}, [value, originalTimezone]);
|
|
470
|
+
}, [value, originalTimezone, type]);
|
|
460
471
|
|
|
461
472
|
// TODO Support 24 hour time for time picker.
|
|
462
473
|
// Note: do not call this if waiting on a state change.
|
|
@@ -469,7 +480,9 @@ export const DateTimeActionSheet = ({
|
|
|
469
480
|
militaryHour = Number(hour) + 12;
|
|
470
481
|
}
|
|
471
482
|
|
|
472
|
-
|
|
483
|
+
// For type="date" the date state is always UTC midnight — parse it in UTC, not the component
|
|
484
|
+
// timezone, to avoid shifting the date when converting back to UTC.
|
|
485
|
+
const dateTime = DateTime.fromISO(date, {zone: type === "date" ? "UTC" : timezone});
|
|
473
486
|
|
|
474
487
|
if (type === "date") {
|
|
475
488
|
const v = dateTime.set({hour: 0, millisecond: 0, minute: 0, second: 0}).toUTC().toISO();
|