diff --git a/docs/LockOnMount.md b/docs/LockOnMount.md new file mode 100644 index 00000000000..c9a2b5f7280 --- /dev/null +++ b/docs/LockOnMount.md @@ -0,0 +1,40 @@ +--- +layout: default +title: "LockOnMount" +--- + +# `` + +`` is the component version of the [`useLockOnMount`](./useLockOnMount.md) hook. It locks the current record on mount and unlocks it on unmount. It relies on `authProvider.getIdentity()` to get the identity of the current user. It guesses the current `resource` and `recordId` from the context (or the route) if not provided. + + + +## Usage + +Use this hook e.g. in an `` component to lock the record so that it only accepts updates from the current user. + +```tsx +import { Edit, SimpleForm, TextInput } from 'react-admin'; +import { LockOnMount } from '@react-admin/ra-realtime'; + +const PostEdit = () => ( + + + + + + + + +); +``` + +**Note**: If users close their tab/browser when on a page with a locked record, `LockOnMount` will block the navigation and show a notification until the record is unlocked. + +## Parameters + +`` accepts the same props as the [`useLockOnMount`](./useLockOnMount.md) hook. + diff --git a/docs/LockStatus.md b/docs/LockStatus.md new file mode 100644 index 00000000000..a30ecd5f131 --- /dev/null +++ b/docs/LockStatus.md @@ -0,0 +1,78 @@ +--- +layout: default +title: "LockStatus" +--- + +# `` + +`` is an [Enterprise Edition](https://react-admin-ee.marmelab.com)React Admin Enterprise Edition icon component that displays the lock status of the current record. It allows to visually indicate whether the record is locked or not, by the current user or not, and provides an easy way to lock or unlock the record. + + + +## Usage + +Use `` e.g. in a toolbar, to let the user know the lock status of the current record: + +{% raw %} +```tsx +import { Toolbar, SaveButton } from 'react-admin'; +import { LockStatus } from '@react-admin/ra-realtime'; + +const CustomToolbar = () => { + return ( + + + + + ); +}; +``` +{% endraw %} + +You can also use it in a DataTable to show the lock status of each record: + +```tsx +import { List, DataTable } from 'react-admin'; +import { LockStatus } from '@react-admin/ra-realtime'; + +const PostList = () => { + return ( + + + + + + + + + + + + ); +}; +``` + +**Tip:** You can use the `hideWhenUnlocked` prop to hide the lock status when the record is not locked. This is useful to avoid showing too many lock icons in the DataTable when most records are not locked. + +## Props + +| Name | Required | Type | Default Value | Description | +| ----------------------- | -------- | ------------ | --------------------------------- | --------------------------------------------------------------------------------------------- | +| `hideWhenUnlocked` | No | `boolean` | - | Set to true to hide the lock status when the record is not locked. | +| `identity` | No | `Identifier` | From `AuthProvider.getIdentity()` | An identifier for the user who owns the lock. | +| `resource` | No | `string` | From `ResourceContext` | The resource name (e.g. `'posts'`). | +| `id` | No | `Identifier` | From `RecordContext` | The record id (e.g. `123`). | +| `meta` | No | `object` | - | Additional metadata forwarded to the dataProvider `lock()`, `unlock()` and `getLock()` calls. | +| `lockMutationOptions` | No | `object` | - | `react-query` mutation options, used to customize the lock side-effects. | +| `unlockMutationOptions` | No | `object` | - | `react-query` mutation options, used to customize the unlock side-effects. | +| `queryOptions` | No | `object` | - | `react-query` query options, used to customize the lock query side-effects. | + +## Customizing the Tooltip Messages + +You can customize the tooltip messages displayed by `` by overriding the following i18n keys in your translations: +- `ra-realtime.locks.status.locked_by_you`: The tooltip message when the record is locked by the current user. +- `ra-realtime.locks.status.locked_by_another_user`: The tooltip message when the record is locked by another user. +- `ra-realtime.locks.status.unlocked`: The tooltip message when the record is unlocked. \ No newline at end of file diff --git a/docs/navigation.html b/docs/navigation.html index 0bff2e6b209..a1289e2e1dc 100644 --- a/docs/navigation.html +++ b/docs/navigation.html @@ -305,11 +305,14 @@
  • useGetLockLive
  • useGetLocks
  • useGetLocksLive
  • +
  • useLockCallbacks
  • useLockOnMount
  • useLockOnCall
  • useGetListLive
  • useGetOneLive
  • <ListLiveUpdate>
  • +
  • <LockOnMount>
  • +
  • <LockStatus>
  • <EditLive>
  • <ShowLive>
  • <MenuLive>
  • diff --git a/docs/useLockCallbacks.md b/docs/useLockCallbacks.md new file mode 100644 index 00000000000..7a6827e95a9 --- /dev/null +++ b/docs/useLockCallbacks.md @@ -0,0 +1,140 @@ +--- +layout: default +title: "useLockCallbacks" +--- + +# `useLockCallbacks` + +This [Enterprise Edition](https://react-admin-ee.marmelab.com)React Admin Enterprise Edition icon hook returns callbacks to **lock** and **unlock** a record, as well as the current **lock status**. + +## Usage + +Use this hook e.g. to build a lock button: + +{% raw %} +```tsx +import LockIcon from '@mui/icons-material/Lock'; +import LockOpenIcon from '@mui/icons-material/LockOpen'; +import { CircularProgress, IconButton, Tooltip } from '@mui/material'; +import { useLockCallbacks } from '@react-admin/ra-realtime'; + +export const LockButton = () => { + const { + lock, + isLocked, + isLockedByCurrentUser, + isPending, + isLocking, + isUnlocking, + doLock, + doUnlock, + } = useLockCallbacks(); + if (isPending) { + return null; + } + return isLocked ? ( + isLockedByCurrentUser ? ( + + ) => { + e.stopPropagation(); + doUnlock(); + }} + > + {isUnlocking ? ( + + ) : ( + + )} + + + ) : ( + + + + ) + ) : ( + + ) => { + e.stopPropagation(); + doLock(); + }} + color="warning" + > + {isLocking ? : } + + + ); +}; +``` +{% endraw %} + +You can also leverage this hook as a quick way to access the lock status of the current record: + +```tsx +import { useLockCallbacks } from '@react-admin/ra-realtime'; +import { SaveButton, Toolbar } from 'react-admin'; + +export const MyToolbar = () => { + const { isLockedByCurrentUser } = useLockCallbacks(); + return ( + + + + ); +}; +``` + +## Parameters + +`useLockCallbacks` accepts a single options parameter, with the following properties: + +| Name | Required | Type | Default Value | Description | +| ----------------------- | -------- | ------------ | --------------------------------- | --------------------------------------------------------------------------------------------- | +| `identity` | No | `Identifier` | From `AuthProvider.getIdentity()` | An identifier for the user who owns the lock. | +| `resource` | No | `string` | From `ResourceContext` | The resource name (e.g. `'posts'`). | +| `id` | No | `Identifier` | From `RecordContext` | The record id (e.g. `123`). | +| `meta` | No | `object` | - | Additional metadata forwarded to the dataProvider `lock()`, `unlock()` and `getLock()` calls. | +| `lockMutationOptions` | No | `object` | - | `react-query` mutation options, used to customize the lock side-effects. | +| `unlockMutationOptions` | No | `object` | - | `react-query` mutation options, used to customize the unlock side-effects. | +| `queryOptions` | No | `object` | - | `react-query` query options, used to customize the lock query side-effects. | + +You can call `useLockCallbacks` with no parameter, and it will guess the resource and record id from the context (or the route): + +```tsx +const { isLocked, error, isLocking } = useLockCallbacks(); +``` + +Or you can provide them explicitly: + +```tsx +const { isLocked, error, isLocking } = useLockCallbacks({ + resource: 'venues', + id: 123, + identity: 'John Doe', +}); +``` + +## Return value + +`useLockCallbacks` returns an object with the following properties: + +| Name | Type | Description | +| ----------------------- | ---------- | ------------------------------------------------------------------------- | +| `isLocked` | `boolean` | Whether the record is currently locked (possibly by another user) or not. | +| `isLockedByCurrentUser` | `boolean` | Whether the record is locked by the current user or not. | +| `lock` | `object` | The lock data. | +| `error` | `object` | The error object if any of the mutations or the query fails. | +| `isPending` | `boolean` | Whether the lock query is in progress. | +| `isLocking` | `boolean` | Whether the lock mutation is in progress. | +| `isUnlocking` | `boolean` | Whether the unlock mutation is in progress. | +| `doLock` | `function` | A callback to manually lock the record. | +| `doUnlock` | `function` | A callback to manually unlock the record. | +| `doLockAsync` | `function` | A callback to manually lock the record asynchronously. | +| `doUnlockAsync` | `function` | A callback to manually unlock the record asynchronously. | +| `lockQuery` | `object` | The `react-query` query object for the lock status. | +| `lockMutation` | `object` | The `react-query` mutation object for the lock mutation. | +| `unlockMutation` | `object` | The `react-query` mutation object for the unlock mutation. | diff --git a/docs/useLockOnCall.md b/docs/useLockOnCall.md index edba7be3071..85dd687919f 100644 --- a/docs/useLockOnCall.md +++ b/docs/useLockOnCall.md @@ -42,7 +42,7 @@ const PostAside = () => { Post locked Only you can edit it. ) : ( - )} @@ -61,6 +61,8 @@ const PostEdit = () => ( ``` {% endraw %} +**Note**: If users close their tab/browser when on a page with a locked record, `useLockOnCall` will block the navigation and show a notification until the record is unlocked. Hence it's a good practice to give them a way to unlock the record manually, e.g. by using the `doUnlock` callback returned by the [`useLockCallbacks`](./useLockCallbacks.md) hook or the [``](./LockStatus.md) component. + ## Parameters `useLockOnCall` accepts a single options parameter, with the following properties (all optional): @@ -76,7 +78,7 @@ const PostEdit = () => ( const LockButton = ({ resource, id, identity }) => { const [doLock, lockMutation] = useLockOnCall({ resource, id, identity }); return ( - ); diff --git a/docs/useLockOnMount.md b/docs/useLockOnMount.md index 49a1bcd7c17..0d17c4229a1 100644 --- a/docs/useLockOnMount.md +++ b/docs/useLockOnMount.md @@ -58,7 +58,7 @@ const PostEdit = () => ( ``` {% endraw %} -**Note**: If users close their tab/browser when on a page with a locked record, `useLockOnMount` will block the navigation until the record is unlocked and show a notification. +**Note**: If users close their tab/browser when on a page with a locked record, `useLockOnMount` will block the navigation and show a notification until the record is unlocked. Hence it's a good practice to give them a way to unlock the record manually, e.g. by using the `doUnlock` callback returned by the hook or the [``](./LockStatus.md) component. ## Parameters @@ -98,3 +98,18 @@ const { isLocked, error, isLoading } = useLockOnMount({ }, }); ``` + +## Return value + +`useLockOnMount` returns an object with the following properties: + +- `isLocked`: Whether the record is successfully locked by this hook or not. +- `isLockedByCurrentUser`: Whether the record is locked by the current user or not. +- `lock`: The lock data. +- `error`: The error object if the lock attempt failed. +- `isLocking`: Whether the lock mutation is in progress. +- `isUnlocking`: Whether the unlock mutation is in progress. +- `doLock`: A callback to manually lock the record. +- `doUnlock`: A callback to manually unlock the record. +- `doLockAsync`: A callback to manually lock the record asynchronously. +- `doUnlockAsync`: A callback to manually unlock the record asynchronously. \ No newline at end of file