@seamapi/react 0.17.0 → 0.19.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 (73) hide show
  1. package/README.md +23 -0
  2. package/lib/seam/access-codes/use-access-code.d.ts +3 -6
  3. package/lib/seam/access-codes/use-access-code.js +4 -19
  4. package/lib/seam/access-codes/use-access-code.js.map +1 -1
  5. package/lib/seam/access-codes/use-access-codes.d.ts +1 -5
  6. package/lib/seam/access-codes/use-access-codes.js +2 -1
  7. package/lib/seam/access-codes/use-access-codes.js.map +1 -1
  8. package/lib/seam/devices/use-device.d.ts +2 -2
  9. package/lib/seam/devices/use-device.js.map +1 -1
  10. package/lib/seam/devices/use-devices.js +2 -1
  11. package/lib/seam/devices/use-devices.js.map +1 -1
  12. package/lib/sort-by.d.ts +5 -0
  13. package/lib/sort-by.js +9 -0
  14. package/lib/sort-by.js.map +1 -0
  15. package/lib/ui/AccessCodeDetails/AccessCodeDetails.d.ts +4 -2
  16. package/lib/ui/AccessCodeDetails/AccessCodeDetails.js +11 -10
  17. package/lib/ui/AccessCodeDetails/AccessCodeDetails.js.map +1 -1
  18. package/lib/ui/AccessCodeDetails/AccessCodeDevice.d.ts +4 -2
  19. package/lib/ui/AccessCodeDetails/AccessCodeDevice.js +3 -6
  20. package/lib/ui/AccessCodeDetails/AccessCodeDevice.js.map +1 -1
  21. package/lib/ui/AccessCodeTable/AccessCodeTable.d.ts +2 -1
  22. package/lib/ui/AccessCodeTable/AccessCodeTable.js +15 -10
  23. package/lib/ui/AccessCodeTable/AccessCodeTable.js.map +1 -1
  24. package/lib/ui/AccessCodeTable/AccessCodeTableContent.d.ts +1 -0
  25. package/lib/ui/AccessCodeTable/AccessCodeTableContent.js +2 -2
  26. package/lib/ui/AccessCodeTable/AccessCodeTableContent.js.map +1 -1
  27. package/lib/ui/Button.d.ts +1 -1
  28. package/lib/ui/DeviceDetails/DeviceDetails.d.ts +3 -1
  29. package/lib/ui/DeviceDetails/DeviceDetails.js +12 -14
  30. package/lib/ui/DeviceDetails/DeviceDetails.js.map +1 -1
  31. package/lib/ui/DeviceDetails/DeviceModel.js +1 -1
  32. package/lib/ui/DeviceDetails/DeviceModel.js.map +1 -1
  33. package/lib/ui/DeviceTable/DeviceTable.d.ts +1 -1
  34. package/lib/ui/DeviceTable/DeviceTable.js +18 -8
  35. package/lib/ui/DeviceTable/DeviceTable.js.map +1 -1
  36. package/lib/ui/Table/TableCell.d.ts +1 -1
  37. package/lib/ui/Table/TableCell.js +2 -2
  38. package/lib/ui/Table/TableCell.js.map +1 -1
  39. package/lib/ui/TextField/SearchTextField.d.ts +1 -1
  40. package/lib/ui/TextField/SearchTextField.js +2 -2
  41. package/lib/ui/TextField/SearchTextField.js.map +1 -1
  42. package/lib/ui/device/BatteryStatus.d.ts +1 -1
  43. package/lib/ui/layout/ContentHeader.d.ts +1 -0
  44. package/lib/ui/layout/ContentHeader.js +3 -5
  45. package/lib/ui/layout/ContentHeader.js.map +1 -1
  46. package/package.json +2 -5
  47. package/src/lib/seam/access-codes/use-access-code.ts +18 -29
  48. package/src/lib/seam/access-codes/use-access-codes.ts +8 -10
  49. package/src/lib/seam/devices/use-device.ts +4 -1
  50. package/src/lib/seam/devices/use-devices.ts +2 -4
  51. package/src/lib/sort-by.ts +11 -0
  52. package/src/lib/ui/AccessCodeDetails/AccessCodeDetails.tsx +28 -18
  53. package/src/lib/ui/AccessCodeDetails/AccessCodeDevice.tsx +11 -6
  54. package/src/lib/ui/AccessCodeTable/AccessCodeTable.tsx +29 -14
  55. package/src/lib/ui/AccessCodeTable/AccessCodeTableContent.tsx +4 -7
  56. package/src/lib/ui/Button.tsx +1 -1
  57. package/src/lib/ui/DeviceDetails/DeviceDetails.tsx +22 -18
  58. package/src/lib/ui/DeviceDetails/DeviceModel.tsx +1 -1
  59. package/src/lib/ui/DeviceTable/DeviceTable.tsx +39 -10
  60. package/src/lib/ui/Table/TableCell.tsx +2 -4
  61. package/src/lib/ui/TextField/SearchTextField.tsx +2 -2
  62. package/src/lib/ui/device/BatteryStatus.tsx +2 -2
  63. package/src/lib/ui/device/LockStatus.tsx +1 -1
  64. package/src/lib/ui/device/OnlineStatus.tsx +1 -1
  65. package/src/lib/ui/layout/ContentHeader.tsx +7 -7
  66. package/lib/NavigationProvider.d.ts +0 -27
  67. package/lib/NavigationProvider.js +0 -36
  68. package/lib/NavigationProvider.js.map +0 -1
  69. package/lib/NavigationView.d.ts +0 -5
  70. package/lib/NavigationView.js +0 -25
  71. package/lib/NavigationView.js.map +0 -1
  72. package/src/lib/NavigationProvider.tsx +0 -86
  73. package/src/lib/NavigationView.tsx +0 -32
