feat(ui): hides lock icon when locked by current user (#8309)

This commit is contained in:
Patrik
2024-09-19 14:13:14 -04:00
committed by GitHub
parent 405a6c3447
commit 879f690161
5 changed files with 80 additions and 33 deletions

View File

@@ -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

View File

@@ -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 || (

View File

@@ -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} />
} }

View File

@@ -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(

View File

@@ -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)