@seamapi/react 1.61.0 → 1.61.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.
Files changed (127) hide show
  1. package/README.md +1 -1
  2. package/dist/elements.js +3679 -3727
  3. package/dist/elements.js.map +1 -1
  4. package/dist/index.css +134 -25
  5. package/dist/index.css.map +1 -1
  6. package/dist/index.min.css +1 -1
  7. package/dist/index.min.css.map +1 -1
  8. package/lib/dates.d.ts +6 -67
  9. package/lib/dates.js +13 -111
  10. package/lib/dates.js.map +1 -1
  11. package/lib/icons/CheckGreen.d.ts +2 -0
  12. package/lib/icons/CheckGreen.js +7 -0
  13. package/lib/icons/CheckGreen.js.map +1 -0
  14. package/lib/icons/CloseWhite.d.ts +2 -0
  15. package/lib/icons/CloseWhite.js +7 -0
  16. package/lib/icons/CloseWhite.js.map +1 -0
  17. package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js +14 -20
  18. package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js.map +1 -1
  19. package/lib/seam/components/AccessCodeTable/CodeDetails.js +4 -6
  20. package/lib/seam/components/AccessCodeTable/CodeDetails.js.map +1 -1
  21. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.js +8 -8
  22. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.js.map +1 -1
  23. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.js +2 -2
  24. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.js.map +1 -1
  25. package/lib/seam/components/ClimateSettingScheduleDetails/dates.d.ts +1 -0
  26. package/lib/seam/components/ClimateSettingScheduleDetails/dates.js +9 -0
  27. package/lib/seam/components/ClimateSettingScheduleDetails/dates.js.map +1 -0
  28. package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleRowDetails.js +4 -6
  29. package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleRowDetails.js.map +1 -1
  30. package/lib/seam/components/CreateAccessCodeForm/CreateAccessCodeForm.js +3 -4
  31. package/lib/seam/components/CreateAccessCodeForm/CreateAccessCodeForm.js.map +1 -1
  32. package/lib/seam/components/CreateClimateSettingScheduleForm/CreateClimateSettingScheduleForm.js +3 -4
  33. package/lib/seam/components/CreateClimateSettingScheduleForm/CreateClimateSettingScheduleForm.js.map +1 -1
  34. package/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.js +1 -2
  35. package/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.js.map +1 -1
  36. package/lib/seam/components/EditAccessCodeForm/EditAccessCodeForm.js +3 -4
  37. package/lib/seam/components/EditAccessCodeForm/EditAccessCodeForm.js.map +1 -1
  38. package/lib/seam/components/SupportedDeviceTable/FilterCategoryMenu.js +12 -9
  39. package/lib/seam/components/SupportedDeviceTable/FilterCategoryMenu.js.map +1 -1
  40. package/lib/ui/AccessCodeForm/AccessCodeForm.d.ts +1 -1
  41. package/lib/ui/AccessCodeForm/AccessCodeForm.js +45 -46
  42. package/lib/ui/AccessCodeForm/AccessCodeForm.js.map +1 -1
  43. package/lib/ui/AccessCodeForm/AccessCodeFormDatePicker.d.ts +8 -7
  44. package/lib/ui/AccessCodeForm/AccessCodeFormDatePicker.js +8 -4
  45. package/lib/ui/AccessCodeForm/AccessCodeFormDatePicker.js.map +1 -1
  46. package/lib/ui/AccessCodeForm/AccessCodeFormTimeZonePicker.d.ts +8 -0
  47. package/lib/ui/AccessCodeForm/AccessCodeFormTimeZonePicker.js +17 -0
  48. package/lib/ui/AccessCodeForm/{AccessCodeFormTimezonePicker.js.map → AccessCodeFormTimeZonePicker.js.map} +1 -1
  49. package/lib/ui/AccessCodeForm/AccessCodeFormTimes.d.ts +3 -2
  50. package/lib/ui/AccessCodeForm/AccessCodeFormTimes.js +3 -2
  51. package/lib/ui/AccessCodeForm/AccessCodeFormTimes.js.map +1 -1
  52. package/lib/ui/ClimateSettingForm/ClimateSettingScheduleForm.d.ts +2 -2
  53. package/lib/ui/ClimateSettingForm/ClimateSettingScheduleForm.js +9 -9
  54. package/lib/ui/ClimateSettingForm/ClimateSettingScheduleForm.js.map +1 -1
  55. package/lib/ui/ClimateSettingForm/ClimateSettingScheduleFormNameAndSchedule.d.ts +3 -3
  56. package/lib/ui/ClimateSettingForm/ClimateSettingScheduleFormNameAndSchedule.js +4 -4
  57. package/lib/ui/ClimateSettingForm/ClimateSettingScheduleFormNameAndSchedule.js.map +1 -1
  58. package/lib/ui/ClimateSettingForm/{ClimateSettingScheduleFormTimezonePicker.d.ts → ClimateSettingScheduleFormTimeZonePicker.d.ts} +2 -2
  59. package/lib/ui/ClimateSettingForm/{ClimateSettingScheduleFormTimezonePicker.js → ClimateSettingScheduleFormTimeZonePicker.js} +5 -5
  60. package/lib/ui/ClimateSettingForm/{ClimateSettingScheduleFormTimezonePicker.js.map → ClimateSettingScheduleFormTimeZonePicker.js.map} +1 -1
  61. package/lib/ui/LoadingToast/LoadingToast.js +12 -14
  62. package/lib/ui/LoadingToast/LoadingToast.js.map +1 -1
  63. package/lib/ui/Menu/Menu.js +32 -25
  64. package/lib/ui/Menu/Menu.js.map +1 -1
  65. package/lib/ui/Snackbar/Snackbar.d.ts +16 -0
  66. package/lib/ui/Snackbar/Snackbar.js +38 -0
  67. package/lib/ui/Snackbar/Snackbar.js.map +1 -0
  68. package/lib/ui/TimeZonePicker/TimeZonePicker.d.ts +8 -0
  69. package/lib/ui/TimeZonePicker/TimeZonePicker.js +28 -0
  70. package/lib/ui/TimeZonePicker/TimeZonePicker.js.map +1 -0
  71. package/lib/ui/device/BatteryStatus.js +10 -6
  72. package/lib/ui/device/BatteryStatus.js.map +1 -1
  73. package/lib/ui/thermostat/ClimateModeMenu.js +4 -4
  74. package/lib/ui/thermostat/ClimateModeMenu.js.map +1 -1
  75. package/lib/ui/use-now.d.ts +2 -0
  76. package/lib/ui/{use-current-time.js → use-now.js} +2 -2
  77. package/lib/ui/use-now.js.map +1 -0
  78. package/lib/version.d.ts +1 -1
  79. package/lib/version.js +1 -1
  80. package/package.json +2 -2
  81. package/src/lib/dates.ts +19 -135
  82. package/src/lib/icons/CheckGreen.tsx +36 -0
  83. package/src/lib/icons/CloseWhite.tsx +36 -0
  84. package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.tsx +6 -9
  85. package/src/lib/seam/components/AccessCodeTable/CodeDetails.tsx +2 -3
  86. package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.tsx +9 -9
  87. package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.tsx +5 -8
  88. package/src/lib/seam/components/ClimateSettingScheduleDetails/dates.ts +10 -0
  89. package/src/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleRowDetails.tsx +2 -3
  90. package/src/lib/seam/components/CreateAccessCodeForm/CreateAccessCodeForm.tsx +3 -4
  91. package/src/lib/seam/components/CreateClimateSettingScheduleForm/CreateClimateSettingScheduleForm.tsx +3 -5
  92. package/src/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.tsx +1 -6
  93. package/src/lib/seam/components/EditAccessCodeForm/EditAccessCodeForm.tsx +3 -4
  94. package/src/lib/seam/components/SupportedDeviceTable/FilterCategoryMenu.tsx +20 -15
  95. package/src/lib/telemetry/client.ts +1 -0
  96. package/src/lib/ui/AccessCodeForm/AccessCodeForm.tsx +61 -67
  97. package/src/lib/ui/AccessCodeForm/AccessCodeFormDatePicker.tsx +28 -18
  98. package/src/lib/ui/AccessCodeForm/{AccessCodeFormTimezonePicker.tsx → AccessCodeFormTimeZonePicker.tsx} +10 -10
  99. package/src/lib/ui/AccessCodeForm/AccessCodeFormTimes.tsx +9 -5
  100. package/src/lib/ui/ClimateSettingForm/ClimateSettingScheduleForm.tsx +12 -12
  101. package/src/lib/ui/ClimateSettingForm/ClimateSettingScheduleFormNameAndSchedule.tsx +10 -10
  102. package/src/lib/ui/ClimateSettingForm/{ClimateSettingScheduleFormTimezonePicker.tsx → ClimateSettingScheduleFormTimeZonePicker.tsx} +8 -8
  103. package/src/lib/ui/LoadingToast/LoadingToast.tsx +13 -16
  104. package/src/lib/ui/Menu/Menu.tsx +50 -40
  105. package/src/lib/ui/Snackbar/Snackbar.tsx +97 -0
  106. package/src/lib/ui/TimeZonePicker/TimeZonePicker.tsx +69 -0
  107. package/src/lib/ui/device/BatteryStatus.tsx +23 -14
  108. package/src/lib/ui/thermostat/ClimateModeMenu.tsx +4 -4
  109. package/src/lib/ui/{use-current-time.ts → use-now.ts} +1 -1
  110. package/src/lib/version.ts +1 -1
  111. package/src/styles/_access-code-form.scss +4 -4
  112. package/src/styles/_climate-setting-schedule-form.scss +1 -1
  113. package/src/styles/_colors.scss +2 -0
  114. package/src/styles/_loading_toast.scss +5 -16
  115. package/src/styles/_main.scss +4 -2
  116. package/src/styles/_motion.scss +34 -0
  117. package/src/styles/_snackbar.scss +107 -0
  118. package/src/styles/_supported-device-table.scss +9 -0
  119. package/src/styles/{_timezone-picker.scss → _time-zone-picker.scss} +3 -3
  120. package/lib/ui/AccessCodeForm/AccessCodeFormTimezonePicker.d.ts +0 -8
  121. package/lib/ui/AccessCodeForm/AccessCodeFormTimezonePicker.js +0 -17
  122. package/lib/ui/TimezonePicker/TimezonePicker.d.ts +0 -8
  123. package/lib/ui/TimezonePicker/TimezonePicker.js +0 -28
  124. package/lib/ui/TimezonePicker/TimezonePicker.js.map +0 -1
  125. package/lib/ui/use-current-time.d.ts +0 -2
  126. package/lib/ui/use-current-time.js.map +0 -1
  127. package/src/lib/ui/TimezonePicker/TimezonePicker.tsx +0 -70
