@seamapi/react 0.16.1 → 0.17.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 (196) hide show
  1. package/README.md +5 -2
  2. package/index.css +294 -69
  3. package/index.css.map +1 -1
  4. package/index.min.css +1 -1
  5. package/index.min.css.map +1 -1
  6. package/lib/NavigationProvider.d.ts +27 -0
  7. package/lib/NavigationProvider.js +36 -0
  8. package/lib/NavigationProvider.js.map +1 -0
  9. package/lib/NavigationView.d.ts +5 -0
  10. package/lib/NavigationView.js +25 -0
  11. package/lib/NavigationView.js.map +1 -0
  12. package/lib/icons/LockLocked.d.ts +2 -0
  13. package/lib/icons/LockLocked.js +5 -0
  14. package/lib/icons/LockLocked.js.map +1 -0
  15. package/lib/icons/LockUnlocked.d.ts +2 -0
  16. package/lib/icons/LockUnlocked.js +5 -0
  17. package/lib/icons/LockUnlocked.js.map +1 -0
  18. package/lib/seam/access-codes/use-access-code.d.ts +8 -0
  19. package/lib/seam/access-codes/use-access-code.js +32 -0
  20. package/lib/seam/access-codes/use-access-code.js.map +1 -0
  21. package/lib/seam/access-codes/use-access-codes.d.ts +9 -0
  22. package/lib/seam/access-codes/use-access-codes.js +18 -0
  23. package/lib/seam/access-codes/use-access-codes.js.map +1 -0
  24. package/lib/seam/devices/types.d.ts +2 -0
  25. package/lib/seam/devices/types.js +2 -0
  26. package/lib/seam/devices/types.js.map +1 -0
  27. package/lib/seam/devices/use-device.d.ts +9 -0
  28. package/lib/seam/devices/use-device.js +56 -0
  29. package/lib/seam/devices/use-device.js.map +1 -0
  30. package/lib/seam/devices/use-devices.d.ts +4 -9
  31. package/lib/seam/devices/use-devices.js +2 -1
  32. package/lib/seam/devices/use-devices.js.map +1 -1
  33. package/lib/seam/index.d.ts +4 -1
  34. package/lib/seam/index.js +4 -1
  35. package/lib/seam/index.js.map +1 -1
  36. package/lib/seam/use-seam-client.js +3 -3
  37. package/lib/seam/use-seam-client.js.map +1 -1
  38. package/lib/seam/use-seam-query-result.js.map +1 -0
  39. package/lib/ui/AccessCodeDetails/AccessCodeDetails.d.ts +4 -0
  40. package/lib/ui/AccessCodeDetails/AccessCodeDetails.js +63 -0
  41. package/lib/ui/AccessCodeDetails/AccessCodeDetails.js.map +1 -0
  42. package/lib/ui/AccessCodeDetails/AccessCodeDevice.d.ts +3 -0
  43. package/lib/ui/AccessCodeDetails/AccessCodeDevice.js +25 -0
  44. package/lib/ui/AccessCodeDetails/AccessCodeDevice.js.map +1 -0
  45. package/lib/ui/AccessCodeTable/AccessCodeTable.d.ts +0 -1
  46. package/lib/ui/AccessCodeTable/AccessCodeTable.js +14 -4
  47. package/lib/ui/AccessCodeTable/AccessCodeTable.js.map +1 -1
  48. package/lib/ui/AccessCodeTable/AccessCodeTableContent.d.ts +0 -1
  49. package/lib/ui/AccessCodeTable/AccessCodeTableContent.js +7 -3
  50. package/lib/ui/AccessCodeTable/AccessCodeTableContent.js.map +1 -1
  51. package/lib/ui/AccessCodeTable/CodeDetails.js +1 -1
  52. package/lib/ui/AccessCodeTable/CodeDetails.js.map +1 -1
  53. package/lib/ui/Button.d.ts +8 -0
  54. package/lib/ui/Button.js +8 -0
  55. package/lib/ui/Button.js.map +1 -0
  56. package/lib/ui/DeviceDetails/DeviceDetails.d.ts +2 -3
  57. package/lib/ui/DeviceDetails/DeviceDetails.js +31 -13
  58. package/lib/ui/DeviceDetails/DeviceDetails.js.map +1 -1
  59. package/lib/ui/DeviceDetails/DeviceModel.d.ts +7 -0
  60. package/lib/ui/DeviceDetails/DeviceModel.js +18 -0
  61. package/lib/ui/DeviceDetails/DeviceModel.js.map +1 -0
  62. package/lib/ui/DeviceTable/DeviceTable.d.ts +7 -0
  63. package/lib/ui/DeviceTable/DeviceTable.js +43 -0
  64. package/lib/ui/DeviceTable/DeviceTable.js.map +1 -0
  65. package/lib/ui/IconButton.js +1 -1
  66. package/lib/ui/IconButton.js.map +1 -1
  67. package/lib/ui/Table/TableRow.d.ts +3 -1
  68. package/lib/ui/Table/TableRow.js +1 -1
  69. package/lib/ui/Table/TableRow.js.map +1 -1
  70. package/lib/ui/TextButton.d.ts +7 -0
  71. package/lib/ui/TextButton.js +5 -0
  72. package/lib/ui/TextButton.js.map +1 -0
  73. package/lib/ui/TextField/SearchTextField.d.ts +1 -1
  74. package/lib/ui/TextField/SearchTextField.js +1 -1
  75. package/lib/ui/TextField/SearchTextField.js.map +1 -1
  76. package/lib/ui/device/BatteryStatus.d.ts +5 -0
  77. package/lib/ui/{DeviceDetails → device}/BatteryStatus.js +5 -6
  78. package/lib/ui/device/BatteryStatus.js.map +1 -0
  79. package/lib/ui/{DeviceDetails → device}/DeviceImage.d.ts +2 -1
  80. package/lib/ui/{DeviceDetails → device}/DeviceImage.js +2 -2
  81. package/lib/ui/device/DeviceImage.js.map +1 -0
  82. package/lib/ui/device/LockStatus.d.ts +5 -0
  83. package/lib/ui/device/LockStatus.js +19 -0
  84. package/lib/ui/device/LockStatus.js.map +1 -0
  85. package/lib/ui/device/OnlineStatus.d.ts +5 -0
  86. package/lib/ui/{DeviceDetails → device}/OnlineStatus.js +4 -5
  87. package/lib/ui/device/OnlineStatus.js.map +1 -0
  88. package/lib/ui/index.d.ts +3 -1
  89. package/lib/ui/index.js +3 -1
  90. package/lib/ui/index.js.map +1 -1
  91. package/lib/ui/layout/ContentHeader.d.ts +1 -2
  92. package/lib/ui/layout/ContentHeader.js +7 -1
  93. package/lib/ui/layout/ContentHeader.js.map +1 -1
  94. package/package.json +4 -3
  95. package/src/icons/lock-locked.svg +8 -0
  96. package/src/icons/lock-unlocked.svg +9 -0
  97. package/src/lib/NavigationProvider.tsx +86 -0
  98. package/src/lib/NavigationView.tsx +32 -0
  99. package/src/lib/icons/LockLocked.tsx +32 -0
  100. package/src/lib/icons/LockUnlocked.tsx +34 -0
  101. package/src/lib/seam/access-codes/use-access-code.ts +45 -0
  102. package/src/lib/seam/access-codes/use-access-codes.ts +39 -0
  103. package/src/lib/seam/devices/types.ts +5 -0
  104. package/src/lib/seam/devices/use-device.ts +79 -0
  105. package/src/lib/seam/devices/use-devices.ts +13 -8
  106. package/src/lib/seam/index.ts +4 -1
  107. package/src/lib/seam/use-seam-client.ts +3 -3
  108. package/src/lib/ui/AccessCodeDetails/AccessCodeDetails.tsx +144 -0
  109. package/src/lib/ui/AccessCodeDetails/AccessCodeDevice.tsx +43 -0
  110. package/src/lib/ui/AccessCodeTable/AccessCodeTable.tsx +20 -16
  111. package/src/lib/ui/AccessCodeTable/AccessCodeTableContent.tsx +7 -11
  112. package/src/lib/ui/AccessCodeTable/CodeDetails.tsx +1 -1
  113. package/src/lib/ui/Button.tsx +26 -0
  114. package/src/lib/ui/DeviceDetails/DeviceDetails.tsx +54 -28
  115. package/src/lib/ui/DeviceDetails/DeviceModel.tsx +34 -0
  116. package/src/lib/ui/DeviceTable/DeviceTable.tsx +94 -0
  117. package/src/lib/ui/IconButton.tsx +1 -1
  118. package/src/lib/ui/Table/TableRow.tsx +8 -2
  119. package/src/lib/ui/TextButton.tsx +15 -0
  120. package/src/lib/ui/TextField/SearchTextField.tsx +1 -1
  121. package/src/lib/ui/{DeviceDetails → device}/BatteryStatus.tsx +12 -14
  122. package/src/lib/ui/device/DeviceImage.tsx +15 -0
  123. package/src/lib/ui/device/LockStatus.tsx +46 -0
  124. package/src/lib/ui/{DeviceDetails → device}/OnlineStatus.tsx +15 -11
  125. package/src/lib/ui/index.ts +3 -1
  126. package/src/lib/ui/layout/ContentHeader.tsx +11 -6
  127. package/src/styles/_access-code-details.scss +121 -0
  128. package/src/styles/_access-code-table.scss +34 -38
  129. package/src/styles/_buttons.scss +125 -8
  130. package/src/styles/_colors.scss +30 -0
  131. package/src/styles/_device-details.scss +21 -10
  132. package/src/styles/_device-table.scss +63 -0
  133. package/src/styles/_icons.scss +9 -0
  134. package/src/styles/_inputs.scss +3 -1
  135. package/src/styles/_layout.scss +5 -1
  136. package/src/styles/_main.scss +8 -14
  137. package/src/styles/_tables.scss +6 -0
  138. package/src/styles/_typography.scss +18 -2
  139. package/lib/hooks/use-access-codes.d.ts +0 -5
  140. package/lib/hooks/use-access-codes.js +0 -16
  141. package/lib/hooks/use-access-codes.js.map +0 -1
  142. package/lib/hooks/use-device.d.ts +0 -5
  143. package/lib/hooks/use-device.js +0 -17
  144. package/lib/hooks/use-device.js.map +0 -1
  145. package/lib/hooks/use-devices.d.ts +0 -1
  146. package/lib/hooks/use-devices.js +0 -2
  147. package/lib/hooks/use-devices.js.map +0 -1
  148. package/lib/hooks/use-seam-client.d.ts +0 -1
  149. package/lib/hooks/use-seam-client.js +0 -2
  150. package/lib/hooks/use-seam-client.js.map +0 -1
  151. package/lib/seam/devices/index.d.ts +0 -1
  152. package/lib/seam/devices/index.js +0 -2
  153. package/lib/seam/devices/index.js.map +0 -1
  154. package/lib/types/use-seam-query-result.js.map +0 -1
  155. package/lib/ui/AccessCodeTable/index.d.ts +0 -2
  156. package/lib/ui/AccessCodeTable/index.js +0 -3
  157. package/lib/ui/AccessCodeTable/index.js.map +0 -1
  158. package/lib/ui/DeviceDetails/BatteryStatus.d.ts +0 -4
  159. package/lib/ui/DeviceDetails/BatteryStatus.js.map +0 -1
  160. package/lib/ui/DeviceDetails/DeviceImage.js.map +0 -1
  161. package/lib/ui/DeviceDetails/ModelStatus.d.ts +0 -4
  162. package/lib/ui/DeviceDetails/ModelStatus.js +0 -13
  163. package/lib/ui/DeviceDetails/ModelStatus.js.map +0 -1
  164. package/lib/ui/DeviceDetails/OnlineStatus.d.ts +0 -4
  165. package/lib/ui/DeviceDetails/OnlineStatus.js.map +0 -1
  166. package/lib/ui/DeviceDetails/index.d.ts +0 -5
  167. package/lib/ui/DeviceDetails/index.js +0 -6
  168. package/lib/ui/DeviceDetails/index.js.map +0 -1
  169. package/lib/ui/DeviceManager/DeviceManager.d.ts +0 -6
  170. package/lib/ui/DeviceManager/DeviceManager.js +0 -24
  171. package/lib/ui/DeviceManager/DeviceManager.js.map +0 -1
  172. package/lib/ui/DeviceManager/index.d.ts +0 -1
  173. package/lib/ui/DeviceManager/index.js +0 -2
  174. package/lib/ui/DeviceManager/index.js.map +0 -1
  175. package/lib/ui/Table/index.d.ts +0 -5
  176. package/lib/ui/Table/index.js +0 -6
  177. package/lib/ui/Table/index.js.map +0 -1
  178. package/lib/ui/TextField/index.d.ts +0 -2
  179. package/lib/ui/TextField/index.js +0 -3
  180. package/lib/ui/TextField/index.js.map +0 -1
  181. package/src/lib/hooks/use-access-codes.ts +0 -21
  182. package/src/lib/hooks/use-device.ts +0 -26
  183. package/src/lib/hooks/use-devices.ts +0 -1
  184. package/src/lib/hooks/use-seam-client.ts +0 -1
  185. package/src/lib/seam/devices/index.ts +0 -1
  186. package/src/lib/ui/AccessCodeTable/index.ts +0 -2
  187. package/src/lib/ui/DeviceDetails/DeviceImage.tsx +0 -12
  188. package/src/lib/ui/DeviceDetails/ModelStatus.tsx +0 -25
  189. package/src/lib/ui/DeviceDetails/index.ts +0 -5
  190. package/src/lib/ui/DeviceManager/DeviceManager.tsx +0 -52
  191. package/src/lib/ui/DeviceManager/index.ts +0 -1
  192. package/src/lib/ui/Table/index.ts +0 -5
  193. package/src/lib/ui/TextField/index.ts +0 -2
  194. /package/lib/{types → seam}/use-seam-query-result.d.ts +0 -0
  195. /package/lib/{types → seam}/use-seam-query-result.js +0 -0
  196. /package/src/lib/{types → seam}/use-seam-query-result.ts +0 -0
