@seamapi/react 0.16.0 → 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 (197) hide show
  1. package/README.md +5 -2
  2. package/index.css +403 -71
  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 +12 -9
  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 +5 -0
  49. package/lib/ui/AccessCodeTable/AccessCodeTableContent.js +19 -0
  50. package/lib/ui/AccessCodeTable/AccessCodeTableContent.js.map +1 -0
  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 +32 -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 +8 -7
  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 +12 -9
  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 +57 -0
  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 +56 -29
  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 +28 -9
  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 -2
  136. package/src/styles/_main.scss +10 -16
  137. package/src/styles/_reset.scss +114 -0
  138. package/src/styles/_tables.scss +6 -0
  139. package/src/styles/_typography.scss +18 -2
  140. package/lib/hooks/use-access-codes.d.ts +0 -5
  141. package/lib/hooks/use-access-codes.js +0 -16
  142. package/lib/hooks/use-access-codes.js.map +0 -1
  143. package/lib/hooks/use-device.d.ts +0 -5
  144. package/lib/hooks/use-device.js +0 -17
  145. package/lib/hooks/use-device.js.map +0 -1
  146. package/lib/hooks/use-devices.d.ts +0 -1
  147. package/lib/hooks/use-devices.js +0 -2
  148. package/lib/hooks/use-devices.js.map +0 -1
  149. package/lib/hooks/use-seam-client.d.ts +0 -1
  150. package/lib/hooks/use-seam-client.js +0 -2
  151. package/lib/hooks/use-seam-client.js.map +0 -1
  152. package/lib/seam/devices/index.d.ts +0 -1
  153. package/lib/seam/devices/index.js +0 -2
  154. package/lib/seam/devices/index.js.map +0 -1
  155. package/lib/types/use-seam-query-result.js.map +0 -1
  156. package/lib/ui/AccessCodeTable/index.d.ts +0 -2
  157. package/lib/ui/AccessCodeTable/index.js +0 -3
  158. package/lib/ui/AccessCodeTable/index.js.map +0 -1
  159. package/lib/ui/DeviceDetails/BatteryStatus.d.ts +0 -4
  160. package/lib/ui/DeviceDetails/BatteryStatus.js.map +0 -1
  161. package/lib/ui/DeviceDetails/DeviceImage.js.map +0 -1
  162. package/lib/ui/DeviceDetails/ModelStatus.d.ts +0 -4
  163. package/lib/ui/DeviceDetails/ModelStatus.js +0 -13
  164. package/lib/ui/DeviceDetails/ModelStatus.js.map +0 -1
  165. package/lib/ui/DeviceDetails/OnlineStatus.d.ts +0 -4
  166. package/lib/ui/DeviceDetails/OnlineStatus.js.map +0 -1
  167. package/lib/ui/DeviceDetails/index.d.ts +0 -5
  168. package/lib/ui/DeviceDetails/index.js +0 -6
  169. package/lib/ui/DeviceDetails/index.js.map +0 -1
  170. package/lib/ui/DeviceManager/DeviceManager.d.ts +0 -6
  171. package/lib/ui/DeviceManager/DeviceManager.js +0 -24
  172. package/lib/ui/DeviceManager/DeviceManager.js.map +0 -1
  173. package/lib/ui/DeviceManager/index.d.ts +0 -1
  174. package/lib/ui/DeviceManager/index.js +0 -2
  175. package/lib/ui/DeviceManager/index.js.map +0 -1
  176. package/lib/ui/Table/index.d.ts +0 -5
  177. package/lib/ui/Table/index.js +0 -6
  178. package/lib/ui/Table/index.js.map +0 -1
  179. package/lib/ui/TextField/index.d.ts +0 -2
  180. package/lib/ui/TextField/index.js +0 -3
  181. package/lib/ui/TextField/index.js.map +0 -1
  182. package/src/lib/hooks/use-access-codes.ts +0 -21
  183. package/src/lib/hooks/use-device.ts +0 -26
  184. package/src/lib/hooks/use-devices.ts +0 -1
  185. package/src/lib/hooks/use-seam-client.ts +0 -1
  186. package/src/lib/seam/devices/index.ts +0 -1
  187. package/src/lib/ui/AccessCodeTable/index.ts +0 -2
  188. package/src/lib/ui/DeviceDetails/DeviceImage.tsx +0 -12
  189. package/src/lib/ui/DeviceDetails/ModelStatus.tsx +0 -25
  190. package/src/lib/ui/DeviceDetails/index.ts +0 -5
  191. package/src/lib/ui/DeviceManager/DeviceManager.tsx +0 -52
  192. package/src/lib/ui/DeviceManager/index.ts +0 -1
  193. package/src/lib/ui/Table/index.ts +0 -5
  194. package/src/lib/ui/TextField/index.ts +0 -2
  195. /package/lib/{types → seam}/use-seam-query-result.d.ts +0 -0
  196. /package/lib/{types → seam}/use-seam-query-result.js +0 -0
  197. /package/src/lib/{types → seam}/use-seam-query-result.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seamapi/react",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Seam React component library.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -44,7 +44,7 @@
