@seamapi/react 1.23.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 (34) hide show
  1. package/README.md +1 -1
  2. package/dist/elements.js +4170 -4105
  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/seam/components/AccessCodeTable/AccessCodeTable.d.ts +2 -2
  9. package/lib/seam/components/AccessCodeTable/AccessCodeTable.js +1 -1
  10. package/lib/seam/components/AccessCodeTable/AccessCodeTable.js.map +1 -1
  11. package/lib/seam/components/DeviceTable/DeviceHealthBar.d.ts +3 -2
  12. package/lib/seam/components/DeviceTable/DeviceHealthBar.js +23 -11
  13. package/lib/seam/components/DeviceTable/DeviceHealthBar.js.map +1 -1
  14. package/lib/seam/components/DeviceTable/DeviceRow.js +19 -1
  15. package/lib/seam/components/DeviceTable/DeviceRow.js.map +1 -1
  16. package/lib/seam/components/DeviceTable/DeviceTable.d.ts +2 -2
  17. package/lib/seam/components/DeviceTable/DeviceTable.js +6 -2
  18. package/lib/seam/components/DeviceTable/DeviceTable.js.map +1 -1
  19. package/lib/ui/device/OnlineStatus.js +14 -4
  20. package/lib/ui/device/OnlineStatus.js.map +1 -1
  21. package/lib/version.d.ts +1 -1
  22. package/lib/version.js +1 -1
  23. package/package.json +1 -1
  24. package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.element.ts +1 -0
  25. package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.element.ts +1 -0
  26. package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx +2 -2
  27. package/src/lib/seam/components/DeviceDetails/DeviceDetails.element.ts +1 -0
  28. package/src/lib/seam/components/DeviceTable/DeviceHealthBar.tsx +49 -19
  29. package/src/lib/seam/components/DeviceTable/DeviceRow.tsx +48 -9
  30. package/src/lib/seam/components/DeviceTable/DeviceTable.element.ts +1 -0
  31. package/src/lib/seam/components/DeviceTable/DeviceTable.tsx +16 -4
  32. package/src/lib/ui/device/OnlineStatus.tsx +23 -7
  33. package/src/lib/version.ts +1 -1
  34. package/src/styles/_device-table.scss +4 -0
@@ -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,11 +27,11 @@ 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
32
34
  preventDefaultOnDeviceClick?: boolean
33
- disableLockUnlock?: boolean
34
35
  onBack?: () => void
35
36
  className?: string
36
37
  }
@@ -47,9 +48,9 @@ const defaultDeviceFilter = (
47
48
  export function DeviceTable({
48
49
  deviceIds,
49
50
  connectedAccountIds,
51
+ disableLockUnlock = false,
50
52
  onDeviceClick = () => {},
51
53
  preventDefaultOnDeviceClick = false,
52
- disableLockUnlock = false,
53
54
  onBack,
54
55
  deviceFilter = defaultDeviceFilter,
55
56
  deviceComparator = compareByCreatedAtDesc,
@@ -127,7 +128,9 @@ function Content(props: {
127
128
  onDeviceClick: (deviceId: string) => void
128
129
  }): JSX.Element {
129
130
  const { devices, onDeviceClick } = props
130
- const [filter, setFilter] = useState<DeviceFilter | null>(null)
131
+ const [filter, setFilter] = useState<AccountFilter | DeviceFilter | null>(
132
+ null
133
+ )
131
134
 
132
135
  if (devices.length === 0) {
133
136
  return <EmptyPlaceholder>{t.noDevicesMessage}</EmptyPlaceholder>
@@ -138,8 +141,17 @@ function Content(props: {
138
141
  return true
139
142
  }
140
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
+
141
151
  if (filter === 'device_issues') {
142
- return device.errors.length > 0
152
+ return (
153
+ device.errors.filter((error) => 'is_device_error' in error).length > 0
154
+ )
143
155
  }
144
156
 
145
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.23.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;