@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.
- package/README.md +5 -2
- package/index.css +403 -71
- 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 +12 -9
- 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 +5 -0
- package/lib/ui/AccessCodeTable/AccessCodeTableContent.js +19 -0
- package/lib/ui/AccessCodeTable/AccessCodeTableContent.js.map +1 -0
- 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 +32 -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 +8 -7
- 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 +12 -9
- 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 +57 -0
- package/src/lib/ui/AccessCodeTable/CodeDetails.tsx +1 -1
- package/src/lib/ui/Button.tsx +26 -0
- package/src/lib/ui/DeviceDetails/DeviceDetails.tsx +56 -29
- 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 +28 -9
- 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 -2
- package/src/styles/_main.scss +10 -16
- package/src/styles/_reset.scss +114 -0
- 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,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 />
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { AccessCode } from 'seamapi'
|
|
2
|
+
|
|
3
|
+
import { AccessCodeKeyIcon } from 'lib/icons/AccessCodeKey.js'
|
|
4
|
+
import { CodeDetails } from 'lib/ui/AccessCodeTable/CodeDetails.js'
|
|
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'
|
|
11
|
+
import { Caption } from 'lib/ui/typography/Caption.js'
|
|
12
|
+
import { Title } from 'lib/ui/typography/Title.js'
|
|
13
|
+
|
|
14
|
+
export interface AccessCodeTableContentProps {
|
|
15
|
+
accessCodes: AccessCode[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function AccessCodeTableContent(
|
|
19
|
+
props: AccessCodeTableContentProps
|
|
20
|
+
): JSX.Element {
|
|
21
|
+
const { accessCodes } = props
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className='seam-access-code-table'>
|
|
25
|
+
<ContentHeader />
|
|
26
|
+
<TableHeader>
|
|
27
|
+
<TableTitle>
|
|
28
|
+
{t.accessCodes} <Caption>({accessCodes.length})</Caption>
|
|
29
|
+
</TableTitle>
|
|
30
|
+
</TableHeader>
|
|
31
|
+
<TableBody>
|
|
32
|
+
{accessCodes.map((code) => (
|
|
33
|
+
<TableRow key={code.access_code_id}>
|
|
34
|
+
<TableCell className='seam-icon-cell'>
|
|
35
|
+
<div>
|
|
36
|
+
<AccessCodeKeyIcon />
|
|
37
|
+
</div>
|
|
38
|
+
</TableCell>
|
|
39
|
+
<TableCell className='seam-name-cell'>
|
|
40
|
+
<Title>{code.name}</Title>
|
|
41
|
+
<CodeDetails accessCode={code} />
|
|
42
|
+
</TableCell>
|
|
43
|
+
<TableCell className='seam-action-cell'>
|
|
44
|
+
{/* <IconButton>
|
|
45
|
+
<DotsEllipsisMoreIcon />
|
|
46
|
+
</IconButton> */}
|
|
47
|
+
</TableCell>
|
|
48
|
+
</TableRow>
|
|
49
|
+
))}
|
|
50
|
+
</TableBody>
|
|
51
|
+
</div>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const t = {
|
|
56
|
+
accessCodes: 'Access Codes',
|
|
57
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -1,37 +1,49 @@
|
|
|
1
|
-
import { type LockDevice } from 'seamapi'
|
|
2
|
-
|
|
3
1
|
import { ChevronRightIcon } from 'lib/icons/ChevronRight.js'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
2
|
+
import { useNavigation } from 'lib/NavigationProvider.js'
|
|
3
|
+
import { useAccessCodes } from 'lib/seam/access-codes/use-access-codes.js'
|
|
4
|
+
import { isLockDevice } from 'lib/seam/devices/types.js'
|
|
5
|
+
import { useFakeDevice } from 'lib/seam/devices/use-device.js'
|
|
6
|
+
import { Button } from 'lib/ui/Button.js'
|
|
7
|
+
import { BatteryStatus } from 'lib/ui/device/BatteryStatus.js'
|
|
8
|
+
import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
|
|
9
|
+
import { OnlineStatus } from 'lib/ui/device/OnlineStatus.js'
|
|
10
|
+
import { DeviceModel } from 'lib/ui/DeviceDetails/DeviceModel.js'
|
|
9
11
|
import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
|
|
10
|
-
import useToggle from 'lib/use-toggle.js'
|
|
11
12
|
|
|
12
13
|
export interface DeviceDetailsProps {
|
|
13
|
-
|
|
14
|
+
deviceId: string
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export function DeviceDetails(props: DeviceDetailsProps): JSX.Element {
|
|
17
|
-
const {
|
|
17
|
+
export function DeviceDetails(props: DeviceDetailsProps): JSX.Element | null {
|
|
18
|
+
const { deviceId } = props
|
|
18
19
|
|
|
19
|
-
const
|
|
20
|
+
const { isLoading: isLoadingDevice, device } = useFakeDevice({
|
|
21
|
+
device_id: deviceId,
|
|
22
|
+
})
|
|
20
23
|
|
|
21
|
-
const
|
|
22
|
-
|
|
24
|
+
const { isLoading: isLoadingAccessCodes, accessCodes } = useAccessCodes({
|
|
25
|
+
device_id: deviceId,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const isLoading = isLoadingDevice || isLoadingAccessCodes
|
|
23
29
|
|
|
24
|
-
const
|
|
30
|
+
const { show } = useNavigation()
|
|
25
31
|
|
|
26
|
-
if (
|
|
27
|
-
return
|
|
28
|
-
<AccessCodeTable
|
|
29
|
-
deviceId={device.device_id}
|
|
30
|
-
onClickBack={toggleAccessCodes}
|
|
31
|
-
/>
|
|
32
|
-
)
|
|
32
|
+
if (isLoading || !device) {
|
|
33
|
+
return null
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
if (!isLockDevice(device)) {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const lockStatus = device.properties.locked ? t.locked : t.unlocked
|
|
41
|
+
|
|
42
|
+
const accessCodeCount = accessCodes?.length
|
|
43
|
+
|
|
44
|
+
const accessCodeLength =
|
|
45
|
+
device.properties?.schlage_metadata?.access_code_length
|
|
46
|
+
|
|
35
47
|
return (
|
|
36
48
|
<div className='seam-device-details'>
|
|
37
49
|
<ContentHeader title='Device' />
|
|
@@ -41,13 +53,15 @@ export function DeviceDetails(props: DeviceDetailsProps): JSX.Element {
|
|
|
41
53
|
<div className='seam-image'>
|
|
42
54
|
<DeviceImage device={device} />
|
|
43
55
|
</div>
|
|
44
|
-
|
|
45
56
|
<div className='seam-info'>
|
|
57
|
+
<span className='seam-label'>{t.device}</span>
|
|
46
58
|
<h4 className='seam-device-name'>{device.properties.name}</h4>
|
|
47
59
|
<div className='seam-properties'>
|
|
60
|
+
<span className='seam-label'>{t.status}:</span>{' '}
|
|
48
61
|
<OnlineStatus device={device} />
|
|
62
|
+
<span className='seam-label'>{t.power}:</span>{' '}
|
|
49
63
|
<BatteryStatus device={device} />
|
|
50
|
-
<
|
|
64
|
+
<DeviceModel device={device} />
|
|
51
65
|
</div>
|
|
52
66
|
</div>
|
|
53
67
|
</div>
|
|
@@ -55,17 +69,26 @@ export function DeviceDetails(props: DeviceDetailsProps): JSX.Element {
|
|
|
55
69
|
<div className='seam-box'>
|
|
56
70
|
<div
|
|
57
71
|
className='seam-content seam-access-codes'
|
|
58
|
-
onClick={
|
|
72
|
+
onClick={() => {
|
|
73
|
+
show({ name: 'access_code_table', deviceId: device.device_id })
|
|
74
|
+
}}
|
|
59
75
|
>
|
|
60
|
-
<span className='seam-value'>
|
|
76
|
+
<span className='seam-value'>
|
|
77
|
+
{accessCodeCount} {t.accessCodes}
|
|
78
|
+
</span>
|
|
61
79
|
<ChevronRightIcon />
|
|
62
80
|
</div>
|
|
63
81
|
</div>
|
|
64
82
|
|
|
65
83
|
<div className='seam-box'>
|
|
66
|
-
<div className='seam-content'>
|
|
67
|
-
<
|
|
68
|
-
|
|
84
|
+
<div className='seam-content seam-lock-status'>
|
|
85
|
+
<div>
|
|
86
|
+
<span className='seam-label'>{t.lockStatus}</span>
|
|
87
|
+
<span className='seam-value'>{lockStatus}</span>
|
|
88
|
+
</div>
|
|
89
|
+
<div className='seam-right'>
|
|
90
|
+
<Button size='small'>{t.unlock}</Button>
|
|
91
|
+
</div>
|
|
69
92
|
</div>
|
|
70
93
|
<AccessCodeLength accessCodeLength={accessCodeLength} />
|
|
71
94
|
</div>
|
|
@@ -93,10 +116,14 @@ function AccessCodeLength(props: {
|
|
|
93
116
|
}
|
|
94
117
|
|
|
95
118
|
const t = {
|
|
119
|
+
device: 'Device',
|
|
120
|
+
unlock: 'Unlock',
|
|
96
121
|
locked: 'Locked',
|
|
97
122
|
unlocked: 'Unlocked',
|
|
98
123
|
accessCodes: 'access codes',
|
|
99
124
|
codeLength: 'Code length',
|
|
100
125
|
digits: 'digits',
|
|
101
126
|
lockStatus: 'Lock status',
|
|
127
|
+
status: 'Status',
|
|
128
|
+
power: 'Power',
|
|
102
129
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { LockDevice } from 'seamapi'
|
|
2
|
+
|
|
3
|
+
interface DeviceModelProps {
|
|
4
|
+
device: LockDevice
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function DeviceModel({ device }: DeviceModelProps): JSX.Element | null {
|
|
8
|
+
const model = getDeviceModel(device)
|
|
9
|
+
if (!model) {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<>
|
|
15
|
+
<span className='seam-label'>{t.model}:</span>{' '}
|
|
16
|
+
<div className='seam-device-model'>{model}</div>
|
|
17
|
+
</>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const getDeviceModel = ({
|
|
22
|
+
properties,
|
|
23
|
+
}: LockDevice): string | undefined => {
|
|
24
|
+
return (
|
|
25
|
+
properties?.august_metadata?.model ??
|
|
26
|
+
properties?.schlage_metadata?.model ??
|
|
27
|
+
t.fallbackModel
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const t = {
|
|
32
|
+
model: 'Model',
|
|
33
|
+
fallbackModel: 'Unknown Lock',
|
|
34
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useNavigation } from 'lib/NavigationProvider.js'
|
|
2
|
+
import { isLockDevice } from 'lib/seam/devices/types.js'
|
|
3
|
+
import {
|
|
4
|
+
useDevices,
|
|
5
|
+
type UseDevicesData,
|
|
6
|
+
type UseDevicesParams,
|
|
7
|
+
} from 'lib/seam/devices/use-devices.js'
|
|
8
|
+
import { BatteryStatus } from 'lib/ui/device/BatteryStatus.js'
|
|
9
|
+
import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
|
|
10
|
+
import { LockStatus } from 'lib/ui/device/LockStatus.js'
|
|
11
|
+
import { OnlineStatus } from 'lib/ui/device/OnlineStatus.js'
|
|
12
|
+
import { getDeviceModel } from 'lib/ui/DeviceDetails/DeviceModel.js'
|
|
13
|
+
import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
|
|
14
|
+
import { TableBody } from 'lib/ui/Table/TableBody.js'
|
|
15
|
+
import { TableCell } from 'lib/ui/Table/TableCell.js'
|
|
16
|
+
import { TableHeader } from 'lib/ui/Table/TableHeader.js'
|
|
17
|
+
import { TableRow } from 'lib/ui/Table/TableRow.js'
|
|
18
|
+
import { TableTitle } from 'lib/ui/Table/TableTitle.js'
|
|
19
|
+
import { Caption } from 'lib/ui/typography/Caption.js'
|
|
20
|
+
import { Title } from 'lib/ui/typography/Title.js'
|
|
21
|
+
|
|
22
|
+
export type DeviceTableProps = Props & UseDevicesParams
|
|
23
|
+
|
|
24
|
+
interface Props {
|
|
25
|
+
onBack?: () => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function DeviceTable({ onBack, ...props }: DeviceTableProps) {
|
|
29
|
+
const { devices, isLoading, isError, error } = useDevices(props)
|
|
30
|
+
const { show } = useNavigation()
|
|
31
|
+
|
|
32
|
+
if (isLoading) return <p>...</p>
|
|
33
|
+
if (isError) return <p>{error?.message}</p>
|
|
34
|
+
if (devices == null) return null
|
|
35
|
+
|
|
36
|
+
const deviceCount = devices.length
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className='seam-device-table'>
|
|
40
|
+
<ContentHeader />
|
|
41
|
+
<TableHeader>
|
|
42
|
+
<TableTitle>
|
|
43
|
+
{t.devices} <Caption>({deviceCount})</Caption>
|
|
44
|
+
</TableTitle>
|
|
45
|
+
</TableHeader>
|
|
46
|
+
<TableBody>
|
|
47
|
+
{devices.map((device) => (
|
|
48
|
+
<DeviceRow
|
|
49
|
+
device={device}
|
|
50
|
+
key={device.device_id}
|
|
51
|
+
onClick={() => {
|
|
52
|
+
show({ name: 'device_detail', deviceId: device.device_id })
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
))}
|
|
56
|
+
</TableBody>
|
|
57
|
+
</div>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function DeviceRow(props: {
|
|
62
|
+
device: UseDevicesData[number]
|
|
63
|
+
onClick: () => void
|
|
64
|
+
}) {
|
|
65
|
+
const { device, onClick } = props
|
|
66
|
+
|
|
67
|
+
if (!isLockDevice(device)) return null
|
|
68
|
+
|
|
69
|
+
const deviceModel = getDeviceModel(device) ?? t.unknownLock
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<TableRow key={device.device_id} onClick={onClick}>
|
|
73
|
+
<TableCell className='seam-image-cell'>
|
|
74
|
+
<DeviceImage device={device} />
|
|
75
|
+
</TableCell>
|
|
76
|
+
<TableCell className='seam-body-cell'>
|
|
77
|
+
<Title>{device.properties.name}</Title>
|
|
78
|
+
<div className='seam-bottom'>
|
|
79
|
+
<span className='seam-device-model'>{deviceModel}</span>
|
|
80
|
+
<div className='seam-device-statuses'>
|
|
81
|
+
<OnlineStatus device={device} />
|
|
82
|
+
<BatteryStatus device={device} />
|
|
83
|
+
<LockStatus device={device} />
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</TableCell>
|
|
87
|
+
</TableRow>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const t = {
|
|
92
|
+
devices: 'Devices',
|
|
93
|
+
unknownLock: 'Unknown Lock',
|
|
94
|
+
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { DivProps } from 'lib/ui/types.js'
|
|
2
2
|
|
|
3
|
-
export function TableRow(
|
|
4
|
-
|
|
3
|
+
export function TableRow(
|
|
4
|
+
props: DivProps & { onClick?: () => void }
|
|
5
|
+
): JSX.Element {
|
|
6
|
+
return (
|
|
7
|
+
<div className='seam-table-row' {...props}>
|
|
8
|
+
{props.children}
|
|
9
|
+
</div>
|
|
10
|
+
)
|
|
5
11
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ButtonProps } from 'lib/ui/types.js'
|
|
2
|
+
|
|
3
|
+
interface TextButtonProps extends ButtonProps {
|
|
4
|
+
color?: 'primary' | 'inherit'
|
|
5
|
+
disabled?: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function TextButton({
|
|
9
|
+
color = 'primary',
|
|
10
|
+
...buttonProps
|
|
11
|
+
}: TextButtonProps): JSX.Element {
|
|
12
|
+
return (
|
|
13
|
+
<button className={`seam-text-btn seam-color-${color}`} {...buttonProps} />
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -5,32 +5,31 @@ import { BatteryLevelFullIcon } from 'lib/icons/BatteryLevelFull.js'
|
|
|
5
5
|
import { BatteryLevelHighIcon } from 'lib/icons/BatteryLevelHigh.js'
|
|
6
6
|
import { BatteryLevelLowIcon } from 'lib/icons/BatteryLevelLow.js'
|
|
7
7
|
|
|
8
|
-
export
|
|
8
|
+
export interface BatteryStatusProps {
|
|
9
9
|
device: LockDevice
|
|
10
|
-
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function BatteryStatus(props: BatteryStatusProps) {
|
|
11
13
|
const {
|
|
12
14
|
device: {
|
|
13
15
|
properties: { battery_level: batteryLevel },
|
|
14
16
|
},
|
|
15
17
|
} = props
|
|
16
18
|
|
|
17
|
-
if (batteryLevel == null) {
|
|
18
|
-
return null
|
|
19
|
-
}
|
|
20
|
-
|
|
21
19
|
return (
|
|
22
|
-
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
<Status batteryLevel={batteryLevel} />
|
|
26
|
-
</div>
|
|
27
|
-
</>
|
|
20
|
+
<div className='seam-battery-status'>
|
|
21
|
+
<Content batteryLevel={batteryLevel} />
|
|
22
|
+
</div>
|
|
28
23
|
)
|
|
29
24
|
}
|
|
30
25
|
|
|
31
|
-
function
|
|
26
|
+
function Content(props: { batteryLevel?: number }) {
|
|
32
27
|
const { batteryLevel } = props
|
|
33
28
|
|
|
29
|
+
if (batteryLevel == null) {
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
|
|
34
33
|
if (batteryLevel > batteryThreshold.full) {
|
|
35
34
|
return (
|
|
36
35
|
<>
|
|
@@ -73,7 +72,6 @@ const batteryThreshold = {
|
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
const t = {
|
|
76
|
-
power: 'Power',
|
|
77
75
|
full: 'Good',
|
|
78
76
|
high: 'Good',
|
|
79
77
|
low: 'Low',
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react'
|
|
2
|
+
import type { LockDevice } from 'seamapi'
|
|
3
|
+
|
|
4
|
+
export function DeviceImage(
|
|
5
|
+
props: { device: LockDevice } & HTMLAttributes<HTMLImageElement>
|
|
6
|
+
): JSX.Element {
|
|
7
|
+
const { device, ...imageProps } = props
|
|
8
|
+
|
|
9
|
+
const url = device.properties.image_url ?? fallbackImageUrl
|
|
10
|
+
|
|
11
|
+
return <img src={url} alt={device.properties.name} {...imageProps} />
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const fallbackImageUrl =
|
|
15
|
+
'https://connect.getseam.com/assets/images/devices/unknown-lock.png'
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { LockDevice } from 'seamapi'
|
|
2
|
+
|
|
3
|
+
import { LockLockedIcon } from 'lib/icons/LockLocked.js'
|
|
4
|
+
import { LockUnlockedIcon } from 'lib/icons/LockUnlocked.js'
|
|
5
|
+
|
|
6
|
+
export interface LockStatusProps {
|
|
7
|
+
device: LockDevice
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function LockStatus(props: LockStatusProps): JSX.Element {
|
|
11
|
+
const {
|
|
12
|
+
device: {
|
|
13
|
+
properties: { locked },
|
|
14
|
+
},
|
|
15
|
+
} = props
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className='seam-lock-status'>
|
|
19
|
+
<Content isLocked={locked} />
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function Content(props: { isLocked: boolean }) {
|
|
25
|
+
const { isLocked } = props
|
|
26
|
+
if (isLocked) {
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<LockLockedIcon className='seam-lock-icon' />
|
|
30
|
+
<span className='seam-status-text'>{t.locked}</span>
|
|
31
|
+
</>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
<LockUnlockedIcon className='seam-lock-icon' />
|
|
38
|
+
<span className='seam-status-text'>{t.unlocked}</span>
|
|
39
|
+
</>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const t = {
|
|
44
|
+
locked: 'Locked',
|
|
45
|
+
unlocked: 'Unlocked',
|
|
46
|
+
}
|
|
@@ -3,21 +3,26 @@ import type { LockDevice } from 'seamapi'
|
|
|
3
3
|
import { OnlineStatusDeviceOfflineIcon } from 'lib/icons/OnlineStatusDeviceOffline.js'
|
|
4
4
|
import { OnlineStatusOnlineIcon } from 'lib/icons/OnlineStatusOnline.js'
|
|
5
5
|
|
|
6
|
-
export
|
|
7
|
-
|
|
6
|
+
export interface OnlineStatusProps {
|
|
7
|
+
device: LockDevice
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function OnlineStatus(props: OnlineStatusProps): JSX.Element {
|
|
11
|
+
const {
|
|
12
|
+
device: {
|
|
13
|
+
properties: { online },
|
|
14
|
+
},
|
|
15
|
+
} = props
|
|
16
|
+
|
|
8
17
|
return (
|
|
9
|
-
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
<Status isOnline={device.properties.online} />
|
|
13
|
-
</div>
|
|
14
|
-
</>
|
|
18
|
+
<div className='seam-online-status'>
|
|
19
|
+
<Content isOnline={online} />
|
|
20
|
+
</div>
|
|
15
21
|
)
|
|
16
22
|
}
|
|
17
23
|
|
|
18
|
-
function
|
|
24
|
+
function Content(props: { isOnline: boolean }) {
|
|
19
25
|
const { isOnline } = props
|
|
20
|
-
|
|
21
26
|
if (isOnline) {
|
|
22
27
|
return (
|
|
23
28
|
<>
|
|
@@ -38,7 +43,6 @@ function Status(props: { isOnline: boolean }): JSX.Element {
|
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
const t = {
|
|
41
|
-
status: 'Status',
|
|
42
46
|
online: 'Online',
|
|
43
47
|
deviceOffline: 'Device Offline',
|
|
44
48
|
}
|