@seamapi/react 1.6.0 → 1.7.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 (207) hide show
  1. package/README.md +7 -5
  2. package/dist/elements.js +25020 -0
  3. package/dist/elements.js.map +1 -0
  4. package/hooks.d.ts +1 -0
  5. package/hooks.js +2 -0
  6. package/hooks.js.map +1 -0
  7. package/index.css +6 -0
  8. package/index.css.map +1 -1
  9. package/index.min.css +1 -1
  10. package/index.min.css.map +1 -1
  11. package/lib/{sort-by.d.ts → dates.d.ts} +1 -1
  12. package/lib/{sort-by.js → dates.js} +2 -2
  13. package/lib/dates.js.map +1 -0
  14. package/lib/index.d.ts +2 -2
  15. package/lib/index.js +2 -2
  16. package/lib/index.js.map +1 -1
  17. package/lib/{SeamProvider.d.ts → seam/SeamProvider.d.ts} +9 -6
  18. package/lib/{SeamProvider.js → seam/SeamProvider.js} +29 -30
  19. package/lib/seam/SeamProvider.js.map +1 -0
  20. package/lib/seam/access-codes/use-access-codes.js +2 -2
  21. package/lib/seam/access-codes/use-access-codes.js.map +1 -1
  22. package/lib/{ui → seam/components}/AccessCodeDetails/AccessCodeDetails.js +5 -5
  23. package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js.map +1 -0
  24. package/lib/{ui → seam/components}/AccessCodeDetails/AccessCodeDevice.js +7 -7
  25. package/lib/seam/components/AccessCodeDetails/AccessCodeDevice.js.map +1 -0
  26. package/lib/seam/components/AccessCodeTable/AccessCodeTable.js +77 -0
  27. package/lib/seam/components/AccessCodeTable/AccessCodeTable.js.map +1 -0
  28. package/lib/{ui → seam/components}/AccessCodeTable/CodeDetails.js +3 -3
  29. package/lib/seam/components/AccessCodeTable/CodeDetails.js.map +1 -0
  30. package/lib/{ui → seam/components}/DeviceDetails/DeviceDetails.js +14 -14
  31. package/lib/seam/components/DeviceDetails/DeviceDetails.js.map +1 -0
  32. package/lib/seam/components/DeviceDetails/DeviceModel.js.map +1 -0
  33. package/lib/{ui → seam/components}/DeviceTable/DeviceHealthBar.d.ts +3 -2
  34. package/lib/{ui → seam/components}/DeviceTable/DeviceHealthBar.js +4 -4
  35. package/lib/seam/components/DeviceTable/DeviceHealthBar.js.map +1 -0
  36. package/lib/{ui → seam/components}/DeviceTable/DeviceRow.d.ts +1 -1
  37. package/lib/{ui → seam/components}/DeviceTable/DeviceRow.js +9 -9
  38. package/lib/seam/components/DeviceTable/DeviceRow.js.map +1 -0
  39. package/lib/{ui → seam/components}/DeviceTable/DeviceTable.d.ts +1 -1
  40. package/lib/{ui → seam/components}/DeviceTable/DeviceTable.js +11 -11
  41. package/lib/seam/components/DeviceTable/DeviceTable.js.map +1 -0
  42. package/lib/seam/components/SupportedDeviceTable/FilterCategoryMenu.d.ts +20 -0
  43. package/lib/{ui → seam/components}/SupportedDeviceTable/FilterCategoryMenu.js +8 -5
  44. package/lib/seam/components/SupportedDeviceTable/FilterCategoryMenu.js.map +1 -0
  45. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.d.ts +9 -0
  46. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.js +39 -0
  47. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.js.map +1 -0
  48. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterArea.d.ts +9 -0
  49. package/lib/{ui → seam/components}/SupportedDeviceTable/SupportedDeviceFilterArea.js +33 -28
  50. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterArea.js.map +1 -0
  51. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceHeader.d.ts +2 -0
  52. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceHeader.js +12 -0
  53. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceHeader.js.map +1 -0
  54. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.d.ts +6 -0
  55. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.js +19 -0
  56. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.js.map +1 -0
  57. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.d.ts +5 -0
  58. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.js +16 -0
  59. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.js.map +1 -0
  60. package/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.d.ts +7 -0
  61. package/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js +18 -0
  62. package/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js.map +1 -0
  63. package/lib/seam/components/index.js.map +1 -0
  64. package/lib/seam/device-models/use-device-models.d.ts +5 -0
  65. package/lib/seam/device-models/use-device-models.js +17 -0
  66. package/lib/seam/device-models/use-device-models.js.map +1 -0
  67. package/lib/seam/devices/use-devices.js +3 -3
  68. package/lib/seam/devices/use-devices.js.map +1 -1
  69. package/lib/seam/devices/use-toggle-lock.js +1 -1
  70. package/lib/seam/devices/use-toggle-lock.js.map +1 -1
  71. package/lib/seam/index.d.ts +1 -0
  72. package/lib/seam/index.js +1 -0
  73. package/lib/seam/index.js.map +1 -1
  74. package/lib/seam/use-seam-client.js +1 -1
  75. package/lib/seam/use-seam-client.js.map +1 -1
  76. package/lib/seam/use-seam-font.d.ts +1 -1
  77. package/lib/seam/use-seam-font.js +1 -1
  78. package/lib/seam/use-seam-font.js.map +1 -1
  79. package/lib/seam/use-seam-styles.js.map +1 -1
  80. package/lib/{capitalize.js → strings.js} +1 -1
  81. package/lib/strings.js.map +1 -0
  82. package/lib/ui/Menu/Menu.d.ts +1 -1
  83. package/lib/ui/Menu/Menu.js.map +1 -1
  84. package/lib/ui/Menu/MenuItem.d.ts +1 -1
  85. package/lib/ui/Menu/MoreActionsMenu.d.ts +1 -1
  86. package/lib/ui/Table/TableFilterBar/TableFilterBar.d.ts +2 -1
  87. package/lib/ui/Table/TableFilterBar/TableFilterItem.d.ts +2 -1
  88. package/lib/ui/TextField/SearchTextField.js.map +1 -1
  89. package/lib/{copy-to-clipboard.js → ui/clipboard.js} +1 -1
  90. package/lib/ui/clipboard.js.map +1 -0
  91. package/lib/ui/use-interval.js.map +1 -0
  92. package/lib/{use-is-date-in-past.js → ui/use-is-date-in-past.js} +1 -1
  93. package/lib/ui/use-is-date-in-past.js.map +1 -0
  94. package/lib/ui/use-toggle.js.map +1 -0
  95. package/lib/version.d.ts +2 -2
  96. package/lib/version.js +3 -1
  97. package/lib/version.js.map +1 -1
  98. package/package.json +21 -8
  99. package/src/elements.ts +27 -0
  100. package/src/hooks.ts +1 -0
  101. package/src/lib/{sort-by.ts → dates.ts} +1 -1
  102. package/src/lib/element.tsx +86 -0
  103. package/src/lib/index.ts +2 -2
  104. package/src/lib/{SeamProvider.tsx → seam/SeamProvider.tsx} +53 -46
  105. package/src/lib/seam/access-codes/use-access-codes.ts +2 -2
  106. package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.element.ts +12 -0
  107. package/src/lib/{ui → seam/components}/AccessCodeDetails/AccessCodeDetails.tsx +4 -5
  108. package/src/lib/{ui → seam/components}/AccessCodeDetails/AccessCodeDevice.tsx +2 -2
  109. package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.element.ts +12 -0
  110. package/src/lib/{ui → seam/components}/AccessCodeTable/AccessCodeTable.tsx +20 -5
  111. package/src/lib/{ui → seam/components}/AccessCodeTable/CodeDetails.tsx +2 -3
  112. package/src/lib/seam/components/DeviceDetails/DeviceDetails.element.ts +12 -0
  113. package/src/lib/{ui → seam/components}/DeviceDetails/DeviceDetails.tsx +8 -6
  114. package/src/lib/{ui → seam/components}/DeviceTable/DeviceHealthBar.tsx +1 -1
  115. package/src/lib/{ui → seam/components}/DeviceTable/DeviceRow.tsx +1 -1
  116. package/src/lib/seam/components/DeviceTable/DeviceTable.element.ts +11 -0
  117. package/src/lib/{ui → seam/components}/DeviceTable/DeviceTable.tsx +7 -7
  118. package/src/lib/{ui → seam/components}/SupportedDeviceTable/FilterCategoryMenu.tsx +26 -13
  119. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.tsx +120 -0
  120. package/src/lib/{ui → seam/components}/SupportedDeviceTable/SupportedDeviceFilterArea.tsx +43 -39
  121. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceHeader.tsx +22 -0
  122. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.tsx +48 -0
  123. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.element.ts +11 -0
  124. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.tsx +40 -0
  125. package/src/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.ts +35 -0
  126. package/src/lib/seam/components/elements.ts +5 -0
  127. package/src/lib/seam/device-models/use-device-models.ts +34 -0
  128. package/src/lib/seam/devices/use-devices.ts +3 -3
  129. package/src/lib/seam/devices/use-toggle-lock.ts +1 -2
  130. package/src/lib/seam/index.ts +1 -0
  131. package/src/lib/seam/use-seam-client.ts +1 -1
  132. package/src/lib/seam/use-seam-font.ts +5 -1
  133. package/src/lib/seam/use-seam-styles.ts +1 -1
  134. package/src/lib/ui/Menu/Menu.tsx +4 -4
  135. package/src/lib/ui/Menu/MenuItem.tsx +1 -1
  136. package/src/lib/ui/Menu/MoreActionsMenu.tsx +1 -1
  137. package/src/lib/ui/Table/TableFilterBar/TableFilterBar.tsx +2 -2
  138. package/src/lib/ui/Table/TableFilterBar/TableFilterItem.tsx +1 -1
  139. package/src/lib/ui/TextField/SearchTextField.tsx +3 -3
  140. package/src/lib/ui/clipboard.ts +3 -0
  141. package/src/lib/{use-interval.ts → ui/use-interval.ts} +1 -1
  142. package/src/lib/{use-is-date-in-past.ts → ui/use-is-date-in-past.ts} +1 -1
  143. package/src/lib/version.ts +3 -1
  144. package/src/styles/_access-code-table.scss +7 -0
  145. package/lib/SeamProvider.js.map +0 -1
  146. package/lib/capitalize.js.map +0 -1
  147. package/lib/copy-to-clipboard.js.map +0 -1
  148. package/lib/sort-by.js.map +0 -1
  149. package/lib/ui/AccessCodeDetails/AccessCodeDetails.js.map +0 -1
  150. package/lib/ui/AccessCodeDetails/AccessCodeDevice.js.map +0 -1
  151. package/lib/ui/AccessCodeTable/AccessCodeTable.js +0 -69
  152. package/lib/ui/AccessCodeTable/AccessCodeTable.js.map +0 -1
  153. package/lib/ui/AccessCodeTable/CodeDetails.js.map +0 -1
  154. package/lib/ui/DeviceDetails/DeviceDetails.js.map +0 -1
  155. package/lib/ui/DeviceDetails/DeviceModel.js.map +0 -1
  156. package/lib/ui/DeviceTable/DeviceHealthBar.js.map +0 -1
  157. package/lib/ui/DeviceTable/DeviceRow.js.map +0 -1
  158. package/lib/ui/DeviceTable/DeviceTable.js.map +0 -1
  159. package/lib/ui/SupportedDeviceTable/FilterCategoryMenu.d.ts +0 -15
  160. package/lib/ui/SupportedDeviceTable/FilterCategoryMenu.js.map +0 -1
  161. package/lib/ui/SupportedDeviceTable/SupportedDeviceContent.d.ts +0 -4
  162. package/lib/ui/SupportedDeviceTable/SupportedDeviceContent.js +0 -46
  163. package/lib/ui/SupportedDeviceTable/SupportedDeviceContent.js.map +0 -1
  164. package/lib/ui/SupportedDeviceTable/SupportedDeviceFilterArea.d.ts +0 -11
  165. package/lib/ui/SupportedDeviceTable/SupportedDeviceFilterArea.js.map +0 -1
  166. package/lib/ui/SupportedDeviceTable/SupportedDeviceHeader.d.ts +0 -1
  167. package/lib/ui/SupportedDeviceTable/SupportedDeviceHeader.js +0 -5
  168. package/lib/ui/SupportedDeviceTable/SupportedDeviceHeader.js.map +0 -1
  169. package/lib/ui/SupportedDeviceTable/SupportedDeviceRow.d.ts +0 -5
  170. package/lib/ui/SupportedDeviceTable/SupportedDeviceRow.js +0 -12
  171. package/lib/ui/SupportedDeviceTable/SupportedDeviceRow.js.map +0 -1
  172. package/lib/ui/SupportedDeviceTable/SupportedDeviceTable.d.ts +0 -3
  173. package/lib/ui/SupportedDeviceTable/SupportedDeviceTable.js +0 -8
  174. package/lib/ui/SupportedDeviceTable/SupportedDeviceTable.js.map +0 -1
  175. package/lib/ui/SupportedDeviceTable/types.d.ts +0 -15
  176. package/lib/ui/SupportedDeviceTable/types.js +0 -2
  177. package/lib/ui/SupportedDeviceTable/types.js.map +0 -1
  178. package/lib/ui/index.js.map +0 -1
  179. package/lib/use-interval.js.map +0 -1
  180. package/lib/use-is-date-in-past.js.map +0 -1
  181. package/lib/use-toggle.js.map +0 -1
  182. package/src/lib/copy-to-clipboard.ts +0 -3
  183. package/src/lib/ui/SupportedDeviceTable/SupportedDeviceContent.tsx +0 -135
  184. package/src/lib/ui/SupportedDeviceTable/SupportedDeviceHeader.tsx +0 -14
  185. package/src/lib/ui/SupportedDeviceTable/SupportedDeviceRow.tsx +0 -35
  186. package/src/lib/ui/SupportedDeviceTable/SupportedDeviceTable.tsx +0 -16
  187. package/src/lib/ui/SupportedDeviceTable/types.ts +0 -16
  188. /package/lib/{ui → seam/components}/AccessCodeDetails/AccessCodeDetails.d.ts +0 -0
  189. /package/lib/{ui → seam/components}/AccessCodeDetails/AccessCodeDevice.d.ts +0 -0
  190. /package/lib/{ui → seam/components}/AccessCodeTable/AccessCodeTable.d.ts +0 -0
  191. /package/lib/{ui → seam/components}/AccessCodeTable/CodeDetails.d.ts +0 -0
  192. /package/lib/{ui → seam/components}/DeviceDetails/DeviceDetails.d.ts +0 -0
  193. /package/lib/{ui → seam/components}/DeviceDetails/DeviceModel.d.ts +0 -0
  194. /package/lib/{ui → seam/components}/DeviceDetails/DeviceModel.js +0 -0
  195. /package/lib/{ui → seam/components}/index.d.ts +0 -0
  196. /package/lib/{ui → seam/components}/index.js +0 -0
  197. /package/lib/{capitalize.d.ts → strings.d.ts} +0 -0
  198. /package/lib/{copy-to-clipboard.d.ts → ui/clipboard.d.ts} +0 -0
  199. /package/lib/{use-interval.d.ts → ui/use-interval.d.ts} +0 -0
  200. /package/lib/{use-interval.js → ui/use-interval.js} +0 -0
  201. /package/lib/{use-is-date-in-past.d.ts → ui/use-is-date-in-past.d.ts} +0 -0
  202. /package/lib/{use-toggle.d.ts → ui/use-toggle.d.ts} +0 -0
  203. /package/lib/{use-toggle.js → ui/use-toggle.js} +0 -0
  204. /package/src/lib/{ui → seam/components}/DeviceDetails/DeviceModel.tsx +0 -0
  205. /package/src/lib/{ui → seam/components}/index.ts +0 -0
  206. /package/src/lib/{capitalize.ts → strings.ts} +0 -0
  207. /package/src/lib/{use-toggle.ts → ui/use-toggle.ts} +0 -0