44
44
  "!src/stories"
45
45
  ],
46
46
  "scripts": {
47
- "start": "concurrently --raw --kill-others npm:examples npm:storybook",
47
+ "start": "concurrently --raw --kill-others npm:examples:storybook npm:storybook",
48
48
  "dev": "npm run start",
49
49
  "build": "npm run build:js",
50
50
  "prebuild": "del 'index.*' lib",
@@ -54,7 +54,7 @@
54
54
  "postbuild:js": "tsc-alias --project tsconfig.build.json",
55
55
  "build:css": "sass --load-path=node_modules src/index.scss:index.css",
56
56
  "postbuild:css": "sass --style=compressed --load-path=node_modules src/index.scss:index.min.css",
57
- "docs:start": "concurrently --raw --kill-others npm:examples npm:storybook:docs",
57
+ "docs:start": "concurrently --raw --kill-others npm:examples:storybook npm:storybook:docs",
58
58
  "docs:build": "storybook build --docs --output-dir docs",
59
59
  "predocs:build": "npm run examples:build",
60
60
  "typecheck": "tsc",
@@ -71,9 +71,10 @@
71
71
  "storybook:docs": "storybook dev --docs --port 6007",
72
72
  "storybook:build": "storybook build",
73
73
  "prestorybook:build": "vite build examples",
74
- "examples": "vite examples --host --clearScreen false",
74
+ "examples": "vite examples",
75
75
  "examples:build": "vite build examples",
76
76
  "examples:preview": "vite preview examples",
77
+ "examples:storybook": "vite examples --host --clearScreen false --mode storybook",
77
78
  "preexamples:preview": "npm run examples:build",
78
79
  "generate": "svgr --out-dir src/lib/icons -- src/icons",
79
80
  "pregenerate": "del src/lib/icons",
@@ -94,7 +95,7 @@
94
95
  "@tanstack/react-query": "^4.28.0",
95
96
  "classnames": "^2.3.2",
96
97
  "luxon": "^3.3.0",
97
- "seamapi": "^5.19.0-alpha.3",
98
+ "seamapi": "^5.21.0",
98
99
  "uuid": "^9.0.0"
99
100
  },
100
101
  "devDependencies": {
@@ -148,12 +149,12 @@
148
149
  "storybook-addon-designs": "^7.0.0-beta.2",
149
150
  "stylelint": "^15.6.0",
150
151
  "stylelint-config-standard-scss": "^9.0.0",
151
- "the-new-css-reset": "^1.8.4",
152
152
  "ts-jest": "^29.1.0",
153
153
  "tsc-alias": "^1.8.5",
154
154
  "tsconfig-paths-webpack-plugin": "^4.0.1",
155
155
  "typescript": "^5.0.2",
156
156
  "vite": "^4.2.1",
157
- "vite-tsconfig-paths": "^4.0.8"
157
+ "vite-tsconfig-paths": "^4.0.8",
158
+ "zustand-hoist": "^1.0.0"
158
159
  }
