@seamapi/react 1.22.0 → 1.24.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 (58) hide show
  1. package/README.md +1 -1
  2. package/dist/elements.js +4998 -4842
  3. package/dist/elements.js.map +1 -1
  4. package/dist/index.css +3 -0
  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/icons/TriangleWarningOutline.d.ts +2 -0
  9. package/lib/icons/TriangleWarningOutline.js +3 -0
  10. package/lib/icons/TriangleWarningOutline.js.map +1 -0
  11. package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.d.ts +2 -1
  12. package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js +3 -3
  13. package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js.map +1 -1
  14. package/lib/seam/components/AccessCodeDetails/AccessCodeDevice.d.ts +2 -1
  15. package/lib/seam/components/AccessCodeDetails/AccessCodeDevice.js +8 -6
  16. package/lib/seam/components/AccessCodeDetails/AccessCodeDevice.js.map +1 -1
  17. package/lib/seam/components/AccessCodeTable/AccessCodeHealthBar.d.ts +10 -0
  18. package/lib/seam/components/AccessCodeTable/AccessCodeHealthBar.js +29 -0
  19. package/lib/seam/components/AccessCodeTable/AccessCodeHealthBar.js.map +1 -0
  20. package/lib/seam/components/AccessCodeTable/AccessCodeTable.d.ts +2 -1
  21. package/lib/seam/components/AccessCodeTable/AccessCodeTable.js +30 -10
  22. package/lib/seam/components/AccessCodeTable/AccessCodeTable.js.map +1 -1
  23. package/lib/seam/components/DeviceDetails/DeviceDetails.d.ts +2 -1
  24. package/lib/seam/components/DeviceDetails/DeviceDetails.js +2 -2
  25. package/lib/seam/components/DeviceDetails/DeviceDetails.js.map +1 -1
  26. package/lib/seam/components/DeviceDetails/LockDeviceDetails.d.ts +1 -0
  27. package/lib/seam/components/DeviceDetails/LockDeviceDetails.js +4 -4
  28. package/lib/seam/components/DeviceDetails/LockDeviceDetails.js.map +1 -1
  29. package/lib/seam/components/DeviceTable/DeviceHealthBar.d.ts +3 -2
  30. package/lib/seam/components/DeviceTable/DeviceHealthBar.js +23 -11
  31. package/lib/seam/components/DeviceTable/DeviceHealthBar.js.map +1 -1
  32. package/lib/seam/components/DeviceTable/DeviceRow.js +19 -1
  33. package/lib/seam/components/DeviceTable/DeviceRow.js.map +1 -1
  34. package/lib/seam/components/DeviceTable/DeviceTable.d.ts +2 -1
  35. package/lib/seam/components/DeviceTable/DeviceTable.js +7 -3
  36. package/lib/seam/components/DeviceTable/DeviceTable.js.map +1 -1
  37. package/lib/ui/device/OnlineStatus.js +14 -4
  38. package/lib/ui/device/OnlineStatus.js.map +1 -1
  39. package/lib/version.d.ts +1 -1
  40. package/lib/version.js +1 -1
  41. package/package.json +1 -1
  42. package/src/lib/icons/TriangleWarningOutline.tsx +18 -0
  43. package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.element.ts +1 -0
  44. package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.tsx +4 -0
  45. package/src/lib/seam/components/AccessCodeDetails/AccessCodeDevice.tsx +23 -10
  46. package/src/lib/seam/components/AccessCodeTable/AccessCodeHealthBar.tsx +68 -0
  47. package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.element.ts +1 -0
  48. package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx +47 -6
  49. package/src/lib/seam/components/DeviceDetails/DeviceDetails.element.ts +1 -0
  50. package/src/lib/seam/components/DeviceDetails/DeviceDetails.tsx +3 -0
  51. package/src/lib/seam/components/DeviceDetails/LockDeviceDetails.tsx +14 -9
  52. package/src/lib/seam/components/DeviceTable/DeviceHealthBar.tsx +49 -19
  53. package/src/lib/seam/components/DeviceTable/DeviceRow.tsx +48 -9
  54. package/src/lib/seam/components/DeviceTable/DeviceTable.element.ts +1 -0
  55. package/src/lib/seam/components/DeviceTable/DeviceTable.tsx +17 -2
  56. package/src/lib/ui/device/OnlineStatus.tsx +23 -7
  57. package/src/lib/version.ts +1 -1
  58. package/src/styles/_device-table.scss +4 -0
