feat(ui): hides lock icon when locked by current user (#8309)
This commit is contained in:
@@ -140,7 +140,7 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
|
|||||||
<li key={entityIndex}>
|
<li key={entityIndex}>
|
||||||
<Card
|
<Card
|
||||||
actions={
|
actions={
|
||||||
lockStatus ? (
|
lockStatus && user?.id !== userEditing?.id ? (
|
||||||
<Locked className={`${baseClass}__locked`} user={userEditing} />
|
<Locked className={`${baseClass}__locked`} user={userEditing} />
|
||||||
) : hasCreatePermission && type === EntityType.collection ? (
|
) : hasCreatePermission && type === EntityType.collection ? (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
StaggeredShimmers,
|
StaggeredShimmers,
|
||||||
Table,
|
Table,
|
||||||
UnpublishMany,
|
UnpublishMany,
|
||||||
|
useAuth,
|
||||||
useBulkUpload,
|
useBulkUpload,
|
||||||
useConfig,
|
useConfig,
|
||||||
useEditDepth,
|
useEditDepth,
|
||||||
@@ -43,6 +44,7 @@ const baseClass = 'collection-list'
|
|||||||
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
||||||
|
|
||||||
export const DefaultListView: React.FC = () => {
|
export const DefaultListView: React.FC = () => {
|
||||||
|
const { user } = useAuth()
|
||||||
const {
|
const {
|
||||||
beforeActions,
|
beforeActions,
|
||||||
collectionSlug,
|
collectionSlug,
|
||||||
@@ -125,7 +127,7 @@ export const DefaultListView: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<div className={`${baseClass} ${baseClass}--${collectionSlug}`}>
|
<div className={`${baseClass} ${baseClass}--${collectionSlug}`}>
|
||||||
<SetViewActions actions={actions} />
|
<SetViewActions actions={actions} />
|
||||||
<SelectionProvider docs={data.docs} totalDocs={data.totalDocs}>
|
<SelectionProvider docs={data.docs} totalDocs={data.totalDocs} user={user}>
|
||||||
<RenderComponent mappedComponent={beforeList} />
|
<RenderComponent mappedComponent={beforeList} />
|
||||||
<Gutter className={`${baseClass}__wrap`}>
|
<Gutter className={`${baseClass}__wrap`}>
|
||||||
{Header || (
|
{Header || (
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React from 'react'
|
|||||||
|
|
||||||
import { useTableCell } from '../../elements/Table/TableCellProvider/index.js'
|
import { useTableCell } from '../../elements/Table/TableCellProvider/index.js'
|
||||||
import { CheckboxInput } from '../../fields/Checkbox/Input.js'
|
import { CheckboxInput } from '../../fields/Checkbox/Input.js'
|
||||||
|
import { useAuth } from '../../providers/Auth/index.js'
|
||||||
import { useSelection } from '../../providers/Selection/index.js'
|
import { useSelection } from '../../providers/Selection/index.js'
|
||||||
import { Locked } from '../Locked/index.js'
|
import { Locked } from '../Locked/index.js'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
@@ -10,13 +11,14 @@ import './index.scss'
|
|||||||
const baseClass = 'select-row'
|
const baseClass = 'select-row'
|
||||||
|
|
||||||
export const SelectRow: React.FC = () => {
|
export const SelectRow: React.FC = () => {
|
||||||
|
const { user } = useAuth()
|
||||||
const { selected, setSelection } = useSelection()
|
const { selected, setSelection } = useSelection()
|
||||||
const { rowData } = useTableCell()
|
const { rowData } = useTableCell()
|
||||||
const { _isLocked, _userEditing } = rowData || {}
|
const { _isLocked, _userEditing } = rowData || {}
|
||||||
|
|
||||||
const documentIsLocked = _isLocked && _userEditing
|
const documentIsLocked = _isLocked && _userEditing
|
||||||
|
|
||||||
if (documentIsLocked) {
|
if (documentIsLocked && _userEditing.id !== user?.id) {
|
||||||
return <Locked user={_userEditing} />
|
return <Locked user={_userEditing} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { Where } from 'payload'
|
import type { ClientUser, Where } from 'payload'
|
||||||
|
|
||||||
import * as qs from 'qs-esm'
|
import * as qs from 'qs-esm'
|
||||||
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'
|
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'
|
||||||
@@ -33,9 +33,10 @@ type Props = {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
readonly docs: any[]
|
readonly docs: any[]
|
||||||
readonly totalDocs: number
|
readonly totalDocs: number
|
||||||
|
user: ClientUser
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SelectionProvider: React.FC<Props> = ({ children, docs = [], totalDocs }) => {
|
export const SelectionProvider: React.FC<Props> = ({ children, docs = [], totalDocs, user }) => {
|
||||||
const contextRef = useRef({} as SelectionContext)
|
const contextRef = useRef({} as SelectionContext)
|
||||||
|
|
||||||
const { code: locale } = useLocale()
|
const { code: locale } = useLocale()
|
||||||
@@ -56,8 +57,8 @@ export const SelectionProvider: React.FC<Props> = ({ children, docs = [], totalD
|
|||||||
const rows = new Map()
|
const rows = new Map()
|
||||||
if (allAvailable) {
|
if (allAvailable) {
|
||||||
setSelectAll(SelectAllStatus.AllAvailable)
|
setSelectAll(SelectAllStatus.AllAvailable)
|
||||||
docs.forEach(({ id, _isLocked }) => {
|
docs.forEach(({ id, _isLocked, _userEditing }) => {
|
||||||
if (!_isLocked) {
|
if (!_isLocked || _userEditing?.id === user?.id) {
|
||||||
rows.set(id, true)
|
rows.set(id, true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -67,22 +68,22 @@ export const SelectionProvider: React.FC<Props> = ({ children, docs = [], totalD
|
|||||||
) {
|
) {
|
||||||
setSelectAll(SelectAllStatus.None)
|
setSelectAll(SelectAllStatus.None)
|
||||||
} else {
|
} else {
|
||||||
docs.forEach(({ id, _isLocked }) => {
|
docs.forEach(({ id, _isLocked, _userEditing }) => {
|
||||||
if (!_isLocked) {
|
if (!_isLocked || _userEditing?.id === user?.id) {
|
||||||
rows.set(id, selectAll !== SelectAllStatus.Some)
|
rows.set(id, selectAll !== SelectAllStatus.Some)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
setSelected(rows)
|
setSelected(rows)
|
||||||
},
|
},
|
||||||
[docs, selectAll],
|
[docs, selectAll, user?.id],
|
||||||
)
|
)
|
||||||
|
|
||||||
const setSelection = useCallback(
|
const setSelection = useCallback(
|
||||||
(id) => {
|
(id) => {
|
||||||
const doc = docs.find((doc) => doc.id === id)
|
const doc = docs.find((doc) => doc.id === id)
|
||||||
|
|
||||||
if (doc?._isLocked) {
|
if (doc?._isLocked && user?.id !== doc?._userEditing.id) {
|
||||||
return // Prevent selection if the document is locked
|
return // Prevent selection if the document is locked
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@ export const SelectionProvider: React.FC<Props> = ({ children, docs = [], totalD
|
|||||||
|
|
||||||
setSelected(newMap)
|
setSelected(newMap)
|
||||||
},
|
},
|
||||||
[selected, docs],
|
[selected, docs, user?.id],
|
||||||
)
|
)
|
||||||
|
|
||||||
const getQueryParams = useCallback(
|
const getQueryParams = useCallback(
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ describe('locked documents', () => {
|
|||||||
|
|
||||||
describe('list view - collections', () => {
|
describe('list view - collections', () => {
|
||||||
let postDoc
|
let postDoc
|
||||||
|
let anotherPostDoc
|
||||||
let user2
|
let user2
|
||||||
let lockedDoc
|
let lockedDoc
|
||||||
|
|
||||||
@@ -78,6 +79,10 @@ describe('locked documents', () => {
|
|||||||
text: 'hello',
|
text: 'hello',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
anotherPostDoc = await createPostDoc({
|
||||||
|
text: 'another post',
|
||||||
|
})
|
||||||
|
|
||||||
user2 = await payload.create({
|
user2 = await payload.create({
|
||||||
collection: 'users',
|
collection: 'users',
|
||||||
data: {
|
data: {
|
||||||
@@ -114,6 +119,11 @@ describe('locked documents', () => {
|
|||||||
id: postDoc.id,
|
id: postDoc.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await payload.delete({
|
||||||
|
collection: 'posts',
|
||||||
|
id: anotherPostDoc.id,
|
||||||
|
})
|
||||||
|
|
||||||
await payload.delete({
|
await payload.delete({
|
||||||
collection: 'users',
|
collection: 'users',
|
||||||
id: user2.id,
|
id: user2.id,
|
||||||
@@ -124,14 +134,29 @@ describe('locked documents', () => {
|
|||||||
await page.goto(postsUrl.list)
|
await page.goto(postsUrl.list)
|
||||||
await page.waitForURL(postsUrl.list)
|
await page.waitForURL(postsUrl.list)
|
||||||
|
|
||||||
await expect(page.locator('.table .row-1 .locked svg')).toBeVisible()
|
await expect(page.locator('.table .row-2 .locked svg')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should show no lock icon on document row if unlocked', async () => {
|
test('should not show lock icon on document row if unlocked', async () => {
|
||||||
await page.goto(postsUrl.list)
|
await page.goto(postsUrl.list)
|
||||||
await page.waitForURL(postsUrl.list)
|
await page.waitForURL(postsUrl.list)
|
||||||
|
|
||||||
await expect(page.locator('.table .row-2 .checkbox-input__input')).toBeVisible()
|
await expect(page.locator('.table .row-3 .checkbox-input__input')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not show lock icon on document row if locked by current user', async () => {
|
||||||
|
await page.goto(postsUrl.edit(anotherPostDoc.id))
|
||||||
|
await page.waitForURL(postsUrl.edit(anotherPostDoc.id))
|
||||||
|
|
||||||
|
const textInput = page.locator('#field-text')
|
||||||
|
await textInput.fill('testing')
|
||||||
|
|
||||||
|
await page.reload()
|
||||||
|
|
||||||
|
await page.goto(postsUrl.list)
|
||||||
|
await page.waitForURL(postsUrl.list)
|
||||||
|
|
||||||
|
await expect(page.locator('.table .row-1 .checkbox-input__input')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should only allow bulk delete on unlocked documents', async () => {
|
test('should only allow bulk delete on unlocked documents', async () => {
|
||||||
@@ -139,7 +164,7 @@ describe('locked documents', () => {
|
|||||||
await page.locator('input#select-all').check()
|
await page.locator('input#select-all').check()
|
||||||
await page.locator('.delete-documents__toggle').click()
|
await page.locator('.delete-documents__toggle').click()
|
||||||
await expect(page.locator('.delete-documents__content p')).toHaveText(
|
await expect(page.locator('.delete-documents__content p')).toHaveText(
|
||||||
'You are about to delete 1 Posts',
|
'You are about to delete 2 Posts',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -744,6 +769,7 @@ describe('locked documents', () => {
|
|||||||
|
|
||||||
describe('dashboard - globals', () => {
|
describe('dashboard - globals', () => {
|
||||||
let user2
|
let user2
|
||||||
|
let lockedGlobal
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
user2 = await payload.create({
|
user2 = await payload.create({
|
||||||
@@ -753,6 +779,19 @@ describe('locked documents', () => {
|
|||||||
password: '1234',
|
password: '1234',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
lockedGlobal = await payload.create({
|
||||||
|
collection: lockedDocumentCollection,
|
||||||
|
data: {
|
||||||
|
document: undefined,
|
||||||
|
editedAt: new Date().toISOString(),
|
||||||
|
globalSlug: 'menu',
|
||||||
|
user: {
|
||||||
|
relationTo: 'users',
|
||||||
|
value: user2.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@@ -763,17 +802,17 @@ describe('locked documents', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should show lock on document card in dashboard view if locked', async () => {
|
test('should show lock on document card in dashboard view if locked', async () => {
|
||||||
const lockedGlobal = await payload.create({
|
await page.goto(postsUrl.admin)
|
||||||
|
await page.waitForURL(postsUrl.admin)
|
||||||
|
|
||||||
|
const globalCardList = page.locator('.dashboard__group').nth(1)
|
||||||
|
await expect(globalCardList.locator('#card-menu .locked svg')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not show lock on document card in dashboard view if unlocked', async () => {
|
||||||
|
await payload.delete({
|
||||||
collection: lockedDocumentCollection,
|
collection: lockedDocumentCollection,
|
||||||
data: {
|
id: lockedGlobal.id,
|
||||||
document: undefined,
|
|
||||||
editedAt: new Date().toISOString(),
|
|
||||||
globalSlug: 'menu',
|
|
||||||
user: {
|
|
||||||
relationTo: 'users',
|
|
||||||
value: user2.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// eslint-disable-next-line payload/no-wait-function
|
// eslint-disable-next-line payload/no-wait-function
|
||||||
@@ -783,15 +822,18 @@ describe('locked documents', () => {
|
|||||||
await page.waitForURL(postsUrl.admin)
|
await page.waitForURL(postsUrl.admin)
|
||||||
|
|
||||||
const globalCardList = page.locator('.dashboard__group').nth(1)
|
const globalCardList = page.locator('.dashboard__group').nth(1)
|
||||||
await expect(globalCardList.locator('#card-menu .locked svg')).toBeVisible()
|
await expect(globalCardList.locator('#card-menu .locked')).toBeHidden()
|
||||||
|
|
||||||
await payload.delete({
|
|
||||||
collection: lockedDocumentCollection,
|
|
||||||
id: lockedGlobal.id,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should show no lock on document card in dashboard view if unlocked', async () => {
|
test('should not show lock on document card in dashboard view if locked by current user', async () => {
|
||||||
|
await page.goto(postsUrl.edit('menu'))
|
||||||
|
await page.waitForURL(postsUrl.edit('menu'))
|
||||||
|
|
||||||
|
const textInput = page.locator('#field-text')
|
||||||
|
await textInput.fill('this is a global menu text field')
|
||||||
|
|
||||||
|
await page.reload()
|
||||||
|
|
||||||
await page.goto(postsUrl.admin)
|
await page.goto(postsUrl.admin)
|
||||||
await page.waitForURL(postsUrl.admin)
|
await page.waitForURL(postsUrl.admin)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user