From 959f01739c30450f3a6d052dd6083fdacf1527a4 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Thu, 9 Mar 2023 15:09:09 +0100 Subject: [PATCH 01/25] fix: check relationships indexed access for undefined --- .../forms/field-types/Relationship/createRelationMap.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/admin/components/forms/field-types/Relationship/createRelationMap.ts b/src/admin/components/forms/field-types/Relationship/createRelationMap.ts index 91b925bd1c..e12ca5f1e8 100644 --- a/src/admin/components/forms/field-types/Relationship/createRelationMap.ts +++ b/src/admin/components/forms/field-types/Relationship/createRelationMap.ts @@ -31,7 +31,11 @@ export const createRelationMap: CreateRelationMap = ({ const add = (relation: string, id: unknown) => { if (((typeof id === 'string') || typeof id === 'number') && typeof relation === 'string') { - relationMap[relation].push(id); + if (relationMap[relation]) { + relationMap[relation].push(id); + } else { + relationMap[relation] = [id]; + } } }; From 4e1748fb8a3554586b377e60738130d03ec12f38 Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Fri, 10 Mar 2023 08:56:31 -0500 Subject: [PATCH 02/25] fix: renders presentational table columns --- .../elements/TableColumns/buildColumns.tsx | 2 +- test/admin/config.ts | 15 ++++++++++++++- test/admin/e2e.spec.ts | 6 ++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/admin/components/elements/TableColumns/buildColumns.tsx b/src/admin/components/elements/TableColumns/buildColumns.tsx index 612389733b..a86d58f013 100644 --- a/src/admin/components/elements/TableColumns/buildColumns.tsx +++ b/src/admin/components/elements/TableColumns/buildColumns.tsx @@ -44,7 +44,7 @@ const buildColumns = ({ return [...acc, field]; }, collection.fields); - const flattenedFields = flattenFields(combinedFields); + const flattenedFields = flattenFields(combinedFields, true); // sort the fields to the order of activeColumns const sortedFields = flattenedFields.sort((a, b) => { diff --git a/test/admin/config.ts b/test/admin/config.ts index c10cc9b199..9dcb5e7d1b 100644 --- a/test/admin/config.ts +++ b/test/admin/config.ts @@ -9,6 +9,8 @@ import BeforeLogin from './components/BeforeLogin'; import AfterNavLinks from './components/AfterNavLinks'; import { slug, globalSlug } from './shared'; import Logout from './components/Logout'; +import DemoUIFieldField from './components/DemoUIField/Field'; +import DemoUIFieldCell from './components/DemoUIField/Cell'; export interface Post { id: string; @@ -83,7 +85,7 @@ export default buildConfig({ listSearchableFields: ['title', 'description', 'number'], group: { en: 'One', es: 'Una' }, useAsTitle: 'title', - defaultColumns: ['id', 'number', 'title', 'description'], + defaultColumns: ['id', 'number', 'title', 'description', 'demoUIField'], }, fields: [ { @@ -111,6 +113,17 @@ export default buildConfig({ ], }, }, + { + type: 'ui', + name: 'demoUIField', + label: 'Demo UI Field', + admin: { + components: { + Field: DemoUIFieldField, + Cell: DemoUIFieldCell, + }, + }, + }, ], }, { diff --git a/test/admin/e2e.spec.ts b/test/admin/e2e.spec.ts index 2d478052b2..69ecdf4020 100644 --- a/test/admin/e2e.spec.ts +++ b/test/admin/e2e.spec.ts @@ -409,6 +409,12 @@ describe('admin', () => { // ensure that the "number" column is still deselected await expect(await page.locator('[id^=list-drawer_1_] .list-controls .column-selector .column-selector__column').first()).not.toHaveClass('column-selector__column--active'); }); + + test('should render custom table cell component', async () => { + await createPost(); + await page.goto(url.list); + await expect(await page.locator('table >> thead >> tr >> th >> text=Demo UI Field')).toBeVisible(); + }); }); describe('pagination', () => { From e9c796e42c1bb1e0ce72d057ee88dee624b94c24 Mon Sep 17 00:00:00 2001 From: Jesse Sivonen Date: Wed, 1 Mar 2023 20:48:46 +0200 Subject: [PATCH 03/25] feat: provide refresh permissions for auth context --- src/admin/components/utilities/Auth/index.tsx | 31 ++++++++++--------- src/admin/components/utilities/Auth/types.ts | 1 + 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/admin/components/utilities/Auth/index.tsx b/src/admin/components/utilities/Auth/index.tsx index 30af6c4280..85257fb12f 100644 --- a/src/admin/components/utilities/Auth/index.tsx +++ b/src/admin/components/utilities/Auth/index.tsx @@ -81,6 +81,21 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children requests.post(`${serverURL}${api}/${userSlug}/logout`); }, [serverURL, api, userSlug]); + const refreshPermissions = useCallback(async () => { + const request = await requests.get(`${serverURL}${api}/access`, { + headers: { + 'Accept-Language': i18n.language, + }, + }); + + if (request.status === 200) { + const json: Permissions = await request.json(); + setPermissions(json); + } else { + throw new Error("Fetching permissions failed with status code " + request.status); + } + }, [serverURL, api, i18n]); + // On mount, get user and set useEffect(() => { const fetchMe = async () => { @@ -117,21 +132,8 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children // When user changes, get new access useEffect(() => { - async function getPermissions() { - const request = await requests.get(`${serverURL}${api}/access`, { - headers: { - 'Accept-Language': i18n.language, - }, - }); - - if (request.status === 200) { - const json: Permissions = await request.json(); - setPermissions(json); - } - } - if (id) { - getPermissions(); + refreshPermissions(); } }, [i18n, id, api, serverURL]); @@ -174,6 +176,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children user, logOut, refreshCookie, + refreshPermissions, permissions, setToken, token: tokenInMemory, diff --git a/src/admin/components/utilities/Auth/types.ts b/src/admin/components/utilities/Auth/types.ts index e083a63916..a9562e653e 100644 --- a/src/admin/components/utilities/Auth/types.ts +++ b/src/admin/components/utilities/Auth/types.ts @@ -6,5 +6,6 @@ export type AuthContext = { refreshCookie: () => void setToken: (token: string) => void token?: string + refreshPermissions: () => Promise permissions?: Permissions } From c1f205c2cf32d0c76d12920d0bd18ad755846982 Mon Sep 17 00:00:00 2001 From: Jesse Sivonen Date: Sat, 11 Mar 2023 16:54:48 +0200 Subject: [PATCH 04/25] test: refresh-permissions --- .../GlobalViewWithRefresh.tsx | 22 ++++++++ test/refresh-permissions/config.ts | 53 +++++++++++++++++++ test/refresh-permissions/e2e.spec.ts | 35 ++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 test/refresh-permissions/GlobalViewWithRefresh.tsx create mode 100644 test/refresh-permissions/config.ts create mode 100644 test/refresh-permissions/e2e.spec.ts diff --git a/test/refresh-permissions/GlobalViewWithRefresh.tsx b/test/refresh-permissions/GlobalViewWithRefresh.tsx new file mode 100644 index 0000000000..9248d3123b --- /dev/null +++ b/test/refresh-permissions/GlobalViewWithRefresh.tsx @@ -0,0 +1,22 @@ +import React, { useCallback } from 'react'; +import { useAuth } from '../../src/admin/components/utilities/Auth'; +import { Props } from '../../src/admin/components/views/Global/types'; +import DefaultGlobalView from '../../src/admin/components/views/Global/Default'; + +const GlobalView: React.FC = (props) => { + const { onSave } = props; + const { refreshPermissions } = useAuth(); + const modifiedOnSave = useCallback((...args) => { + onSave.call(null, ...args); + refreshPermissions(); + }, [onSave, refreshPermissions]); + + return ( + + ); +}; + +export default GlobalView; diff --git a/test/refresh-permissions/config.ts b/test/refresh-permissions/config.ts new file mode 100644 index 0000000000..b7a2bb3ce8 --- /dev/null +++ b/test/refresh-permissions/config.ts @@ -0,0 +1,53 @@ +import { buildConfig } from '../buildConfig'; +import { devUser } from '../credentials'; +import GlobalViewWithRefresh from './GlobalViewWithRefresh'; + +export const pagesSlug = 'pages'; + +export default buildConfig({ + globals: [ + { + slug: 'settings', + fields: [ + { + type: 'checkbox', + name: 'test', + label: 'Allow access to test global', + }, + ], + admin: { + components: { + views: { + Edit: GlobalViewWithRefresh, + }, + }, + }, + }, + { + slug: 'test', + fields: [], + access: { + read: async ({ req: { payload } }) => { + const access = await payload.findGlobal({ slug: 'settings' }); + return access.test; + }, + }, + }, + ], + collections: [ + { + slug: 'users', + auth: true, + fields: [], + }, + ], + onInit: async (payload) => { + await payload.create({ + collection: 'users', + data: { + email: devUser.email, + password: devUser.password, + }, + }); + }, +}); diff --git a/test/refresh-permissions/e2e.spec.ts b/test/refresh-permissions/e2e.spec.ts new file mode 100644 index 0000000000..c61cd1a054 --- /dev/null +++ b/test/refresh-permissions/e2e.spec.ts @@ -0,0 +1,35 @@ +import { expect, Page, test } from '@playwright/test'; +import { login } from '../helpers'; +import { initPayloadE2E } from '../helpers/configHelpers'; + +const { beforeAll, describe } = test; + +describe('refresh-permissions', () => { + let serverURL: string; + let page: Page; + + beforeAll(async ({ browser }) => { + ({ serverURL } = await initPayloadE2E(__dirname)); + const context = await browser.newContext(); + page = await context.newPage(); + await login({ page, serverURL }); + }); + + test('should show test global immediately after allowing access', async () => { + await page.goto(`${serverURL}/admin/globals/settings`); + + // Ensure that we have loaded accesses by checking that settings collection + // at least is visible in the menu. + await expect(page.locator('#nav-global-settings')).toBeVisible(); + + // Test collection should be hidden at first. + await expect(page.locator('#nav-global-test')).toBeHidden(); + + // Allow access to test global. + await page.locator('.custom-checkbox:has(#field-test) button').click(); + await page.locator('#action-save').click(); + + // Now test collection should appear in the menu. + await expect(page.locator('#nav-global-test')).toBeVisible(); + }); +}); From 8d1df966375e91e0bdd964030ef315c76e503d59 Mon Sep 17 00:00:00 2001 From: Jesse Sivonen Date: Sat, 11 Mar 2023 17:18:52 +0200 Subject: [PATCH 05/25] docs: add refreshPermissions --- docs/admin/hooks.mdx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/admin/hooks.mdx b/docs/admin/hooks.mdx index 019a081a49..c955a0cd2a 100644 --- a/docs/admin/hooks.mdx +++ b/docs/admin/hooks.mdx @@ -226,14 +226,15 @@ const Greeting: React.FC = () => { Useful to retrieve info about the currently logged in user as well as methods for interacting with it. It sends back an object with the following properties: -| Property | Description | -|---------------------|-----------------------------------------------------------------------------------------| -| **`user`** | The currently logged in user | -| **`logOut`** | A method to log out the currently logged in user | -| **`refreshCookie`** | A method to trigger the silent refreshing of a user's auth token | -| **`setToken`** | Set the token of the user, to be decoded and used to reset the user and token in memory | -| **`token`** | The logged in user's token (useful for creating preview links, etc.) | -| **`permissions`** | The permissions of the current user | +| Property | Description | +|--------------------------|-----------------------------------------------------------------------------------------| +| **`user`** | The currently logged in user | +| **`logOut`** | A method to log out the currently logged in user | +| **`refreshCookie`** | A method to trigger the silent refreshing of a user's auth token | +| **`setToken`** | Set the token of the user, to be decoded and used to reset the user and token in memory | +| **`token`** | The logged in user's token (useful for creating preview links, etc.) | +| **`refreshPermissions`** | Load new permissions (useful when content that effects permissions has been changed) | +| **`permissions`** | The permissions of the current user | ```tsx import { useAuth } from 'payload/components/utilities'; From fd8ea88488c80627346733e0595a2ef34c964a87 Mon Sep 17 00:00:00 2001 From: Elliot67 Date: Sun, 12 Mar 2023 00:09:07 +0100 Subject: [PATCH 06/25] fix: Prevent browser initial favicon request --- src/admin/components/utilities/Meta/index.tsx | 8 ++++++++ src/admin/index.html | 1 + 2 files changed, 9 insertions(+) diff --git a/src/admin/components/utilities/Meta/index.tsx b/src/admin/components/utilities/Meta/index.tsx index 67f422a629..57561fbf54 100644 --- a/src/admin/components/utilities/Meta/index.tsx +++ b/src/admin/components/utilities/Meta/index.tsx @@ -4,6 +4,7 @@ import { useConfig } from '../Config'; import { Props } from './types'; import payloadFavicon from '../../../assets/images/favicon.svg'; import payloadOgImage from '../../../assets/images/og-image.png'; +import useMountEffect from '../../../hooks/useMountEffect'; const Meta: React.FC = ({ description, @@ -17,6 +18,13 @@ const Meta: React.FC = ({ const favicon = config.admin.meta.favicon ?? payloadFavicon; const ogImage = config.admin.meta.ogImage ?? payloadOgImage; + useMountEffect(() => { + const faviconElement = document.querySelector('link[data-placeholder-favicon]'); + if (faviconElement) { + faviconElement.remove(); + } + }); + return ( + From 32b38439e3229b1751f5628bb2285db7ef99cc97 Mon Sep 17 00:00:00 2001 From: Igor Loskutov Date: Sun, 12 Mar 2023 16:08:50 +0700 Subject: [PATCH 07/25] chore: rename index.tsx of Pages collection of Preview example into index.ts --- .../preview/cms/src/collections/Pages/{index.tsx => index.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/preview/cms/src/collections/Pages/{index.tsx => index.ts} (100%) diff --git a/examples/preview/cms/src/collections/Pages/index.tsx b/examples/preview/cms/src/collections/Pages/index.ts similarity index 100% rename from examples/preview/cms/src/collections/Pages/index.tsx rename to examples/preview/cms/src/collections/Pages/index.ts From aae6d716e5608270ca142f2f4df214f9e271deb4 Mon Sep 17 00:00:00 2001 From: Jessica Boezwinkle Date: Mon, 13 Mar 2023 11:29:56 +0000 Subject: [PATCH 08/25] fix: allow thumbnails in upload gallery to show useAsTitle value --- .../components/elements/ThumbnailCard/index.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/admin/components/elements/ThumbnailCard/index.tsx b/src/admin/components/elements/ThumbnailCard/index.tsx index 92e28769a9..a15ceb3465 100644 --- a/src/admin/components/elements/ThumbnailCard/index.tsx +++ b/src/admin/components/elements/ThumbnailCard/index.tsx @@ -1,4 +1,4 @@ -import React, { Fragment } from 'react'; +import React from 'react'; import { useTranslation } from 'react-i18next'; import { Props } from './types'; import Thumbnail from '../Thumbnail'; @@ -13,6 +13,11 @@ export const ThumbnailCard: React.FC = (props) => { onClick, doc, collection, + collection: { + admin: { + useAsTitle, + }, + }, thumbnail, label, alignLabel, @@ -28,6 +33,8 @@ export const ThumbnailCard: React.FC = (props) => { alignLabel && `${baseClass}--align-label-${alignLabel}`, ].filter(Boolean).join(' '); + const title: any = doc?.[useAsTitle] || doc?.filename || `[${t('untitled')}]`; + return (
= (props) => { )}
- {label && label} - {!label && doc && ( - - {typeof doc?.filename === 'string' ? doc?.filename : `[${t('untitled')}]`} - - )} + {label || title}
); From 51dc66b5d9dbff73af88e9005a96c9a627e208b6 Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Mon, 13 Mar 2023 09:27:08 -0400 Subject: [PATCH 09/25] poc: tooltip position #2108 --- .../components/elements/Tooltip/index.scss | 21 ++++++++++++++++--- .../components/elements/Tooltip/index.tsx | 16 ++++++++++++++ .../components/elements/Tooltip/types.ts | 1 + 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/admin/components/elements/Tooltip/index.scss b/src/admin/components/elements/Tooltip/index.scss index 090169dad7..d8026518f0 100644 --- a/src/admin/components/elements/Tooltip/index.scss +++ b/src/admin/components/elements/Tooltip/index.scss @@ -7,7 +7,6 @@ $caretSize: 6; background-color: var(--theme-elevation-800); position: absolute; z-index: 2; - bottom: 100%; left: 50%; transform: translate3d(-50%, calc(#{$caretSize}px * -1), 0); padding: base(.2) base(.4); @@ -22,14 +21,12 @@ $caretSize: 6; content: ' '; display: block; position: absolute; - bottom: 0; left: 50%; transform: translate3d(-50%, 100%, 0); width: 0; height: 0; border-left: #{$caretSize}px solid transparent; border-right: #{$caretSize}px solid transparent; - border-top: #{$caretSize}px solid var(--theme-elevation-800); } &--show { @@ -39,6 +36,24 @@ $caretSize: 6; cursor: default; } + &--position-top { + bottom: 100%; + &::after { + bottom: 0; + border-top: #{$caretSize}px solid var(--theme-elevation-800); + } + } + + &--position-bottom { + top: 100%; + bottom: unset; + + &::after { + bottom: 100%; + border-bottom: #{$caretSize}px solid var(--theme-elevation-800); + } + } + @include mid-break { display: none; } diff --git a/src/admin/components/elements/Tooltip/index.tsx b/src/admin/components/elements/Tooltip/index.tsx index 3df38483fd..80f1e2e073 100644 --- a/src/admin/components/elements/Tooltip/index.tsx +++ b/src/admin/components/elements/Tooltip/index.tsx @@ -1,5 +1,6 @@ import React, { useEffect } from 'react'; import { Props } from './types'; +import useIntersect from '../../../hooks/useIntersect'; import './index.scss'; @@ -9,9 +10,18 @@ const Tooltip: React.FC = (props) => { children, show: showFromProps = true, delay = 350, + boundingRef, } = props; const [show, setShow] = React.useState(showFromProps); + const [position, setPosition] = React.useState<'top' | 'bottom'>('top'); + + const [ref, intersectionEntry] = useIntersect({ + threshold: 0, + rootMargin: '-150px 0px 0px 100px', + root: boundingRef?.current || null, + }); + useEffect(() => { let timerId: NodeJS.Timeout; @@ -30,12 +40,18 @@ const Tooltip: React.FC = (props) => { }; }, [showFromProps, delay]); + useEffect(() => { + setPosition(intersectionEntry?.isIntersecting ? 'top' : 'bottom'); + }, [intersectionEntry]); + return (