@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.
Files changed (86) hide show
  1. package/dist/BooleanField.js +23 -23
  2. package/dist/BooleanField.js.map +1 -1
  3. package/dist/ConsentFormScreen.d.ts +14 -0
  4. package/dist/ConsentFormScreen.js +93 -0
  5. package/dist/ConsentFormScreen.js.map +1 -0
  6. package/dist/ConsentHistory.d.ts +8 -0
  7. package/dist/ConsentHistory.js +70 -0
  8. package/dist/ConsentHistory.js.map +1 -0
  9. package/dist/ConsentNavigator.d.ts +9 -0
  10. package/dist/ConsentNavigator.js +72 -0
  11. package/dist/ConsentNavigator.js.map +1 -0
  12. package/dist/DataTable.js +1 -1
  13. package/dist/DataTable.js.map +1 -1
  14. package/dist/DateTimeActionSheet.js +24 -8
  15. package/dist/DateTimeActionSheet.js.map +1 -1
  16. package/dist/DateTimeField.d.ts +22 -0
  17. package/dist/DateTimeField.js +187 -67
  18. package/dist/DateTimeField.js.map +1 -1
  19. package/dist/DraggableList.d.ts +66 -0
  20. package/dist/DraggableList.js +241 -0
  21. package/dist/DraggableList.js.map +1 -0
  22. package/dist/Link.js +1 -1
  23. package/dist/Link.js.map +1 -1
  24. package/dist/MarkdownEditor.d.ts +12 -0
  25. package/dist/MarkdownEditor.js +12 -0
  26. package/dist/MarkdownEditor.js.map +1 -0
  27. package/dist/MarkdownEditorField.d.ts +1 -0
  28. package/dist/MarkdownEditorField.js +16 -16
  29. package/dist/MarkdownEditorField.js.map +1 -1
  30. package/dist/Modal.js +11 -1
  31. package/dist/Modal.js.map +1 -1
  32. package/dist/PickerSelect.js +10 -0
  33. package/dist/PickerSelect.js.map +1 -1
  34. package/dist/Slider.js +2 -8
  35. package/dist/Slider.js.map +1 -1
  36. package/dist/TerrenoProvider.js +10 -1
  37. package/dist/TerrenoProvider.js.map +1 -1
  38. package/dist/UpgradeRequiredScreen.d.ts +8 -0
  39. package/dist/UpgradeRequiredScreen.js +10 -0
  40. package/dist/UpgradeRequiredScreen.js.map +1 -0
  41. package/dist/generateConsentHistoryPdf.d.ts +2 -0
  42. package/dist/generateConsentHistoryPdf.js +185 -0
  43. package/dist/generateConsentHistoryPdf.js.map +1 -0
  44. package/dist/index.d.ts +9 -0
  45. package/dist/index.js +9 -0
  46. package/dist/index.js.map +1 -1
  47. package/dist/useConsentForms.d.ts +29 -0
  48. package/dist/useConsentForms.js +50 -0
  49. package/dist/useConsentForms.js.map +1 -0
  50. package/dist/useConsentHistory.d.ts +31 -0
  51. package/dist/useConsentHistory.js +17 -0
  52. package/dist/useConsentHistory.js.map +1 -0
  53. package/dist/useSubmitConsent.d.ts +12 -0
  54. package/dist/useSubmitConsent.js +23 -0
  55. package/dist/useSubmitConsent.js.map +1 -0
  56. package/package.json +4 -2
  57. package/src/BooleanField.test.tsx +3 -5
  58. package/src/BooleanField.tsx +33 -31
  59. package/src/ConsentFormScreen.tsx +216 -0
  60. package/src/ConsentHistory.tsx +249 -0
  61. package/src/ConsentNavigator.test.tsx +111 -0
  62. package/src/ConsentNavigator.tsx +128 -0
  63. package/src/DataTable.tsx +1 -1
  64. package/src/DateTimeActionSheet.tsx +21 -8
  65. package/src/DateTimeField.tsx +416 -133
  66. package/src/DraggableList.tsx +424 -0
  67. package/src/Link.tsx +1 -1
  68. package/src/MarkdownEditor.tsx +66 -0
  69. package/src/MarkdownEditorField.tsx +32 -28
  70. package/src/Modal.tsx +19 -1
  71. package/src/PickerSelect.tsx +11 -0
  72. package/src/Slider.tsx +2 -1
  73. package/src/TerrenoProvider.tsx +10 -1
  74. package/src/TimezonePicker.test.tsx +9 -1
  75. package/src/UpgradeRequiredScreen.tsx +52 -0
  76. package/src/__snapshots__/BooleanField.test.tsx.snap +167 -203
  77. package/src/__snapshots__/DataTable.test.tsx.snap +0 -114
  78. package/src/__snapshots__/Field.test.tsx.snap +53 -69
  79. package/src/__snapshots__/Link.test.tsx.snap +14 -21
  80. package/src/__snapshots__/Slider.test.tsx.snap +0 -7
  81. package/src/__snapshots__/TimezonePicker.test.tsx.snap +0 -4710
  82. package/src/generateConsentHistoryPdf.ts +211 -0
  83. package/src/index.tsx +9 -1
  84. package/src/useConsentForms.ts +70 -0
  85. package/src/useConsentHistory.ts +40 -0
  86. 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" paddingX={2}>
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={String(n)} />
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={String(n)} />
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
- const displayDate = timezone
356
- ? DateTime.fromISO(dateString).setZone(timezone).toFormat("yyyy-MM-dd")
357
- : DateTime.fromISO(dateString).toFormat("yyyy-MM-dd");
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
- datetime = DateTime.fromISO(value).setZone(originalTimezone).set({millisecond: 0, second: 0});
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
- const dateTime = DateTime.fromISO(date, {zone: timezone});
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();