@@ -6,9 +6,9 @@ import type {
6
6
  SeamError,
7
7
  } from 'seamapi'
8
8
 
9
+ import { compareByCreatedAtDesc } from 'lib/dates.js'
9
10
  import { useSeamClient } from 'lib/seam/use-seam-client.js'
10
11
  import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js'
11
- import { byCreatedAt } from 'lib/sort-by.js'
12
12
 
13
13
  export type UseAccessCodesParams = AccessCodesListRequest | string
14
14
  export type UseAccessCodesData = AccessCode[]
@@ -29,7 +29,7 @@ export function useAccessCodes(
29
29
  queryFn: async () => {
30
30
  if (client == null) return []
31
31
  const accessCodes = await client?.accessCodes.list(normalizedParams)
32
- return accessCodes.sort(byCreatedAt)
32
+ return accessCodes.sort(compareByCreatedAtDesc)
33
33
  },
34
34
  })
35
35
 
@@ -0,0 +1,12 @@
1
+ import type { ElementProps } from 'lib/element.js'
2
+
3
+ import type { AccessCodeDetailsProps } from './AccessCodeDetails.js'
4
+
5
+ export const name = 'seam-access-code-details'
6
+
7
+ export const props: ElementProps<AccessCodeDetailsProps> = {
8
+ accessCodeId: 'string',
9
+ onBack: 'function',
10
+ }
11
+
12
+ export { AccessCodeDetails as Component } from './AccessCodeDetails.js'
@@ -2,12 +2,11 @@ import { DateTime } from 'luxon'
2
2
  import { useState } from 'react'
