From 4f78a89d27a9a3514d305155323e339adac1dc11 Mon Sep 17 00:00:00 2001 From: Patrik Kozak Date: Wed, 30 Oct 2024 15:32:09 -0400 Subject: [PATCH] fix: properly handles locked documents when read access control exists on users --- .../src/elements/DocumentLocked/index.tsx | 8 ++- .../src/views/Dashboard/Default/index.tsx | 2 +- packages/next/src/views/Dashboard/index.tsx | 62 ++++++++++--------- .../next/src/views/Edit/Default/index.tsx | 17 +++-- .../src/views/LivePreview/index.client.tsx | 17 +++-- .../src/collections/operations/find.ts | 1 + .../src/collections/operations/findByID.ts | 1 + .../payload/src/globals/operations/findOne.ts | 25 ++++++-- packages/translations/src/clientKeys.ts | 1 + packages/translations/src/languages/ar.ts | 1 + packages/translations/src/languages/az.ts | 1 + packages/translations/src/languages/bg.ts | 1 + packages/translations/src/languages/cs.ts | 1 + packages/translations/src/languages/da.ts | 1 + packages/translations/src/languages/de.ts | 1 + packages/translations/src/languages/en.ts | 1 + packages/translations/src/languages/es.ts | 1 + packages/translations/src/languages/fa.ts | 1 + packages/translations/src/languages/fr.ts | 1 + packages/translations/src/languages/he.ts | 1 + packages/translations/src/languages/hr.ts | 1 + packages/translations/src/languages/hu.ts | 1 + packages/translations/src/languages/it.ts | 1 + packages/translations/src/languages/ja.ts | 1 + packages/translations/src/languages/ko.ts | 1 + packages/translations/src/languages/my.ts | 1 + packages/translations/src/languages/nb.ts | 1 + packages/translations/src/languages/nl.ts | 1 + packages/translations/src/languages/pl.ts | 1 + packages/translations/src/languages/pt.ts | 1 + packages/translations/src/languages/ro.ts | 1 + packages/translations/src/languages/rs.ts | 1 + .../translations/src/languages/rsLatin.ts | 1 + packages/translations/src/languages/ru.ts | 1 + packages/translations/src/languages/sk.ts | 1 + packages/translations/src/languages/sl.ts | 1 + packages/translations/src/languages/sv.ts | 1 + packages/translations/src/languages/th.ts | 1 + packages/translations/src/languages/tr.ts | 1 + packages/translations/src/languages/uk.ts | 1 + packages/translations/src/languages/vi.ts | 1 + packages/translations/src/languages/zh.ts | 1 + packages/translations/src/languages/zhTw.ts | 1 + .../src/elements/DocumentControls/index.tsx | 2 +- packages/ui/src/elements/Locked/index.tsx | 10 +-- packages/ui/src/exports/shared/index.ts | 1 + .../ui/src/providers/DocumentInfo/index.tsx | 19 ++++-- .../ui/src/providers/DocumentInfo/types.ts | 2 +- packages/ui/src/utilities/buildFormState.ts | 4 +- packages/ui/src/utilities/getFormState.ts | 4 +- packages/ui/src/utilities/handleTakeOver.tsx | 12 ++-- .../ui/src/utilities/isClientUserObject.ts | 5 ++ .../collections/Users/index.ts | 16 +++++ test/locked-documents/config.ts | 2 + test/locked-documents/e2e.spec.ts | 7 +++ test/locked-documents/payload-types.ts | 1 + 56 files changed, 189 insertions(+), 64 deletions(-) create mode 100644 packages/ui/src/utilities/isClientUserObject.ts diff --git a/packages/next/src/elements/DocumentLocked/index.tsx b/packages/next/src/elements/DocumentLocked/index.tsx index a02eede8af2..dc7453fe640 100644 --- a/packages/next/src/elements/DocumentLocked/index.tsx +++ b/packages/next/src/elements/DocumentLocked/index.tsx @@ -2,6 +2,7 @@ import type { ClientUser } from 'payload' import { Button, Modal, useModal, useTranslation } from '@payloadcms/ui' +import { isClientUserObject } from '@payloadcms/ui/shared' import React, { useEffect } from 'react' import './index.scss' @@ -30,7 +31,7 @@ export const DocumentLocked: React.FC<{ onReadOnly: () => void onTakeOver: () => void updatedAt?: null | number - user?: ClientUser + user?: ClientUser | number | string }> = ({ handleGoBack, isActive, onReadOnly, onTakeOver, updatedAt, user }) => { const { closeModal, openModal } = useModal() const { t } = useTranslation() @@ -49,7 +50,10 @@ export const DocumentLocked: React.FC<{

{t('general:documentLocked')}

- {user?.email ?? user?.id} {t('general:currentlyEditing')} + + {isClientUserObject(user) ? (user.email ?? user.id) : `${t('general:user')}: ${user}`} + {' '} + {t('general:currentlyEditing')}

{t('general:editedSince')} {formatDate(updatedAt)} diff --git a/packages/next/src/views/Dashboard/Default/index.tsx b/packages/next/src/views/Dashboard/Default/index.tsx index 18dbe71694b..bba3164ba7b 100644 --- a/packages/next/src/views/Dashboard/Default/index.tsx +++ b/packages/next/src/views/Dashboard/Default/index.tsx @@ -17,7 +17,7 @@ const baseClass = 'dashboard' export type DashboardProps = { globalData: Array<{ - data: { _isLocked: boolean; _lastEditedAt: string; _userEditing: ClientUser | null } + data: { _isLocked: boolean; _lastEditedAt: string; _userEditing: ClientUser | number | string } lockDuration?: number slug: string }> diff --git a/packages/next/src/views/Dashboard/index.tsx b/packages/next/src/views/Dashboard/index.tsx index 7a9a2593b31..8c8ebdbe665 100644 --- a/packages/next/src/views/Dashboard/index.tsx +++ b/packages/next/src/views/Dashboard/index.tsx @@ -31,11 +31,10 @@ export const Dashboard: React.FC = async ({ payload, user, }, + req, visibleEntities, } = initPageResult - const lockDurationDefault = 300 // Default 5 minutes in seconds - const CustomDashboardComponent = config.admin.components?.views?.Dashboard const collections = config.collections.filter( @@ -50,39 +49,44 @@ export const Dashboard: React.FC = async ({ visibleEntities.globals.includes(global.slug), ) - const globalConfigs = config.globals.map((global) => ({ - slug: global.slug, - lockDuration: - global.lockDocuments === false - ? null // Set lockDuration to null if locking is disabled - : typeof global.lockDocuments === 'object' + // Query locked global documents only if there are globals in the config + let globalData = [] + + if (config.globals.length > 0) { + const lockedDocuments = await payload.find({ + collection: 'payload-locked-documents', + depth: 1, + overrideAccess: false, + pagination: false, + req, + where: { + globalSlug: { + exists: true, + }, + }, + }) + + // Map over globals to include `lockDuration` and lock data for each global slug + globalData = config.globals.map((global) => { + const lockDurationDefault = 300 + const lockDuration = + typeof global.lockDocuments === 'object' ? global.lockDocuments.duration - : lockDurationDefault, - })) - - // Filter the slugs based on permissions and visibility - const filteredGlobalConfigs = globalConfigs.filter( - ({ slug, lockDuration }) => - lockDuration !== null && // Ensure lockDuration is valid - permissions?.globals?.[slug]?.read?.permission && - visibleEntities.globals.includes(slug), - ) + : lockDurationDefault - const globalData = await Promise.all( - filteredGlobalConfigs.map(async ({ slug, lockDuration }) => { - const data = await payload.findGlobal({ - slug, - depth: 0, - includeLockStatus: true, - }) + const lockedDoc = lockedDocuments.docs.find((doc) => doc.globalSlug === global.slug) return { - slug, - data, + slug: global.slug, + data: { + _isLocked: !!lockedDoc, + _lastEditedAt: lockedDoc?.updatedAt ?? null, + _userEditing: lockedDoc?.user?.value ?? null, + }, lockDuration, } - }), - ) + }) + } const navGroups = groupNavItems( [ diff --git a/packages/next/src/views/Edit/Default/index.tsx b/packages/next/src/views/Edit/Default/index.tsx index 961cd405b02..e71db1df90f 100644 --- a/packages/next/src/views/Edit/Default/index.tsx +++ b/packages/next/src/views/Edit/Default/index.tsx @@ -144,7 +144,7 @@ export const DefaultEditView: React.FC = () => { const documentLockStateRef = useRef<{ hasShownLockedModal: boolean isLocked: boolean - user: ClientUser + user: ClientUser | number | string } | null>({ hasShownLockedModal: false, isLocked: false, @@ -268,7 +268,10 @@ export const DefaultEditView: React.FC = () => { setDocumentIsLocked(true) if (isLockingEnabled) { - const previousOwnerId = documentLockStateRef.current?.user?.id + const previousOwnerId = + typeof documentLockStateRef.current?.user === 'object' + ? documentLockStateRef.current?.user?.id + : documentLockStateRef.current?.user if (lockedState) { if (!documentLockStateRef.current || lockedState.user.id !== previousOwnerId) { @@ -328,7 +331,11 @@ export const DefaultEditView: React.FC = () => { // Unlock the document only if we're actually navigating away from the document if (documentId && documentIsLocked && !isStayingWithinDocument) { // Check if this user is still the current editor - if (documentLockStateRef.current?.user?.id === user?.id) { + if ( + typeof documentLockStateRef.current?.user === 'object' + ? documentLockStateRef.current?.user?.id === user?.id + : documentLockStateRef.current?.user === user?.id + ) { void unlockDocument(id, collectionSlug ?? globalSlug) setDocumentIsLocked(false) setCurrentEditor(null) @@ -352,7 +359,9 @@ export const DefaultEditView: React.FC = () => { const shouldShowDocumentLockedModal = documentIsLocked && currentEditor && - currentEditor.id !== user?.id && + (typeof currentEditor === 'object' + ? currentEditor.id !== user?.id + : currentEditor !== user?.id) && !isReadOnlyForIncomingUser && !showTakeOverModal && !documentLockStateRef.current?.hasShownLockedModal && diff --git a/packages/next/src/views/LivePreview/index.client.tsx b/packages/next/src/views/LivePreview/index.client.tsx index 1c6346d3acf..debd76bf1b1 100644 --- a/packages/next/src/views/LivePreview/index.client.tsx +++ b/packages/next/src/views/LivePreview/index.client.tsx @@ -129,7 +129,7 @@ const PreviewView: React.FC = ({ const documentLockStateRef = useRef<{ hasShownLockedModal: boolean isLocked: boolean - user: ClientUser + user: ClientUser | number | string } | null>({ hasShownLockedModal: false, isLocked: false, @@ -208,7 +208,10 @@ const PreviewView: React.FC = ({ setDocumentIsLocked(true) if (isLockingEnabled) { - const previousOwnerId = documentLockStateRef.current?.user?.id + const previousOwnerId = + typeof documentLockStateRef.current?.user === 'object' + ? documentLockStateRef.current?.user?.id + : documentLockStateRef.current?.user if (lockedState) { if (!documentLockStateRef.current || lockedState.user.id !== previousOwnerId) { @@ -267,7 +270,11 @@ const PreviewView: React.FC = ({ // Unlock the document only if we're actually navigating away from the document if (documentId && documentIsLocked && !isStayingWithinDocument) { // Check if this user is still the current editor - if (documentLockStateRef.current?.user?.id === user?.id) { + if ( + typeof documentLockStateRef.current?.user === 'object' + ? documentLockStateRef.current?.user?.id === user?.id + : documentLockStateRef.current?.user === user?.id + ) { void unlockDocument(id, collectionSlug ?? globalSlug) setDocumentIsLocked(false) setCurrentEditor(null) @@ -291,7 +298,9 @@ const PreviewView: React.FC = ({ const shouldShowDocumentLockedModal = documentIsLocked && currentEditor && - currentEditor.id !== user.id && + (typeof currentEditor === 'object' + ? currentEditor.id !== user?.id + : currentEditor !== user?.id) && !isReadOnlyForIncomingUser && !showTakeOverModal && // eslint-disable-next-line react-compiler/react-compiler diff --git a/packages/payload/src/collections/operations/find.ts b/packages/payload/src/collections/operations/find.ts index 140d9af3ebf..2a9da1a48da 100644 --- a/packages/payload/src/collections/operations/find.ts +++ b/packages/payload/src/collections/operations/find.ts @@ -169,6 +169,7 @@ export const findOperation = async ( collection: 'payload-locked-documents', depth: 1, limit: sanitizedLimit, + overrideAccess: false, pagination: false, req, where: { diff --git a/packages/payload/src/collections/operations/findByID.ts b/packages/payload/src/collections/operations/findByID.ts index cfb74de5d15..dc2c244d0b8 100644 --- a/packages/payload/src/collections/operations/findByID.ts +++ b/packages/payload/src/collections/operations/findByID.ts @@ -123,6 +123,7 @@ export const findByIDOperation = async ( collection: 'payload-locked-documents', depth: 1, limit: 1, + overrideAccess: false, pagination: false, req, where: { diff --git a/packages/payload/src/globals/operations/findOne.ts b/packages/payload/src/globals/operations/findOne.ts index 70989a28506..a2d36107f95 100644 --- a/packages/payload/src/globals/operations/findOne.ts +++ b/packages/payload/src/globals/operations/findOne.ts @@ -61,21 +61,37 @@ export const findOneOperation = async >( // ///////////////////////////////////// // Include Lock Status if required // ///////////////////////////////////// - if (includeLockStatus && slug) { let lockStatus = null try { + const lockDocumentsProp = globalConfig?.lockDocuments + + const lockDurationDefault = 300 // Default 5 minutes in seconds + const lockDuration = + typeof lockDocumentsProp === 'object' ? lockDocumentsProp.duration : lockDurationDefault + const lockDurationInMilliseconds = lockDuration * 1000 + const lockedDocument = await req.payload.find({ collection: 'payload-locked-documents', depth: 1, limit: 1, + overrideAccess: false, pagination: false, req, where: { - globalSlug: { - equals: slug, - }, + and: [ + { + globalSlug: { + equals: slug, + }, + }, + { + updatedAt: { + greater_than: new Date(new Date().getTime() - lockDurationInMilliseconds), + }, + }, + ], }, }) @@ -87,7 +103,6 @@ export const findOneOperation = async >( } doc._isLocked = !!lockStatus - doc._lastEditedAt = lockStatus?.updatedAt ?? null doc._userEditing = lockStatus?.user?.value ?? null } diff --git a/packages/translations/src/clientKeys.ts b/packages/translations/src/clientKeys.ts index 6901ba11cb1..fdf6383d71c 100644 --- a/packages/translations/src/clientKeys.ts +++ b/packages/translations/src/clientKeys.ts @@ -126,6 +126,7 @@ export const clientTranslationKeys = createClientTranslationKeys([ 'general:addFilter', 'general:adminTheme', 'general:and', + 'general:anotherUser', 'general:anotherUserTakenOver', 'general:applyChanges', 'general:ascending', diff --git a/packages/translations/src/languages/ar.ts b/packages/translations/src/languages/ar.ts index f3e355cef94..bc33b6215ba 100644 --- a/packages/translations/src/languages/ar.ts +++ b/packages/translations/src/languages/ar.ts @@ -177,6 +177,7 @@ export const arTranslations: DefaultTranslationsObject = { addFilter: 'أضف فلتر', adminTheme: 'شكل واجهة المستخدم', and: 'و', + anotherUser: 'مستخدم آخر', anotherUserTakenOver: 'قام مستخدم آخر بالاستيلاء على تحرير هذا المستند.', applyChanges: 'طبق التغييرات', ascending: 'تصاعدي', diff --git a/packages/translations/src/languages/az.ts b/packages/translations/src/languages/az.ts index f60668c88df..b7d99745352 100644 --- a/packages/translations/src/languages/az.ts +++ b/packages/translations/src/languages/az.ts @@ -178,6 +178,7 @@ export const azTranslations: DefaultTranslationsObject = { addFilter: 'Filter əlavə et', adminTheme: 'Admin Mövzusu', and: 'Və', + anotherUser: 'Başqa bir istifadəçi', anotherUserTakenOver: 'Başqa bir istifadəçi bu sənədin redaktəsini ələ keçirdi.', applyChanges: 'Dəyişiklikləri Tətbiq Edin', ascending: 'Artan', diff --git a/packages/translations/src/languages/bg.ts b/packages/translations/src/languages/bg.ts index 1494370e2b6..c08406d5684 100644 --- a/packages/translations/src/languages/bg.ts +++ b/packages/translations/src/languages/bg.ts @@ -178,6 +178,7 @@ export const bgTranslations: DefaultTranslationsObject = { addFilter: 'Добави филтър', adminTheme: 'Цветова тема', and: 'И', + anotherUser: 'Друг потребител', anotherUserTakenOver: 'Друг потребител пое редактирането на този документ.', applyChanges: 'Приложи промените', ascending: 'Възходящ', diff --git a/packages/translations/src/languages/cs.ts b/packages/translations/src/languages/cs.ts index 5b83192f289..32382b331cb 100644 --- a/packages/translations/src/languages/cs.ts +++ b/packages/translations/src/languages/cs.ts @@ -178,6 +178,7 @@ export const csTranslations: DefaultTranslationsObject = { addFilter: 'Přidat filtr', adminTheme: 'Motiv administračního rozhraní', and: 'a', + anotherUser: 'Jiný uživatel', anotherUserTakenOver: 'Jiný uživatel převzal úpravy tohoto dokumentu.', applyChanges: 'Použít změny', ascending: 'Vzestupně', diff --git a/packages/translations/src/languages/da.ts b/packages/translations/src/languages/da.ts index 1e0685288aa..32f615abb58 100644 --- a/packages/translations/src/languages/da.ts +++ b/packages/translations/src/languages/da.ts @@ -177,6 +177,7 @@ export const daTranslations: DefaultTranslationsObject = { addFilter: 'Tilføj filter', adminTheme: 'Admin tema', and: 'Og', + anotherUser: 'En anden bruger', anotherUserTakenOver: 'En anden bruger har overtaget denne ressource.', applyChanges: 'Tilføj ændringer', ascending: 'Stigende', diff --git a/packages/translations/src/languages/de.ts b/packages/translations/src/languages/de.ts index 5a94770b851..7b802658fd6 100644 --- a/packages/translations/src/languages/de.ts +++ b/packages/translations/src/languages/de.ts @@ -182,6 +182,7 @@ export const deTranslations: DefaultTranslationsObject = { addFilter: 'Filter hinzufügen', adminTheme: 'Admin-Farbthema', and: 'Und', + anotherUser: 'Ein anderer Benutzer', anotherUserTakenOver: 'Ein anderer Benutzer hat die Bearbeitung dieses Dokuments übernommen.', applyChanges: 'Änderungen anwenden', ascending: 'Aufsteigend', diff --git a/packages/translations/src/languages/en.ts b/packages/translations/src/languages/en.ts index 312db967a4f..28797345652 100644 --- a/packages/translations/src/languages/en.ts +++ b/packages/translations/src/languages/en.ts @@ -179,6 +179,7 @@ export const enTranslations = { addFilter: 'Add Filter', adminTheme: 'Admin Theme', and: 'And', + anotherUser: 'Another user', anotherUserTakenOver: 'Another user has taken over editing this document.', applyChanges: 'Apply Changes', ascending: 'Ascending', diff --git a/packages/translations/src/languages/es.ts b/packages/translations/src/languages/es.ts index fa123ff0287..df34b75ebca 100644 --- a/packages/translations/src/languages/es.ts +++ b/packages/translations/src/languages/es.ts @@ -182,6 +182,7 @@ export const esTranslations: DefaultTranslationsObject = { addFilter: 'Añadir filtro', adminTheme: 'Tema del admin', and: 'Y', + anotherUser: 'Otro usuario', anotherUserTakenOver: 'Otro usuario ha tomado el control de la edición de este documento.', applyChanges: 'Aplicar Cambios', ascending: 'Ascendente', diff --git a/packages/translations/src/languages/fa.ts b/packages/translations/src/languages/fa.ts index 49e61bb8e45..3ecd15f96c6 100644 --- a/packages/translations/src/languages/fa.ts +++ b/packages/translations/src/languages/fa.ts @@ -177,6 +177,7 @@ export const faTranslations: DefaultTranslationsObject = { addFilter: 'افزودن علامت', adminTheme: 'پوسته پیشخوان', and: 'و', + anotherUser: 'کاربر دیگر', anotherUserTakenOver: 'کاربر دیگری ویرایش این سند را به دست گرفته است.', applyChanges: 'اعمال تغییرات', ascending: 'صعودی', diff --git a/packages/translations/src/languages/fr.ts b/packages/translations/src/languages/fr.ts index 14b38f6e822..9e3b6977e21 100644 --- a/packages/translations/src/languages/fr.ts +++ b/packages/translations/src/languages/fr.ts @@ -185,6 +185,7 @@ export const frTranslations: DefaultTranslationsObject = { addFilter: 'Ajouter un filtre', adminTheme: 'Thème d’administration', and: 'Et', + anotherUser: 'Un autre utilisateur', anotherUserTakenOver: 'Un autre utilisateur a pris en charge la modification de ce document.', applyChanges: 'Appliquer les modifications', ascending: 'Ascendant', diff --git a/packages/translations/src/languages/he.ts b/packages/translations/src/languages/he.ts index 1acdb4305c5..ef624e24b5c 100644 --- a/packages/translations/src/languages/he.ts +++ b/packages/translations/src/languages/he.ts @@ -174,6 +174,7 @@ export const heTranslations: DefaultTranslationsObject = { addFilter: 'הוסף מסנן', adminTheme: 'ערכת נושא ממשק הניהול', and: 'וגם', + anotherUser: 'משתמש אחר', anotherUserTakenOver: 'משתמש אחר השתלט על עריכת מסמך זה.', applyChanges: 'החל שינויים', ascending: 'בסדר עולה', diff --git a/packages/translations/src/languages/hr.ts b/packages/translations/src/languages/hr.ts index 3d60e33324e..3120d0cd49f 100644 --- a/packages/translations/src/languages/hr.ts +++ b/packages/translations/src/languages/hr.ts @@ -179,6 +179,7 @@ export const hrTranslations: DefaultTranslationsObject = { addFilter: 'Dodaj filter', adminTheme: 'Administratorska tema', and: 'i', + anotherUser: 'Drugi korisnik', anotherUserTakenOver: 'Drugi korisnik je preuzeo uređivanje ovog dokumenta.', applyChanges: 'Primijeni promjene', ascending: 'Uzlazno', diff --git a/packages/translations/src/languages/hu.ts b/packages/translations/src/languages/hu.ts index ebdc487137e..750d21e89fd 100644 --- a/packages/translations/src/languages/hu.ts +++ b/packages/translations/src/languages/hu.ts @@ -180,6 +180,7 @@ export const huTranslations: DefaultTranslationsObject = { addFilter: 'Szűrő hozzáadása', adminTheme: 'Admin téma', and: 'És', + anotherUser: 'Egy másik felhasználó', anotherUserTakenOver: 'Egy másik felhasználó átvette ennek a dokumentumnak a szerkesztését.', applyChanges: 'Változtatások alkalmazása', ascending: 'Növekvő', diff --git a/packages/translations/src/languages/it.ts b/packages/translations/src/languages/it.ts index de8a0d105fd..f510659565c 100644 --- a/packages/translations/src/languages/it.ts +++ b/packages/translations/src/languages/it.ts @@ -181,6 +181,7 @@ export const itTranslations: DefaultTranslationsObject = { addFilter: 'Aggiungi Filtro', adminTheme: 'Tema Admin', and: 'E', + anotherUser: 'Un altro utente', anotherUserTakenOver: 'Un altro utente ha preso il controllo della modifica di questo documento.', applyChanges: 'Applica modifiche', diff --git a/packages/translations/src/languages/ja.ts b/packages/translations/src/languages/ja.ts index 98c1cb0b357..37de9c4dcc3 100644 --- a/packages/translations/src/languages/ja.ts +++ b/packages/translations/src/languages/ja.ts @@ -179,6 +179,7 @@ export const jaTranslations: DefaultTranslationsObject = { addFilter: '絞り込みを追加', adminTheme: '管理画面のテーマ', and: 'かつ', + anotherUser: '別のユーザー', anotherUserTakenOver: '別のユーザーがこのドキュメントの編集を引き継ぎました。', applyChanges: '変更を適用する', ascending: '昇順', diff --git a/packages/translations/src/languages/ko.ts b/packages/translations/src/languages/ko.ts index 771d100667b..5ba04d1e5be 100644 --- a/packages/translations/src/languages/ko.ts +++ b/packages/translations/src/languages/ko.ts @@ -178,6 +178,7 @@ export const koTranslations: DefaultTranslationsObject = { addFilter: '필터 추가', adminTheme: '관리자 테마', and: '및', + anotherUser: '다른 사용자', anotherUserTakenOver: '다른 사용자가 이 문서의 편집을 인수했습니다.', applyChanges: '변경 사항 적용', ascending: '오름차순', diff --git a/packages/translations/src/languages/my.ts b/packages/translations/src/languages/my.ts index 90896b72645..df75a396cdc 100644 --- a/packages/translations/src/languages/my.ts +++ b/packages/translations/src/languages/my.ts @@ -180,6 +180,7 @@ export const myTranslations: DefaultTranslationsObject = { addFilter: 'ဇကာထည့်ပါ။', adminTheme: 'အက်ပ်ဒိုင်များစပ်စွာ', and: 'နှင့်', + anotherUser: 'တစ်ခြားအသုံးပြုသူ', anotherUserTakenOver: 'တစ်ခြားအသုံးပြုသူသည်ဤစာရွက်စာတမ်းကိုပြင်ဆင်မှုကိုရယူလိုက်သည်။', applyChanges: 'ပြောင်းလဲမှုများ အသုံးပြုပါ', ascending: 'တက်နေသည်', diff --git a/packages/translations/src/languages/nb.ts b/packages/translations/src/languages/nb.ts index 558d7e16c8b..dbe22cba614 100644 --- a/packages/translations/src/languages/nb.ts +++ b/packages/translations/src/languages/nb.ts @@ -178,6 +178,7 @@ export const nbTranslations: DefaultTranslationsObject = { addFilter: 'Legg til filter', adminTheme: 'Admin-tema', and: 'Og', + anotherUser: 'En annen bruker', anotherUserTakenOver: 'En annen bruker har tatt over redigeringen av dette dokumentet.', applyChanges: 'Bruk endringer', ascending: 'Stigende', diff --git a/packages/translations/src/languages/nl.ts b/packages/translations/src/languages/nl.ts index 8aabe0a9ac0..cc6694d2a2a 100644 --- a/packages/translations/src/languages/nl.ts +++ b/packages/translations/src/languages/nl.ts @@ -180,6 +180,7 @@ export const nlTranslations: DefaultTranslationsObject = { addFilter: 'Filter toevoegen', adminTheme: 'Adminthema', and: 'En', + anotherUser: 'Een andere gebruiker', anotherUserTakenOver: 'Een andere gebruiker heeft de bewerking van dit document overgenomen.', applyChanges: 'Breng wijzigingen aan', ascending: 'Oplopend', diff --git a/packages/translations/src/languages/pl.ts b/packages/translations/src/languages/pl.ts index f8ff36c5fe8..37f10e41a22 100644 --- a/packages/translations/src/languages/pl.ts +++ b/packages/translations/src/languages/pl.ts @@ -178,6 +178,7 @@ export const plTranslations: DefaultTranslationsObject = { addFilter: 'Dodaj filtr', adminTheme: 'Motyw administratora', and: 'i', + anotherUser: 'Inny użytkownik', anotherUserTakenOver: 'Inny użytkownik przejął edycję tego dokumentu.', applyChanges: 'Zastosuj zmiany', ascending: 'Rosnąco', diff --git a/packages/translations/src/languages/pt.ts b/packages/translations/src/languages/pt.ts index 7849984db48..e6411d554dc 100644 --- a/packages/translations/src/languages/pt.ts +++ b/packages/translations/src/languages/pt.ts @@ -179,6 +179,7 @@ export const ptTranslations: DefaultTranslationsObject = { addFilter: 'Adicionar Filtro', adminTheme: 'Tema do Admin', and: 'E', + anotherUser: 'Outro usuário', anotherUserTakenOver: 'Outro usuário assumiu a edição deste documento.', applyChanges: 'Aplicar alterações', ascending: 'Ascendente', diff --git a/packages/translations/src/languages/ro.ts b/packages/translations/src/languages/ro.ts index c4d36241248..74686ea0606 100644 --- a/packages/translations/src/languages/ro.ts +++ b/packages/translations/src/languages/ro.ts @@ -182,6 +182,7 @@ export const roTranslations: DefaultTranslationsObject = { addFilter: 'Adaugă filtru', adminTheme: 'Tema Admin', and: 'Şi', + anotherUser: 'Un alt utilizator', anotherUserTakenOver: 'Un alt utilizator a preluat editarea acestui document.', applyChanges: 'Aplicați modificările', ascending: 'Ascendant', diff --git a/packages/translations/src/languages/rs.ts b/packages/translations/src/languages/rs.ts index dcde3aeae87..c14fcef54c0 100644 --- a/packages/translations/src/languages/rs.ts +++ b/packages/translations/src/languages/rs.ts @@ -178,6 +178,7 @@ export const rsTranslations: DefaultTranslationsObject = { addFilter: 'Додај филтер', adminTheme: 'Администраторска тема', and: 'И', + anotherUser: 'Други корисник', anotherUserTakenOver: 'Други корисник је преузео уређивање овог документа.', applyChanges: 'Примени промене', ascending: 'Узлазно', diff --git a/packages/translations/src/languages/rsLatin.ts b/packages/translations/src/languages/rsLatin.ts index 0eeb5673664..f628e26850e 100644 --- a/packages/translations/src/languages/rsLatin.ts +++ b/packages/translations/src/languages/rsLatin.ts @@ -178,6 +178,7 @@ export const rsLatinTranslations: DefaultTranslationsObject = { addFilter: 'Dodaj filter', adminTheme: 'Administratorska tema', and: 'I', + anotherUser: 'Drugi korisnik', anotherUserTakenOver: 'Drugi korisnik je preuzeo uređivanje ovog dokumenta.', applyChanges: 'Primeni promene', ascending: 'Uzlazno', diff --git a/packages/translations/src/languages/ru.ts b/packages/translations/src/languages/ru.ts index c505d0d1ae4..c1de9701893 100644 --- a/packages/translations/src/languages/ru.ts +++ b/packages/translations/src/languages/ru.ts @@ -180,6 +180,7 @@ export const ruTranslations: DefaultTranslationsObject = { addFilter: 'Добавить фильтр', adminTheme: 'Тема Панели', and: 'А также', + anotherUser: 'Другой пользователь', anotherUserTakenOver: 'Другой пользователь взял на себя редактирование этого документа.', applyChanges: 'Применить изменения', ascending: 'Восходящий', diff --git a/packages/translations/src/languages/sk.ts b/packages/translations/src/languages/sk.ts index 95d81cc8fdc..df9007acefa 100644 --- a/packages/translations/src/languages/sk.ts +++ b/packages/translations/src/languages/sk.ts @@ -180,6 +180,7 @@ export const skTranslations: DefaultTranslationsObject = { addFilter: 'Pridať filter', adminTheme: 'Motív administračného rozhrania', and: 'a', + anotherUser: 'Iný používateľ', anotherUserTakenOver: 'Iný používateľ prevzal úpravy tohto dokumentu.', applyChanges: 'Použiť zmeny', ascending: 'Vzostupne', diff --git a/packages/translations/src/languages/sl.ts b/packages/translations/src/languages/sl.ts index 2a2850e22b5..e79426fe5c5 100644 --- a/packages/translations/src/languages/sl.ts +++ b/packages/translations/src/languages/sl.ts @@ -178,6 +178,7 @@ export const slTranslations = { addFilter: 'Dodaj filter', adminTheme: 'Tema skrbnika', and: 'In', + anotherUser: 'Drug uporabnik', anotherUserTakenOver: 'Drug uporabnik je prevzel urejanje tega dokumenta.', applyChanges: 'Uporabi spremembe', ascending: 'Naraščajoče', diff --git a/packages/translations/src/languages/sv.ts b/packages/translations/src/languages/sv.ts index f57df90f5d2..99ac82316b6 100644 --- a/packages/translations/src/languages/sv.ts +++ b/packages/translations/src/languages/sv.ts @@ -178,6 +178,7 @@ export const svTranslations: DefaultTranslationsObject = { addFilter: 'Lägg Till Filter', adminTheme: 'Admin Tema', and: 'Och', + anotherUser: 'En annan användare', anotherUserTakenOver: 'En annan användare har tagit över redigeringen av detta dokument.', applyChanges: 'Verkställ ändringar', ascending: 'Stigande', diff --git a/packages/translations/src/languages/th.ts b/packages/translations/src/languages/th.ts index f85be73b863..90b6849df91 100644 --- a/packages/translations/src/languages/th.ts +++ b/packages/translations/src/languages/th.ts @@ -175,6 +175,7 @@ export const thTranslations: DefaultTranslationsObject = { addFilter: 'เพิ่มการกรอง', adminTheme: 'ธีมผู้ดูแลระบบ', and: 'และ', + anotherUser: 'ผู้ใช้อื่น', anotherUserTakenOver: 'ผู้ใช้อื่นเข้าครอบครองการแก้ไขเอกสารนี้แล้ว', applyChanges: 'ใช้การเปลี่ยนแปลง', ascending: 'น้อยไปมาก', diff --git a/packages/translations/src/languages/tr.ts b/packages/translations/src/languages/tr.ts index 7fac9e7e89d..a1586879ce4 100644 --- a/packages/translations/src/languages/tr.ts +++ b/packages/translations/src/languages/tr.ts @@ -181,6 +181,7 @@ export const trTranslations: DefaultTranslationsObject = { addFilter: 'Filtre ekle', adminTheme: 'Admin arayüzü', and: 've', + anotherUser: 'Başka bir kullanıcı', anotherUserTakenOver: 'Başka bir kullanıcı bu belgenin düzenlemesini devraldı.', applyChanges: 'Değişiklikleri Uygula', ascending: 'artan', diff --git a/packages/translations/src/languages/uk.ts b/packages/translations/src/languages/uk.ts index 3cf4410e018..e5f4a849f05 100644 --- a/packages/translations/src/languages/uk.ts +++ b/packages/translations/src/languages/uk.ts @@ -179,6 +179,7 @@ export const ukTranslations: DefaultTranslationsObject = { addFilter: 'Додати фільтр', adminTheme: 'Тема адмін панелі', and: 'і', + anotherUser: 'Інший користувач', anotherUserTakenOver: 'Інший користувач взяв на себе редагування цього документа.', applyChanges: 'Застосувати зміни', ascending: 'В порядку зростання', diff --git a/packages/translations/src/languages/vi.ts b/packages/translations/src/languages/vi.ts index 229c1b5f35f..1cf5793fa4d 100644 --- a/packages/translations/src/languages/vi.ts +++ b/packages/translations/src/languages/vi.ts @@ -177,6 +177,7 @@ export const viTranslations: DefaultTranslationsObject = { addFilter: 'Thêm bộ lọc', adminTheme: 'Giao diện bảng điều khiển', and: 'Và', + anotherUser: 'Người dùng khác', anotherUserTakenOver: 'Người dùng khác đã tiếp quản việc chỉnh sửa tài liệu này.', applyChanges: 'Áp dụng Thay đổi', ascending: 'Sắp xếp theo thứ tự tăng dần', diff --git a/packages/translations/src/languages/zh.ts b/packages/translations/src/languages/zh.ts index 32d76d7d9df..038478947c5 100644 --- a/packages/translations/src/languages/zh.ts +++ b/packages/translations/src/languages/zh.ts @@ -172,6 +172,7 @@ export const zhTranslations: DefaultTranslationsObject = { addFilter: '添加过滤器', adminTheme: '管理页面主题', and: '和', + anotherUser: '另一位用户', anotherUserTakenOver: '另一位用户接管了此文档的编辑。', applyChanges: '应用更改', ascending: '升序', diff --git a/packages/translations/src/languages/zhTw.ts b/packages/translations/src/languages/zhTw.ts index bdace266acd..1f2463c5558 100644 --- a/packages/translations/src/languages/zhTw.ts +++ b/packages/translations/src/languages/zhTw.ts @@ -172,6 +172,7 @@ export const zhTwTranslations: DefaultTranslationsObject = { addFilter: '新增過濾器', adminTheme: '管理頁面主題', and: '和', + anotherUser: '另一位使用者', anotherUserTakenOver: '另一位使用者接管了此文件的編輯。', applyChanges: '套用更改', ascending: '升冪', diff --git a/packages/ui/src/elements/DocumentControls/index.tsx b/packages/ui/src/elements/DocumentControls/index.tsx index 9a75be31631..c2d6b147ea4 100644 --- a/packages/ui/src/elements/DocumentControls/index.tsx +++ b/packages/ui/src/elements/DocumentControls/index.tsx @@ -55,7 +55,7 @@ export const DocumentControls: React.FC<{ readonly redirectAfterDelete?: boolean readonly redirectAfterDuplicate?: boolean readonly slug: SanitizedCollectionConfig['slug'] - readonly user?: ClientUser + readonly user?: ClientUser | number | string }> = (props) => { const { id, diff --git a/packages/ui/src/elements/Locked/index.tsx b/packages/ui/src/elements/Locked/index.tsx index ddb2f11caf4..a2fe5c4603a 100644 --- a/packages/ui/src/elements/Locked/index.tsx +++ b/packages/ui/src/elements/Locked/index.tsx @@ -3,20 +3,22 @@ import type { ClientUser } from 'payload' import React, { useState } from 'react' -import { useTableCell } from '../../elements/Table/TableCellProvider/index.js' import { LockIcon } from '../../icons/Lock/index.js' import { useTranslation } from '../../providers/Translation/index.js' +import { isClientUserObject } from '../../utilities/isClientUserObject.js' import { Tooltip } from '../Tooltip/index.js' import './index.scss' const baseClass = 'locked' -export const Locked: React.FC<{ className?: string; user: ClientUser }> = ({ className, user }) => { - const { rowData } = useTableCell() +export const Locked: React.FC<{ className?: string; user: ClientUser | number | string }> = ({ + className, + user, +}) => { const [hovered, setHovered] = useState(false) const { t } = useTranslation() - const userToUse = user ? (user?.email ?? user?.id) : rowData?.id + const userToUse = isClientUserObject(user) ? (user.email ?? user.id) : t('general:anotherUser') return (

(false) - const [currentEditor, setCurrentEditor] = useState(null) + const [currentEditor, setCurrentEditor] = useState(null) const [lastUpdateTime, setLastUpdateTime] = useState(null) const isInitializing = initialState === undefined || data === undefined @@ -175,7 +175,7 @@ const DocumentInfo: React.FC< ) const updateDocumentEditor = useCallback( - async (docId: number | string, slug: string, user: ClientUser) => { + async (docId: number | string, slug: string, user: ClientUser | number | string) => { try { const isGlobal = slug === globalSlug @@ -191,10 +191,15 @@ const DocumentInfo: React.FC< if (docs.length > 0) { const lockId = docs[0].id + const userData = + typeof user === 'object' + ? { relationTo: user.collection, value: user.id } + : { relationTo: 'users', value: user } + // Send a patch request to update the _lastEdited info await requests.patch(`${serverURL}${api}/payload-locked-documents/${lockId}`, { body: JSON.stringify({ - user: { relationTo: user?.collection, value: user?.id }, + user: userData, }), headers: { 'Content-Type': 'application/json', @@ -230,7 +235,12 @@ const DocumentInfo: React.FC< if (docs.length > 0) { const newEditor = docs[0].user?.value const lastUpdatedAt = new Date(docs[0].updatedAt).getTime() - if (newEditor && newEditor.id !== currentEditor?.id) { + + if ( + newEditor && typeof newEditor === 'object' && typeof currentEditor === 'object' + ? newEditor.id !== currentEditor?.id + : newEditor !== currentEditor + ) { setCurrentEditor(newEditor) setDocumentIsLocked(true) setLastUpdateTime(lastUpdatedAt) @@ -238,6 +248,7 @@ const DocumentInfo: React.FC< } else { setDocumentIsLocked(false) } + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { // swallow error } diff --git a/packages/ui/src/providers/DocumentInfo/types.ts b/packages/ui/src/providers/DocumentInfo/types.ts index 810d8f28de4..ed457e0fc9e 100644 --- a/packages/ui/src/providers/DocumentInfo/types.ts +++ b/packages/ui/src/providers/DocumentInfo/types.ts @@ -57,7 +57,7 @@ export type DocumentInfoProps = { } export type DocumentInfoContext = { - currentEditor?: ClientUser + currentEditor?: ClientUser | null | number | string docConfig?: ClientCollectionConfig | ClientGlobalConfig documentIsLocked?: boolean getDocPermissions: (data?: Data) => Promise diff --git a/packages/ui/src/utilities/buildFormState.ts b/packages/ui/src/utilities/buildFormState.ts index ef69f6d19e6..34575ba1518 100644 --- a/packages/ui/src/utilities/buildFormState.ts +++ b/packages/ui/src/utilities/buildFormState.ts @@ -39,7 +39,7 @@ export const buildFormState = async ({ }: { req: PayloadRequest }): Promise<{ - lockedState?: { isLocked: boolean; lastEditedAt: string; user: ClientUser | number | string } + lockedState?: { isLocked: boolean; user: ClientUser | number | string } state: FormState }> => { const reqData: BuildFormStateArgs = (req.data || {}) as BuildFormStateArgs @@ -275,7 +275,6 @@ export const buildFormState = async ({ if (lockedDocument.docs && lockedDocument.docs.length > 0) { const lockedState = { isLocked: true, - lastEditedAt: lockedDocument.docs[0]?.updatedAt, user: lockedDocument.docs[0]?.user?.value, } @@ -344,7 +343,6 @@ export const buildFormState = async ({ const lockedState = { isLocked: true, - lastEditedAt: new Date().toISOString(), user: req.user, } diff --git a/packages/ui/src/utilities/getFormState.ts b/packages/ui/src/utilities/getFormState.ts index 02ebccab8cf..1d293995f73 100644 --- a/packages/ui/src/utilities/getFormState.ts +++ b/packages/ui/src/utilities/getFormState.ts @@ -10,7 +10,7 @@ export const getFormState = async (args: { signal?: AbortSignal token?: string }): Promise<{ - lockedState?: { isLocked: boolean; lastEditedAt: string; user: ClientUser } + lockedState?: { isLocked: boolean; user: ClientUser } state: FormState }> => { const { apiRoute, body, onError, serverURL, signal, token } = args @@ -27,7 +27,7 @@ export const getFormState = async (args: { }) const json = (await res.json()) as { - lockedState?: { isLocked: boolean; lastEditedAt: string; user: ClientUser } + lockedState?: { isLocked: boolean; user: ClientUser } state: FormState } diff --git a/packages/ui/src/utilities/handleTakeOver.tsx b/packages/ui/src/utilities/handleTakeOver.tsx index 19eddcaa5ec..878d7450568 100644 --- a/packages/ui/src/utilities/handleTakeOver.tsx +++ b/packages/ui/src/utilities/handleTakeOver.tsx @@ -4,14 +4,18 @@ export const handleTakeOver = ( id: number | string, collectionSlug: string, globalSlug: string, - user: ClientUser, + user: ClientUser | number | string, isWithinDoc: boolean, - updateDocumentEditor: (docId: number | string, slug: string, user: ClientUser) => Promise, - setCurrentEditor: (value: React.SetStateAction) => void, + updateDocumentEditor: ( + docId: number | string, + slug: string, + user: ClientUser | number | string, + ) => Promise, + setCurrentEditor: (value: React.SetStateAction) => void, documentLockStateRef: React.RefObject<{ hasShownLockedModal: boolean isLocked: boolean - user: ClientUser + user: ClientUser | number | string }>, isLockingEnabled: boolean, setIsReadOnlyForIncomingUser?: (value: React.SetStateAction) => void, diff --git a/packages/ui/src/utilities/isClientUserObject.ts b/packages/ui/src/utilities/isClientUserObject.ts new file mode 100644 index 00000000000..856a2e59c18 --- /dev/null +++ b/packages/ui/src/utilities/isClientUserObject.ts @@ -0,0 +1,5 @@ +import type { ClientUser } from 'payload' + +export const isClientUserObject = (user): user is ClientUser => { + return user && typeof user === 'object' +} diff --git a/test/locked-documents/collections/Users/index.ts b/test/locked-documents/collections/Users/index.ts index c2d95459368..7fe7f5e6c9b 100644 --- a/test/locked-documents/collections/Users/index.ts +++ b/test/locked-documents/collections/Users/index.ts @@ -6,10 +6,26 @@ export const Users: CollectionConfig = { useAsTitle: 'name', }, auth: true, + access: { + read: ({ req: { user }, id }) => { + // Allow access if the user has the 'is_admin' role or if they are reading their own record + return Boolean(user?.roles?.includes('is_admin') || user?.id === id) + }, + }, fields: [ { name: 'name', type: 'text', }, + { + name: 'roles', + type: 'select', + hasMany: true, + // required: true, + options: [ + { label: 'User', value: 'is_user' }, + { label: 'Admin', value: 'is_admin' }, + ], + }, ], } diff --git a/test/locked-documents/config.ts b/test/locked-documents/config.ts index eeeea82d748..0fe077fb520 100644 --- a/test/locked-documents/config.ts +++ b/test/locked-documents/config.ts @@ -29,6 +29,7 @@ export default buildConfigWithDefaults({ email: devUser.email, password: devUser.password, name: 'Admin', + roles: ['is_admin', 'is_user'], }, }) @@ -38,6 +39,7 @@ export default buildConfigWithDefaults({ email: regularUser.email, password: regularUser.password, name: 'Dev', + roles: ['is_user'], }, }) diff --git a/test/locked-documents/e2e.spec.ts b/test/locked-documents/e2e.spec.ts index a7252e25b44..1b1059bb5fe 100644 --- a/test/locked-documents/e2e.spec.ts +++ b/test/locked-documents/e2e.spec.ts @@ -106,6 +106,7 @@ describe('locked documents', () => { data: { email: 'user2@payloadcms.com', password: '1234', + roles: ['is_user'], }, }) @@ -349,6 +350,7 @@ describe('locked documents', () => { data: { email: 'user2@payloadcms.com', password: '1234', + roles: ['is_user'], }, }) @@ -650,6 +652,7 @@ describe('locked documents', () => { data: { email: 'user2@payloadcms.com', password: '1234', + roles: ['is_user'], }, }) @@ -810,6 +813,7 @@ describe('locked documents', () => { data: { email: 'user2@payloadcms.com', password: '1234', + roles: ['is_user'], }, }) @@ -899,6 +903,7 @@ describe('locked documents', () => { data: { email: 'user2@payloadcms.com', password: '1234', + roles: ['is_user'], }, }) @@ -989,6 +994,7 @@ describe('locked documents', () => { data: { email: 'user2@payloadcms.com', password: '1234', + roles: ['is_user'], }, }) }) @@ -1174,6 +1180,7 @@ describe('locked documents', () => { data: { email: 'user2@payloadcms.com', password: '1234', + roles: ['is_user'], }, }) diff --git a/test/locked-documents/payload-types.ts b/test/locked-documents/payload-types.ts index e036b46f6ba..26a48b0c224 100644 --- a/test/locked-documents/payload-types.ts +++ b/test/locked-documents/payload-types.ts @@ -89,6 +89,7 @@ export interface Test { export interface User { id: string; name?: string | null; + roles?: ('is_user' | 'is_admin')[] | null; updatedAt: string; createdAt: string; email: string;