@@ -1,2 +1,2 @@
1
1
  import type { DivProps } from '../../../lib/ui/types.js';
2
- export declare function TableCell({ className, ...otherProps }: DivProps): JSX.Element;
2
+ export declare function TableCell({ className, ...props }: DivProps): JSX.Element;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import classNames from 'classnames';
3
- export function TableCell({ className, ...otherProps }) {
4
- return (_jsx("div", { className: classNames('seam-table-cell', className), ...otherProps }));
3
+ export function TableCell({ className, ...props }) {
4
+ return _jsx("div", { className: classNames('seam-table-cell', className), ...props });
5
5
  }
6
6
  //# sourceMappingURL=TableCell.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TableCell.js","sourceRoot":"","sources":["../../../src/lib/ui/Table/TableCell.tsx"],"names":[],"mappings":";AAAA,OAAO,UAAU,MAAM,YAAY,CAAA;AAInC,MAAM,UAAU,SAAS,CAAC,EAAE,SAAS,EAAE,GAAG,UAAU,EAAY;IAC9D,OAAO,CACL,cAAK,SAAS,EAAE,UAAU,CAAC,iBAAiB,EAAE,SAAS,CAAC,KAAM,UAAU,GAAI,CAC7E,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"TableCell.js","sourceRoot":"","sources":["../../../src/lib/ui/Table/TableCell.tsx"],"names":[],"mappings":";AAAA,OAAO,UAAU,MAAM,YAAY,CAAA;AAInC,MAAM,UAAU,SAAS,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAY;IACzD,OAAO,cAAK,SAAS,EAAE,UAAU,CAAC,iBAAiB,EAAE,SAAS,CAAC,KAAM,KAAK,GAAI,CAAA;AAChF,CAAC"}
@@ -1,2 +1,2 @@
1
1
  import { type TextFieldProps } from '../../../lib/ui/TextField/TextField.js';
2
- export declare function SearchTextField({ className, ...otherProps }: TextFieldProps): JSX.Element;
2
+ export declare function SearchTextField({ className, ...props }: TextFieldProps): JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import classNames from 'classnames';
3
3
  import { TextField } from '../../../lib/ui/TextField/TextField.js';
4
- export function SearchTextField({ className, ...otherProps }) {
5
- return (_jsx(TextField, { ...otherProps, className: classNames(className, 'seam-search-text-field') }));
4
+ export function SearchTextField({ className, ...props }) {
5
+ return (_jsx(TextField, { ...props, className: classNames(className, 'seam-search-text-field') }));
6
6
  }
7
7
  //# sourceMappingURL=SearchTextField.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SearchTextField.js","sourceRoot":"","sources":["../../../src/lib/ui/TextField/SearchTextField.tsx"],"names":[],"mappings":";AAAA,OAAO,UAAU,MAAM,YAAY,CAAA;AAEnC,OAAO,EAAE,SAAS,EAAuB,MAAM,+BAA+B,CAAA;AAE9E,MAAM,UAAU,eAAe,CAAC,EAC9B,SAAS,EACT,GAAG,UAAU,EACE;IACf,OAAO,CACL,KAAC,SAAS,OACJ,UAAU,EACd,SAAS,EAAE,UAAU,CAAC,SAAS,EAAE,wBAAwB,CAAC,GAC1D,CACH,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"SearchTextField.js","sourceRoot":"","sources":["../../../src/lib/ui/TextField/SearchTextField.tsx"],"names":[],"mappings":";AAAA,OAAO,UAAU,MAAM,YAAY,CAAA;AAEnC,OAAO,EAAE,SAAS,EAAuB,MAAM,+BAA+B,CAAA;AAE9E,MAAM,UAAU,eAAe,CAAC,EAC9B,SAAS,EACT,GAAG,KAAK,EACO;IACf,OAAO,CACL,KAAC,SAAS,OACJ,KAAK,EACT,SAAS,EAAE,UAAU,CAAC,SAAS,EAAE,wBAAwB,CAAC,GAC1D,CACH,CAAA;AACH,CAAC"}
@@ -2,4 +2,4 @@ import type { LockDevice } from 'seamapi';
2
2
  export interface BatteryStatusProps {
3
3
  device: LockDevice;
4
4
  }
5
- export declare function BatteryStatus(props: BatteryStatusProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare function BatteryStatus(props: BatteryStatusProps): JSX.Element;
@@ -1,3 +1,4 @@
1
1
  export declare function ContentHeader(props: {
2
2
  title?: string;
3
+ onBack?: () => void;
3
4
  }): JSX.Element | null;
@@ -1,13 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ArrowBackIcon } from '../../../lib/icons/ArrowBack.js';
3
- import { useNavigation } from '../../../lib/NavigationProvider.js';
4
3
  export function ContentHeader(props) {
5
- const { title } = props;
6
- const { goBack } = useNavigation();
7
- if (!title && !goBack) {
4
+ const { title, onBack } = props;
5
+ if (title == null && onBack == null) {
8
6
  return null;
9
7
  }
10
- return (_jsxs("div", { className: 'seam-content-header', children: [goBack && _jsx(BackIcon, { onClick: goBack }), _jsx("span", { className: 'seam-title', children: title })] }));
8
+ return (_jsxs("div", { className: 'seam-content-header', children: [_jsx(BackIcon, { onClick: onBack }), _jsx("span", { className: 'seam-title', children: title })] }));
11
9
  }
12
10
  function BackIcon(props) {
13
11
  if (props.onClick == null) {
@@ -1 +1 @@
1
- {"version":3,"file":"ContentHeader.js","sourceRoot":"","sources":["../../../src/lib/ui/layout/ContentHeader.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAEzD,MAAM,UAAU,aAAa,CAAC,KAAyB;IACrD,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,CAAA;IAEvB,MAAM,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,CAAA;IAClC,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE;QACrB,OAAO,IAAI,CAAA;KACZ;IAED,OAAO,CACL,eAAK,SAAS,EAAC,qBAAqB,aACjC,MAAM,IAAI,KAAC,QAAQ,IAAC,OAAO,EAAE,MAAM,GAAI,EACxC,eAAM,SAAS,EAAC,YAAY,YAAE,KAAK,GAAQ,IACvC,CACP,CAAA;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,KAA+B;IAC/C,IAAI,KAAK,CAAC,OAAO,IAAI,IAAI,EAAE;QACzB,OAAO,IAAI,CAAA;KACZ;IAED,OAAO,KAAC,aAAa,IAAC,SAAS,EAAC,gBAAgB,EAAC,OAAO,EAAE,KAAK,CAAC,OAAO,GAAI,CAAA;AAC7E,CAAC"}
1
+ {"version":3,"file":"ContentHeader.js","sourceRoot":"","sources":["../../../src/lib/ui/layout/ContentHeader.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAEtD,MAAM,UAAU,aAAa,CAAC,KAG7B;IACC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,KAAK,CAAA;IAC/B,IAAI,KAAK,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,EAAE;QACnC,OAAO,IAAI,CAAA;KACZ;IAED,OAAO,CACL,eAAK,SAAS,EAAC,qBAAqB,aAClC,KAAC,QAAQ,IAAC,OAAO,EAAE,MAAM,GAAI,EAC7B,eAAM,SAAS,EAAC,YAAY,YAAE,KAAK,GAAQ,IACvC,CACP,CAAA;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,KAA+B;IAC/C,IAAI,KAAK,CAAC,OAAO,IAAI,IAAI,EAAE;QACzB,OAAO,IAAI,CAAA;KACZ;IAED,OAAO,KAAC,aAAa,IAAC,SAAS,EAAC,gBAAgB,EAAC,OAAO,EAAE,KAAK,CAAC,OAAO,GAAI,CAAA;AAC7E,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seamapi/react",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "description": "Seam React component library.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -58,7 +58,6 @@
58
58
  "docs:build": "storybook build --docs --output-dir docs",
59
59
  "predocs:build": "npm run examples:build",
60
60
  "typecheck": "tsc",
61
- "dependencies": "dpdm --exit-code circular:1 src/index.ts",
62
61
  "test": "jest --collect-coverage",
63
62
  "test:update": "jest --updateSnapshot",
64
63
  "test:watch": "jest --watch",
@@ -95,7 +94,7 @@
95
94
  "@tanstack/react-query": "^4.28.0",
96
95
  "classnames": "^2.3.2",
97
96
  "luxon": "^3.3.0",
98
- "seamapi": "^5.21.0",
97
+ "seamapi": "^5.21.1",
99
98
  "uuid": "^9.0.0"
100
99
  },
101
100
  "devDependencies": {
@@ -126,7 +125,6 @@
126
125
  "concurrently": "^8.0.1",
127
126
  "copy-webpack-plugin": "^11.0.0",
128
127
  "del-cli": "^5.0.0",
129
- "dpdm": "^3.13.0",
130
128
  "eslint": "^8.9.0",
131
129
  "eslint-config-prettier": "^8.4.0",
132
130
  "eslint-config-standard-jsx": "^11.0.0",
@@ -137,7 +135,6 @@
137
135
  "eslint-plugin-unused-imports": "^2.0.0",
138
136
  "fast-glob": "^3.2.12",
139
137
  "http-proxy-middleware": "^2.0.6",
140
- "isomorphic-fetch": "^3.0.0",
141
138
  "jest": "^29.5.0",
142
139
  "jest-environment-jsdom": "^29.5.0",
143
140
  "prettier": "^2.3.0",
@@ -1,45 +1,34 @@
1
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'
2
+ import type {
3
+ AccessCode,
4
+ AccessCodeGetRequest,
5
+ AccessCodeGetResponse,
6
+ SeamError,
7
+ } from 'seamapi'
5
8
 
6
9
  import { useSeamClient } from 'lib/seam/use-seam-client.js'
10
+ import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js'
7
11
 
8
12
  export type UseAccessCodeParams = AccessCodeGetRequest | string
9
- export type UseAccessCodeData = AccessCode
10
-
11
- export function useAccessCode(params: UseAccessCodeParams) {
12
- const { client } = useSeamClient()
13
+ export type UseAccessCodeData = AccessCode | null
13
14
 
15
+ export function useAccessCode(
16
+ params: UseAccessCodeParams
17
+ ): UseSeamQueryResult<'accessCode', UseAccessCodeData> {
14
18
  const normalizedParams =
15
19
  typeof params === 'string' ? { access_code_id: params } : params
16
20
 
17
- return useQuery({
21
+ const { client } = useSeamClient()
22
+ const { data, ...rest } = useQuery<
23
+ AccessCodeGetResponse['access_code'] | null,
24
+ SeamError
25
+ >({
18
26
  enabled: client != null,
19
- queryKey: ['access_code', 'get', normalizedParams],
27
+ queryKey: ['access_codes', 'get', normalizedParams],
20
28
  queryFn: async () => {
21
29
  if (client == null) return null
22
-
23
30
  return await client?.accessCodes.get(normalizedParams)
24
31
  },
25
32
  })
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 }
33
+ return { ...rest, accessCode: data }
45
34
  }
@@ -1,8 +1,14 @@
1
1
  import { useQuery } from '@tanstack/react-query'
2
- import type { AccessCode, AccessCodesListResponse, SeamError } from 'seamapi'
2
+ import type {
3
+ AccessCode,
4
+ AccessCodesListRequest,
5
+ AccessCodesListResponse,
6
+ SeamError,
7
+ } from 'seamapi'
3
8
 
4
9
  import { useSeamClient } from 'lib/seam/use-seam-client.js'
5
10
  import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js'
11
+ import { byCreatedAt } from 'lib/sort-by.js'
6
12
 
7
13
  export type UseAccessCodesParams = AccessCodesListRequest | string
8
14
  export type UseAccessCodesData = AccessCode[]
@@ -23,17 +29,9 @@ export function useAccessCodes(
23
29
  queryFn: async () => {
24
30
  if (client == null) return []
25
31
  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
- )
32
+ return accessCodes.sort(byCreatedAt)
30
33
  },
31
34
  })
32
35
 
33
36
  return { ...rest, accessCodes: data }
34
37
  }
35
-
36
- // UPSTREAM: https://github.com/seamapi/javascript/pull/200
37
- interface AccessCodesListRequest {
38
- device_id: string
39
- }
@@ -36,7 +36,10 @@ export function useDevice(
36
36
  return { ...rest, device: data }
37
37
  }
38
38
 
39
- export function useFakeDevice(_params: DeviceGetRequest) {
39
+ export function useFakeDevice(_params: DeviceGetRequest): {
40
+ isLoading: boolean
41
+ device: UseDeviceData
42
+ } {
40
43
  const device: LockDevice = {
41
44
  device_id: 'f9a9ab36-9e14-4390-a88c-b4c78304c6aa',
42
45
  device_type: 'august_lock',
@@ -9,6 +9,7 @@ import type {
9
9
 
10
10
  import { useSeamClient } from 'lib/seam/use-seam-client.js'
11
11
  import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js'
12
+ import { byCreatedAt } from 'lib/sort-by.js'
12
13
 
13
14
  export type UseDevicesParams = DevicesListRequest
14
15
  export type UseDevicesData = Array<Device<CommonDeviceProperties>>
@@ -24,10 +25,7 @@ export function useDevices(
24
25
  queryFn: async () => {
25
26
  if (client == null) return []
26
27
  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
- )
28
+ return devices.sort(byCreatedAt)
31
29
  },
32
30
  }
33
31
  )
@@ -0,0 +1,11 @@
1
+ import { DateTime } from 'luxon'
2
+
3
+ export const byCreatedAt = (
4
+ a: { created_at: string },
5
+ b: { created_at: string }
6
+ ): number => {
7
+ const t1 = DateTime.fromISO(a.created_at)
8
+ const t2 = DateTime.fromISO(b.created_at)
9
+ if (!t1.isValid || !t2.isValid) return 0
10
+ return t1.toMillis() - t2.toMillis()
11
+ }
@@ -1,31 +1,38 @@
1
1
  import { DateTime } from 'luxon'
2
- import type { AccessCode } from 'seamapi'
2
+ import { useState } from 'react'
3
+ import type { AccessCode, CommonDeviceProperties, Device } from 'seamapi'
3
4
 
4
- import { useFakeAccessCode } from 'lib/index.js'
5
-
6
- import AccessCodeDevice from 'lib/ui/AccessCodeDetails/AccessCodeDevice.js'
5
+ import { AccessCodeDevice } from 'lib/ui/AccessCodeDetails/AccessCodeDevice.js'
6
+ import { DeviceDetails } from 'lib/ui/DeviceDetails/DeviceDetails.js'
7
7
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
8
8
 
9
9
  export interface AccessCodeDetailsProps {
10
- accessCodeId: string
10
+ accessCode: AccessCode
11
+ onBack?: () => void
11
12
  }
12
13
 
13
- export default function AccessCodeDetails({
14
- accessCodeId,
15
- }: AccessCodeDetailsProps) {
16
- const { isLoading, data: accessCode } = useFakeAccessCode({
17
- access_code_id: accessCodeId,
18
- })
14
+ export function AccessCodeDetails({
15
+ accessCode,
16
+ onBack,
17
+ }: AccessCodeDetailsProps): JSX.Element {
18
+ const name = accessCode.name ?? t.fallbackName
19
+ const [selectedDevice, selectDevice] =
20
+ useState<Device<CommonDeviceProperties> | null>(null)
19
21
 
20
- if (isLoading || !accessCode) {
21
- return null
22
+ if (selectedDevice != null) {
23
+ return (
24
+ <DeviceDetails
25
+ device={selectedDevice}
26
+ onBack={() => {
27
+ selectDevice(null)
28
+ }}
29
+ />
30
+ )
22
31
  }
23
32
 
24
- const name = accessCode.name ?? t.fallbackName
25
-
26
33
  return (
27
34
  <div className='seam-access-code-details'>
28
- <ContentHeader title='Access code' />
35
+ <ContentHeader title='Access code' onBack={onBack} />
29
36
  <div className='seam-summary'>
30
37
  <div className='seam-top'>
31
38
  <span className='seam-label'>{t.accessCode}</span>
@@ -35,7 +42,10 @@ export default function AccessCodeDetails({
35
42
  <Duration accessCode={accessCode} />
36
43
  </div>
37
44
  </div>
38
- <AccessCodeDevice deviceId={accessCode.device_id} />
45
+ <AccessCodeDevice
46
+ deviceId={accessCode.device_id}
47
+ onSelectDevice={selectDevice}
48
+ />
39
49
  </div>
40
50
  <div className='seam-details'>
41
51
  <div className='seam-row'>
@@ -60,7 +70,7 @@ export default function AccessCodeDetails({
60
70
  )
61
71
  }
62
72
 
63
- function ScheduleInfo({ accessCode }: { accessCode: AccessCode }) {
73
+ function ScheduleInfo({ accessCode }: { accessCode: AccessCode }): JSX.Element {
64
74
  if (accessCode.type === 'ongoing') {
65
75
  return <span>{t.ongoing}</span>
66
76
  }
@@ -1,4 +1,5 @@
1
- import { useNavigation } from 'lib/NavigationProvider.js'
1
+ import type { CommonDeviceProperties, Device } from 'seamapi'
2
+
2
3
  import { isLockDevice } from 'lib/seam/devices/types.js'
3
4
  import { useFakeDevice } from 'lib/seam/devices/use-device.js'
4
5
  import { Button } from 'lib/ui/Button.js'
@@ -6,13 +7,17 @@ import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
6
7
  import { getDeviceModel } from 'lib/ui/DeviceDetails/DeviceModel.js'
7
8
  import { TextButton } from 'lib/ui/TextButton.js'
8
9
 
9
- export default function AccessCodeDevice({ deviceId }: { deviceId: string }) {
10
+ export function AccessCodeDevice({
11
+ deviceId,
12
+ onSelectDevice,
13
+ }: {
14
+ deviceId: string
15
+ onSelectDevice: (device: Device<CommonDeviceProperties>) => void
16
+ }): JSX.Element | null {
10
17
  // TODO: Replace with `useDevice()` once ready
11
18
  const { isLoading, device } = useFakeDevice({ device_id: deviceId })
12
- const { show } = useNavigation()
13
19
 
14
- // TODO: Do we want to return a skeleton loader here instead?
15
- if (isLoading || !device || !isLockDevice(device)) {
20
+ if (isLoading || device == null || !isLockDevice(device)) {
16
21
  return null
17
22
  }
18
23
 
@@ -27,7 +32,7 @@ export default function AccessCodeDevice({ deviceId }: { deviceId: string }) {
27
32
  <div className='seam-model'>{model}</div>
28
33
  <TextButton
29
34
  onClick={() => {
30
- show({ name: 'device_detail', deviceId: device.device_id })
35
+ onSelectDevice(device)
31
36
  }}
32
37
  >
33
38
  {t.deviceDetails}
@@ -1,6 +1,9 @@
1
+ import { useState } from 'react'
2
+ import type { AccessCode } from 'seamapi'
3
+
1
4
  import { AccessCodeKeyIcon } from 'lib/icons/AccessCodeKey.js'
2
- import { useNavigation } from 'lib/NavigationProvider.js'
3
5
  import { useAccessCodes } from 'lib/seam/access-codes/use-access-codes.js'
6
+ import { AccessCodeDetails } from 'lib/ui/AccessCodeDetails/AccessCodeDetails.js'
4
7
  import { CodeDetails } from 'lib/ui/AccessCodeTable/CodeDetails.js'
5
8
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
6
9
  import { TableBody } from 'lib/ui/Table/TableBody.js'
@@ -11,18 +14,37 @@ import { TableTitle } from 'lib/ui/Table/TableTitle.js'
11
14
  import { Caption } from 'lib/ui/typography/Caption.js'
12
15
  import { Title } from 'lib/ui/typography/Title.js'
13
16
 
14
- export function AccessCodeTable(props: { deviceId: string }): JSX.Element {
17
+ export function AccessCodeTable(props: {
18
+ deviceId: string
19
+ onBack?: () => void
20
+ }): JSX.Element | null {
15
21
  const { accessCodes } = useAccessCodes({
16
22
  device_id: props.deviceId,
17
23
  })
24
+ const { onBack } = props
25
+
26
+ const [selectedAccessCode, selectAccessCode] = useState<AccessCode | null>(
27
+ null
28
+ )
18
29
 
19
- const { show } = useNavigation()
30
+ if (selectedAccessCode != null) {
31
+ return (
32
+ <AccessCodeDetails
33
+ accessCode={selectedAccessCode}
34
+ onBack={() => {
35
+ selectAccessCode(null)
36
+ }}
37
+ />
38
+ )
39
+ }
20
40
 
21
- if (!accessCodes) return <>{null}</>
41
+ if (accessCodes == null) {
42
+ return null
43
+ }
22
44
 
23
45
  return (
24
46
  <div className='seam-access-code-table'>
25
- <ContentHeader />
47
+ <ContentHeader onBack={onBack} />
26
48
  <TableHeader>
27
49
  <TableTitle>
28
50
  {t.accessCodes} <Caption>({accessCodes.length})</Caption>
@@ -33,10 +55,7 @@ export function AccessCodeTable(props: { deviceId: string }): JSX.Element {
33
55
  <TableRow
34
56
  key={code.access_code_id}
35
57
  onClick={() => {
36
- show({
37
- name: 'access_code_detail',
38
- accessCodeId: code.access_code_id,
39
- })
58
+ selectAccessCode(code)
40
59
  }}
41
60
  >
42
61
  <TableCell className='seam-icon-cell'>
@@ -48,11 +67,7 @@ export function AccessCodeTable(props: { deviceId: string }): JSX.Element {
48
67
  <Title>{code.name}</Title>
49
68
  <CodeDetails accessCode={code} />
50
69
  </TableCell>
51
- <TableCell className='seam-action-cell'>
52
- {/* <IconButton>
53
- <DotsEllipsisMoreIcon />
54
- </IconButton> */}
55
- </TableCell>
70
+ <TableCell className='seam-action-cell' />
56
71
  </TableRow>
57
72
  ))}
58
73
  </TableBody>
@@ -13,16 +13,17 @@ import { Title } from 'lib/ui/typography/Title.js'
13
13
 
14
14
  export interface AccessCodeTableContentProps {
15
15
  accessCodes: AccessCode[]
16
+ onBack?: () => void
16
17
  }
17
18
 
18
19
  export function AccessCodeTableContent(
19
20
  props: AccessCodeTableContentProps
20
21
  ): JSX.Element {
21
- const { accessCodes } = props
22
+ const { accessCodes, onBack } = props
22
23
 
23
24
  return (
24
25
  <div className='seam-access-code-table'>
25
- <ContentHeader />
26
+ <ContentHeader onBack={onBack} />
26
27
  <TableHeader>
27
28
  <TableTitle>
28
29
  {t.accessCodes} <Caption>({accessCodes.length})</Caption>
@@ -40,11 +41,7 @@ export function AccessCodeTableContent(
40
41
  <Title>{code.name}</Title>
41
42
  <CodeDetails accessCode={code} />
42
43
  </TableCell>
43
- <TableCell className='seam-action-cell'>
44
- {/* <IconButton>
45
- <DotsEllipsisMoreIcon />
46
- </IconButton> */}
47
- </TableCell>
44
+ <TableCell className='seam-action-cell' />
48
45
  </TableRow>
49
46
  ))}
50
47
  </TableBody>
@@ -12,7 +12,7 @@ export function Button({
12
12
  children,
13
13
  size = 'medium',
14
14
  disabled,
15
- }: ButtonProps) {
15
+ }: ButtonProps): JSX.Element {
16
16
  return (
17
17
  <button
18
18
  className={classNames(`seam-btn seam-btn-${variant} seam-btn-${size}`, {
@@ -1,38 +1,44 @@
1
+ import { type CommonDeviceProperties, type Device } from 'seamapi'
2
+
1
3
  import { ChevronRightIcon } from 'lib/icons/ChevronRight.js'
2
- import { useNavigation } from 'lib/NavigationProvider.js'
3
4
  import { useAccessCodes } from 'lib/seam/access-codes/use-access-codes.js'
4
5
  import { isLockDevice } from 'lib/seam/devices/types.js'
5
- import { useFakeDevice } from 'lib/seam/devices/use-device.js'
6
+ import { AccessCodeTable } from 'lib/ui/AccessCodeTable/AccessCodeTable.js'
6
7
  import { Button } from 'lib/ui/Button.js'
7
8
  import { BatteryStatus } from 'lib/ui/device/BatteryStatus.js'
8
9
  import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
9
10
  import { OnlineStatus } from 'lib/ui/device/OnlineStatus.js'
10
11
  import { DeviceModel } from 'lib/ui/DeviceDetails/DeviceModel.js'
11
12
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
13
+ import useToggle from 'lib/use-toggle.js'
12
14
 
13
15
  export interface DeviceDetailsProps {
14
- deviceId: string
16
+ device: Device<CommonDeviceProperties>
17
+ onBack?: () => void
15
18
  }
16
19
 
17
20
  export function DeviceDetails(props: DeviceDetailsProps): JSX.Element | null {
18
- const { deviceId } = props
21
+ const { device, onBack } = props
19
22
 
20
- const { isLoading: isLoadingDevice, device } = useFakeDevice({
21
- device_id: deviceId,
22
- })
23
+ const [accessCodesOpen, toggleAccessCodesOpen] = useToggle()
23
24
 
24
- const { isLoading: isLoadingAccessCodes, accessCodes } = useAccessCodes({
25
- device_id: deviceId,
25
+ const { isLoading, accessCodes } = useAccessCodes({
26
+ device_id: device.device_id,
26
27
  })
27
28
 
28
- const isLoading = isLoadingDevice || isLoadingAccessCodes
29
-
30
- const { show } = useNavigation()
31
-
32
- if (isLoading || !device) {
29
+ if (isLoading) {
33
30
  return null
34
31
  }
35
32
 
33
+ if (accessCodesOpen) {
34
+ return (
35
+ <AccessCodeTable
36
+ deviceId={device.device_id}
37
+ onBack={toggleAccessCodesOpen}
38
+ />
39
+ )
40
+ }
41
+
36
42
  if (!isLockDevice(device)) {
37
43
  return null
38
44
  }
@@ -46,7 +52,7 @@ export function DeviceDetails(props: DeviceDetailsProps): JSX.Element | null {
46
52
 
47
53
  return (
48
54
  <div className='seam-device-details'>
49
- <ContentHeader title='Device' />
55
+ <ContentHeader title='Device' onBack={onBack} />
50
56
  <div className='seam-body'>
51
57
  <div className='seam-summary'>
52
58
  <div className='seam-content'>
@@ -69,9 +75,7 @@ export function DeviceDetails(props: DeviceDetailsProps): JSX.Element | null {
69
75
  <div className='seam-box'>
70
76
  <div
71
77
  className='seam-content seam-access-codes'
72
- onClick={() => {
73
- show({ name: 'access_code_table', deviceId: device.device_id })
74
- }}
78
+ onClick={toggleAccessCodesOpen}
75
79
  >
76
80
  <span className='seam-value'>
77
81
  {accessCodeCount} {t.accessCodes}
@@ -6,7 +6,7 @@ interface DeviceModelProps {
6
6
 
7
7
  export function DeviceModel({ device }: DeviceModelProps): JSX.Element | null {
8
8
  const model = getDeviceModel(device)
9
- if (!model) {
9
+ if (model == null) {
10
10
  return null
11
11
  }
12
12