@@ -1,20 +1,16 @@
1
1
  import classNames from 'classnames'
2
+ import { DateTime } from 'luxon'
2
3
  import { useState } from 'react'
3
4
  import { useForm } from 'react-hook-form'
4
5
  import { type AccessCode, type CommonDevice, isLockDevice } from 'seamapi'
5
6
 
6
- import {
7
- get24HoursLater,
8
- getBrowserTimezone,
9
- getNow,
10
- getTimezoneFromIsoDate,
11
- } from 'lib/dates.js'
7
+ import { getSystemTimeZone } from 'lib/dates.js'
12
8
  import type { UseAccessCodeData } from 'lib/seam/access-codes/use-access-code.js'
13
9
  import { useGenerateAccessCodeCode } from 'lib/seam/access-codes/use-generate-access-code-code.js'
14
10
  import type { UseDeviceData } from 'lib/seam/devices/use-device.js'
15
11
  import { AccessCodeFormDatePicker } from 'lib/ui/AccessCodeForm/AccessCodeFormDatePicker.js'
16
12
  import { AccessCodeFormTimes } from 'lib/ui/AccessCodeForm/AccessCodeFormTimes.js'
17
- import { AccessCodeFormTimezonePicker } from 'lib/ui/AccessCodeForm/AccessCodeFormTimezonePicker.js'
13
+ import { AccessCodeFormTimeZonePicker } from 'lib/ui/AccessCodeForm/AccessCodeFormTimeZonePicker.js'
18
14
  import { Button } from 'lib/ui/Button.js'
