diff --git a/README.md b/README.md
index 8bae66aef..306f7cac6 100644
--- a/README.md
+++ b/README.md
@@ -87,7 +87,7 @@ export function App() {
```
@@ -215,7 +215,7 @@ or place the following in the `
` tag:
```html
```
diff --git a/package-lock.json b/package-lock.json
index 2e5a25bdf..572ed212e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,15 +1,15 @@
{
"name": "@seamapi/react",
- "version": "4.3.1",
+ "version": "4.5.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@seamapi/react",
- "version": "4.3.1",
+ "version": "4.5.0",
"license": "MIT",
"dependencies": {
- "@seamapi/http": "^1.14.0",
+ "@seamapi/http": "^1.20.0",
"@tanstack/react-query": "^5.27.5",
"classnames": "^2.3.2",
"luxon": "^3.3.0",
@@ -26,7 +26,7 @@
"@rxfork/r2wc-react-to-web-component": "^2.4.0",
"@seamapi/fake-devicedb": "^1.6.1",
"@seamapi/fake-seam-connect": "^1.69.1",
- "@seamapi/types": "^1.292.2",
+ "@seamapi/types": "^1.344.3",
"@storybook/addon-designs": "^7.0.1",
"@storybook/addon-essentials": "^7.0.2",
"@storybook/addon-links": "^7.0.2",
@@ -83,7 +83,7 @@
"npm": ">= 9.0.0"
},
"peerDependencies": {
- "@seamapi/types": "^1.26.2",
+ "@seamapi/types": "^1.344.3",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"react": "^18.0.0",
@@ -5863,9 +5863,9 @@
}
},
"node_modules/@seamapi/http": {
- "version": "1.14.0",
- "resolved": "https://registry.npmjs.org/@seamapi/http/-/http-1.14.0.tgz",
- "integrity": "sha512-/b1tGZL5aYmoegnvw+H3OSEOwQjawbDHW8BTHZiZkCY8B0k/AhWSPIPXP0AHqLc3278UECFrv1UeOsLPlGrXkQ==",
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/@seamapi/http/-/http-1.20.0.tgz",
+ "integrity": "sha512-5FP9yT4dJUQEbcUdcu3aUE6LbFyl6ZY0Xk9rtyl4rIwfmvTEy4b21dtzMxIjzBNR7zQue7NnPEizKjYlcANv/w==",
"license": "MIT",
"dependencies": {
"@seamapi/url-search-params-serializer": "^1.1.0",
@@ -5878,7 +5878,7 @@
"npm": ">= 9.0.0"
},
"peerDependencies": {
- "@seamapi/types": "^1.292.2"
+ "@seamapi/types": "^1.343.0"
},
"peerDependenciesMeta": {
"@seamapi/types": {
@@ -5887,9 +5887,9 @@
}
},
"node_modules/@seamapi/types": {
- "version": "1.294.0",
- "resolved": "https://registry.npmjs.org/@seamapi/types/-/types-1.294.0.tgz",
- "integrity": "sha512-NcZemUYBNECpMXJnBgteRwPrri0jZSUPKPJTdeOM7X0ba3kWpuj3nvA5JESsLArFB7Tv7kUAVngIvY2vWrc7aw==",
+ "version": "1.344.3",
+ "resolved": "https://registry.npmjs.org/@seamapi/types/-/types-1.344.3.tgz",
+ "integrity": "sha512-YUw1MStsMwQU63H7S0qOQe3wJLt6ArRZQNlxxpIiz8gb+NtefZEmVdvEv3MaRT2YTfL6g6Lkie7s6Td1AB2YRg==",
"devOptional": true,
"license": "MIT",
"engines": {
diff --git a/package.json b/package.json
index 22ac0dc99..8dfcf3e24 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@seamapi/react",
- "version": "4.3.1",
+ "version": "4.5.0",
"description": "Seam Components.",
"type": "module",
"main": "index.js",
@@ -109,7 +109,7 @@
"npm": ">= 9.0.0"
},
"peerDependencies": {
- "@seamapi/types": "^1.26.2",
+ "@seamapi/types": "^1.344.3",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"react": "^18.0.0",
@@ -127,7 +127,7 @@
}
},
"dependencies": {
- "@seamapi/http": "^1.14.0",
+ "@seamapi/http": "^1.20.0",
"@tanstack/react-query": "^5.27.5",
"classnames": "^2.3.2",
"luxon": "^3.3.0",
@@ -144,7 +144,7 @@
"@rxfork/r2wc-react-to-web-component": "^2.4.0",
"@seamapi/fake-devicedb": "^1.6.1",
"@seamapi/fake-seam-connect": "^1.69.1",
- "@seamapi/types": "^1.292.2",
+ "@seamapi/types": "^1.344.3",
"@storybook/addon-designs": "^7.0.1",
"@storybook/addon-essentials": "^7.0.2",
"@storybook/addon-links": "^7.0.2",
diff --git a/src/lib/seam/components/AccessCodeDetails/AccessCodeDevice.tsx b/src/lib/seam/components/AccessCodeDetails/AccessCodeDevice.tsx
index eeb8e9425..db2330025 100644
--- a/src/lib/seam/components/AccessCodeDetails/AccessCodeDevice.tsx
+++ b/src/lib/seam/components/AccessCodeDetails/AccessCodeDevice.tsx
@@ -1,8 +1,11 @@
+import { useState } from 'react'
+
import { useDevice } from 'lib/seam/devices/use-device.js'
import { isLockDevice, type LockDevice } from 'lib/seam/locks/lock-device.js'
import { useToggleLock } from 'lib/seam/locks/use-toggle-lock.js'
import { Button } from 'lib/ui/Button.js'
import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
+import { Snackbar, type SnackbarVariant } from 'lib/ui/Snackbar/Snackbar.js'
import { TextButton } from 'lib/ui/TextButton.js'
export function AccessCodeDevice({
@@ -49,35 +52,64 @@ function Content(props: {
onSelectDevice: (deviceId: string) => void
}): JSX.Element {
const { device, disableLockUnlock, onSelectDevice } = props
- const toggleLock = useToggleLock()
+ const [snackbarVisible, setSnackbarVisible] = useState(false)
+ const [snackbarVariant, setSnackbarVariant] =
+ useState('success')
+
+ const toggleLock = useToggleLock({
+ onSuccess: () => {
+ setSnackbarVisible(true)
+ setSnackbarVariant('success')
+ },
+ onError: () => {
+ setSnackbarVisible(true)
+ setSnackbarVariant('error')
+ },
+ })
const toggleLockLabel = device.properties.locked ? t.unlock : t.lock
return (
-
-
-
-
-
-
{device.properties.name}
-
{
- onSelectDevice(device.device_id)
- }}
- >
- {t.deviceDetails}
-
+ <>
+
{
+ setSnackbarVisible(false)
+ }}
+ message={
+ snackbarVariant === 'success'
+ ? t.successfullyUpdated
+ : t.failedToUpdate
+ }
+ autoDismiss
+ />
+
+
+
+
+
+
+
{device.properties.name}
+
{
+ onSelectDevice(device.device_id)
+ }}
+ >
+ {t.deviceDetails}
+
+
+ {!disableLockUnlock && device.properties.online && (
+
+ )}
- {!disableLockUnlock && device.properties.online && (
-
- )}
-
+ >
)
}
@@ -85,4 +117,6 @@ const t = {
deviceDetails: 'Device details',
unlock: 'Unlock',
lock: 'Lock',
+ successfullyUpdated: 'Lock status has been successfully updated',
+ failedToUpdate: 'Failed to update lock status',
}
diff --git a/src/lib/seam/components/DeviceDetails/LockDeviceDetails.tsx b/src/lib/seam/components/DeviceDetails/LockDeviceDetails.tsx
index 9e19e47d0..cfc741a6c 100644
--- a/src/lib/seam/components/DeviceDetails/LockDeviceDetails.tsx
+++ b/src/lib/seam/components/DeviceDetails/LockDeviceDetails.tsx
@@ -1,4 +1,5 @@
import classNames from 'classnames'
+import { useState } from 'react'
import { ChevronRightIcon } from 'lib/icons/ChevronRight.js'
import { useAccessCodes } from 'lib/seam/access-codes/use-access-codes.js'
@@ -16,6 +17,7 @@ import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
import { EditableDeviceName } from 'lib/ui/device/EditableDeviceName.js'
import { OnlineStatus } from 'lib/ui/device/OnlineStatus.js'
import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
+import { Snackbar, type SnackbarVariant } from 'lib/ui/Snackbar/Snackbar.js'
import { useToggle } from 'lib/ui/use-toggle.js'
interface LockDeviceDetailsProps extends NestedSpecificDeviceDetailsProps {
@@ -38,11 +40,25 @@ export function LockDeviceDetails({
onEditName,
}: LockDeviceDetailsProps): JSX.Element | null {
const [accessCodesOpen, toggleAccessCodesOpen] = useToggle()
- const toggleLock = useToggleLock()
const { accessCodes } = useAccessCodes({
device_id: device.device_id,
})
+ const [snackbarVisible, setSnackbarVisible] = useState(false)
+ const [snackbarVariant, setSnackbarVariant] =
+ useState
('success')
+
+ const toggleLock = useToggleLock({
+ onSuccess: () => {
+ setSnackbarVisible(true)
+ setSnackbarVariant('success')
+ },
+ onError: () => {
+ setSnackbarVisible(true)
+ setSnackbarVariant('error')
+ },
+ })
+
const lockStatus = device.properties.locked ? t.locked : t.unlocked
const toggleLockLabel = device.properties.locked ? t.unlock : t.lock
@@ -88,87 +104,103 @@ export function LockDeviceDetails({
]
return (
-
-
-
-
-
-
-
-
-
-
{t.device}
-
-
-
{t.status}:{' '}
-
- {device.properties.online && (
- <>
-
{t.power}:{' '}
-
- >
- )}
-
+ <>
+
{
+ setSnackbarVisible(false)
+ }}
+ message={
+ snackbarVariant === 'success'
+ ? t.successfullyUpdated
+ : t.failedToUpdate
+ }
+ autoDismiss
+ />
+
+
+
+
+
+
+
+
+
+
+
{t.device}
+
+
+ {t.status}:{' '}
+
+ {device.properties.online && (
+ <>
+ {t.power}:{' '}
+
+ >
+ )}
+
+
+
-
-
-
-
-
- {accessCodeCount} {t.accessCodes}
-
-
+
+
+
+ {accessCodeCount} {t.accessCodes}
+
+
+
-
-
- {device.properties.locked && device.properties.online && (
-
-
- {t.lockStatus}
- {lockStatus}
-
-
- {!disableLockUnlock &&
- device.capabilities_supported.includes('lock') && (
-
- )}
+
+ {device.properties.locked && device.properties.online && (
+
+
+ {t.lockStatus}
+ {lockStatus}
+
+
+ {!disableLockUnlock &&
+ device.capabilities_supported.includes('lock') && (
+
+ )}
+
-
- )}
+ )}
-
+
+
-
-
+ >
)
}
@@ -208,4 +240,6 @@ const t = {
lockStatus: 'Lock status',
status: 'Status',
power: 'Power',
+ successfullyUpdated: 'Lock status has been successfully updated',
+ failedToUpdate: 'Failed to update lock status',
}
diff --git a/src/lib/seam/locks/use-toggle-lock.ts b/src/lib/seam/locks/use-toggle-lock.ts
index 502c44e7a..8c4a87b15 100644
--- a/src/lib/seam/locks/use-toggle-lock.ts
+++ b/src/lib/seam/locks/use-toggle-lock.ts
@@ -12,8 +12,6 @@ import {
import { NullSeamClientError, useSeamClient } from 'lib/seam/use-seam-client.js'
-export type UseToggleLockParams = never
-
export type UseToggleLockData = undefined
export type UseToggleLockMutationVariables = Pick
& {
@@ -29,7 +27,14 @@ type MutationError =
| SeamActionAttemptFailedError
| SeamActionAttemptTimeoutError
-export function useToggleLock(): UseMutationResult<
+interface UseToggleLockParams {
+ onError?: () => void
+ onSuccess?: () => void
+}
+
+export function useToggleLock(
+ params: UseToggleLockParams = {}
+): UseMutationResult<
UseToggleLockData,
MutationError,
UseToggleLockMutationVariables
@@ -92,6 +97,8 @@ export function useToggleLock(): UseMutationResult<
)
},
onError: async (_error, variables) => {
+ params.onError?.()
+
await queryClient.invalidateQueries({
queryKey: ['devices', 'list'],
})
@@ -99,5 +106,8 @@ export function useToggleLock(): UseMutationResult<
queryKey: ['devices', 'get', { device_id: variables.device_id }],
})
},
+ onSuccess() {
+ params.onSuccess?.()
+ },
})
}
diff --git a/src/lib/ui/Snackbar/Snackbar.tsx b/src/lib/ui/Snackbar/Snackbar.tsx
index a9f7a339b..66a49201b 100644
--- a/src/lib/ui/Snackbar/Snackbar.tsx
+++ b/src/lib/ui/Snackbar/Snackbar.tsx
@@ -5,9 +5,9 @@ import { CheckGreenIcon } from 'lib/icons/CheckGreen.js'
import { CloseWhiteIcon } from 'lib/icons/CloseWhite.js'
import { ExclamationCircleIcon } from 'lib/icons/ExclamationCircle.js'
-type SnackbarVariant = 'success' | 'error'
+export type SnackbarVariant = 'success' | 'error'
-interface SnackbarProps {
+export interface SnackbarProps {
message: string
variant: SnackbarVariant
visible: boolean