@@ -0,0 +1,32 @@
1
+ import type { SVGProps } from 'react'
2
+ export const LockLockedIcon = (props: SVGProps<SVGSVGElement>): JSX.Element => (
3
+ <svg
4
+ xmlns='http://www.w3.org/2000/svg'
5
+ width={25}
6
+ height={25}
7
+ fill='none'
8
+ {...props}
9
+ >
10
+ <mask
11
+ id='lock-locked_svg__a'
12
+ width={25}
13
+ height={25}
14
+ x={0}
15
+ y={0}
16
+ maskUnits='userSpaceOnUse'
17
+ style={{
18
+ maskType: 'alpha',
19
+ }}
20
+ >
21
+ <path fill='#D9D9D9' d='M.461.839h24v24h-24z' />
22
+ </mask>
23
+ <g mask='url(#lock-locked_svg__a)'>
24
+ <path
25
+ fill='#232B3A'
26
+ fillRule='evenodd'
27
+ d='M5.924 3.893a4.463 4.463 0 0 0-4.463 4.464v3.345a1 1 0 1 0 2 0V8.357a2.463 2.463 0 0 1 4.926 0v4.482H7.72a1.26 1.26 0 0 0-1.26 1.259v5.481c0 .696.565 1.26 1.26 1.26h9.481a1.26 1.26 0 0 0 1.26-1.26v-5.481a1.26 1.26 0 0 0-1.26-1.26h-6.814V8.357a4.463 4.463 0 0 0-4.463-4.464Z'
28
+ clipRule='evenodd'
29
+ />
30
+ </g>
31
+ </svg>
32
+ )
@@ -0,0 +1,34 @@
1
+ import type { SVGProps } from 'react'
2
+ export const LockUnlockedIcon = (
3
+ props: SVGProps<SVGSVGElement>
4
+ ): JSX.Element => (
5
+ <svg
6
+ xmlns='http://www.w3.org/2000/svg'
7
+ width={25}
8
+ height={25}
9
+ fill='none'
10
+ {...props}
11
+ >
12
+ <mask
13
+ id='lock-unlocked_svg__a'
14
+ width={25}
15
+ height={25}
16
+ x={0}
17
+ y={0}
18
+ maskUnits='userSpaceOnUse'
19
+ style={{
20
+ maskType: 'alpha',
21
+ }}
22
+ >
23
+ <path fill='#D9D9D9' d='M.461.839h24v24h-24z' />
24
+ </mask>
25
+ <g fill='#232B3A' mask='url(#lock-unlocked_svg__a)'>
26
+ <rect width={12} height={8.152} x={6.461} y={12.839} rx={1.259} />
27
+ <path
28
+ fillRule='evenodd'
29
+ d='M7.94 10.359a4.52 4.52 0 0 1 9.04 0v5.692a1 1 0 1 1-2 0v-5.692a2.52 2.52 0 0 0-5.04 0v3.531a1 1 0 1 1-2 0V10.36Z'
30
+ clipRule='evenodd'
31
+ />
32
+ </g>
33
+ </svg>
34
+ )
@@ -0,0 +1,45 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import { DateTime } from 'luxon'
3
+ import type { AccessCode, AccessCodeGetRequest } from 'seamapi'
4
+ import { v4 as uuid } from 'uuid'
5
+
6
+ import { useSeamClient } from 'lib/seam/use-seam-client.js'
7
+
8
+ export type UseAccessCodeParams = AccessCodeGetRequest | string
9
+ export type UseAccessCodeData = AccessCode
10
+
11
+ export function useAccessCode(params: UseAccessCodeParams) {
12
+ const { client } = useSeamClient()
13
+
14
+ const normalizedParams =
15
+ typeof params === 'string' ? { access_code_id: params } : params
16
+
17
+ return useQuery({
18
+ enabled: client != null,
19
+ queryKey: ['access_code', 'get', normalizedParams],
20
+ queryFn: async () => {
21
+ if (client == null) return null
22
+
23
+ return await client?.accessCodes.get(normalizedParams)
24
+ },
25
+ })
26
+ }
27
+
28
+ export function useFakeAccessCode(_params: UseAccessCodeParams): {
29
+ isLoading: boolean
30
+ data: AccessCode
31
+ } {
32
+ const accessCode: AccessCode = {
33
+ access_code_id: uuid(),
34
+ type: 'time_bound',
35
+ starts_at: DateTime.now().minus({ days: 2 }).toISO() ?? '',
36
+ ends_at: DateTime.now().plus({ days: 1 }).toISO() ?? '',
37
+ code: '1234',
38
+ created_at: DateTime.now().toISO() ?? '',
39
+ device_id: 'some_device_id',
40
+ status: 'set',
41
+ name: 'Guest - Kranz',
42
+ }
43
+
44
+ return { isLoading: false, data: accessCode }
45
+ }
@@ -0,0 +1,39 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import type { AccessCode, AccessCodesListResponse, SeamError } from 'seamapi'
3
+
4
+ import { useSeamClient } from 'lib/seam/use-seam-client.js'
5
+ import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js'
6
+
7
+ export type UseAccessCodesParams = AccessCodesListRequest | string
8
+ export type UseAccessCodesData = AccessCode[]
9
+
10
+ export function useAccessCodes(
11
+ params: UseAccessCodesParams
12
+ ): UseSeamQueryResult<'accessCodes', UseAccessCodesData> {
13
+ const normalizedParams =
14
+ typeof params === 'string' ? { device_id: params } : params
15
+
16
+ const { client } = useSeamClient()
17
+ const { data, ...rest } = useQuery<
18
+ AccessCodesListResponse['access_codes'],
19
+ SeamError
20
+ >({
21
+ enabled: client != null,
22
+ queryKey: ['access_codes', 'list', normalizedParams],
23
+ queryFn: async () => {
24
+ if (client == null) return []
25
+ const accessCodes = await client?.accessCodes.list(normalizedParams)
26
+ return accessCodes.sort(
27
+ (a, b) =>
28
+ new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
29
+ )
30
+ },
31
+ })
32
+
33
+ return { ...rest, accessCodes: data }
34
+ }
35
+
36
+ // UPSTREAM: https://github.com/seamapi/javascript/pull/200
37
+ interface AccessCodesListRequest {
38
+ device_id: string
39
+ }
@@ -0,0 +1,5 @@
1
+ import type { CommonDeviceProperties, Device, LockDevice } from 'seamapi'
2
+
3
+ export const isLockDevice = (
4
+ device: Device<CommonDeviceProperties, string> | LockDevice
5
+ ): device is LockDevice => 'locked' in device.properties
@@ -0,0 +1,79 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import type {
3
+ CommonDeviceProperties,
4
+ Device,
5
+ DeviceGetRequest,
6
+ DeviceGetResponse,
7
+ LockDevice,
8
+ SeamError,
9
+ } from 'seamapi'
10
+
11
+ import { useSeamClient } from 'lib/seam/use-seam-client.js'
12
+ import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js'
13
+
14
+ export type UseDeviceParams = DeviceGetRequest | string
15
+ export type UseDeviceData = Device<CommonDeviceProperties> | null
16
+
17
+ export function useDevice(
18
+ params: DeviceGetRequest
19
+ ): UseSeamQueryResult<'device', UseDeviceData> {
20
+ const normalizedParams =
21
+ typeof params === 'string' ? { device_id: params } : params
22
+
23
+ const { client } = useSeamClient()
24
+ const { data, ...rest } = useQuery<
25
+ DeviceGetResponse['device'] | null,
26
+ SeamError
27
+ >({
28
+ enabled: client != null,
29
+ queryKey: ['devices', 'get', normalizedParams],
30
+ queryFn: async () => {
31
+ if (client == null) return null
32
+ return await client?.devices.get(normalizedParams)
33
+ },
34
+ })
35
+
36
+ return { ...rest, device: data }
37
+ }
38
+
39
+ export function useFakeDevice(_params: DeviceGetRequest) {
40
+ const device: LockDevice = {
41
+ device_id: 'f9a9ab36-9e14-4390-a88c-b4c78304c6aa',
42
+ device_type: 'august_lock',
43
+ capabilities_supported: ['access_code', 'lock'],
44
+ properties: {
45
+ locked: true,
46
+ online: true,
47
+ door_open: false,
48
+ manufacturer: 'august',
49
+ battery_level: 0.9999532347993827,
50
+ serial_number: '00000004-992d-45a0-bea1-9128fdcd8d12',
51
+ august_metadata: {
52
+ lock_id: 'lock-1',
53
+ house_id: 'house-1',
54
+ lock_name: 'FRONT DOOR',
55
+ has_keypad: true,
56
+ house_name: 'My House',
57
+ },
58
+ supported_code_lengths: [6],
59
+ name: 'FRONT DOOR',
60
+ battery: {
61
+ level: 0.9999532347993827,
62
+ status: 'full',
63
+ },
64
+ image_url:
65
+ 'https://connect.getseam.com/assets/images/devices/august_wifi-smart-lock-3rd-gen_silver_front.png',
66
+ },
67
+ location: {
68
+ timezone: 'America/Los_Angeles',
69
+ location_name: 'My House',
70
+ },
71
+ connected_account_id: '6fc0f854-cb5a-461a-af93-e543ce1f730f',
72
+ workspace_id: 'a9598de8-e627-4d01-b119-61ab7b9e07bd',
73
+ created_at: '2023-05-08T22:37:24.206Z',
74
+ errors: [],
75
+ warnings: [],
76
+ }
77
+
78
+ return { isLoading: false, device }
79
+ }
@@ -1,20 +1,21 @@
1
- import { useQuery, type UseQueryResult } from '@tanstack/react-query'
1
+ import { useQuery } from '@tanstack/react-query'
2
2
  import type {
3
+ CommonDeviceProperties,
4
+ Device,
3
5
  DevicesListRequest,
4
6
  DevicesListResponse,
5
7
  SeamError,
6
8
  } from 'seamapi'