19
15
  import { FormField } from 'lib/ui/FormField.js'
20
16
  import { InputLabel } from 'lib/ui/InputLabel.js'
@@ -30,7 +26,7 @@ export interface AccessCodeFormSubmitData {
30
26
  device: NonNullable<UseDeviceData>
31
27
  startDate: string
32
28
  endDate: string
33
- timezone: string
29
+ timeZone: string
34
30
  }
35
31
 
36
32
  export interface ResponseErrors {
@@ -71,14 +67,13 @@ function Content({
71
67
  accessCode?.type ?? 'ongoing'
72
68
  )
73
69
  const [datePickerVisible, setDatePickerVisible] = useState(false)
74
- const [timezone, setTimezone] = useState<string>(
75
- getAccessCodeTimezone(accessCode) ?? getBrowserTimezone()
76
- )
77
- const [startDate, setStartDate] = useState<string>(
78
- getAccessCodeDate('starts_at', accessCode) ?? getNow()
70
+ const [timeZone, setTimeZone] = useState<string>(getSystemTimeZone())
71
+
72
+ const [startDate, setStartDate] = useState<DateTime>(
73
+ getAccessCodeDate('starts_at', accessCode) ?? getNow(timeZone)
79
74
  )
80
- const [endDate, setEndDate] = useState<string>(
81
- getAccessCodeDate('ends_at', accessCode) ?? get24HoursLater()
75
+ const [endDate, setEndDate] = useState<DateTime>(
76
+ getAccessCodeDate('ends_at', accessCode) ?? getOneDayFromNow(timeZone)
82
77
  )
83
78
 
84
79
  const save = (data: { name: string; code: string }): void => {
@@ -86,14 +81,24 @@ function Content({
86
81
  return
87
82
  }
88
83
 
84
+ const start = startDate.toISO()
85
+ if (start == null) {
86
+ throw new Error(`Invalid start date: ${startDate.invalidReason}`)
87
+ }
88
+
89
+ const end = endDate.toISO()
90
+ if (end == null) {
91
+ throw new Error(`Invalid end date: ${endDate.invalidReason}`)
92
+ }
93
+
89
94
  onSubmit({
90
95
  name: data.name,
91
96
  code: data.code,
92
97
  type,
93
98
  device,
94
- startDate,
95
- endDate,
96
- timezone,
99
+ startDate: start,
100
+ endDate: end,
101
+ timeZone,
97
102
  })
98
103
  }
99
104
 
@@ -108,19 +113,23 @@ function Content({
108
113
  code: accessCode?.code ?? '',
109
114
  },
110
115
  })
111
- const [timezonePickerVisible, toggleTimezonePicker] = useToggle()
116
+ const [timeZonePickerVisible, toggleTimeZonePicker] = useToggle()
112
117
 
113
118
  const { isLoading: isGeneratingCode, mutate: generateCode } =
114
119
  useGenerateAccessCodeCode()
115
120
 
116
121
  const submit = (): void => {}
117
122
 
118
- if (timezonePickerVisible) {
123
+ if (timeZonePickerVisible) {
119
124
  return (
120
- <AccessCodeFormTimezonePicker
121
- value={timezone}
122
- onChange={setTimezone}
123
- onClose={toggleTimezonePicker}
125
+ <AccessCodeFormTimeZonePicker
126
+ value={timeZone}
127
+ onChange={(timeZone: string) => {
128
+ setTimeZone(timeZone)
129
+ setStartDate(startDate.setZone(timeZone))
130
+ setEndDate(endDate.setZone(timeZone))
131
+ }}
132
+ onClose={toggleTimeZonePicker}
124
133
  />
125
134
  )
126
135
  }
@@ -132,8 +141,8 @@ function Content({
132
141
  setStartDate={setStartDate}
133
142
  endDate={endDate}
134
143
  setEndDate={setEndDate}
135
- timezone={timezone}
136
- onChangeTimezone={toggleTimezonePicker}
144
+ timeZone={timeZone}
145
+ onChangeTimeZone={toggleTimeZonePicker}
137
146
  onBack={() => {
138
147
  setDatePickerVisible(false)
139
148
  }}
@@ -158,24 +167,6 @@ function Content({
158
167
  )
159
168
  }
160
169
 
161
- const validateCodeLength = (value: string): boolean | string => {
162
- if (!isLockDevice(device)) {
163
- return true
164
- }
165
-
166
- if (device.properties.supported_code_lengths == null) {
167
- return true
168
- }
169
-
170
- if (device.properties.supported_code_lengths.includes(value.length)) {
171
- return true
172
- }
173
-
174
- return t.codeLengthError(
175
- device.properties.supported_code_lengths.join(', ')
176
- )
177
- }
178
-
179
170
  const hasCodeError = errors.code != null || responseErrors?.code != null
180
171
 
181
172
  const codeLengthRequirement = getCodeLengthRequirement(device)
@@ -226,7 +217,8 @@ function Content({
226
217
  inputProps={{
227
218
  ...register('code', {
228
219
  required: t.codeRequiredError,
229
- validate: validateCodeLength,
220
+ validate: (value: string) =>
221
+ validateCodeLength(device, value),
230
222
  }),
231
223
  }}
232
224
  />
@@ -306,28 +298,26 @@ function Content({
306
298
  )
307
299
  }
308
300
 
309
- function getAccessCodeTimezone(
310
- accessCode?: NonNullable<UseAccessCodeData>
311
- ): undefined | string {
312
- if (accessCode == null) {
313
- return undefined
301
+ const validateCodeLength = (
302
+ device: CommonDevice,
303
+ value: string
304
+ ): boolean | string => {
305
+ if (!isLockDevice(device)) {
306
+ return true
314
307
  }
315
308
 
316
- if (accessCode.type === 'ongoing') {
317
- return undefined
309
+ if (device.properties.supported_code_lengths == null) {
310
+ return true
318
311
  }
319
312
 
320
- const date = accessCode.starts_at
321
-
322
- const timezone = getTimezoneFromIsoDate(date)
323
- if (timezone == null) {
324
- return undefined
313
+ if (device.properties.supported_code_lengths.includes(value.length)) {
314
+ return true
325
315
  }
326
316
 
327
- return timezone
317
+ return t.codeLengthError(device.properties.supported_code_lengths.join(', '))
328
318
  }
329
319
 
330
- function getCodeLengthRequirement(device: CommonDevice): string | null {
320
+ const getCodeLengthRequirement = (device: CommonDevice): string | null => {
331
321
  if (!isLockDevice(device)) {
332
322
  return null
333
323
  }
@@ -354,25 +344,29 @@ const sequentialNumbers = Array.from({ length: 100 }, (_, index) => index).join(
354
344
  ''
355
345
  )
356
346
 
357
- function isSequential(numbers: string): boolean {
358
- return sequentialNumbers.includes(numbers)
359
- }
347
+ const isSequential = (numbers: string): boolean =>
348
+ sequentialNumbers.includes(numbers)
360
349
 
361
- function getAccessCodeDate(
350
+ const getAccessCodeDate = (
362
351
  date: 'starts_at' | 'ends_at',
363
352
  accessCode?: NonNullable<UseAccessCodeData>
364
- ): string | undefined {
353
+ ): DateTime | null => {
365
354
  if (accessCode == null) {
366
- return undefined
355
+ return null
367
356
  }
368
357
 
369
358
  if (accessCode.type !== 'time_bound') {
370
- return undefined
359
+ return null
371
360
  }
372
361
 
373
- return accessCode[date]
362
+ return DateTime.fromISO(accessCode[date])
374
363
  }
375
364
 
365
+ const getNow = (timeZone: string): DateTime => DateTime.now().setZone(timeZone)
366
+
367
+ const getOneDayFromNow = (timeZone: string): DateTime =>
368
+ DateTime.now().setZone(timeZone).plus({ days: 1 })
369
+
376
370
  const t = {
377
371
  addNewAccessCode: 'Add new access code',
378
372
  editAccessCode: 'Edit access code',
@@ -1,4 +1,10 @@
1
- import { getTimezoneLabel } from 'lib/dates.js'
1
+ import type { DateTime } from 'luxon'
2
+
3
+ import {
4
+ formatTimeZone,
5
+ parseDateTimePickerValue,
6
+ serializeDateTimePickerValue,
7
+ } from 'lib/dates.js'
2
8
  import { ChevronRightIcon } from 'lib/icons/ChevronRight.js'
3
9
  import { DateTimePicker } from 'lib/ui/DateTimePicker/DateTimePicker.js'
4
10
  import { FormField } from 'lib/ui/FormField.js'
@@ -6,48 +12,52 @@ import { InputLabel } from 'lib/ui/InputLabel.js'
6
12
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
7
13
 
8
14
  interface AccessCodeFormDatePickerProps {
9
- startDate: string
10
- setStartDate: (date: string) => void
11
- endDate: string
12
- setEndDate: (date: string) => void
13
- timezone: string
14
- onChangeTimezone: () => void
15
+ startDate: DateTime
16
+ setStartDate: (dateTime: DateTime) => void
17
+ endDate: DateTime
18
+ setEndDate: (dateTime: DateTime) => void
19
+ timeZone: string
20
+ onChangeTimeZone: () => void
15
21
  onBack: (() => void) | undefined
16
22
  }
17
23
 
18
24
  export function AccessCodeFormDatePicker({
19
- timezone,
25
+ timeZone,
20
26
  onBack,
21
27
  startDate,
22
28
  setStartDate,
23
29
  endDate,
24
30
  setEndDate,
25
- onChangeTimezone,
31
+ onChangeTimeZone,
26
32
  }: AccessCodeFormDatePickerProps): JSX.Element {
27
33
  return (
28
34
  <div className='seam-schedule-picker'>
29
35
  <ContentHeader title={t.timingTitle} onBack={onBack} />
30
36
  <div className='seam-content'>
31
- <div className='seam-timezone'>
32
- <span className='seam-label'>{t.selectedTimezoneLabel}</span>
33
- <span className='seam-selected' onClick={onChangeTimezone}>
34
- {getTimezoneLabel(timezone)}
37
+ <div className='seam-time-zone'>
38
+ <span className='seam-label'>{t.selectedTimeZoneLabel}</span>
39
+ <span className='seam-selected' onClick={onChangeTimeZone}>
40
+ {formatTimeZone(timeZone)}
35
41
  <ChevronRightIcon />
36
42
  </span>
37
43
  </div>
38
44
  <FormField>
39
45
  <InputLabel>{t.startTimeLabel}</InputLabel>
40
46
  <DateTimePicker
41
- value={startDate ?? ''}
42
- onChange={setStartDate}
47
+ value={serializeDateTimePickerValue(startDate, timeZone) ?? ''}
48
+ onChange={(value: string) => {
49
+ setStartDate(parseDateTimePickerValue(value, timeZone))
50
+ }}
43
51
  size='large'
44
52
  />
45
53
  </FormField>
46
54
  <FormField>
47
55
  <InputLabel>{t.endTimeLabel}</InputLabel>
48
56
  <DateTimePicker
49
- value={endDate ?? ''}
50
- onChange={setEndDate}
57
+ value={serializeDateTimePickerValue(endDate, timeZone) ?? ''}
58
+ onChange={(value: string) => {
59
+ setEndDate(parseDateTimePickerValue(value, timeZone))
60
+ }}
51
61
  size='large'
52
62
  />
53
63
  </FormField>
@@ -58,7 +68,7 @@ export function AccessCodeFormDatePicker({
58
68
 
59
69
  const t = {
60
70
  timingTitle: 'Timing',
61
- selectedTimezoneLabel: 'All times in',
71
+ selectedTimeZoneLabel: 'All times in',
62
72
  startTimeLabel: 'Start',
63
73
  endTimeLabel: 'End',
64
74
  }
@@ -1,30 +1,30 @@
1
1
  import { useState } from 'react'
2
2
 
3
3
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
4
- import { TimezonePicker } from 'lib/ui/TimezonePicker/TimezonePicker.js'
4
+ import { TimeZonePicker } from 'lib/ui/TimeZonePicker/TimeZonePicker.js'
5
5
 
6
- interface AccessCodeFormTimezonePickerProps {
6
+ interface AccessCodeFormTimeZonePickerProps {
7
7
  value: string
8
- onChange: (timezone: string) => void
8
+ onChange: (timeZone: string) => void
9
9
  onClose: () => void
10
10
  }
11
11
 
12
- export function AccessCodeFormTimezonePicker({
12
+ export function AccessCodeFormTimeZonePicker({
13
13
  onChange,
14
14
  value,
15
15
  onClose,
16
- }: AccessCodeFormTimezonePickerProps): JSX.Element {
16
+ }: AccessCodeFormTimeZonePickerProps): JSX.Element {
17
17
  const [title, setTitle] = useState(t.titleAuto)
18
18
 
19
19
  return (
20
- <div className='seam-access-code-timezone-picker'>
20
+ <div className='seam-access-code-time-zone-picker'>
21
21
  <ContentHeader title={title} onBack={onClose} />
22
22
  <div className='seam-content'>
23
- <TimezonePicker
23
+ <TimeZonePicker
24
24
  value={value}
25
25
  onChange={onChange}
26
- onManualTimezoneSelected={(manualTimezoneSelected) => {
27
- setTitle(manualTimezoneSelected ? t.titleManual : t.titleAuto)
26
+ onManualTimeZoneSelected={(manualTimeZoneSelected) => {
27
+ setTitle(manualTimeZoneSelected ? t.titleManual : t.titleAuto)
28
28
  }}
29
29
  />
30
30
  </div>
@@ -36,5 +36,5 @@ const t = {
36
36
  titleAuto: 'Time Zone (automatic)',
37
37
  titleManual: 'Time Zone (manual)',
38
38
  utc: 'UTC',
39
- setTimezoneManuallyLabel: 'Use local time zone',
39
+ setTimeZoneManuallyLabel: 'Use local time zone',
40
40
  }
@@ -1,10 +1,11 @@
1
- import { formatDateTimeReadable } from 'lib/dates.js'
1
+ import { DateTime } from 'luxon'
2
+
2
3
  import { EditIcon } from 'lib/icons/Edit.js'
3
4
  import { IconButton } from 'lib/ui/IconButton.js'
4
5
 
5
6
  interface AccessCodeFormTimesProps {
6
- startDate: string
7
- endDate: string
7
+ startDate: DateTime
8
+ endDate: DateTime
8
9
  onEdit: () => void
9
10
  }
10
11
 
@@ -17,9 +18,9 @@ export function AccessCodeFormTimes({
17
18
  <div className='seam-selected-times'>
18
19
  <div>
19
20
  <span className='seam-label'>{t.startTimeLabel}</span>
20
- <span className='seam-time'>{formatDateTimeReadable(startDate)}</span>
21
+ <span className='seam-time'>{formatDateTime(startDate)}</span>
21
22
  <span className='seam-label'>{t.endTimeLabel}</span>
22
- <span className='seam-time'>{formatDateTimeReadable(endDate)}</span>
23
+ <span className='seam-time'>{formatDateTime(endDate)}</span>
23
24
  </div>
24
25
  <IconButton onClick={onEdit} className='seam-show-date-picker-btn'>
25
26
  <EditIcon />
@@ -28,6 +29,9 @@ export function AccessCodeFormTimes({
28
29
  )
29
30
  }
30
31
 
32
+ const formatDateTime = (dateTime: DateTime): string =>
33
+ dateTime.toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)
34
+
31
35
  const t = {
32
36
  startTimeLabel: 'Start',
33
37
  endTimeLabel: 'End',
@@ -3,17 +3,17 @@ import { useState } from 'react'
3
3
  import { useForm } from 'react-hook-form'
4
4
  import type { ClimateSetting } from 'seamapi'
5
5
 
6
- import { getBrowserTimezone } from 'lib/dates.js'
6
+ import { getSystemTimeZone } from 'lib/dates.js'
7
7
  import { ClimateSettingScheduleFormDeviceSelect } from 'lib/ui/ClimateSettingForm/ClimateSettingScheduleFormDeviceSelect.js'
8
8
  import { ClimateSettingScheduleFormNameAndSchedule } from 'lib/ui/ClimateSettingForm/ClimateSettingScheduleFormNameAndSchedule.js'
9
- import { ClimateSettingScheduleFormTimezonePicker } from 'lib/ui/ClimateSettingForm/ClimateSettingScheduleFormTimezonePicker.js'
9
+ import { ClimateSettingScheduleFormTimeZonePicker } from 'lib/ui/ClimateSettingForm/ClimateSettingScheduleFormTimeZonePicker.js'
10
10
 
11
11
  export interface ClimateSettingScheduleFormSubmitData {
12
12
  name: string
13
13
  deviceId: string
14
14
  startDate: string
15
15
  endDate: string
16
- timezone: string
16
+ timeZone: string
17
17
  climateSetting: ClimateSetting
18
18
  }
19
19
 
@@ -29,7 +29,7 @@ export interface ClimateSettingScheduleFormFields {
29
29
  deviceId: string
30
30
  startDate: string
31
31
  endDate: string
32
- timezone: string
32
+ timeZone: string
33
33
  }
34
34
 
35
35
  export function ClimateSettingScheduleForm({
@@ -54,18 +54,18 @@ function Content({
54
54
  name: '',
55
55
  startDate: '',
56
56
  endDate: '',
57
- timezone: getBrowserTimezone(),
57
+ timeZone: getSystemTimeZone(),
58
58
  },
59
59
  })
60
60
 
61
61
  const deviceId = watch('deviceId')
62
- const timezone = watch('timezone')
62
+ const timeZone = watch('timeZone')
63
63
 
64
64
  const [page, setPage] = useState<
65
65
  | 'device_select'
66
66
  | 'default_setting'
67
67
  | 'name_and_schedule'
68
- | 'timezone_select'
68
+ | 'time_zone_select'
69
69
  | 'climate_setting'
70
70
  >('device_select')
71
71
 
@@ -95,17 +95,17 @@ function Content({
95
95
  onNext={() => {
96
96
  setPage('climate_setting')
97
97
  }}
98
- onChangeTimezone={() => {
99
- setPage('timezone_select')
98
+ onChangeTimeZone={() => {
99
+ setPage('time_zone_select')
100
100
  }}
101
- timezone={timezone}
101
+ timeZone={timeZone}
102
102
  />
103
103
  )
104
104
  }
105
105
 
106
- if (page === 'timezone_select') {
106
+ if (page === 'time_zone_select') {
107
107
  return (
108
- <ClimateSettingScheduleFormTimezonePicker
108
+ <ClimateSettingScheduleFormTimeZonePicker
109
109
  control={control}
110
110
  onClose={() => {
111
111
  setPage('name_and_schedule')
@@ -1,6 +1,6 @@
1
1
  import { type Control, Controller } from 'react-hook-form'
2
2
 
3
- import { getTimezoneLabel } from 'lib/dates.js'
3
+ import { formatTimeZone } from 'lib/dates.js'
4
4
  import { ChevronRightIcon } from 'lib/icons/ChevronRight.js'
5
5
  import { useDevice } from 'lib/seam/devices/use-device.js'
6
6
  import { Button } from 'lib/ui/Button.js'
@@ -15,22 +15,22 @@ interface ClimateSettingScheduleFormNameAndScheduleProps {
15
15
  title: string
16
16
  control: Control<ClimateSettingScheduleFormFields>
17
17
  deviceId: string
18
- timezone: string
18
+ timeZone: string
19
19
  onBack: () => void
20
20
  onCancel: (() => void) | undefined
21
21
  onNext: () => void
22
- onChangeTimezone: () => void
22
+ onChangeTimeZone: () => void
23
23
  }
24
24
 
25
25
  export function ClimateSettingScheduleFormNameAndSchedule({
26
26
  title,
27
27
  control,
28
28
  deviceId,
29
- timezone,
29
+ timeZone,
30
30
  onBack,
31
31
  onCancel,
32
32
  onNext,
33
- onChangeTimezone,
33
+ onChangeTimeZone,
34
34
  }: ClimateSettingScheduleFormNameAndScheduleProps): JSX.Element {
35
35
  const { device } = useDevice({
36
36
  device_id: deviceId,
@@ -68,10 +68,10 @@ export function ClimateSettingScheduleFormNameAndSchedule({
68
68
 
69
69
  <FormField>
70
70
  <InputLabel>{t.startTimeLabel}</InputLabel>
71
- <div className='seam-timezone'>
72
- <span className='seam-label'>{t.selectedTimezoneLabel}</span>
73
- <span className='seam-selected' onClick={onChangeTimezone}>
74
- {getTimezoneLabel(timezone)}
71
+ <div className='seam-time-zone'>
72
+ <span className='seam-label'>{t.selectedTimeZoneLabel}</span>
73
+ <span className='seam-selected' onClick={onChangeTimeZone}>
74
+ {formatTimeZone(timeZone)}
75
75
  <ChevronRightIcon />
76
76
  </span>
77
77
  </div>
@@ -116,5 +116,5 @@ const t = {
116
116
  cancel: 'Cancel',
117
117
  save: 'Save',
118
118
  next: 'Next',
119
- selectedTimezoneLabel: 'All times in',
119
+ selectedTimeZoneLabel: 'All times in',
120
120
  }
@@ -3,17 +3,17 @@ import { type Control, Controller } from 'react-hook-form'
3
3
 
4
4
  import type { ClimateSettingScheduleFormFields } from 'lib/ui/ClimateSettingForm/ClimateSettingScheduleForm.js'
5
5
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
6
- import { TimezonePicker } from 'lib/ui/TimezonePicker/TimezonePicker.js'
6
+ import { TimeZonePicker } from 'lib/ui/TimeZonePicker/TimeZonePicker.js'
7
7
 
8
- interface ClimateSettingScheduleFormTimezonePickerProps {
8
+ interface ClimateSettingScheduleFormTimeZonePickerProps {
9
9
  control: Control<ClimateSettingScheduleFormFields>
10
10
  onClose: () => void
11
11
  }
12
12
 
13
- export function ClimateSettingScheduleFormTimezonePicker({
13
+ export function ClimateSettingScheduleFormTimeZonePicker({
14
14
  control,
15
15
  onClose,
16
- }: ClimateSettingScheduleFormTimezonePickerProps): JSX.Element {
16
+ }: ClimateSettingScheduleFormTimeZonePickerProps): JSX.Element {
17
17
  const [title, setTitle] = useState(t.titleAuto)
18
18
 
19
19
  return (
@@ -21,13 +21,13 @@ export function ClimateSettingScheduleFormTimezonePicker({
21
21
  <ContentHeader title={title} onBack={onClose} />
22
22
  <div className='seam-main'>
23
23
  <Controller
24
- name='timezone'
24
+ name='timeZone'
25
25
  control={control}
26
26
  render={({ field }) => (
27
- <TimezonePicker
27
+ <TimeZonePicker
28
28
  {...field}
29
- onManualTimezoneSelected={(manualTimezoneSelected) => {
30
- setTitle(manualTimezoneSelected ? t.titleManual : t.titleAuto)
29
+ onManualTimeZoneSelected={(manualTimeZoneSelected) => {
30
+ setTitle(manualTimeZoneSelected ? t.titleManual : t.titleAuto)
31
31
  }}
32
32
  />
33
33
  )}
@@ -21,26 +21,23 @@ export function LoadingToast({
21
21
  const [showToast, setShowToast] = useState(isLoading)
22
22
 
23
23
  useEffect(() => {
24
- if (!isLoading) {
25
- const hideTimeout = globalThis.setTimeout(() => {
26
- setHidden(true)
27
- }, 1000)
28
-
29
- const removeTimeout = globalThis.setTimeout(() => {
30
- setShowToast(false)
31
- }, 1500)
32
-
33
- return () => {
34
- globalThis.clearTimeout(hideTimeout)
35
- globalThis.clearTimeout(removeTimeout)
36
- }
24
+ if (isLoading) {
25
+ setHidden(false)
26
+ setShowToast(true)
27
+ return () => {}
37
28
  }
38
29
 
39
- setHidden(false)
40
- setShowToast(true)
30
+ const hideTimeout = globalThis.setTimeout(() => {
31
+ setHidden(true)
32
+ }, 1000)
33
+
34
+ const removeTimeout = globalThis.setTimeout(() => {
35
+ setShowToast(false)
36
+ }, 1500)
41
37
 
42
38
  return () => {
43
- // noop
39
+ globalThis.clearTimeout(hideTimeout)
40
+ globalThis.clearTimeout(removeTimeout)
44
41
  }
45
42
  }, [isLoading])
46
43