159
160
  }
@@ -0,0 +1,8 @@
1
+ <svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <mask id="mask0_221_23114" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="25" height="25">
3
+ <rect x="0.460693" y="0.838623" width="24" height="24" fill="#D9D9D9"/>
4
+ </mask>
5
+ <g mask="url(#mask0_221_23114)">
6
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M5.924 3.89346C3.45898 3.89346 1.46069 5.89176 1.46069 8.35678V11.702C1.46069 12.2543 1.90841 12.702 2.46069 12.702C3.01298 12.702 3.46069 12.2543 3.46069 11.702V8.35678C3.46069 6.99633 4.56356 5.89346 5.924 5.89346C7.28445 5.89346 8.38731 6.99633 8.38731 8.35677V12.8385H7.72008C7.02454 12.8385 6.46069 13.4024 6.46069 14.0979V19.5791C6.46069 20.2747 7.02454 20.8385 7.72008 20.8385H17.2013C17.8968 20.8385 18.4607 20.2747 18.4607 19.5791V14.0979C18.4607 13.4024 17.8968 12.8385 17.2013 12.8385H10.3873V8.35677C10.3873 5.89176 8.38902 3.89346 5.924 3.89346Z" fill="#232B3A"/>
7
+ </g>
8
+ </svg>
@@ -0,0 +1,9 @@
1
+ <svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <mask id="mask0_221_23076" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="25" height="25">
3
+ <rect x="0.460693" y="0.838623" width="24" height="24" fill="#D9D9D9"/>
4
+ </mask>
5
+ <g mask="url(#mask0_221_23076)">
6
+ <rect x="6.46069" y="12.8386" width="12" height="8.15217" rx="1.25939" fill="#232B3A"/>
7
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M7.94067 10.3586C7.94067 7.8623 9.96435 5.83862 12.4607 5.83862C14.957 5.83862 16.9807 7.86229 16.9807 10.3586V16.0506C16.9807 16.6029 16.533 17.0506 15.9807 17.0506C15.4284 17.0506 14.9807 16.6029 14.9807 16.0506V10.3586C14.9807 8.96686 13.8524 7.83862 12.4607 7.83862C11.0689 7.83862 9.94067 8.96687 9.94067 10.3586V13.8902C9.94067 14.4425 9.49296 14.8902 8.94067 14.8902C8.38839 14.8902 7.94067 14.4425 7.94067 13.8902V10.3586Z" fill="#232B3A"/>
8
+ </g>
9
+ </svg>
@@ -0,0 +1,86 @@
1
+ import React, { useState } from 'react'
2
+
3
+ interface DeviceTableView {
4
+ name: 'device_table'
5
+ }
6
+
7
+ interface DeviceDetailView {
8
+ name: 'device_detail'
9
+ deviceId: string
10
+ }
11
+
12
+ interface AccessCodeTableView {
13
+ name: 'access_code_table'
14
+ deviceId: string
15
+ }
16
+
17
+ interface AccessCodeDetailView {
18
+ name: 'access_code_detail'
19
+ accessCodeId: string
20
+ }
21
+
22
+ export type View =
23
+ | DeviceTableView
24
+ | DeviceDetailView
25
+ | AccessCodeTableView
26
+ | AccessCodeDetailView
27
+
28
+ export interface NavigationStackProps {
29
+ children: JSX.Element
30
+ }
31
+
32
+ interface NavigationContextProps {
33
+ views: View[]
34
+ show: (view: View) => void
35
+ goBack?: () => void
36
+ }
37
+
38
+ const NavigationContext = React.createContext<
39
+ NavigationContextProps | undefined
40
+ >(undefined)
41
+
42
+ export default function NavigationProvider({ children }: NavigationStackProps) {
43
+ const [views, setViews] = useState<View[]>([])
44
+
45
+ const pushView = (view: View) => {
46
+ setViews((current) => [view, ...current])
47
+ }
48
+
49
+ const popView = () => {
50
+ setViews((current) => {
51
+ if (current.length === 0) {
52
+ return current
53
+ }
54
+
55
+ const [_showing, ...others] = current
56
+ return others
57
+ })
58
+ }
59
+
60
+ const hasViews = views.length > 0
61
+
62
+ return (
63
+ <NavigationContext.Provider
64
+ value={{
65
+ views,
66
+ show: pushView,
67
+ goBack: hasViews ? popView : undefined,
68
+ }}
69
+ >
70
+ {children}
71
+ </NavigationContext.Provider>
72
+ )
73
+ }
74
+
75
+ export function useNavigation(): NavigationContextProps {
76
+ const context = React.useContext(NavigationContext)
77
+ if (context === undefined) {
78
+ return {
79
+ show: () => {},
80
+ goBack: undefined,
81
+ views: [],
82
+ }
83
+ }
84
+
85
+ return context
86
+ }
@@ -0,0 +1,32 @@
1
+ import { AccessCodeTable, DeviceDetails, DeviceTable } from 'lib/index.js'
2
+
3
+ import { useNavigation, type View } from 'lib/NavigationProvider.js'
4
+ import AccessCodeDetails from 'lib/ui/AccessCodeDetails/AccessCodeDetails.js'
5
+
6
+ interface NavigationViewProps {
7
+ children: JSX.Element
8
+ }
9
+
10
+ export function NavigationView({ children: rootView }: NavigationViewProps) {
11
+ const { views } = useNavigation()
12
+
13
+ const firstView = views[0]
14
+ if (firstView != null) {
15
+ return <Subview view={firstView} />
16
+ }
17
+
18
+ return rootView
19
+ }
20
+
21
+ function Subview({ view }: { view: View }) {
22
+ switch (view.name) {
23
+ case 'device_detail':
24
+ return <DeviceDetails deviceId={view.deviceId} />
25
+ case 'access_code_detail':
26
+ return <AccessCodeDetails accessCodeId={view.accessCodeId} />
27
+ case 'device_table':
28
+ return <DeviceTable />
29
+ case 'access_code_table':
30
+ return <AccessCodeTable deviceId={view.deviceId} />
31
+ }
32
+ }
@@ -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'
@@ -1,4 +1,5 @@
1
1
  import { useQuery } from '@tanstack/react-query'