7
9
 
8
10
  import { useSeamClient } from 'lib/seam/use-seam-client.js'
11
+ import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js'
9
12
 
10
13
  export type UseDevicesParams = DevicesListRequest
11
- export type UseDevicesData = DevicesListResponse['devices']
14
+ export type UseDevicesData = Array<Device<CommonDeviceProperties>>
12
15
 
13
- type Result = UseQueryResult<UseDevicesData, SeamError>
14
-
15
- type UseDevicesResult = Omit<Result, 'data'> & { devices: Result['data'] }
16
-
17
- export function useDevices(params: DevicesListRequest): UseDevicesResult {
16
+ export function useDevices(
17
+ params?: UseDevicesParams
18
+ ): UseSeamQueryResult<'devices', UseDevicesData> {
18
19
  const { client } = useSeamClient()
19
20
  const { data, ...rest } = useQuery<DevicesListResponse['devices'], SeamError>(
20
21
  {
@@ -22,7 +23,11 @@ export function useDevices(params: DevicesListRequest): UseDevicesResult {
22
23
  queryKey: ['devices', 'list', params],
23
24
  queryFn: async () => {
24
25
  if (client == null) return []
25
- return await client?.devices.list(params)
26
+ const devices = await client?.devices.list(params)
27
+ return devices.sort(
28
+ (a, b) =>
29
+ new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
30
+ )
26
31
  },
27
32
  }
28
33
  )
@@ -1,2 +1,5 @@
1
- export * from './devices/index.js'
1
+ export * from './access-codes/use-access-code.js'
2
+ export * from './access-codes/use-access-codes.js'
3
+ export * from './devices/use-device.js'
4
+ export * from './devices/use-devices.js'
2
5
  export * from './use-seam-client.js'
@@ -84,11 +84,11 @@ This is not recommended because the client session is now bound to this machine
84
84
  return userIdentifierKey
85
85
  }
86
86
 
87
- const fingerprintValue =
87
+ const fingerprint =
88
88
  globalThis?.localStorage?.getItem('seam_user_fingerprint') ??
89
89
  `fingerprint_${uuidv4()}`
90
90
 
91
- globalThis?.localStorage?.setItem('seam_user_fingerprint', fingerprintValue)
91
+ globalThis?.localStorage?.setItem('seam_user_fingerprint', fingerprint)
92
92
 
93
- return fingerprintValue
93
+ return fingerprint
94
94
  }
@@ -0,0 +1,144 @@
1
+ import { DateTime } from 'luxon'
2
+ import type { AccessCode } from 'seamapi'
3
+
4
+ import { useFakeAccessCode } from 'lib/index.js'
5
+
6
+ import AccessCodeDevice from 'lib/ui/AccessCodeDetails/AccessCodeDevice.js'
7
+ import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
8
+
9
+ export interface AccessCodeDetailsProps {
10
+ accessCodeId: string
11
+ }
12
+
13
+ export default function AccessCodeDetails({
14
+ accessCodeId,
15
+ }: AccessCodeDetailsProps) {
16
+ const { isLoading, data: accessCode } = useFakeAccessCode({
17
+ access_code_id: accessCodeId,
18
+ })
19
+
20
+ if (isLoading || !accessCode) {
21
+ return null
22
+ }
23
+
24
+ const name = accessCode.name ?? t.fallbackName
25
+
26
+ return (
27
+ <div className='seam-access-code-details'>
28
+ <ContentHeader title='Access code' />
29
+ <div className='seam-summary'>
30
+ <div className='seam-top'>
31
+ <span className='seam-label'>{t.accessCode}</span>
32
+ <h5 className='seam-access-code-name'>{name}</h5>
33
+ <div className='seam-code'>{accessCode.code}</div>
34
+ <div className='seam-duration'>
35
+ <Duration accessCode={accessCode} />
36
+ </div>
37
+ </div>
38
+ <AccessCodeDevice deviceId={accessCode.device_id} />
39
+ </div>
40
+ <div className='seam-details'>
41
+ <div className='seam-row'>
42
+ <div className='seam-heading'>{t.id}:</div>
43
+ <div className='seam-content'>{accessCode.access_code_id}</div>
44
+ </div>
45
+ <div className='seam-row'>
46
+ <div className='seam-heading'>{t.created}:</div>
47
+ <div className='seam-content'>
48
+ {formatDate(accessCode.created_at)}
49
+ </div>
50
+ </div>
51
+
52
+ <div className='seam-row seam-schedule'>
53
+ <div className='seam-heading'>{t.timing}:</div>
54
+ <div className='seam-content'>
55
+ <ScheduleInfo accessCode={accessCode} />
56
+ </div>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ )
61
+ }
62
+
63
+ function ScheduleInfo({ accessCode }: { accessCode: AccessCode }) {
64
+ if (accessCode.type === 'ongoing') {
65
+ return <span>{t.ongoing}</span>
66
+ }
67
+ return (
68
+ <div className='seam-times'>
69
+ <div>
70
+ <div className='seam-label'>{t.start}</div>
71
+ <div className='seam-date'>{formatDate(accessCode.starts_at)}</div>
72
+ <div className='seam-time'>{formatTime(accessCode.starts_at)}</div>
73
+ </div>
74
+ <div>
75
+ <div className='seam-label'>{t.end}</div>
76
+ <div className='seam-date'>{formatDate(accessCode.ends_at)}</div>
77
+ <div className='seam-time'>{formatTime(accessCode.ends_at)}</div>
78
+ </div>
79
+ </div>
80
+ )
81
+ }
82
+
83
+ function Duration(props: { accessCode: AccessCode }): JSX.Element {
84
+ const { accessCode } = props
85
+ if (accessCode.type === 'ongoing') {
86
+ return (
87
+ <span>
88
+ <span className='seam-label'>Active</span> (ongoing)
89
+ </span>
90
+ )
91
+ }
92
+
93
+ const hasStarted = new Date(accessCode.starts_at).valueOf() < Date.now()
94
+ if (hasStarted) {
95
+ return (
96
+ <span>
97
+ <span className='seam-label'>Active</span> until{' '}
98
+ {formatDurationDate(accessCode.ends_at)} at{' '}
99
+ {formatTime(accessCode.ends_at)}
100
+ </span>
101
+ )
102
+ }
103
+
104
+ return (
105
+ <span>
106
+ Starts {formatDurationDate(accessCode.starts_at)} as{' '}
107
+ {formatTime(accessCode.starts_at)}
108
+ </span>
109
+ )
110
+ }
111
+
112
+ function formatDurationDate(date: string): string {
113
+ return DateTime.fromISO(date).toLocaleString({
114
+ month: 'short',
115
+ day: 'numeric',
116
+ })
117
+ }
118
+
119
+ function formatTime(date: string): string {
120
+ return DateTime.fromISO(date).toLocaleString({
121
+ hour: 'numeric',
122
+ minute: '2-digit',
123
+ })
124
+ }
125
+
126
+ function formatDate(date: string): string {
127
+ return DateTime.fromISO(date).toLocaleString({
128
+ weekday: 'short',
129
+ month: 'long',
130
+ day: 'numeric',
131
+ year: 'numeric',
132
+ })
133
+ }
134
+
135
+ const t = {
136
+ accessCode: 'Access code',
137
+ fallbackName: 'Code',
138
+ id: 'ID',
139
+ created: 'Created',
140
+ timing: 'Timing',
141
+ ongoing: 'Ongoing',
142
+ start: 'Start',
143
+ end: 'End',
144
+ }
@@ -0,0 +1,43 @@
1
+ import { useNavigation } from 'lib/NavigationProvider.js'
2
+ import { isLockDevice } from 'lib/seam/devices/types.js'
3
+ import { useFakeDevice } from 'lib/seam/devices/use-device.js'
4
+ import { Button } from 'lib/ui/Button.js'
5
+ import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
6
+ import { getDeviceModel } from 'lib/ui/DeviceDetails/DeviceModel.js'
7
+ import { TextButton } from 'lib/ui/TextButton.js'
8
+
9
+ export default function AccessCodeDevice({ deviceId }: { deviceId: string }) {
10
+ // TODO: Replace with `useDevice()` once ready
11
+ const { isLoading, device } = useFakeDevice({ device_id: deviceId })
12
+ const { show } = useNavigation()
13
+
14
+ // TODO: Do we want to return a skeleton loader here instead?
15
+ if (isLoading || !device || !isLockDevice(device)) {
16
+ return null
17
+ }
18
+
19
+ const model = getDeviceModel(device)
20
+
21
+ return (
22
+ <div className='seam-access-code-device'>
23
+ <div className='seam-device-image'>
24
+ <DeviceImage device={device} />
25
+ </div>
26
+ <div className='seam-body'>
27
+ <div className='seam-model'>{model}</div>
28
+ <TextButton
29
+ onClick={() => {
30
+ show({ name: 'device_detail', deviceId: device.device_id })
31
+ }}
32
+ >
33
+ {t.deviceDetails}
34
+ </TextButton>
35
+ </div>
36
+ <Button>Unlock</Button>
37
+ </div>
38
+ )
39
+ }
40
+
41
+ const t = {
42
+ deviceDetails: 'Device details',
43
+ }
@@ -1,32 +1,28 @@
1
- import {
2
- TableBody,
3
- TableCell,
4
- TableHeader,
5
- TableRow,
6
- TableTitle,
7
- } from 'lib/ui/Table/index.js'
8
-
9
- import { useAccessCodes } from 'lib/hooks/use-access-codes.js'
10
1
  import { AccessCodeKeyIcon } from 'lib/icons/AccessCodeKey.js'
2
+ import { useNavigation } from 'lib/NavigationProvider.js'
3
+ import { useAccessCodes } from 'lib/seam/access-codes/use-access-codes.js'
11
4
  import { CodeDetails } from 'lib/ui/AccessCodeTable/CodeDetails.js'
12
5
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
6
+ import { TableBody } from 'lib/ui/Table/TableBody.js'
7
+ import { TableCell } from 'lib/ui/Table/TableCell.js'
8
+ import { TableHeader } from 'lib/ui/Table/TableHeader.js'
9
+ import { TableRow } from 'lib/ui/Table/TableRow.js'
10
+ import { TableTitle } from 'lib/ui/Table/TableTitle.js'
13
11
  import { Caption } from 'lib/ui/typography/Caption.js'
14
12
  import { Title } from 'lib/ui/typography/Title.js'
15
13
 
16
- export function AccessCodeTable(props: {
17
- deviceId: string
18
- onClickBack?: () => void
19
- }): JSX.Element {
14
+ export function AccessCodeTable(props: { deviceId: string }): JSX.Element {
20
15
  const { accessCodes } = useAccessCodes({
21
16
  device_id: props.deviceId,
22
17
  })
23
- const { onClickBack } = props
18
+
19
+ const { show } = useNavigation()
24
20
 
25
21
  if (!accessCodes) return <>{null}</>
26
22
 
27
23
  return (
28
24
  <div className='seam-access-code-table'>
29
- <ContentHeader onClickBack={onClickBack} />
25
+ <ContentHeader />
30
26
  <TableHeader>
31
27
  <TableTitle>
32
28
  {t.accessCodes} <Caption>({accessCodes.length})</Caption>
@@ -34,7 +30,15 @@ export function AccessCodeTable(props: {
34
30
  </TableHeader>
35
31
  <TableBody>
36
32
  {accessCodes.map((code) => (
37
- <TableRow key={code.access_code_id}>
33
+ <TableRow
34
+ key={code.access_code_id}
35
+ onClick={() => {
36
+ show({
37
+ name: 'access_code_detail',
38
+ accessCodeId: code.access_code_id,
39
+ })
40
+ }}
41
+ >
38
42
  <TableCell className='seam-icon-cell'>
39
43
  <div>
40
44
  <AccessCodeKeyIcon />
@@ -1,32 +1,28 @@
1
1
  import type { AccessCode } from 'seamapi'
2
2
 
3
- import {
4
- TableBody,
5
- TableCell,
6
- TableHeader,
7
- TableRow,
8
- TableTitle,
9
- } from 'lib/ui/Table/index.js'
10
-
11
3
  import { AccessCodeKeyIcon } from 'lib/icons/AccessCodeKey.js'
12
4
  import { CodeDetails } from 'lib/ui/AccessCodeTable/CodeDetails.js'
13
5
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
6
+ import { TableBody } from 'lib/ui/Table/TableBody.js'
7
+ import { TableCell } from 'lib/ui/Table/TableCell.js'
8
+ import { TableHeader } from 'lib/ui/Table/TableHeader.js'
9
+ import { TableRow } from 'lib/ui/Table/TableRow.js'
10
+ import { TableTitle } from 'lib/ui/Table/TableTitle.js'
14
11
  import { Caption } from 'lib/ui/typography/Caption.js'
15
12
  import { Title } from 'lib/ui/typography/Title.js'
16
13
 
17
14
  export interface AccessCodeTableContentProps {
18
15
  accessCodes: AccessCode[]
19
- onClickBack?: () => void
20
16
  }
21
17
 
22
18
  export function AccessCodeTableContent(
23
19
  props: AccessCodeTableContentProps
24
20
  ): JSX.Element {
25
- const { accessCodes, onClickBack } = props
21
+ const { accessCodes } = props
26
22
 
27
23
  return (
28
24
  <div className='seam-access-code-table'>
29
- <ContentHeader onClickBack={onClickBack} />
25
+ <ContentHeader />
30
26
  <TableHeader>
31
27
  <TableTitle>
32
28
  {t.accessCodes} <Caption>({accessCodes.length})</Caption>
@@ -7,7 +7,7 @@ export function CodeDetails(props: { accessCode: AccessCode }): JSX.Element {
7
7
  const { accessCode } = props
8
8
 
9
9
  return (
10
- <div className='seam-access-code-details'>
10
+ <div className='seam-code-details'>
11
11
  Unit 110
12
12
  <DotDivider />
13
13
  <Duration accessCode={accessCode} />
@@ -0,0 +1,26 @@
1
+ import classNames from 'classnames'
2
+
3
+ interface ButtonProps {
4
+ children?: string
5
+ variant?: 'solid' | 'outline' | 'neutral'
6
+ size?: 'small' | 'medium' | 'large'
7
+ disabled?: boolean
8
+ }
9
+
10
+ export function Button({
11
+ variant = 'outline',
12
+ children,
13
+ size = 'medium',
14
+ disabled,
15
+ }: ButtonProps) {
16
+ return (
17
+ <button
18
+ className={classNames(`seam-btn seam-btn-${variant} seam-btn-${size}`, {
19
+ 'seam-btn-disabled': disabled,
20
+ })}
21
+ disabled={disabled}
22
+ >
23
+ {children}
24
+ </button>
25
+ )
26
+ }