3
3
  import type { AccessCode } from 'seamapi'
4
4
 
5
- import { useAccessCode } from 'lib/index.js'
6
-
7
- import { AccessCodeDevice } from 'lib/ui/AccessCodeDetails/AccessCodeDevice.js'
8
- import { DeviceDetails } from 'lib/ui/DeviceDetails/DeviceDetails.js'
5
+ import { useAccessCode } from 'lib/seam/access-codes/use-access-code.js'
6
+ import { AccessCodeDevice } from 'lib/seam/components/AccessCodeDetails/AccessCodeDevice.js'
7
+ import { DeviceDetails } from 'lib/seam/components/DeviceDetails/DeviceDetails.js'
9
8
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
10
- import { useIsDateInPast } from 'lib/use-is-date-in-past.js'
9
+ import { useIsDateInPast } from 'lib/ui/use-is-date-in-past.js'
11
10
 
12
11
  export interface AccessCodeDetailsProps {
13
12
  accessCodeId: string
@@ -1,11 +1,11 @@
1
1
  import type { LockDevice } from 'seamapi'
2
2
 
3
+ import { getDeviceModel } from 'lib/seam/components/DeviceDetails/DeviceModel.js'
3
4
  import { isLockDevice } from 'lib/seam/devices/types.js'
4
5
  import { useDevice } from 'lib/seam/devices/use-device.js'
5
6
  import { useToggleLock } from 'lib/seam/devices/use-toggle-lock.js'
6
7
  import { Button } from 'lib/ui/Button.js'
7
8
  import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
8
- import { getDeviceModel } from 'lib/ui/DeviceDetails/DeviceModel.js'
9
9
  import { TextButton } from 'lib/ui/TextButton.js'
10
10
 
11
11
  export function AccessCodeDevice({
@@ -27,7 +27,7 @@ export function AccessCodeDevice({
27
27
  function LockDeviceDetails(props: {
28
28
  device: LockDevice
29
29
  onSelectDevice: (deviceId: string) => void
30
- }) {
30
+ }): JSX.Element {
31
31
  const { device, onSelectDevice } = props
32
32
  const toggleLock = useToggleLock(device)
33
33
  const model = getDeviceModel(device)
@@ -0,0 +1,12 @@
1
+ import type { ElementProps } from 'lib/element.js'
2
+
3
+ import type { AccessCodeTableProps } from './AccessCodeTable.js'
4
+
5
+ export const name = 'seam-access-code-table'
6
+
7
+ export const props: ElementProps<AccessCodeTableProps> = {
8
+ deviceId: 'string',
9
+ onBack: 'function',
10
+ }
11
+
12
+ export { AccessCodeTable as Component } from './AccessCodeTable.js'
@@ -1,15 +1,16 @@
1
1
  import { useState } from 'react'
2
2
  import type { AccessCode } from 'seamapi'
3
3
 
4
- import { copyToClipboard } from 'lib/copy-to-clipboard.js'
5
4
  import { AccessCodeKeyIcon } from 'lib/icons/AccessCodeKey.js'
6
5
  import { CopyIcon } from 'lib/icons/Copy.js'
6
+ import { ExclamationCircleOutlineIcon } from 'lib/icons/ExclamationCircleOutline.js'
7
7
  import {
8
8
  useAccessCodes,
9
9
  type UseAccessCodesData,
10
10
  } from 'lib/seam/access-codes/use-access-codes.js'
11
- import { AccessCodeDetails } from 'lib/ui/AccessCodeDetails/AccessCodeDetails.js'
12
- import { CodeDetails } from 'lib/ui/AccessCodeTable/CodeDetails.js'
11
+ import { AccessCodeDetails } from 'lib/seam/components/AccessCodeDetails/AccessCodeDetails.js'
12
+ import { CodeDetails } from 'lib/seam/components/AccessCodeTable/CodeDetails.js'
13
+ import { copyToClipboard } from 'lib/ui/clipboard.js'
13
14
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
14
15
  import { MenuItem } from 'lib/ui/Menu/MenuItem.js'
15
16
  import { MoreActionsMenu } from 'lib/ui/Menu/MoreActionsMenu.js'
@@ -89,7 +90,7 @@ export function AccessCodeTable(
89
90
  function Body(props: {
90
91
  accessCodes: Array<UseAccessCodesData[number]>
91
92
  selectAccessCode: (accessCode: AccessCode) => void
92
- }) {
93
+ }): JSX.Element {
93
94
  const { accessCodes, selectAccessCode } = props
94
95
 
95
96
  if (accessCodes.length === 0) {
@@ -114,8 +115,15 @@ function Body(props: {
114
115
  function AccessCodeRow(props: {
115
116
  accessCode: UseAccessCodesData[number]
116
117
  onClick: () => void
117
- }) {
118
+ }): JSX.Element {
118
119
  const { onClick, accessCode } = props
120
+
121
+ const errorCount = accessCode.errors?.length ?? 0
122
+ const isPlural = errorCount === 0 || errorCount > 1
123
+ const issueIconTitle = isPlural
124
+ ? `${errorCount} ${t.codeIssues}`
125
+ : `${errorCount} ${t.codeIssue}`
126
+
119
127
  return (
120
128
  <TableRow key={accessCode.access_code_id} onClick={onClick}>
121
129
  <TableCell className='seam-icon-cell'>
@@ -128,6 +136,11 @@ function AccessCodeRow(props: {
128
136
  <CodeDetails accessCode={accessCode} />
129
137
  </TableCell>
130
138
  <TableCell className='seam-action-cell'>
139
+ {errorCount > 0 && (
140
+ <div className='seam-code-issue-icon-wrap' title={issueIconTitle}>
141
+ <ExclamationCircleOutlineIcon />
142
+ </div>
143
+ )}
131
144
  <MoreActionsMenu
132
145
  menuProps={{
133
146
  backgroundProps: {
@@ -157,4 +170,6 @@ const t = {
157
170
  accessCodes: 'Access Codes',
158
171
  copyCode: 'Copy code',
159
172
  noAccessCodesMessage: 'Sorry, no access codes were found',
173
+ codeIssue: 'code issue',
174
+ codeIssues: 'code issues',
160
175
  }
@@ -1,10 +1,9 @@
1
1
  import { DateTime } from 'luxon'
2
2
  import type { AccessCode } from 'seamapi'
3
3
 
4
- import { useDevice } from 'lib/index.js'
5
-
4
+ import { useDevice } from 'lib/seam/devices/use-device.js'
6
5
  import { DotDivider } from 'lib/ui/layout/DotDivider.js'
7
- import { useIsDateInPast } from 'lib/use-is-date-in-past.js'
6
+ import { useIsDateInPast } from 'lib/ui/use-is-date-in-past.js'
8
7
 
9
8
  export function CodeDetails(props: { accessCode: AccessCode }): JSX.Element {
10
9
  const { accessCode } = props
@@ -0,0 +1,12 @@
1
+ import type { ElementProps } from 'lib/element.js'
2
+
3
+ import type { DeviceDetailsProps } from './DeviceDetails.js'
4
+
5
+ export const name = 'seam-device-details'
6
+
7
+ export const props: ElementProps<DeviceDetailsProps> = {
8
+ deviceId: 'string',
9
+ onBack: 'function',
10
+ }
11
+
12
+ export { DeviceDetails as Component } from './DeviceDetails.js'
@@ -1,20 +1,19 @@
1
1
  import type { LockDevice } from 'seamapi'
2
2
 
3
- import { useDevice } from 'lib/index.js'
4
-
5
3
  import { ChevronRightIcon } from 'lib/icons/ChevronRight.js'
6
4
  import { useAccessCodes } from 'lib/seam/access-codes/use-access-codes.js'
5
+ import { AccessCodeTable } from 'lib/seam/components/AccessCodeTable/AccessCodeTable.js'
6
+ import { DeviceModel } from 'lib/seam/components/DeviceDetails/DeviceModel.js'
7
7
  import { isLockDevice } from 'lib/seam/devices/types.js'
8
+ import { useDevice } from 'lib/seam/devices/use-device.js'
8
9
  import { useToggleLock } from 'lib/seam/devices/use-toggle-lock.js'
9
- import { AccessCodeTable } from 'lib/ui/AccessCodeTable/AccessCodeTable.js'
10
10
  import { Alerts } from 'lib/ui/Alert/Alerts.js'
11
11
  import { Button } from 'lib/ui/Button.js'
12
12
  import { BatteryStatus } from 'lib/ui/device/BatteryStatus.js'
13
13
  import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
14
14
  import { OnlineStatus } from 'lib/ui/device/OnlineStatus.js'
15
- import { DeviceModel } from 'lib/ui/DeviceDetails/DeviceModel.js'
16
15
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
17
- import { useToggle } from 'lib/use-toggle.js'
16
+ import { useToggle } from 'lib/ui/use-toggle.js'
18
17
 
19
18
  export interface DeviceDetailsProps {
20
19
  deviceId: string
@@ -39,7 +38,10 @@ export function DeviceDetails(props: DeviceDetailsProps): JSX.Element | null {
39
38
  return <LockDeviceDetails device={device} onBack={onBack} />
40
39
  }
41
40
 
42
- function LockDeviceDetails(props: { device: LockDevice; onBack?: () => void }) {
41
+ function LockDeviceDetails(props: {
42
+ device: LockDevice
43
+ onBack?: () => void
44
+ }): JSX.Element | null {
43
45
  const { device, onBack } = props
44
46
 
45
47
  const [accessCodesOpen, toggleAccessCodesOpen] = useToggle()
@@ -16,7 +16,7 @@ export function DeviceHealthBar({
16
16
  devices,
17
17
  filter,
18
18
  onFilterSelect,
19
- }: DeviceHealthBarProps) {
19
+ }: DeviceHealthBarProps): JSX.Element {
20
20
  const erroredDevices = devices.filter((device) => device.errors.length > 0)
21
21
  const issueCount = erroredDevices.length
22
22
 
@@ -1,3 +1,4 @@
1
+ import { getDeviceModel } from 'lib/seam/components/DeviceDetails/DeviceModel.js'
1
2
  import { isLockDevice } from 'lib/seam/devices/types.js'
2
3
  import type {
3
4
  UseDevicesData,
@@ -7,7 +8,6 @@ import { BatteryStatus } from 'lib/ui/device/BatteryStatus.js'
7
8
  import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
8
9
  import { LockStatus } from 'lib/ui/device/LockStatus.js'
9
10
  import { OnlineStatus } from 'lib/ui/device/OnlineStatus.js'
10
- import { getDeviceModel } from 'lib/ui/DeviceDetails/DeviceModel.js'
11
11
  import { TableCell } from 'lib/ui/Table/TableCell.js'
12
12
  import { TableRow } from 'lib/ui/Table/TableRow.js'
13
13
  import { Title } from 'lib/ui/typography/Title.js'
@@ -0,0 +1,11 @@
1
+ import type { ElementProps } from 'lib/element.js'
2
+
3
+ import type { DeviceTableProps } from './DeviceTable.js'
4
+
5
+ export const name = 'seam-device-table'
6
+
7
+ export const props: ElementProps<DeviceTableProps> = {
8
+ onBack: 'function',
9
+ }
10
+
11
+ export { DeviceTable as Component } from './DeviceTable.js'
@@ -1,16 +1,16 @@
1
1
  import { useState } from 'react'
2
2
 
3
+ import { DeviceDetails } from 'lib/seam/components/DeviceDetails/DeviceDetails.js'
4
+ import {
5
+ type DeviceFilter,
6
+ DeviceHealthBar,
7
+ } from 'lib/seam/components/DeviceTable/DeviceHealthBar.js'
8
+ import { DeviceRow } from 'lib/seam/components/DeviceTable/DeviceRow.js'
3
9
  import {
4
10
  useDevices,
5
11
  type UseDevicesData,
6
12
  type UseDevicesParams,
7
13
  } from 'lib/seam/devices/use-devices.js'
8
- import { DeviceDetails } from 'lib/ui/DeviceDetails/DeviceDetails.js'
9
- import {
10
- type DeviceFilter,
11
- DeviceHealthBar,
12
- } from 'lib/ui/DeviceTable/DeviceHealthBar.js'
13
- import { DeviceRow } from 'lib/ui/DeviceTable/DeviceRow.js'
14
14
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
15
15
  import { EmptyPlaceholder } from 'lib/ui/Table/EmptyPlaceholder.js'
16
16
  import { TableBody } from 'lib/ui/Table/TableBody.js'
@@ -90,7 +90,7 @@ export function DeviceTable({
90
90
  function Body(props: {
91
91
  devices: Array<UseDevicesData[number]>
92
92
  selectDevice: (id: string) => void
93
- }) {
93
+ }): JSX.Element {
94
94
  const { devices, selectDevice } = props
95
95
  const [filter, setFilter] = useState<DeviceFilter | null>(null)
96
96
 
@@ -2,6 +2,24 @@ import { ChevronDownIcon } from 'lib/icons/ChevronDown.js'
2
2
  import { Menu } from 'lib/ui/Menu/Menu.js'
3
3
  import { MenuItem } from 'lib/ui/Menu/MenuItem.js'
4
4
 
5
+ export type FilterCategoryMenuProps =
6
+ | FilterCategoryMenuPropsWithAllOption
7
+ | FilterCategoryMenuPropsWithoutAllOption
8
+
9
+ interface FilterCategoryMenuPropsWithAllOption
10
+ extends FilterCategoryMenuBaseProps {
11
+ hideAllOption?: false
12
+ allLabel: string
13
+ onAllOptionSelect: () => void
14
+ }
15
+
16
+ interface FilterCategoryMenuPropsWithoutAllOption
17
+ extends FilterCategoryMenuBaseProps {
18
+ hideAllOption: true
19
+ allLabel: never
20
+ onAllOptionSelect?: never
21
+ }
22
+
5
23
  interface FilterCategoryMenuBaseProps {
6
24
  label: string
7
25
  options: string[]
@@ -9,24 +27,15 @@ interface FilterCategoryMenuBaseProps {
9
27
  buttonLabel?: string
10
28
  }
11
29
 
12
- export type FilterCategoryMenuProps =
13
- | (FilterCategoryMenuBaseProps & {
14
- hideAllOption?: false
15
- onAllOptionSelect: () => void
16
- })
17
- | (FilterCategoryMenuBaseProps & {
18
- hideAllOption: true
19
- onAllOptionSelect?: never
20
- })
21
-
22
30
  export function FilterCategoryMenu({
23
- label = 'Filter',
31
+ label = t.filter,
32
+ allLabel,
24
33
  options,
25
34
  hideAllOption = false,
26
35
  onSelect,
27
36
  onAllOptionSelect,
28
37
  buttonLabel,
29
- }: FilterCategoryMenuProps) {
38
+ }: FilterCategoryMenuProps): JSX.Element {
30
39
  const usableOptions = hideAllOption ? options : ['All', ...options]
31
40
 
32
41
  return (
@@ -44,7 +53,7 @@ export function FilterCategoryMenu({
44
53
  <MenuItem
45
54
  key={`${index}:${option}`}
46
55
  onClick={() => {
47
- if (option === 'All') {
56
+ if (option === allLabel) {
48
57
  onAllOptionSelect?.()
49
58
  } else {
50
59
  onSelect(option)
@@ -58,3 +67,7 @@ export function FilterCategoryMenu({
58
67
  </div>
59
68
  )
60
69
  }
70
+
71
+ const t = {
72
+ filter: 'Filter',
73
+ }
@@ -0,0 +1,120 @@
1
+ import { SupportedDeviceHeader } from 'lib/seam/components/SupportedDeviceTable/SupportedDeviceHeader.js'
2
+ import { SupportedDeviceRow } from 'lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.js'
3
+ import {
4
+ type DeviceModelFilters,
5
+ useFilteredDeviceModels,
6
+ } from 'lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js'
7
+ import { Button } from 'lib/ui/Button.js'
8
+
9
+ interface SupportedDeviceContentProps {
10
+ filterValue: string
11
+ resetFilterValue: () => void
12
+ filters: DeviceModelFilters
13
+ }
14
+
15
+ export function SupportedDeviceContent({
16
+ resetFilterValue,
17
+ filterValue,
18
+ filters,
19
+ }: SupportedDeviceContentProps): JSX.Element | null {
20
+ const { deviceModels, isLoading, isError, refetch } = useFilteredDeviceModels(
21
+ filterValue,
22
+ filters
23
+ )
24
+
25
+ if (isLoading) {
26
+ return (
27
+ <div className='seam-supported-device-table-content-state-block'>
28
+ <p>{t.loading}</p>
29
+ </div>
30
+ )
31
+ }
32
+
33
+ if (isError) {
34
+ return (
35
+ <div className='seam-supported-device-table-content-state-block'>
36
+ <p>{t.error}</p>
37
+ <Button
38
+ variant='solid'
39
+ size='small'
40
+ onClick={() => {
41
+ void refetch()
42
+ }}
43
+ >
44
+ {t.retry}
45
+ </Button>
46
+ </div>
47
+ )
48
+ }
49
+
50
+ if (deviceModels == null) {
51
+ return null
52
+ }
53
+
54
+ return (
55
+ <table className='seam-supported-device-table-content'>
56
+ <SupportedDeviceHeader />
57
+ <tbody>
58
+ {deviceModels.map((deviceModel, index) => (
59
+ <SupportedDeviceRow
60
+ key={[
61
+ deviceModel.main_category,
62
+ deviceModel.brand,
63
+ deviceModel.model_name,
64
+ deviceModel.manufacturer_model_id,
65
+ index,
66
+ ].join(':')}
67
+ deviceModel={deviceModel}
68
+ />
69
+ ))}
70
+ {deviceModels.length === 0 && (
71
+ <EmptyResult
72
+ filterValue={filterValue}
73
+ resetFilterValue={resetFilterValue}
74
+ />
75
+ )}
76
+ </tbody>
77
+ </table>
78
+ )
79
+ }
80
+
81
+ function EmptyResult({
82
+ filterValue,
83
+ resetFilterValue,
84
+ }: Pick<
85
+ SupportedDeviceContentProps,
86
+ 'filterValue' | 'resetFilterValue'
87
+ >): JSX.Element {
88
+ const noMatchingRows = (
89
+ <>
90
+ <p>{t.noMatch}</p>
91
+ <Button
92
+ variant='outline'
93
+ size='small'
94
+ onClick={resetFilterValue}
95
+ className='seam-supported-device-table-content-message-clear-search'
96
+ >
97
+ {t.clear}
98
+ </Button>
99
+ </>
100
+ )
101
+
102
+ return (
103
+ <tr className='seam-supported-device-table-content-message-row'>
104
+ <td colSpan={6}>
105
+ <div className='seam-supported-device-table-content-message'>
106
+ {filterValue.length === 0 ? <p>{t.noneFound}</p> : noMatchingRows}
107
+ </div>
108
+ </td>
109
+ </tr>
110
+ )
111
+ }
112
+
113
+ const t = {
114
+ loading: 'Loading device models...',
115
+ retry: 'Retry',
116
+ error: 'There was an error fetching device models.',
117
+ noneFound: 'No device models found.',
118
+ noMatch: 'No device models matched your search.',
119
+ clear: 'Clear search terms',
120
+ }
@@ -1,58 +1,45 @@
1
1
  import type { Dispatch, SetStateAction } from 'react'
2
+ import type { DeviceModel } from 'seamapi'
2
3
 
3
- import { capitalize } from 'lib/capitalize.js'
4
+ import { FilterCategoryMenu } from 'lib/seam/components/SupportedDeviceTable/FilterCategoryMenu.js'
5
+ import type { DeviceModelFilters } from 'lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js'
6
+ import { useDeviceModels } from 'lib/seam/device-models/use-device-models.js'
7
+ import { capitalize } from 'lib/strings.js'
4
8
  import { Button } from 'lib/ui/Button.js'
5
9
  import { Menu } from 'lib/ui/Menu/Menu.js'
6
- import { FilterCategoryMenu } from 'lib/ui/SupportedDeviceTable/FilterCategoryMenu.js'
7
- import type { DeviceModel, Filters } from 'lib/ui/SupportedDeviceTable/types.js'
8
10
  import { SearchTextField } from 'lib/ui/TextField/SearchTextField.js'
9
11
 
10
- interface SupportedDeviceFilterAreaProps {
11
- deviceModels: DeviceModel[]
12
+ export interface SupportedDeviceFilterAreaProps {
12
13
  filterValue: string
13
14
  setFilterValue: (filter: string) => void
14
- filters: Filters
15
- setFilters: Dispatch<SetStateAction<Filters>>
15
+ filters: DeviceModelFilters
16
+ setFilters: Dispatch<SetStateAction<DeviceModelFilters>>
16
17
  }
17
18
 
18
19
  export function SupportedDeviceFilterArea({
19
- deviceModels,
20
20
  filterValue,
21
21
  setFilterValue,
22
22
  filters,
23
23
  setFilters,
24
- }: SupportedDeviceFilterAreaProps) {
25
- const appliedFilters = Object.keys(filters).filter((key) => {
26
- const filterProperty = filters[key as keyof Filters]
27
- return (
28
- filterProperty !== undefined &&
29
- filterProperty !== null &&
30
- filterProperty !== false
31
- )
32
- })
33
- const appliedFiltersCount = appliedFilters.length
24
+ }: SupportedDeviceFilterAreaProps): JSX.Element {
25
+ const appliedFiltersCount = Object.values(filters).filter(
26
+ (v) => v != null && v !== false
27
+ ).length
34
28
 
35
- const getAvailablePropertiesFromDeviceModels = (
36
- property: keyof DeviceModel
37
- ) => {
38
- const properties = new Set<string>()
39
- deviceModels.forEach((deviceModel) => {
40
- properties.add(capitalize(deviceModel[property]))
41
- })
42
- return Array.from(properties)
43
- }
29
+ const filterProperty = 'brand'
30
+ const availableProperties = useAvailableProperties(filterProperty)
44
31
 
45
- const resetFilter = (filterType: keyof Filters) => {
32
+ const resetFilter = (filterType: keyof DeviceModelFilters): void => {
46
33
  setFilters((filters) => ({
47
34
  ...filters,
48
35
  [filterType]: null,
49
36
  }))
50
37
  }
51
38
 
52
- const isPlural = appliedFiltersCount > 1
53
- const filterButtonLabel = isPlural
54
- ? `Filters (${appliedFiltersCount})`
55
- : 'Filter'
39
+ const filterButtonLabel =
40
+ appliedFiltersCount > 0 ? `${t.filters} (${appliedFiltersCount})` : t.filter
41
+
42
+ const allLabel = t.all
56
43
 
57
44
  return (
58
45
  <div className='seam-supported-device-table-filter-area'>
@@ -77,27 +64,27 @@ export function SupportedDeviceFilterArea({
77
64
  >
78
65
  <div className='seam-filter-menu-row'>
79
66
  <FilterCategoryMenu
80
- label='Brand'
81
- options={getAvailablePropertiesFromDeviceModels('brand')}
67
+ label={t.brand}
68
+ allLabel={allLabel}
69
+ options={availableProperties}
82
70
  onSelect={(brand: string) => {
83
71
  setFilters((filters) => ({
84
72
  ...filters,
85
73
  brand,
86
74
  }))
87
75
  }}
88
- buttonLabel={filters.brand ?? 'All'}
76
+ buttonLabel={filters.brand ?? allLabel}
89
77
  onAllOptionSelect={() => {
90
- resetFilter('brand')
78
+ resetFilter(filterProperty)
91
79
  }}
92
80
  />
93
81
  </div>
94
-
95
82
  <div className='seam-filter-menu-row'>
96
83
  <label
97
84
  htmlFor='supportedOnly'
98
85
  className='seam-filter-checkbox-label'
99
86
  >
100
- <p>Supported</p>
87
+ <p>{t.supported}</p>
101
88
  <input
102
89
  id='supportedOnly'
103
90
  name='supportedOnly'
@@ -115,7 +102,6 @@ export function SupportedDeviceFilterArea({
115
102
  </div>
116
103
  </div>
117
104
  </Menu>
118
-
119
105
  <div className='seam-supported-device-table-filter-area-search-bar-wrap'>
120
106
  <SearchTextField
121
107
  value={filterValue}
@@ -129,3 +115,21 @@ export function SupportedDeviceFilterArea({
129
115
  </div>
130
116
  )
131
117
  }
118
+
119
+ const useAvailableProperties = (property: keyof DeviceModel): string[] => {
120
+ const { deviceModels } = useDeviceModels()
121
+ if (deviceModels == null) return []
122
+ const properties = new Set<string>()
123
+ for (const deviceModel of deviceModels) {
124
+ properties.add(capitalize(deviceModel[property]))
125
+ }
126
+ return Array.from(properties)
127
+ }
128
+
129
+ const t = {
130
+ all: 'All',
131
+ brand: 'Brand',
132
+ supported: 'Supported',
133
+ filter: 'Filter',
134
+ filters: 'Filters',
135
+ }
@@ -0,0 +1,22 @@
1
+ export function SupportedDeviceHeader(): JSX.Element {
2
+ return (
3
+ <thead>
4
+ <tr>
5
+ <th />
6
+ <th>{t.category}</th>
7
+ <th>{t.modelName}</th>
8
+ <th>{t.manufacturerModelId}</th>
9
+ <th>{t.connectionType}</th>
10
+ <th>{t.status}</th>
11
+ </tr>
12
+ </thead>
13
+ )
14
+ }
15
+
16
+ const t = {
17
+ category: 'Category',
18
+ modelName: 'Model Name',
19
+ manufacturerModelId: 'Manufacturer Model ID',
20
+ connectionType: 'Connection Type',
21
+ status: 'Status',
22
+ }