2
+ import { useEffect } from 'react'
2
3
  import { Seam } from 'seamapi'
3
4
  import { v4 as uuidv4 } from 'uuid'
4
5
 
@@ -68,24 +69,26 @@ export function useSeamClient(): {
68
69
  return { client: data ?? null, isLoading, isError, error }
69
70
  }
70
71
 
71
- /**
72
- * You'll almost always want to supply a user identifier key, but if you don't
73
- * we'll automatically pick a fingerprint for this user. The user interface
74
- * will show warnings when you use a fingerprint because this client session
75
- * is bound to this machine, and is effectively ephemeral.
76
- */
77
72
  function useUserIdentifierKeyOrFingerprint(
78
73
  userIdentifierKey: string | undefined
79
74
  ): string {
75
+ useEffect(() => {
76
+ if (userIdentifierKey != null) return
77
+ // eslint-disable-next-line no-console
78
+ console.warn(`Using an automatically generated fingerprint for the SeamProvider userIdentifierKey!
79
+ The user interface will show warnings when using a fingerprint.
80
+ This is not recommended because the client session is now bound to this machine and is effectively ephemeral.`)
81
+ }, [userIdentifierKey])
82
+
80
83
  if (userIdentifierKey != null) {
81
84
  return userIdentifierKey
82
85
  }
83
86
 
84
- const fingerprintValue =
87
+ const fingerprint =
85
88
  globalThis?.localStorage?.getItem('seam_user_fingerprint') ??
86
89
  `fingerprint_${uuidv4()}`
87
90
 
88
- globalThis?.localStorage?.setItem('seam_user_fingerprint', fingerprintValue)
91
+ globalThis?.localStorage?.setItem('seam_user_fingerprint', fingerprint)
89
92
 
90
- return fingerprintValue
93
+ return fingerprint
91
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
+ }