@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.
- package/README.md +5 -2
- package/index.css +294 -69
- package/index.css.map +1 -1
- package/index.min.css +1 -1
- package/index.min.css.map +1 -1
- package/lib/NavigationProvider.d.ts +27 -0
- package/lib/NavigationProvider.js +36 -0
- package/lib/NavigationProvider.js.map +1 -0
- package/lib/NavigationView.d.ts +5 -0
- package/lib/NavigationView.js +25 -0
- package/lib/NavigationView.js.map +1 -0
- package/lib/icons/LockLocked.d.ts +2 -0
- package/lib/icons/LockLocked.js +5 -0
- package/lib/icons/LockLocked.js.map +1 -0
- package/lib/icons/LockUnlocked.d.ts +2 -0
- package/lib/icons/LockUnlocked.js +5 -0
- package/lib/icons/LockUnlocked.js.map +1 -0
- package/lib/seam/access-codes/use-access-code.d.ts +8 -0
- package/lib/seam/access-codes/use-access-code.js +32 -0
- package/lib/seam/access-codes/use-access-code.js.map +1 -0
- package/lib/seam/access-codes/use-access-codes.d.ts +9 -0
- package/lib/seam/access-codes/use-access-codes.js +18 -0
- package/lib/seam/access-codes/use-access-codes.js.map +1 -0
- package/lib/seam/devices/types.d.ts +2 -0
- package/lib/seam/devices/types.js +2 -0
- package/lib/seam/devices/types.js.map +1 -0
- package/lib/seam/devices/use-device.d.ts +9 -0
- package/lib/seam/devices/use-device.js +56 -0
- package/lib/seam/devices/use-device.js.map +1 -0
- package/lib/seam/devices/use-devices.d.ts +4 -9
- package/lib/seam/devices/use-devices.js +2 -1
- package/lib/seam/devices/use-devices.js.map +1 -1
- package/lib/seam/index.d.ts +4 -1
- package/lib/seam/index.js +4 -1
- package/lib/seam/index.js.map +1 -1
- package/lib/seam/use-seam-client.js +3 -3
- package/lib/seam/use-seam-client.js.map +1 -1
- package/lib/seam/use-seam-query-result.js.map +1 -0
- package/lib/ui/AccessCodeDetails/AccessCodeDetails.d.ts +4 -0
- package/lib/ui/AccessCodeDetails/AccessCodeDetails.js +63 -0
- package/lib/ui/AccessCodeDetails/AccessCodeDetails.js.map +1 -0
- package/lib/ui/AccessCodeDetails/AccessCodeDevice.d.ts +3 -0
- package/lib/ui/AccessCodeDetails/AccessCodeDevice.js +25 -0
- package/lib/ui/AccessCodeDetails/AccessCodeDevice.js.map +1 -0
- package/lib/ui/AccessCodeTable/AccessCodeTable.d.ts +0 -1
- package/lib/ui/AccessCodeTable/AccessCodeTable.js +14 -4
- package/lib/ui/AccessCodeTable/AccessCodeTable.js.map +1 -1
- package/lib/ui/AccessCodeTable/AccessCodeTableContent.d.ts +0 -1
- package/lib/ui/AccessCodeTable/AccessCodeTableContent.js +7 -3
- package/lib/ui/AccessCodeTable/AccessCodeTableContent.js.map +1 -1
- package/lib/ui/AccessCodeTable/CodeDetails.js +1 -1
- package/lib/ui/AccessCodeTable/CodeDetails.js.map +1 -1
- package/lib/ui/Button.d.ts +8 -0
- package/lib/ui/Button.js +8 -0
- package/lib/ui/Button.js.map +1 -0
- package/lib/ui/DeviceDetails/DeviceDetails.d.ts +2 -3
- package/lib/ui/DeviceDetails/DeviceDetails.js +31 -13
- package/lib/ui/DeviceDetails/DeviceDetails.js.map +1 -1
- package/lib/ui/DeviceDetails/DeviceModel.d.ts +7 -0
- package/lib/ui/DeviceDetails/DeviceModel.js +18 -0
- package/lib/ui/DeviceDetails/DeviceModel.js.map +1 -0
- package/lib/ui/DeviceTable/DeviceTable.d.ts +7 -0
- package/lib/ui/DeviceTable/DeviceTable.js +43 -0
- package/lib/ui/DeviceTable/DeviceTable.js.map +1 -0
- package/lib/ui/IconButton.js +1 -1
- package/lib/ui/IconButton.js.map +1 -1
- package/lib/ui/Table/TableRow.d.ts +3 -1
- package/lib/ui/Table/TableRow.js +1 -1
- package/lib/ui/Table/TableRow.js.map +1 -1
- package/lib/ui/TextButton.d.ts +7 -0
- package/lib/ui/TextButton.js +5 -0
- package/lib/ui/TextButton.js.map +1 -0
- package/lib/ui/TextField/SearchTextField.d.ts +1 -1
- package/lib/ui/TextField/SearchTextField.js +1 -1
- package/lib/ui/TextField/SearchTextField.js.map +1 -1
- package/lib/ui/device/BatteryStatus.d.ts +5 -0
- package/lib/ui/{DeviceDetails → device}/BatteryStatus.js +5 -6
- package/lib/ui/device/BatteryStatus.js.map +1 -0
- package/lib/ui/{DeviceDetails → device}/DeviceImage.d.ts +2 -1
- package/lib/ui/{DeviceDetails → device}/DeviceImage.js +2 -2
- package/lib/ui/device/DeviceImage.js.map +1 -0
- package/lib/ui/device/LockStatus.d.ts +5 -0
- package/lib/ui/device/LockStatus.js +19 -0
- package/lib/ui/device/LockStatus.js.map +1 -0
- package/lib/ui/device/OnlineStatus.d.ts +5 -0
- package/lib/ui/{DeviceDetails → device}/OnlineStatus.js +4 -5
- package/lib/ui/device/OnlineStatus.js.map +1 -0
- package/lib/ui/index.d.ts +3 -1
- package/lib/ui/index.js +3 -1
- package/lib/ui/index.js.map +1 -1
- package/lib/ui/layout/ContentHeader.d.ts +1 -2
- package/lib/ui/layout/ContentHeader.js +7 -1
- package/lib/ui/layout/ContentHeader.js.map +1 -1
- package/package.json +4 -3
- package/src/icons/lock-locked.svg +8 -0
- package/src/icons/lock-unlocked.svg +9 -0
- package/src/lib/NavigationProvider.tsx +86 -0
- package/src/lib/NavigationView.tsx +32 -0
- package/src/lib/icons/LockLocked.tsx +32 -0
- package/src/lib/icons/LockUnlocked.tsx +34 -0
- package/src/lib/seam/access-codes/use-access-code.ts +45 -0
- package/src/lib/seam/access-codes/use-access-codes.ts +39 -0
- package/src/lib/seam/devices/types.ts +5 -0
- package/src/lib/seam/devices/use-device.ts +79 -0
- package/src/lib/seam/devices/use-devices.ts +13 -8
- package/src/lib/seam/index.ts +4 -1
- package/src/lib/seam/use-seam-client.ts +3 -3
- package/src/lib/ui/AccessCodeDetails/AccessCodeDetails.tsx +144 -0
- package/src/lib/ui/AccessCodeDetails/AccessCodeDevice.tsx +43 -0
- package/src/lib/ui/AccessCodeTable/AccessCodeTable.tsx +20 -16
- package/src/lib/ui/AccessCodeTable/AccessCodeTableContent.tsx +7 -11
- package/src/lib/ui/AccessCodeTable/CodeDetails.tsx +1 -1
- package/src/lib/ui/Button.tsx +26 -0
- package/src/lib/ui/DeviceDetails/DeviceDetails.tsx +54 -28
- package/src/lib/ui/DeviceDetails/DeviceModel.tsx +34 -0
- package/src/lib/ui/DeviceTable/DeviceTable.tsx +94 -0
- package/src/lib/ui/IconButton.tsx +1 -1
- package/src/lib/ui/Table/TableRow.tsx +8 -2
- package/src/lib/ui/TextButton.tsx +15 -0
- package/src/lib/ui/TextField/SearchTextField.tsx +1 -1
- package/src/lib/ui/{DeviceDetails → device}/BatteryStatus.tsx +12 -14
- package/src/lib/ui/device/DeviceImage.tsx +15 -0
- package/src/lib/ui/device/LockStatus.tsx +46 -0
- package/src/lib/ui/{DeviceDetails → device}/OnlineStatus.tsx +15 -11
- package/src/lib/ui/index.ts +3 -1
- package/src/lib/ui/layout/ContentHeader.tsx +11 -6
- package/src/styles/_access-code-details.scss +121 -0
- package/src/styles/_access-code-table.scss +34 -38
- package/src/styles/_buttons.scss +125 -8
- package/src/styles/_colors.scss +30 -0
- package/src/styles/_device-details.scss +21 -10
- package/src/styles/_device-table.scss +63 -0
- package/src/styles/_icons.scss +9 -0
- package/src/styles/_inputs.scss +3 -1
- package/src/styles/_layout.scss +5 -1
- package/src/styles/_main.scss +8 -14
- package/src/styles/_tables.scss +6 -0
- package/src/styles/_typography.scss +18 -2
- package/lib/hooks/use-access-codes.d.ts +0 -5
- package/lib/hooks/use-access-codes.js +0 -16
- package/lib/hooks/use-access-codes.js.map +0 -1
- package/lib/hooks/use-device.d.ts +0 -5
- package/lib/hooks/use-device.js +0 -17
- package/lib/hooks/use-device.js.map +0 -1
- package/lib/hooks/use-devices.d.ts +0 -1
- package/lib/hooks/use-devices.js +0 -2
- package/lib/hooks/use-devices.js.map +0 -1
- package/lib/hooks/use-seam-client.d.ts +0 -1
- package/lib/hooks/use-seam-client.js +0 -2
- package/lib/hooks/use-seam-client.js.map +0 -1
- package/lib/seam/devices/index.d.ts +0 -1
- package/lib/seam/devices/index.js +0 -2
- package/lib/seam/devices/index.js.map +0 -1
- package/lib/types/use-seam-query-result.js.map +0 -1
- package/lib/ui/AccessCodeTable/index.d.ts +0 -2
- package/lib/ui/AccessCodeTable/index.js +0 -3
- package/lib/ui/AccessCodeTable/index.js.map +0 -1
- package/lib/ui/DeviceDetails/BatteryStatus.d.ts +0 -4
- package/lib/ui/DeviceDetails/BatteryStatus.js.map +0 -1
- package/lib/ui/DeviceDetails/DeviceImage.js.map +0 -1
- package/lib/ui/DeviceDetails/ModelStatus.d.ts +0 -4
- package/lib/ui/DeviceDetails/ModelStatus.js +0 -13
- package/lib/ui/DeviceDetails/ModelStatus.js.map +0 -1
- package/lib/ui/DeviceDetails/OnlineStatus.d.ts +0 -4
- package/lib/ui/DeviceDetails/OnlineStatus.js.map +0 -1
- package/lib/ui/DeviceDetails/index.d.ts +0 -5
- package/lib/ui/DeviceDetails/index.js +0 -6
- package/lib/ui/DeviceDetails/index.js.map +0 -1
- package/lib/ui/DeviceManager/DeviceManager.d.ts +0 -6
- package/lib/ui/DeviceManager/DeviceManager.js +0 -24
- package/lib/ui/DeviceManager/DeviceManager.js.map +0 -1
- package/lib/ui/DeviceManager/index.d.ts +0 -1
- package/lib/ui/DeviceManager/index.js +0 -2
- package/lib/ui/DeviceManager/index.js.map +0 -1
- package/lib/ui/Table/index.d.ts +0 -5
- package/lib/ui/Table/index.js +0 -6
- package/lib/ui/Table/index.js.map +0 -1
- package/lib/ui/TextField/index.d.ts +0 -2
- package/lib/ui/TextField/index.js +0 -3
- package/lib/ui/TextField/index.js.map +0 -1
- package/src/lib/hooks/use-access-codes.ts +0 -21
- package/src/lib/hooks/use-device.ts +0 -26
- package/src/lib/hooks/use-devices.ts +0 -1
- package/src/lib/hooks/use-seam-client.ts +0 -1
- package/src/lib/seam/devices/index.ts +0 -1
- package/src/lib/ui/AccessCodeTable/index.ts +0 -2
- package/src/lib/ui/DeviceDetails/DeviceImage.tsx +0 -12
- package/src/lib/ui/DeviceDetails/ModelStatus.tsx +0 -25
- package/src/lib/ui/DeviceDetails/index.ts +0 -5
- package/src/lib/ui/DeviceManager/DeviceManager.tsx +0 -52
- package/src/lib/ui/DeviceManager/index.ts +0 -1
- package/src/lib/ui/Table/index.ts +0 -5
- package/src/lib/ui/TextField/index.ts +0 -2
- /package/lib/{types → seam}/use-seam-query-result.d.ts +0 -0
- /package/lib/{types → seam}/use-seam-query-result.js +0 -0
- /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,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
|
|
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 =
|
|
14
|
+
export type UseDevicesData = Array<Device<CommonDeviceProperties>>
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
)
|
package/src/lib/seam/index.ts
CHANGED
|
@@ -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
|
|
87
|
+
const fingerprint =
|
|
88
88
|
globalThis?.localStorage?.getItem('seam_user_fingerprint') ??
|
|
89
89
|
`fingerprint_${uuidv4()}`
|
|
90
90
|
|
|
91
|
-
globalThis?.localStorage?.setItem('seam_user_fingerprint',
|
|
91
|
+
globalThis?.localStorage?.setItem('seam_user_fingerprint', fingerprint)
|
|
92
92
|
|
|
93
|
-
return
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
21
|
+
const { accessCodes } = props
|
|
26
22
|
|
|
27
23
|
return (
|
|
28
24
|
<div className='seam-access-code-table'>
|
|
29
|
-
<ContentHeader
|
|
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-
|
|
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
|
+
}
|