@@ -13,12 +13,14 @@ import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
13
13
  import { OnlineStatus } from 'lib/ui/device/OnlineStatus.js'
14
14
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
15
15
  import { useToggle } from 'lib/ui/use-toggle.js'
16
+
16
17
  export function LockDeviceDetails(props: {
17
18
  device: LockDevice
19
+ disableLockUnlock: boolean
18
20
  onBack?: () => void
19
21
  className?: string
20
22
  }): JSX.Element | null {
21
- const { device, onBack, className } = props
23
+ const { device, disableLockUnlock, onBack, className } = props
22
24
 
23
25
  const [accessCodesOpen, toggleAccessCodesOpen] = useToggle()
24
26
  const toggleLock = useToggleLock(device)
@@ -41,6 +43,7 @@ export function LockDeviceDetails(props: {
41
43
  className={className}
42
44
  deviceId={device.device_id}
43
45
  onBack={toggleAccessCodesOpen}
46
+ disableLockUnlock={disableLockUnlock}
44
47
  />
45
48
  )
46
49
  }
@@ -98,14 +101,16 @@ export function LockDeviceDetails(props: {
98
101
  <span className='seam-value'>{lockStatus}</span>
99
102
  </div>
100
103
  <div className='seam-right'>
101
- <Button
102
- size='small'
103
- onClick={() => {
104
- toggleLock.mutate()
105
- }}
106
- >
107
- {toggleLockLabel}
108
- </Button>
104
+ {!disableLockUnlock && (
105
+ <Button
106
+ size='small'
107
+ onClick={() => {
108
+ toggleLock.mutate()
109
+ }}
110
+ >
111
+ {toggleLockLabel}
112
+ </Button>
113
+ )}
109
114
  </div>
110
115
  </div>
111
116
  <AccessCodeLength
@@ -1,15 +1,17 @@
1
1
  import { CheckIcon } from 'lib/icons/Check.js'
2
2
  import { ExclamationCircleOutlineIcon } from 'lib/icons/ExclamationCircleOutline.js'
3
+ import { OnlineStatusAccountOfflineIcon } from 'lib/icons/OnlineStatusAccountOffline.js'
3
4
  import type { UseDevicesData } from 'lib/seam/devices/use-devices.js'
4
5
  import { TableFilterBar } from 'lib/ui/Table/TableFilterBar/TableFilterBar.js'
5
6
  import { TableFilterItem } from 'lib/ui/Table/TableFilterBar/TableFilterItem.js'
6
7
 
8
+ export type AccountFilter = 'account_issues'
7
9
  export type DeviceFilter = 'device_issues'
8
10
 
9
11
  interface DeviceHealthBarProps {
10
12
  devices: Array<UseDevicesData[number]>
11
- filter: DeviceFilter | null
12
- onFilterSelect: (filter: DeviceFilter | null) => void
13
+ filter: AccountFilter | DeviceFilter | null
14
+ onFilterSelect: (filter: AccountFilter | DeviceFilter | null) => void
13
15
  }
14
16
 
15
17
  export function DeviceHealthBar({
@@ -17,25 +19,40 @@ export function DeviceHealthBar({
17
19
  filter,
18
20
  onFilterSelect,
19
21
  }: DeviceHealthBarProps): JSX.Element {
20
- const erroredDevices = devices.filter((device) => device.errors.length > 0)
21
- const issueCount = erroredDevices.length
22
+ const erroredAccounts = devices.filter((device) => {
23
+ return (
24
+ device.errors.filter((error) => 'is_connected_account_error' in error)
25
+ .length > 0
26
+ )
27
+ })
28
+ const erroredDevices = devices.filter((device) => {
29
+ return (
30
+ device.errors.filter((error) => 'is_device_error' in error).length > 0
31
+ )
32
+ })
33
+ const accountIssueCount = erroredAccounts.length
34
+ const deviceIssueCount = erroredDevices.length
22
35
 
23
- const toggle = (target: DeviceFilter) => () => {
36
+ const toggle = (target: AccountFilter | DeviceFilter) => () => {
24
37
  const newFilter = target === filter ? null : target
25
38
  onFilterSelect(newFilter)
26
39
  }
27
40
 
28
- const isPlural = issueCount === 0 || issueCount > 1
29
- const label = isPlural
30
- ? `${issueCount} ${t.deviceIssues}`
31
- : `${issueCount} ${t.deviceIssue}`
41
+ const deviceLabel =
42
+ deviceIssueCount > 0
43
+ ? `${deviceIssueCount} ${t.deviceIssues}`
44
+ : `${deviceIssueCount} ${t.deviceIssue}`
45
+ const accountLabel =
46
+ accountIssueCount > 0
47
+ ? `${accountIssueCount} ${t.accountIssues}`
48
+ : `${accountIssueCount} ${t.accountIssue}`
32
49
 
33
- if (issueCount === 0) {
50
+ if (accountIssueCount === 0 && deviceIssueCount === 0) {
34
51
  return (
35
52
  <TableFilterBar filterCleared>
36
53
  <TableFilterItem>
37
54
  <CheckIcon />
38
- {t.devicesOk}
55
+ {t.devicesAndAccountsOk}
39
56
  </TableFilterItem>
40
57
  </TableFilterBar>
41
58
  )
@@ -48,19 +65,32 @@ export function DeviceHealthBar({
48
65
  onFilterSelect(null)
49
66
  }}
50
67
  >
51
- <TableFilterItem
52
- onClick={toggle('device_issues')}
53
- selected={filter === 'device_issues'}
54
- >
55
- <ExclamationCircleOutlineIcon />
56
- {label}
57
- </TableFilterItem>
68
+ {deviceIssueCount > 0 && (
69
+ <TableFilterItem
70
+ onClick={toggle('device_issues')}
71
+ selected={filter === 'device_issues'}
72
+ >
73
+ <ExclamationCircleOutlineIcon />
74
+ {deviceLabel}
75
+ </TableFilterItem>
76
+ )}
77
+ {accountIssueCount > 0 && (
78
+ <TableFilterItem
79
+ onClick={toggle('account_issues')}
80
+ selected={filter === 'account_issues'}
81
+ >
82
+ <OnlineStatusAccountOfflineIcon />
83
+ {accountLabel}
84
+ </TableFilterItem>
85
+ )}
58
86
  </TableFilterBar>
59
87
  )
60
88
  }
61
89
 
62
90
  const t = {
63
- devicesOk: 'Devices OK',
91
+ devicesAndAccountsOk: 'Devices & accounts OK',
92
+ accountIssue: 'account issue',
93
+ accountIssues: 'account issues',
64
94
  deviceIssue: 'device issue',
65
95
  deviceIssues: 'device issues',
66
96
  }
@@ -1,3 +1,5 @@
1
+ import classNames from 'classnames'
2
+
1
3
  import type {
2
4
  UseDevicesData,
3
5
  UseDevicesParams,
@@ -25,24 +27,61 @@ export function DeviceRow({
25
27
  device,
26
28
  onClick,
27
29
  }: DeviceRowProps): JSX.Element | null {
30
+ const opacityClass =
31
+ isAccountOffline(device) || isDeviceOffline(device)
32
+ ? 'seam-offline-fade'
33
+ : undefined
28
34
  return (
29
- <TableRow key={device.device_id} onClick={onClick}>
30
- <TableCell className='seam-image-cell'>
35
+ <TableRow onClick={onClick}>
36
+ <TableCell className={classNames('seam-image-cell', opacityClass)}>
31
37
  <DeviceImage device={device} />
32
38
  </TableCell>
33
39
  <TableCell className='seam-body-cell'>
34
- <Title className='seam-truncated-text'>{device.properties.name}</Title>
40
+ <Title className={classNames('seam-truncated-text', opacityClass)}>
41
+ {device.properties.name}
42
+ </Title>
35
43
  <div className='seam-bottom'>
36
- <span className='seam-device-model'>
44
+ <span className={classNames('seam-device-model', opacityClass)}>
37
45
  {device.properties.model.display_name}
38
46
  </span>
39
- <div className='seam-device-statuses'>
40
- <OnlineStatus device={device} />
41
- <BatteryStatus device={device} />
42
- <LockStatus device={device} />
43
- </div>
47
+ <DeviceStatuses device={device} />
44
48
  </div>
45
49
  </TableCell>
46
50
  </TableRow>
47
51
  )
48
52
  }
53
+
54
+ interface DeviceStatusesProps {
55
+ device: UseDevicesData[number]
56
+ }
57
+
58
+ function DeviceStatuses({ device }: DeviceStatusesProps): JSX.Element | null {
59
+ if (isAccountOffline(device) || isDeviceOffline(device)) {
60
+ return (
61
+ <div className='seam-device-statuses'>
62
+ <OnlineStatus device={device} />
63
+ </div>
64
+ )
65
+ }
66
+ return (
67
+ <div className='seam-device-statuses'>
68
+ <OnlineStatus device={device} />
69
+ <BatteryStatus device={device} />
70
+ <LockStatus device={device} />
71
+ </div>
72
+ )
73
+ }
74
+
75
+ const isAccountOffline = (device: UseDevicesData[number]): boolean => {
76
+ return (
77
+ device.errors.filter((error) => error.error_code === 'account_disconnected')
78
+ .length > 0
79
+ )
80
+ }
81
+
82
+ const isDeviceOffline = (device: UseDevicesData[number]): boolean => {
83
+ return (
84
+ device.errors.filter((error) => error.error_code === 'device_disconnected')
85
+ .length > 0
86
+ )
87
+ }
@@ -7,6 +7,7 @@ export const name = 'seam-device-table'
7
7
  export const props: ElementProps<DeviceTableProps> = {
8
8
  deviceIds: 'json',
9
9
  connectedAccountIds: 'json',
10
+ disableLockUnlock: 'boolean',
10
11
  deviceFilter: 'function',
11
12
  deviceComparator: 'function',
12
13
  onDeviceClick: 'function',
@@ -5,6 +5,7 @@ import { isLockDevice } from 'seamapi'
5
5
  import { compareByCreatedAtDesc } from 'lib/dates.js'
6
6
  import { DeviceDetails } from 'lib/seam/components/DeviceDetails/DeviceDetails.js'
7
7
  import {
8
+ type AccountFilter,
8
9
  type DeviceFilter,
9
10
  DeviceHealthBar,
10
11
  } from 'lib/seam/components/DeviceTable/DeviceHealthBar.js'
@@ -26,6 +27,7 @@ type Device = UseDevicesData[number]
26
27
  export interface DeviceTableProps {
27
28
  deviceIds?: string[]
28
29
  connectedAccountIds?: string[]
30
+ disableLockUnlock?: boolean
29
31
  deviceFilter?: (device: Device, searchInputValue: string) => boolean
30
32
  deviceComparator?: (deviceA: Device, deviceB: Device) => number
31
33
  onDeviceClick?: (deviceId: string) => void
@@ -46,6 +48,7 @@ const defaultDeviceFilter = (
46
48
  export function DeviceTable({
47
49
  deviceIds,
48
50
  connectedAccountIds,
51
+ disableLockUnlock = false,
49
52
  onDeviceClick = () => {},
50
53
  preventDefaultOnDeviceClick = false,
51
54
  onBack,
@@ -87,6 +90,7 @@ export function DeviceTable({
87
90
  onBack={() => {
88
91
  setSelectedDeviceId(null)
89
92
  }}
93
+ disableLockUnlock={disableLockUnlock}
90
94
  />
91
95
  )
92
96
  }
@@ -124,7 +128,9 @@ function Content(props: {
124
128
  onDeviceClick: (deviceId: string) => void
125
129
  }): JSX.Element {
126
130
  const { devices, onDeviceClick } = props
127
- const [filter, setFilter] = useState<DeviceFilter | null>(null)
131
+ const [filter, setFilter] = useState<AccountFilter | DeviceFilter | null>(
132
+ null
133
+ )
128
134
 
129
135
  if (devices.length === 0) {
130
136
  return <EmptyPlaceholder>{t.noDevicesMessage}</EmptyPlaceholder>
@@ -135,8 +141,17 @@ function Content(props: {
135
141
  return true
136
142
  }
137
143
 
144
+ if (filter === 'account_issues') {
145
+ return (
146
+ device.errors.filter((error) => 'is_connected_account_error' in error)
147
+ .length > 0
148
+ )
149
+ }
150
+
138
151
  if (filter === 'device_issues') {
139
- return device.errors.length > 0
152
+ return (
153
+ device.errors.filter((error) => 'is_device_error' in error).length > 0
154
+ )
140
155
  }
141
156
 
142
157
  return true
@@ -1,5 +1,6 @@
1
1
  import type { CommonDevice } from 'seamapi'
2
2
 
3
+ import { OnlineStatusAccountOfflineIcon } from 'lib/icons/OnlineStatusAccountOffline.js'
3
4
  import { OnlineStatusDeviceOfflineIcon } from 'lib/icons/OnlineStatusDeviceOffline.js'
4
5
  import { OnlineStatusOnlineIcon } from 'lib/icons/OnlineStatusOnline.js'
5
6
 
@@ -8,20 +9,34 @@ interface OnlineStatusProps {
8
9
  }
9
10
 
10
11
  export function OnlineStatus(props: OnlineStatusProps): JSX.Element {
11
- const {
12
- device: {
13
- properties: { online },
14
- },
15
- } = props
12
+ const { device } = props
16
13
 
14
+ const isAccountOffline =
15
+ device.errors.filter((error) => error.error_code === 'account_disconnected')
16
+ .length > 0
17
+
18
+ if (isAccountOffline) {
19
+ return <AccountOfflineContent />
20
+ }
21
+
22
+ return (
23
+ <div className='seam-online-status'>
24
+ {isAccountOffline && <OnlineStatusAccountOfflineIcon />}
25
+ <DeviceOnlineContent isOnline={device.properties.online} />
26
+ </div>
27
+ )
28
+ }
29
+
30
+ function AccountOfflineContent() {
17
31
  return (
18
32
  <div className='seam-online-status'>
19
- <Content isOnline={online} />
33
+ <OnlineStatusAccountOfflineIcon />
34
+ <span className='seam-text-danger'>{t.accountOffline}</span>
20
35
  </div>
21
36
  )
22
37
  }
23
38
 
24
- function Content(props: { isOnline: boolean }): JSX.Element {
39
+ function DeviceOnlineContent(props: { isOnline: boolean }): JSX.Element {
25
40
  const { isOnline } = props
26
41
  if (isOnline) {
27
42
  return (
@@ -42,5 +57,6 @@ function Content(props: { isOnline: boolean }): JSX.Element {
42
57
 
43
58
  const t = {
44
59
  online: 'Online',
60
+ accountOffline: 'Account Offline',
45
61
  deviceOffline: 'Device Offline',
46
62
  }
@@ -1,3 +1,3 @@
1
- const seamapiReactVersion = '1.22.0'
1
+ const seamapiReactVersion = '1.24.0'
2
2
 
3
3
  export default seamapiReactVersion
@@ -9,6 +9,10 @@
9
9
  .seam-table-row {
10
10
  cursor: pointer;
11
11
 
12
+ .seam-offline-fade {
13
+ opacity: 0.4;
14
+ }
15
+
12
16
  .seam-image-cell {
13
17
  width: 64px;
14
18
  height: 64px;