@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
package/src/lib/dates.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { DateTime, IANAZone } from 'luxon'
1
+ import { DateTime } from 'luxon'
2
2
 
3
3
  export const compareByCreatedAtDesc = (
4
4
  a: { created_at: string },
@@ -10,148 +10,32 @@ export const compareByCreatedAtDesc = (
10
10
  return t1.toMillis() - t2.toMillis()
11
11
  }
12
12
 
13
- /**
14
- * Get the timezone strings supported by the user's browser.
15
- *
16
- * @returns string[]
17
- */
18
- export function getTimezones(): string[] {
19
- return Intl.supportedValuesOf('timeZone')
13
+ export const getSupportedTimeZones = (): string[] => {
14
+ const timeZones = new Set(Intl.supportedValuesOf('timeZone'))
15
+ timeZones.add('UTC')
16
+ return Array.from(timeZones).sort()
20
17
  }
21
18
 
22
- /**
23
- * Get the default browser timezone.
24
- *
25
- * @returns string
26
- */
27
- export function getBrowserTimezone(): string {
28
- return Intl.DateTimeFormat().resolvedOptions().timeZone
29
- }
30
-
31
- /**
32
- * Takes an IANA timezone, like America/Los_Angeles, into a more readable
33
- * string: Los Angeles (America).
34
- * @param timezone
35
- * @returns string
36
- */
37
- export function getTimezoneLabel(timezone: string): string {
38
- const [region = '', city = ''] = timezone.replace(/_/g, ' ').split('/')
39
- return `${city} (${region})`
40
- }
41
-
42
- /**
43
- * Get a timezones offset from UTC in minutes.
44
- *
45
- * @param timezone
46
- * @returns minutes
47
- */
48
- function getTimezoneOffsetMinutes(timezone: string): number {
49
- return DateTime.local().setZone(timezone).offset
50
- }
51
-
52
- /**
53
- * Compares 2 timezones (America/Los_angeles) by their offset
54
- * minutes in ascending order.
55
- *
56
- * @param timezoneA
57
- * @param timezonB
58
- * @returns number
59
- */
60
- export const compareByTimezoneOffsetAsc = (
61
- timezoneA: string,
62
- timezonB: string
63
- ): number =>
64
- getTimezoneOffsetMinutes(timezoneA) - getTimezoneOffsetMinutes(timezonB)
65
-
66
- /**
67
- * Get the timezone offset
68
- * America/Los_angeles -> -07:00
69
- *
70
- * eg. America/Los_Angeles -> UTC-07:00
71
- *
72
- * @param timezone
73
- * @returns offset
74
- */
75
- export function getTimezoneOffset(timezone: string): string {
76
- return IANAZone.create(timezone).formatOffset(Date.now(), 'short')
77
- }
78
-
79
- const formatDateReadable = (
80
- date: string,
81
- options: {
82
- showWeekday?: boolean
83
- } = {}
84
- ): string => {
85
- const { showWeekday = true } = options
19
+ export const getSystemTimeZone = (): string => DateTime.now().zoneName ?? 'UTC'
86
20
 
87
- // '2023-04-17' to 'Mon Apr 17, 2023' / 'Apr 17, 2023'
88
- const format = showWeekday ? 'EEE MMM d, yyyy' : 'MMM d, yyyy'
89
-
90
- return DateTime.fromFormat(date, 'yyyy-MM-dd').toFormat(format)
21
+ export const formatTimeZone = (timeZone: string): string => {
22
+ const offset = DateTime.now().setZone(timeZone).toFormat("'UTC'Z")
23
+ return `${timeZone.replaceAll('_', ' ')} (${offset})`
91
24
  }
92
25
 
93
- const formatTimeReadable = (time: string): string | null => {
94
- const dateTime = DateTime.fromFormat(time, 'HH:mm:ss')
26
+ export const serializeDateTimePickerValue = (
27
+ dateTime: DateTime,
28
+ timeZone: string
29
+ ): string | null => {
95
30
  if (!dateTime.isValid) {
96
31
  return null
97
32
  }
98
33
 
99
- return dateTime.toFormat('h:mm a')
100
- }
101
-
102
- export const formatDateTimeReadable = (dateTime: string): string => {
103
- const [date = '', time = ''] = dateTime.split('T')
104
- return `${formatDateReadable(date, { showWeekday: false })} at ${
105
- formatTimeReadable(time) ?? ''
106
- }`
34
+ return dateTime.setZone(timeZone).toFormat("yyyy-MM-dd'T'HH:mm:ss")
107
35
  }
108
36
 
109
- export const getNow = (): string => getDateTimeOnly(DateTime.now())
110
- export const get24HoursLater = (): string =>
111
- getDateTimeOnly(DateTime.now().plus({ days: 1 }))
112
-
113
- function getDateTimeOnly(dateTime: DateTime): string {
114
- const date = dateTime.toFormat('yyyy-MM-dd')
115
- const time = dateTime.toFormat('HH:mm:ss')
116
- return `${date}T${time}`
117
- }
118
-
119
- /**
120
- * Takes a date (2023-07-20T00:00:00), and a timezone (America/Los_angeles), and
121
- * returns an ISO8601 Date (2023-07-20T00:00:00.000-07:00).
122
- *
123
- * @param date
124
- * @param timezone
125
- * @returns ISOdate
126
- */
127
- export const createIsoDate = (date: string, timezone: string): string => {
128
- const offset = getTimezoneOffset(timezone)
129
- return `${date}.000${offset}`
130
- }
131
-
132
- /**
133
- * Takes a ISO datetime string (2023-07-20T00:00:00.000-07:00) and returns
134
- * the IANA timezone (America/Los_angeles).
135
- *
136
- * @param date
137
- * @returns string
138
- */
139
- export const getTimezoneFromIsoDate = (date: string): string | null =>
140
- DateTime.fromISO(date).zoneName
141
-
142
- /**
143
- * Takes an ISO datetime string (2023-07-20T00:00:00.000-07:00) and returns a string like
144
- * (Jul 20, 12:00 AM PDT).
145
- *
146
- * @param date
147
- * @returns string
148
- *
149
- */
150
- export const formatDateAndTime = (date: string): string =>
151
- DateTime.fromISO(date).toLocaleString({
152
- month: 'short',
153
- day: 'numeric',
154
- hour: 'numeric',
155
- minute: '2-digit',
156
- timeZoneName: 'short',
157
- })
37
+ export const parseDateTimePickerValue = (
38
+ value: string,
39
+ timeZone: string
40
+ ): DateTime =>
41
+ DateTime.fromISO(value).setZone(timeZone, { keepLocalTime: true })
@@ -0,0 +1,36 @@
1
+ /*
2
+ * Automatically generated by SVGR from assets/icons/*.svg.
3
+ * Do not edit this file or add other components to this directory.
4
+ */
5
+ import type { SVGProps } from 'react'
6
+ export function CheckGreenIcon(props: SVGProps<SVGSVGElement>): JSX.Element {
7
+ return (
8
+ <svg
9
+ xmlns='http://www.w3.org/2000/svg'
10
+ width={24}
11
+ height={24}
12
+ fill='none'
13
+ {...props}
14
+ >
15
+ <mask
16
+ id='check-green_svg__a'
17
+ width={24}
18
+ height={24}
19
+ x={0}
20
+ y={0}
21
+ maskUnits='userSpaceOnUse'
22
+ style={{
23
+ maskType: 'alpha',
24
+ }}
25
+ >
26
+ <path fill='#D9D9D9' d='M0 0h24v24H0z' />
27
+ </mask>
28
+ <g mask='url(#check-green_svg__a)'>
29
+ <path
30
+ fill='#27AE60'
31
+ d='m10.6 16.6 7.05-7.05-1.4-1.4-5.65 5.65-2.85-2.85-1.4 1.4 4.25 4.25ZM12 22a9.733 9.733 0 0 1-3.9-.788 10.092 10.092 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.733 9.733 0 0 1 2 12c0-1.383.263-2.683.788-3.9a10.092 10.092 0 0 1 2.137-3.175c.9-.9 1.958-1.613 3.175-2.138A9.743 9.743 0 0 1 12 2c1.383 0 2.683.262 3.9.787a10.105 10.105 0 0 1 3.175 2.138c.9.9 1.612 1.958 2.137 3.175A9.733 9.733 0 0 1 22 12a9.733 9.733 0 0 1-.788 3.9 10.092 10.092 0 0 1-2.137 3.175c-.9.9-1.958 1.612-3.175 2.137A9.733 9.733 0 0 1 12 22Z'
32
+ />
33
+ </g>
34
+ </svg>
35
+ )
36
+ }
@@ -0,0 +1,36 @@
1
+ /*
2
+ * Automatically generated by SVGR from assets/icons/*.svg.
3
+ * Do not edit this file or add other components to this directory.
4
+ */
5
+ import type { SVGProps } from 'react'
6
+ export function CloseWhiteIcon(props: SVGProps<SVGSVGElement>): JSX.Element {
7
+ return (
8
+ <svg
9
+ xmlns='http://www.w3.org/2000/svg'
10
+ width={24}
11
+ height={24}
12
+ fill='none'
13
+ {...props}
14
+ >
15
+ <mask
16
+ id='close-white_svg__a'
17
+ width={24}
18
+ height={24}
19
+ x={0}
20
+ y={0}
21
+ maskUnits='userSpaceOnUse'
22
+ style={{
23
+ maskType: 'alpha',
24
+ }}
25
+ >
26
+ <path fill='#D9D9D9' d='M0 0h24v24H0z' />
27
+ </mask>
28
+ <g mask='url(#close-white_svg__a)'>
29
+ <path
30
+ fill='#fff'
31
+ d='M6.4 19 5 17.6l5.6-5.6L5 6.4 6.4 5l5.6 5.6L17.6 5 19 6.4 13.4 12l5.6 5.6-1.4 1.4-5.6-5.6L6.4 19Z'
32
+ />
33
+ </g>
34
+ </svg>
35
+ )
36
+ }
@@ -221,28 +221,25 @@ function Duration(props: { accessCode: AccessCode }): JSX.Element {
221
221
  )
222
222
  }
223
223
 
224
- function formatDurationDate(date: string): string {
225
- return DateTime.fromISO(date).toLocaleString({
224
+ const formatDurationDate = (date: string): string =>
225
+ DateTime.fromISO(date).toLocaleString({
226
226
  month: 'short',
227
227
  day: 'numeric',
228
228
  })
229
- }
230
229
 
231
- function formatTime(date: string): string {
232
- return DateTime.fromISO(date).toLocaleString({
230
+ const formatTime = (date: string): string =>
231
+ DateTime.fromISO(date).toLocaleString({
233
232
  hour: 'numeric',
234
233
  minute: '2-digit',
235
234
  })
236
- }
237
235
 
238
- function formatDate(date: string): string {
239
- return DateTime.fromISO(date).toLocaleString({
236
+ const formatDate = (date: string): string =>
237
+ DateTime.fromISO(date).toLocaleString({
240
238
  weekday: 'short',
241
239
  month: 'long',
242
240
  day: 'numeric',
243
241
  year: 'numeric',
244
242
  })
245
- }
246
243
 
247
244
  const errorFilter = (
248
245
  error: AccessCodeError | DeviceError | ConnectedAccountError
@@ -52,12 +52,11 @@ function Duration(props: { accessCode: AccessCode }): JSX.Element {
52
52
  )
53
53
  }
54
54
 
55
- function formatDate(date: string): string {
56
- return DateTime.fromISO(date).toLocaleString({
55
+ const formatDate = (date: string): string =>
56
+ DateTime.fromISO(date).toLocaleString({
57
57
  month: 'long',
58
58
  day: 'numeric',
59
59
  })
60
- }
61
60
 
62
61
  const t = {
63
62
  code: 'Code',
@@ -1,13 +1,14 @@
1
1
  import { DateTime } from 'luxon'
2
2
  import type { ClimateSettingSchedule } from 'seamapi'
3
3
 
4
- import { formatDateAndTime } from 'lib/dates.js'
5
4
  import { ClimateSettingScheduleIcon } from 'lib/icons/ClimateSettingSchedule.js'
6
5
  import { ClimateSettingScheduleDeviceBar } from 'lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.js'
7
6
  import { useClimateSettingSchedule } from 'lib/seam/thermostats/climate-setting-schedules/use-climate-setting-schedule.js'
8
7
  import { DotDivider } from 'lib/ui/layout/DotDivider.js'
9
8
  import { ClimateSettingStatus } from 'lib/ui/thermostat/ClimateSettingStatus.js'
10
- import { useCurrentTime } from 'lib/ui/use-current-time.js'
9
+ import { useNow } from 'lib/ui/use-now.js'
10
+
11
+ import { formatDateTime } from './dates.js'
11
12
 
12
13
  interface ClimateSettingScheduleCardProps {
13
14
  climateSettingScheduleId: string
@@ -74,25 +75,24 @@ function ClimateSettingScheduleTiming(props: {
74
75
  }): JSX.Element | null {
75
76
  const { climateSettingSchedule } = props
76
77
 
77
- const currentTime = useCurrentTime()
78
+ const now = useNow()
78
79
 
79
- if (currentTime === null) return null
80
+ if (now === null) return null
80
81
 
81
82
  const startTime = DateTime.fromISO(climateSettingSchedule.schedule_starts_at)
82
83
  const endTime = DateTime.fromISO(climateSettingSchedule.schedule_ends_at)
83
84
 
84
- if (currentTime < startTime)
85
+ if (now < startTime)
85
86
  return (
86
87
  <span>
87
- {t.starts}{' '}
88
- {formatDateAndTime(climateSettingSchedule.schedule_starts_at)}
88
+ {t.starts} {formatDateTime(climateSettingSchedule.schedule_starts_at)}
89
89
  </span>
90
90
  )
91
91
 
92
- if (startTime <= currentTime && currentTime <= endTime)
92
+ if (startTime <= now && now <= endTime)
93
93
  return (
94
94
  <span>
95
- {t.ends} {formatDateAndTime(climateSettingSchedule.schedule_starts_at)}
95
+ {t.ends} {formatDateTime(climateSettingSchedule.schedule_starts_at)}
96
96
  </span>
97
97
  )
98
98
 
@@ -3,7 +3,6 @@ import { useState } from 'react'
3
3
 
4
4
  import { useComponentTelemetry } from 'lib/telemetry/index.js'
5
5
 
6
- import { formatDateAndTime } from 'lib/dates.js'
7
6
  import { ArrowRightIcon } from 'lib/icons/ArrowRight.js'
8
7
  import { ClimateSettingScheduleCard } from 'lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.js'
9
8
  import {
@@ -18,6 +17,8 @@ import { DetailSection } from 'lib/ui/layout/DetailSection.js'
18
17
  import { DetailSectionGroup } from 'lib/ui/layout/DetailSectionGroup.js'
19
18
  import { ClimateSettingStatus } from 'lib/ui/thermostat/ClimateSettingStatus.js'
20
19
 
20
+ import { formatDateTime } from './dates.js'
21
+
21
22
  export interface ClimateSettingScheduleDetailsProps extends CommonProps {
22
23
  climateSettingScheduleId: string
23
24
  }
@@ -89,13 +90,9 @@ export function ClimateSettingScheduleDetails({
89
90
  <DetailSection>
90
91
  <DetailRow label={t.startEndTime}>
91
92
  <span className='seam-climate-setting-details-value seam-climate-setting-details-schedule-range'>
92
- {`${formatDateAndTime(
93
- climateSettingSchedule.schedule_starts_at
94
- )}`}
93
+ {formatDateTime(climateSettingSchedule.schedule_starts_at)}
95
94
  <ArrowRightIcon />
96
- {`${formatDateAndTime(
97
- climateSettingSchedule.schedule_ends_at
98
- )}`}
95
+ {formatDateTime(climateSettingSchedule.schedule_ends_at)}
99
96
  </span>
100
97
  </DetailRow>
101
98
  <DetailRow label={t.climateSetting}>
@@ -113,7 +110,7 @@ export function ClimateSettingScheduleDetails({
113
110
  <DetailSection>
114
111
  <DetailRow label={t.creationDate}>
115
112
  <div className='seam-creation-date'>
116
- {formatDateAndTime(climateSettingSchedule.created_at)}
113
+ {formatDateTime(climateSettingSchedule.created_at)}
117
114
  </div>
118
115
  </DetailRow>
119
116
  </DetailSection>
@@ -0,0 +1,10 @@
1
+ import { DateTime } from 'luxon'
2
+
3
+ export const formatDateTime = (date: string): string =>
4
+ DateTime.fromISO(date).toLocaleString({
5
+ month: 'short',
6
+ day: 'numeric',
7
+ hour: 'numeric',
8
+ minute: '2-digit',
9
+ timeZoneName: 'short',
10
+ })
@@ -48,12 +48,11 @@ function Duration(props: {
48
48
  )
49
49
  }
50
50
 
51
- function formatDate(date: string): string {
52
- return DateTime.fromISO(date).toLocaleString({
51
+ const formatDate = (date: string): string =>
52
+ DateTime.fromISO(date).toLocaleString({
53
53
  month: 'long',
54
54
  day: 'numeric',
55
55
  })
56
- }
57
56
 
58
57
  const t = {
59
58
  starts: 'Starts',
@@ -3,7 +3,6 @@ import type { SeamError } from 'seamapi'
3
3
 
4
4
  import { useComponentTelemetry } from 'lib/telemetry/index.js'
5
5
 
6
- import { createIsoDate } from 'lib/dates.js'
7
6
  import { useCreateAccessCode } from 'lib/seam/access-codes/use-create-access-code.js'
8
7
  import {
9
8
  type CommonProps,
@@ -82,7 +81,7 @@ function useSubmitCreateAccessCode(params: { onSuccess: () => void }): {
82
81
  const submit = (data: AccessCodeFormSubmitData): void => {
83
82
  resetResponseErrors()
84
83
 
85
- const { name, code, type, device, startDate, endDate, timezone } = data
84
+ const { name, code, type, device, startDate, endDate } = data
86
85
  if (name === '') {
87
86
  return
88
87
  }
@@ -97,8 +96,8 @@ function useSubmitCreateAccessCode(params: { onSuccess: () => void }): {
97
96
  name,
98
97
  code,
99
98
  device_id: device.device_id,
100
- starts_at: createIsoDate(startDate, timezone),
101
- ends_at: createIsoDate(endDate, timezone),
99
+ starts_at: startDate,
100
+ ends_at: endDate,
102
101
  },
103
102
  {
104
103
  onSuccess,
@@ -1,6 +1,5 @@
1
1
  import { useComponentTelemetry } from 'lib/telemetry/index.js'
2
2
 
3
- import { createIsoDate } from 'lib/dates.js'
4
3
  import type { CommonProps } from 'lib/seam/components/common-props.js'
5
4
  import { useCreateClimateSettingSchedule } from 'lib/seam/thermostats/climate-setting-schedules/use-create-climate-setting-schedule.js'
6
5
  import {
@@ -34,8 +33,7 @@ function useSubmitCreateClimateSettingSchedule(onSuccess?: () => void): {
34
33
  } {
35
34
  const { mutate, isLoading: isSubmitting } = useCreateClimateSettingSchedule()
36
35
  const submit = (data: ClimateSettingScheduleFormSubmitData): void => {
37
- const { name, deviceId, startDate, endDate, timezone, climateSetting } =
38
- data
36
+ const { name, deviceId, startDate, endDate, climateSetting } = data
39
37
 
40
38
  if (isSubmitting) {
41
39
  return
@@ -45,8 +43,8 @@ function useSubmitCreateClimateSettingSchedule(onSuccess?: () => void): {
45
43
  {
46
44
  name,
47
45
  device_id: deviceId,
48
- schedule_starts_at: createIsoDate(startDate, timezone),
49
- schedule_ends_at: createIsoDate(endDate, timezone),
46
+ schedule_starts_at: startDate,
47
+ schedule_ends_at: endDate,
50
48
  ...climateSetting,
51
49
  },
52
50
  {
@@ -128,10 +128,7 @@ export function ThermostatDeviceDetails(
128
128
  </DetailRow>
129
129
  </DetailSection>
130
130
 
131
- <DetailSection
132
- label={t.deviceDetails}
133
- tooltipContent={t.deviceDetailsTooltip}
134
- >
131
+ <DetailSection label={t.deviceDetails}>
135
132
  <DetailRow label={t.brand}>
136
133
  <div className='seam-detail-row-hstack'>
137
134
  {device.properties.model.manufacturer_display_name}
@@ -172,8 +169,6 @@ const t = {
172
169
  defaultClimate: 'Default climate',
173
170
  allowManualOverride: 'Allow manual override',
174
171
  deviceDetails: 'Device details',
175
- deviceDetailsTooltip:
176
- 'When a scheduled climate reaches its end time, the default settings will kick in.',
177
172
  brand: 'Brand',
178
173
  linkedAccount: 'Linked account',
179
174
  deviceId: 'Device ID',
@@ -1,6 +1,5 @@
1
1
  import { useComponentTelemetry } from 'lib/telemetry/index.js'
2
2
 
3
- import { createIsoDate } from 'lib/dates.js'
4
3
  import {
5
4
  useAccessCode,
6
5
  type UseAccessCodeData,
@@ -93,7 +92,7 @@ function useSubmitEditAccessCode(
93
92
  const submit = (data: AccessCodeFormSubmitData): void => {
94
93
  resetResponseErrors()
95
94
 
96
- const { name, code, type, device, startDate, endDate, timezone } = data
95
+ const { name, code, type, device, startDate, endDate } = data
97
96
  if (name === '') {
98
97
  return
99
98
  }
@@ -110,8 +109,8 @@ function useSubmitEditAccessCode(
110
109
  code,
111
110
  device_id: device.device_id,
112
111
  type: 'time_bound',
113
- starts_at: createIsoDate(startDate, timezone),
114
- ends_at: createIsoDate(endDate, timezone),
112
+ starts_at: startDate,
113
+ ends_at: endDate,
115
114
  },
116
115
  {
117
116
  onSuccess,
@@ -36,7 +36,10 @@ export function FilterCategoryMenu({
36
36
  onAllOptionSelect,
37
37
  buttonLabel,
38
38
  }: FilterCategoryMenuProps): JSX.Element {
39
- const usableOptions = hideAllOption ? options : [allLabel, ...options]
39
+ const sortedOptions = [...options].sort((a, b) => a.localeCompare(b))
40
+ const usableOptions = hideAllOption
41
+ ? sortedOptions
42
+ : [allLabel, ...sortedOptions]
40
43
 
41
44
  return (
42
45
  <div className='seam-supported-device-table-filter-menu-wrap'>
@@ -49,20 +52,22 @@ export function FilterCategoryMenu({
49
52
  </button>
50
53
  )}
51
54
  >
52
- {usableOptions.map((option, index) => (
53
- <MenuItem
54
- key={`${index}:${option}`}
55
- onClick={() => {
56
- if (option === allLabel) {
57
- onAllOptionSelect?.()
58
- } else {
59
- onSelect(option)
60
- }
61
- }}
62
- >
63
- <span>{option}</span>
64
- </MenuItem>
65
- ))}
55
+ <div className='seam-supported-device-table-filter-menu-content'>
56
+ {usableOptions.map((option, index) => (
57
+ <MenuItem
58
+ key={`${index}:${option}`}
59
+ onClick={() => {
60
+ if (option === allLabel) {
61
+ onAllOptionSelect?.()
62
+ } else {
63
+ onSelect(option)
64
+ }
65
+ }}
66
+ >
67
+ <span>{option}</span>
68
+ </MenuItem>
69
+ ))}
70
+ </div>
66
71
  </Menu>
67
72
  </div>
68
73
  )
@@ -246,6 +246,7 @@ interface CommonSpec {
246
246
  interface Context {
247
247
  traits?: Traits
248
248
  locale?: string
249
+ // According to the Common Spec this is timezone and not timeZone.
249
250
  timezone?: string
250
251
  userAgent?: string
251
252
  userAgentData?: string