feat: 3.0 bulk edit parity (#5208)
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
html {
|
||||
color: blue;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
{/* Layout UI */}
|
||||
<main>{children}</main>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import './test.scss'
|
||||
|
||||
export default () => <h1>hello</h1>
|
||||
@@ -1,5 +0,0 @@
|
||||
@import './another.scss';
|
||||
|
||||
html {
|
||||
background: red;
|
||||
}
|
||||
@@ -78,8 +78,9 @@ function initCollectionsGraphQL({ config, graphqlResult }: InitCollectionsGraphQ
|
||||
collection.graphQL = {} as Collection['graphQL']
|
||||
|
||||
const hasIDField =
|
||||
flattenTopLevelFields(fields).findIndex((field) => fieldAffectsData(field) && field.name === 'id') >
|
||||
-1
|
||||
flattenTopLevelFields(fields).findIndex(
|
||||
(field) => fieldAffectsData(field) && field.name === 'id',
|
||||
) > -1
|
||||
|
||||
const idType = getCollectionIDType(config.db.defaultIDType, collectionConfig)
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "6.0.8",
|
||||
"@faceless-ui/modal": "2.0.1",
|
||||
"@faceless-ui/window-info": "2.1.1",
|
||||
"@payloadcms/graphql": "workspace:*",
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
|
||||
@@ -96,7 +96,7 @@ export const Document = async ({
|
||||
(isEditing && permissions?.collections?.[collectionSlug]?.update?.permission) ||
|
||||
(!isEditing && permissions?.collections?.[collectionSlug]?.create?.permission)
|
||||
|
||||
apiURL = `${serverURL}${api}/${collectionSlug}/${id}?locale=${locale}${
|
||||
apiURL = `${serverURL}${api}/${collectionSlug}/${id}?locale=${locale.code}${
|
||||
collectionConfig.versions?.drafts ? '&draft=true' : ''
|
||||
}`
|
||||
|
||||
@@ -136,7 +136,7 @@ export const Document = async ({
|
||||
hasSavePermission = isEditing && docPermissions?.update?.permission
|
||||
action = `${serverURL}${api}/${globalSlug}`
|
||||
|
||||
apiURL = `${serverURL}${api}/${globalSlug}?locale=${locale}${
|
||||
apiURL = `${serverURL}${api}/${globalSlug}?locale=${locale.code}${
|
||||
globalConfig.versions?.drafts ? '&draft=true' : ''
|
||||
}`
|
||||
|
||||
@@ -236,7 +236,7 @@ export const Document = async ({
|
||||
initialData={data}
|
||||
initialState={initialState}
|
||||
/>
|
||||
<EditDepthProvider depth={1} key={`${collectionSlug || globalSlug}-${locale}`}>
|
||||
<EditDepthProvider depth={1} key={`${collectionSlug || globalSlug}-${locale.code}`}>
|
||||
<FormQueryParamsProvider formQueryParams={formQueryParams}>
|
||||
<RenderCustomComponent
|
||||
CustomComponent={typeof CustomView === 'function' ? CustomView : undefined}
|
||||
|
||||
@@ -41,7 +41,6 @@ export const DefaultEditView: React.FC = () => {
|
||||
disableActions,
|
||||
disableLeaveWithoutSaving,
|
||||
docPermissions,
|
||||
docPreferences,
|
||||
globalSlug,
|
||||
hasSavePermission,
|
||||
initialData: data,
|
||||
@@ -132,14 +131,15 @@ export const DefaultEditView: React.FC = () => {
|
||||
apiRoute,
|
||||
body: {
|
||||
id,
|
||||
docPreferences,
|
||||
collectionSlug,
|
||||
formState: prevFormState,
|
||||
globalSlug,
|
||||
operation,
|
||||
schemaPath,
|
||||
},
|
||||
serverURL,
|
||||
}),
|
||||
[serverURL, apiRoute, id, operation, docPreferences, schemaPath],
|
||||
[serverURL, apiRoute, id, operation, schemaPath, collectionSlug, globalSlug],
|
||||
)
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
'use client'
|
||||
import { useWindowInfo } from '@faceless-ui/window-info'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import {
|
||||
Button,
|
||||
Gutter,
|
||||
ListControls,
|
||||
ListSelection,
|
||||
Pagination,
|
||||
PerPage,
|
||||
Pill,
|
||||
@@ -18,6 +20,10 @@ import {
|
||||
} from '@payloadcms/ui'
|
||||
import React, { Fragment, useEffect } from 'react'
|
||||
|
||||
import DeleteMany from '../../../../../ui/src/elements/DeleteMany'
|
||||
import { EditMany } from '../../../../../ui/src/elements/EditMany'
|
||||
import { PublishMany } from '../../../../../ui/src/elements/PublishMany'
|
||||
import { UnpublishMany } from '../../../../../ui/src/elements/UnpublishMany'
|
||||
import { RelationshipProvider } from './RelationshipProvider'
|
||||
import './index.scss'
|
||||
|
||||
@@ -26,6 +32,7 @@ const baseClass = 'collection-list'
|
||||
export const DefaultListView: React.FC = () => {
|
||||
const {
|
||||
Header,
|
||||
collectionSlug,
|
||||
data,
|
||||
handlePageChange,
|
||||
handlePerPageChange,
|
||||
@@ -36,8 +43,6 @@ export const DefaultListView: React.FC = () => {
|
||||
limit,
|
||||
modifySearchParams,
|
||||
newDocumentURL,
|
||||
// resetParams,
|
||||
collectionSlug,
|
||||
titleField,
|
||||
} = useListInfo()
|
||||
|
||||
@@ -58,6 +63,9 @@ export const DefaultListView: React.FC = () => {
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const { setStepNav } = useStepNav()
|
||||
const {
|
||||
breakpoints: { s: smallBreak },
|
||||
} = useWindowInfo()
|
||||
|
||||
let docs = data.docs || []
|
||||
|
||||
@@ -98,9 +106,9 @@ export const DefaultListView: React.FC = () => {
|
||||
{i18n.t('general:createNew')}
|
||||
</Pill>
|
||||
)}
|
||||
{/* {!smallBreak && (
|
||||
<ListSelection label={getTranslation(collection.labels.plural, i18n)} />
|
||||
)} */}
|
||||
{!smallBreak && (
|
||||
<ListSelection label={getTranslation(collectionConfig.labels.plural, i18n)} />
|
||||
)}
|
||||
{/* {description && (
|
||||
<div className={`${baseClass}__sub-header`}>
|
||||
<ViewDescription description={description} />
|
||||
@@ -110,14 +118,12 @@ export const DefaultListView: React.FC = () => {
|
||||
)}
|
||||
</header>
|
||||
<ListControls
|
||||
collectionPluralLabel={labels?.plural}
|
||||
collectionSlug={collectionSlug}
|
||||
collectionConfig={collectionConfig}
|
||||
// textFieldsToBeSearched={textFieldsToBeSearched}
|
||||
// handleSearchChange={handleSearchChange}
|
||||
// handleSortChange={handleSortChange}
|
||||
// handleWhereChange={handleWhereChange}
|
||||
// modifySearchQuery={modifySearchParams}
|
||||
// resetParams={resetParams}
|
||||
titleField={titleField}
|
||||
/>
|
||||
{BeforeListTable}
|
||||
@@ -181,19 +187,21 @@ export const DefaultListView: React.FC = () => {
|
||||
modifySearchParams={modifySearchParams}
|
||||
resetPage={data.totalDocs <= data.pagingCounter}
|
||||
/>
|
||||
{/* {smallBreak && (
|
||||
{smallBreak && (
|
||||
<div className={`${baseClass}__list-selection`}>
|
||||
<Fragment>
|
||||
<ListSelection label={getTranslation(collection.labels.plural, i18n)} />
|
||||
<ListSelection
|
||||
label={getTranslation(collectionConfig.labels.plural, i18n)}
|
||||
/>
|
||||
<div className={`${baseClass}__list-selection-actions`}>
|
||||
<EditMany resetParams={resetParams} />
|
||||
<PublishMany resetParams={resetParams} />
|
||||
<UnpublishMany resetParams={resetParams} />
|
||||
<DeleteMany resetParams={resetParams} />
|
||||
<EditMany collection={collectionConfig} />
|
||||
<PublishMany collection={collectionConfig} />
|
||||
<UnpublishMany collection={collectionConfig} />
|
||||
<DeleteMany collection={collectionConfig} />
|
||||
</div>
|
||||
</Fragment>
|
||||
</div>
|
||||
)} */}
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -89,7 +89,7 @@ export const ListView = async ({
|
||||
},
|
||||
},
|
||||
})
|
||||
?.then((res) => res?.docs?.[0]?.value)) as unknown as ListPreferences
|
||||
?.then((res) => res?.docs?.[0]?.value)) as ListPreferences
|
||||
} catch (error) {}
|
||||
|
||||
const {
|
||||
@@ -136,7 +136,11 @@ export const ListView = async ({
|
||||
limit={limit}
|
||||
newDocumentURL={`${admin}/collections/${collectionSlug}/create`}
|
||||
>
|
||||
<TableColumnsProvider collectionSlug={collectionSlug} listPreferences={listPreferences}>
|
||||
<TableColumnsProvider
|
||||
collectionSlug={collectionSlug}
|
||||
enableRowSelections
|
||||
listPreferences={listPreferences}
|
||||
>
|
||||
<RenderCustomComponent
|
||||
CustomComponent={CustomListView}
|
||||
DefaultComponent={DefaultListView}
|
||||
|
||||
@@ -51,7 +51,7 @@ export const LoginForm: React.FC<{
|
||||
disableSuccessStatus
|
||||
initialState={initialState}
|
||||
method="POST"
|
||||
redirect={`${admin}${searchParams?.redirect || ''}`}
|
||||
redirect={typeof searchParams?.redirect === 'string' ? searchParams.redirect : ''}
|
||||
waitForAutocomplete
|
||||
>
|
||||
<FormLoadingOverlayToggle action="loading" name="login-form" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { BuildFormStateArgs, FieldSchemaMap } from '@payloadcms/ui'
|
||||
import type { Field, PayloadRequest, SanitizedConfig } from 'payload/types'
|
||||
import type { DocumentPreferences, Field, PayloadRequest, SanitizedConfig } from 'payload/types'
|
||||
|
||||
import { buildFieldSchemaMap, buildStateFromSchema, reduceFieldsToValues } from '@payloadcms/ui'
|
||||
import httpStatus from 'http-status'
|
||||
@@ -22,20 +22,21 @@ export const getFieldSchemaMap = (config: SanitizedConfig): FieldSchemaMap => {
|
||||
}
|
||||
|
||||
export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
|
||||
const { data: reqData, locale, t, user } = req
|
||||
const { locale, t, user } = req
|
||||
const reqData: BuildFormStateArgs = req.data as BuildFormStateArgs
|
||||
|
||||
// TODO: run ADMIN access control for user
|
||||
|
||||
const fieldSchemaMap = getFieldSchemaMap(req.payload.config)
|
||||
|
||||
const {
|
||||
id,
|
||||
collectionSlug,
|
||||
data: incomingData,
|
||||
docPreferences,
|
||||
formState,
|
||||
globalSlug,
|
||||
operation,
|
||||
schemaPath,
|
||||
} = reqData as BuildFormStateArgs
|
||||
} = reqData
|
||||
|
||||
const schemaPathSegments = schemaPath.split('.')
|
||||
|
||||
@@ -64,6 +65,26 @@ export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
|
||||
|
||||
const data = incomingData || reduceFieldsToValues(formState, true)
|
||||
|
||||
let id: number | string | undefined
|
||||
let docPreferencesKey: string
|
||||
if (collectionSlug) {
|
||||
id = reqData.id
|
||||
docPreferencesKey = `collection-${collectionSlug}${id ? `-${id}` : ''}`
|
||||
} else {
|
||||
docPreferencesKey = `global-${globalSlug}`
|
||||
}
|
||||
|
||||
const { docs: [{ value: docPreferences } = { value: null }] = [] } = (await req.payload.find({
|
||||
collection: 'payload-preferences',
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
where: {
|
||||
key: {
|
||||
equals: docPreferencesKey,
|
||||
},
|
||||
},
|
||||
})) as any as { docs: { value: DocumentPreferences }[] }
|
||||
|
||||
const result = await buildStateFromSchema({
|
||||
id,
|
||||
data,
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import { CollectionRouteHandler } from '../types'
|
||||
|
||||
import { BuildFormStateArgs, buildStateFromSchema, reduceFieldsToValues } from '@payloadcms/ui'
|
||||
|
||||
export const buildFormStateCollection: CollectionRouteHandler = async ({ req, collection }) => {
|
||||
const { data: reqData, user, t, locale } = req
|
||||
|
||||
const { id, operation, docPreferences, formState } = reqData as BuildFormStateArgs
|
||||
|
||||
const data = reduceFieldsToValues(formState, true)
|
||||
|
||||
const result = await buildStateFromSchema({
|
||||
id,
|
||||
data,
|
||||
fieldSchema: collection.config.fields,
|
||||
locale,
|
||||
operation,
|
||||
preferences: docPreferences,
|
||||
t,
|
||||
user,
|
||||
})
|
||||
|
||||
return Response.json(result, {
|
||||
status: httpStatus.OK,
|
||||
})
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import { GlobalRouteHandler } from '../types'
|
||||
|
||||
import { BuildFormStateArgs, buildStateFromSchema, reduceFieldsToValues } from '@payloadcms/ui'
|
||||
|
||||
export const buildFormStateGlobal: GlobalRouteHandler = async ({ req, globalConfig }) => {
|
||||
const { data: reqData, user, t, locale } = req
|
||||
|
||||
const { docPreferences, formState } = reqData as BuildFormStateArgs
|
||||
|
||||
const data = reduceFieldsToValues(formState, true)
|
||||
|
||||
const result = await buildStateFromSchema({
|
||||
data,
|
||||
fieldSchema: globalConfig.fields,
|
||||
locale,
|
||||
preferences: docPreferences,
|
||||
t,
|
||||
user,
|
||||
})
|
||||
|
||||
return Response.json(result, {
|
||||
status: httpStatus.OK,
|
||||
})
|
||||
}
|
||||
@@ -326,7 +326,6 @@ export const POST =
|
||||
break
|
||||
case 2:
|
||||
if (slug2 in endpoints.collection.POST) {
|
||||
// /:collection/form-state
|
||||
// /:collection/login
|
||||
// /:collection/logout
|
||||
// /:collection/unlock
|
||||
|
||||
@@ -50,17 +50,17 @@ export const forgotPasswordOperation = async (incomingArgs: Arguments): Promise<
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
|
||||
const {
|
||||
collection: { config: collectionConfig },
|
||||
data,
|
||||
disableEmail,
|
||||
expiration,
|
||||
req: {
|
||||
payload: { config, emailOptions, sendEmail: email },
|
||||
payload,
|
||||
},
|
||||
req,
|
||||
} = args
|
||||
const {
|
||||
collection: { config: collectionConfig },
|
||||
data,
|
||||
disableEmail,
|
||||
expiration,
|
||||
req: {
|
||||
payload: { config, emailOptions, sendEmail: email },
|
||||
payload,
|
||||
},
|
||||
req,
|
||||
} = args
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Forget password
|
||||
|
||||
@@ -66,14 +66,15 @@ export const deleteOperation = async <TSlug extends keyof GeneratedTypes['collec
|
||||
depth,
|
||||
overrideAccess,
|
||||
req: {
|
||||
fallbackLocale,locale,
|
||||
payload: { config },
|
||||
payload,
|
||||
},
|
||||
req,
|
||||
showHiddenFields,
|
||||
where,
|
||||
} = args
|
||||
fallbackLocale,
|
||||
locale,
|
||||
payload: { config },
|
||||
payload,
|
||||
},
|
||||
req,
|
||||
showHiddenFields,
|
||||
where,
|
||||
} = args
|
||||
|
||||
if (!where) {
|
||||
throw new APIError("Missing 'where' query of documents to delete.", httpStatus.BAD_REQUEST)
|
||||
|
||||
@@ -63,7 +63,6 @@ export const deleteByIDOperation = async <TSlug extends keyof GeneratedTypes['co
|
||||
locale,
|
||||
payload: { config },
|
||||
payload,
|
||||
|
||||
},
|
||||
req,
|
||||
showHiddenFields,
|
||||
|
||||
@@ -80,7 +80,6 @@ export const updateByIDOperation = async <TSlug extends keyof GeneratedTypes['co
|
||||
locale,
|
||||
payload: { config },
|
||||
payload,
|
||||
|
||||
},
|
||||
req,
|
||||
showHiddenFields,
|
||||
|
||||
@@ -72,7 +72,7 @@ export const LinkButton: React.FC = () => {
|
||||
|
||||
const { closeModal, openModal } = useModal()
|
||||
const drawerSlug = useDrawerSlug('rich-text-link')
|
||||
const { id, getDocPreferences } = useDocumentInfo()
|
||||
const { id, collectionSlug } = useDocumentInfo()
|
||||
const { schemaPath } = useFieldPath()
|
||||
|
||||
const { richTextComponentMap } = fieldProps
|
||||
@@ -94,13 +94,12 @@ export const LinkButton: React.FC = () => {
|
||||
const data = {
|
||||
text: editor.selection ? Editor.string(editor, editor.selection) : '',
|
||||
}
|
||||
const docPreferences = await getDocPreferences()
|
||||
const state = await getFormState({
|
||||
apiRoute: config.routes.api,
|
||||
body: {
|
||||
id,
|
||||
collectionSlug,
|
||||
data,
|
||||
docPreferences,
|
||||
operation: 'update',
|
||||
schemaPath: `${schemaPath}.${linkFieldsSchemaPath}`,
|
||||
},
|
||||
|
||||
@@ -75,7 +75,7 @@ export const LinkElement = () => {
|
||||
const [renderModal, setRenderModal] = useState(false)
|
||||
const [renderPopup, setRenderPopup] = useState(false)
|
||||
const [initialState, setInitialState] = useState<FormState>({})
|
||||
const { id, getDocPreferences } = useDocumentInfo()
|
||||
const { id, collectionSlug } = useDocumentInfo()
|
||||
|
||||
const drawerSlug = useDrawerSlug('rich-text-link')
|
||||
|
||||
@@ -96,14 +96,12 @@ export const LinkElement = () => {
|
||||
url: element.url,
|
||||
}
|
||||
|
||||
const docPreferences = await getDocPreferences()
|
||||
|
||||
const state = await getFormState({
|
||||
apiRoute: config.routes.api,
|
||||
body: {
|
||||
id,
|
||||
collectionSlug,
|
||||
data,
|
||||
docPreferences,
|
||||
operation: 'update',
|
||||
schemaPath: fieldMapPath,
|
||||
},
|
||||
@@ -114,7 +112,7 @@ export const LinkElement = () => {
|
||||
}
|
||||
|
||||
void awaitInitialState()
|
||||
}, [renderModal, element, user, locale, t, getDocPreferences, config, id, fieldMapPath])
|
||||
}, [renderModal, element, user, locale, t, collectionSlug, config, id, fieldMapPath])
|
||||
|
||||
return (
|
||||
<span className={baseClass} {...attributes}>
|
||||
|
||||
@@ -45,7 +45,7 @@ export const UploadDrawer: React.FC<{
|
||||
const { code: locale } = useLocale()
|
||||
const { user } = useAuth()
|
||||
const { closeModal } = useModal()
|
||||
const { id, getDocPreferences } = useDocumentInfo()
|
||||
const { id, collectionSlug } = useDocumentInfo()
|
||||
const [initialState, setInitialState] = useState({})
|
||||
const { richTextComponentMap } = fieldProps
|
||||
|
||||
@@ -72,14 +72,12 @@ export const UploadDrawer: React.FC<{
|
||||
const data = deepCopyObject(element?.fields || {})
|
||||
|
||||
const awaitInitialState = async () => {
|
||||
const docPreferences = await getDocPreferences()
|
||||
|
||||
const state = await getFormState({
|
||||
apiRoute: config.routes.api,
|
||||
body: {
|
||||
id,
|
||||
collectionSlug,
|
||||
data,
|
||||
docPreferences,
|
||||
operation: 'update',
|
||||
schemaPath: `${schemaPath}.${uploadFieldsSchemaPath}.${relatedCollection.slug}`,
|
||||
},
|
||||
@@ -89,14 +87,14 @@ export const UploadDrawer: React.FC<{
|
||||
setInitialState(state)
|
||||
}
|
||||
|
||||
awaitInitialState()
|
||||
void awaitInitialState()
|
||||
}, [
|
||||
config,
|
||||
element?.fields,
|
||||
user,
|
||||
locale,
|
||||
t,
|
||||
getDocPreferences,
|
||||
collectionSlug,
|
||||
id,
|
||||
schemaPath,
|
||||
relatedCollection.slug,
|
||||
|
||||
@@ -8,18 +8,19 @@ import type { Props } from './types'
|
||||
|
||||
import { useAuth } from '../../providers/Auth'
|
||||
import { useConfig } from '../../providers/Config'
|
||||
import { useSearchParams } from '../../providers/SearchParams'
|
||||
import { SelectAllStatus, useSelection } from '../../providers/SelectionProvider'
|
||||
import { useTranslation } from '../../providers/Translation'
|
||||
// import { requests } from '../../../api'
|
||||
import { MinimalTemplate } from '../../templates/Minimal'
|
||||
import { requests } from '../../utilities/api'
|
||||
import { Button } from '../Button'
|
||||
import Pill from '../Pill'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'delete-documents'
|
||||
|
||||
const DeleteMany: React.FC<Props> = (props) => {
|
||||
const { collection: { labels: { plural }, slug } = {}, resetParams } = props
|
||||
export const DeleteMany: React.FC<Props> = (props) => {
|
||||
const { collection: { slug, labels: { plural } } = {} } = props
|
||||
|
||||
const { permissions } = useAuth()
|
||||
const {
|
||||
@@ -30,6 +31,7 @@ const DeleteMany: React.FC<Props> = (props) => {
|
||||
const { count, getQueryParams, selectAll, toggleAll } = useSelection()
|
||||
const { i18n, t } = useTranslation()
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
const { dispatchSearchParams } = useSearchParams()
|
||||
|
||||
const collectionPermissions = permissions?.collections?.[slug]
|
||||
const hasDeletePermission = collectionPermissions?.delete?.permission
|
||||
@@ -40,37 +42,54 @@ const DeleteMany: React.FC<Props> = (props) => {
|
||||
toast.error(t('error:unknown'))
|
||||
}, [t])
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
const handleDelete = useCallback(async () => {
|
||||
setDeleting(true)
|
||||
// requests
|
||||
// .delete(`${serverURL}${api}/${slug}${getQueryParams()}`, {
|
||||
// headers: {
|
||||
// 'Accept-Language': i18n.language,
|
||||
// 'Content-Type': 'application/json',
|
||||
// },
|
||||
// })
|
||||
// .then(async (res) => {
|
||||
// try {
|
||||
// const json = await res.json()
|
||||
// toggleModal(modalSlug)
|
||||
// if (res.status < 400) {
|
||||
// toast.success(json.message || t('general:deletedSuccessfully'), { autoClose: 3000 })
|
||||
// toggleAll()
|
||||
// resetParams({ page: selectAll ? 1 : undefined })
|
||||
// return null
|
||||
// }
|
||||
await requests
|
||||
.delete(`${serverURL}${api}/${slug}${getQueryParams()}`, {
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(async (res) => {
|
||||
try {
|
||||
const json = await res.json()
|
||||
toggleModal(modalSlug)
|
||||
if (res.status < 400) {
|
||||
toast.success(json.message || t('general:deletedSuccessfully'), { autoClose: 3000 })
|
||||
toggleAll()
|
||||
dispatchSearchParams({
|
||||
type: 'set',
|
||||
browserHistory: 'replace',
|
||||
params: { page: selectAll ? '1' : undefined },
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
// if (json.errors) {
|
||||
// toast.error(json.message)
|
||||
// } else {
|
||||
// addDefaultError()
|
||||
// }
|
||||
// return false
|
||||
// } catch (e) {
|
||||
// return addDefaultError()
|
||||
// }
|
||||
// })
|
||||
}, [])
|
||||
if (json.errors) {
|
||||
toast.error(json.message)
|
||||
} else {
|
||||
addDefaultError()
|
||||
}
|
||||
return false
|
||||
} catch (e) {
|
||||
return addDefaultError()
|
||||
}
|
||||
})
|
||||
}, [
|
||||
addDefaultError,
|
||||
api,
|
||||
dispatchSearchParams,
|
||||
getQueryParams,
|
||||
i18n.language,
|
||||
modalSlug,
|
||||
selectAll,
|
||||
serverURL,
|
||||
slug,
|
||||
t,
|
||||
toggleAll,
|
||||
toggleModal,
|
||||
])
|
||||
|
||||
if (selectAll === SelectAllStatus.None || !hasDeletePermission) {
|
||||
return null
|
||||
|
||||
@@ -2,6 +2,5 @@ import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
export type Props = {
|
||||
collection: SanitizedCollectionConfig
|
||||
resetParams: () => void
|
||||
title?: string
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use client'
|
||||
import type { CollectionPermission } from 'payload/auth'
|
||||
|
||||
import { useModal } from '@faceless-ui/modal'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
@@ -71,7 +70,7 @@ const Content: React.FC<DocumentDrawerProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
setFields(formatFields(fields, true))
|
||||
}, [collectionSlug, collectionConfig])
|
||||
}, [collectionSlug, collectionConfig, fields])
|
||||
|
||||
useEffect(() => {
|
||||
setIsOpen(Boolean(modalState[drawerSlug]?.isOpen))
|
||||
@@ -84,11 +83,9 @@ const Content: React.FC<DocumentDrawerProps> = ({
|
||||
}
|
||||
}, [isError, t, isOpen, data, drawerSlug, closeModal, isLoadingDocument])
|
||||
|
||||
if (isError) return null
|
||||
|
||||
const isEditing = Boolean(id)
|
||||
|
||||
const apiURL = id ? `${serverURL}${apiRoute}/${collectionSlug}/${id}?locale=${locale}` : null
|
||||
const apiURL = id ? `${serverURL}${apiRoute}/${collectionSlug}/${id}?locale=${locale.code}` : null
|
||||
|
||||
const action = `${serverURL}${apiRoute}/${collectionSlug}${
|
||||
isEditing ? `/${id}` : ''
|
||||
@@ -105,8 +102,8 @@ const Content: React.FC<DocumentDrawerProps> = ({
|
||||
apiRoute,
|
||||
body: {
|
||||
id,
|
||||
collectionSlug,
|
||||
data: data || {},
|
||||
docPreferences: null, // TODO: get this
|
||||
operation: isEditing ? 'update' : 'create',
|
||||
schemaPath,
|
||||
},
|
||||
@@ -114,11 +111,14 @@ const Content: React.FC<DocumentDrawerProps> = ({
|
||||
})
|
||||
|
||||
setInitialState(result)
|
||||
hasInitializedState.current = true
|
||||
}
|
||||
|
||||
getInitialState()
|
||||
void getInitialState()
|
||||
}
|
||||
}, [apiRoute, data, id, isEditing, schemaPath, serverURL])
|
||||
}, [apiRoute, data, id, isEditing, schemaPath, serverURL, collectionSlug])
|
||||
|
||||
if (isError) return null
|
||||
|
||||
if (!initialState || isLoadingDocument) {
|
||||
return <LoadingOverlay />
|
||||
@@ -142,6 +142,7 @@ const Content: React.FC<DocumentDrawerProps> = ({
|
||||
aria-label={t('general:close')}
|
||||
className={`${baseClass}__header-close`}
|
||||
onClick={() => toggleModal(drawerSlug)}
|
||||
type="button"
|
||||
>
|
||||
<X />
|
||||
</button>
|
||||
@@ -154,11 +155,9 @@ const Content: React.FC<DocumentDrawerProps> = ({
|
||||
collectionSlug={collectionConfig.slug}
|
||||
disableActions
|
||||
disableLeaveWithoutSaving
|
||||
docPermissions={{} as CollectionPermission} // TODO; get this
|
||||
// hasSavePermission={hasSavePermission}
|
||||
// isEditing={isEditing}
|
||||
// isLoading,
|
||||
docPreferences={null} // TODO: get this
|
||||
id={id}
|
||||
initialData={data}
|
||||
initialState={initialState}
|
||||
|
||||
@@ -3,10 +3,13 @@ import { useModal } from '@faceless-ui/modal'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
|
||||
import type { FormState } from '../..'
|
||||
import type { Props } from './types'
|
||||
|
||||
import { DocumentInfoProvider, FieldPathProvider, getFormState, useComponentMap } from '../..'
|
||||
import Form from '../../forms/Form'
|
||||
import { useForm } from '../../forms/Form/context'
|
||||
import RenderFields from '../../forms/RenderFields'
|
||||
import FormSubmit from '../../forms/Submit'
|
||||
import { X } from '../../icons/X'
|
||||
import { useAuth } from '../../providers/Auth'
|
||||
@@ -81,25 +84,66 @@ const SaveDraft: React.FC<{ action: string; disabled: boolean }> = ({ action, di
|
||||
</FormSubmit>
|
||||
)
|
||||
}
|
||||
const EditMany: React.FC<Props> = (props) => {
|
||||
export const EditMany: React.FC<Props> = (props) => {
|
||||
const { collection: { slug, fields, labels: { plural } } = {}, collection } = props
|
||||
|
||||
const { permissions } = useAuth()
|
||||
const { closeModal } = useModal()
|
||||
const {
|
||||
routes: { api },
|
||||
routes: { api: apiRoute },
|
||||
serverURL,
|
||||
} = useConfig()
|
||||
const { count, getQueryParams, selectAll } = useSelection()
|
||||
const { i18n, t } = useTranslation()
|
||||
const [selected, setSelected] = useState([])
|
||||
const { dispatchSearchParams } = useSearchParams()
|
||||
const { componentMap } = useComponentMap()
|
||||
const [reducedFieldMap, setReducedFieldMap] = useState([])
|
||||
const [initialState, setInitialState] = useState<FormState>()
|
||||
const hasInitializedState = React.useRef(false)
|
||||
|
||||
const collectionPermissions = permissions?.collections?.[slug]
|
||||
const hasUpdatePermission = collectionPermissions?.update?.permission
|
||||
|
||||
const drawerSlug = `edit-${slug}`
|
||||
|
||||
React.useEffect(() => {
|
||||
if (componentMap?.collections?.[slug]?.fieldMap) {
|
||||
const fieldMap = componentMap.collections[slug].fieldMap
|
||||
const reducedFieldMap = []
|
||||
fieldMap.map((field) => {
|
||||
selected.map((selectedField) => {
|
||||
if (field.name === selectedField.name) {
|
||||
reducedFieldMap.push(field)
|
||||
}
|
||||
})
|
||||
})
|
||||
setReducedFieldMap(reducedFieldMap)
|
||||
}
|
||||
}, [componentMap.collections, fields, slug, selected])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!hasInitializedState.current) {
|
||||
const getInitialState = async () => {
|
||||
const result = await getFormState({
|
||||
apiRoute,
|
||||
body: {
|
||||
collectionSlug: slug,
|
||||
data: {},
|
||||
operation: 'update',
|
||||
schemaPath: slug,
|
||||
},
|
||||
serverURL,
|
||||
})
|
||||
|
||||
setInitialState(result)
|
||||
hasInitializedState.current = true
|
||||
}
|
||||
|
||||
void getInitialState()
|
||||
}
|
||||
}, [apiRoute, hasInitializedState, serverURL, slug])
|
||||
|
||||
if (selectAll === SelectAllStatus.None || !hasUpdatePermission) {
|
||||
return null
|
||||
}
|
||||
@@ -128,64 +172,62 @@ const EditMany: React.FC<Props> = (props) => {
|
||||
{/* @ts-expect-error */}
|
||||
<DocumentInfoProvider collection={collection}>
|
||||
<OperationContext.Provider value="update">
|
||||
<Form className={`${baseClass}__form`} onSuccess={onSuccess}>
|
||||
<div className={`${baseClass}__main`}>
|
||||
<div className={`${baseClass}__header`}>
|
||||
<h2 className={`${baseClass}__header__title`}>
|
||||
{t('general:editingLabel', { count, label: getTranslation(plural, i18n) })}
|
||||
</h2>
|
||||
<button
|
||||
aria-label={t('general:close')}
|
||||
className={`${baseClass}__header__close`}
|
||||
id={`close-drawer__${drawerSlug}`}
|
||||
onClick={() => closeModal(drawerSlug)}
|
||||
type="button"
|
||||
>
|
||||
<X />
|
||||
</button>
|
||||
</div>
|
||||
<FieldSelect fields={fields} setSelected={setSelected} />
|
||||
[RenderFields]
|
||||
{/* <RenderFields fieldSchema={selected} fieldTypes={fieldTypes} /> */}
|
||||
<div className={`${baseClass}__sidebar-wrap`}>
|
||||
<div className={`${baseClass}__sidebar`}>
|
||||
<div className={`${baseClass}__sidebar-sticky-wrap`}>
|
||||
<div className={`${baseClass}__document-actions`}>
|
||||
{collection.versions ? (
|
||||
<React.Fragment>
|
||||
<Publish
|
||||
action={`${serverURL}${api}/${slug}${getQueryParams()}`}
|
||||
disabled={selected.length === 0}
|
||||
/>
|
||||
<SaveDraft
|
||||
action={`${serverURL}${api}/${slug}${getQueryParams()}`}
|
||||
disabled={selected.length === 0}
|
||||
/>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<div className={`${baseClass}__main`}>
|
||||
<div className={`${baseClass}__header`}>
|
||||
<h2 className={`${baseClass}__header__title`}>
|
||||
{t('general:editingLabel', { count, label: getTranslation(plural, i18n) })}
|
||||
</h2>
|
||||
<button
|
||||
aria-label={t('general:close')}
|
||||
className={`${baseClass}__header__close`}
|
||||
id={`close-drawer__${drawerSlug}`}
|
||||
onClick={() => closeModal(drawerSlug)}
|
||||
type="button"
|
||||
>
|
||||
<X />
|
||||
</button>
|
||||
</div>
|
||||
<FieldPathProvider path="" schemaPath={slug}>
|
||||
<Form
|
||||
className={`${baseClass}__form`}
|
||||
initialState={initialState}
|
||||
onSuccess={onSuccess}
|
||||
>
|
||||
<FieldSelect fields={fields} setSelected={setSelected} />
|
||||
{reducedFieldMap.length === 0 ? null : (
|
||||
<RenderFields fieldMap={reducedFieldMap} />
|
||||
)}
|
||||
<div className={`${baseClass}__sidebar-wrap`}>
|
||||
<div className={`${baseClass}__sidebar`}>
|
||||
<div className={`${baseClass}__sidebar-sticky-wrap`}>
|
||||
<div className={`${baseClass}__document-actions`}>
|
||||
{collection?.versions?.drafts ? (
|
||||
<React.Fragment>
|
||||
<Publish
|
||||
action={`${serverURL}${apiRoute}/${slug}${getQueryParams()}`}
|
||||
disabled={selected.length === 0}
|
||||
/>
|
||||
<SaveDraft
|
||||
action={`${serverURL}${apiRoute}/${slug}${getQueryParams()}`}
|
||||
disabled={selected.length === 0}
|
||||
/>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<Submit
|
||||
action={`${serverURL}${api}/${slug}${getQueryParams()}`}
|
||||
action={`${serverURL}${apiRoute}/${slug}${getQueryParams()}`}
|
||||
disabled={selected.length === 0}
|
||||
/>
|
||||
<SaveDraft
|
||||
action={`${serverURL}${api}/${slug}${getQueryParams()}`}
|
||||
disabled={selected.length === 0}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</Form>
|
||||
</FieldPathProvider>
|
||||
</div>
|
||||
</OperationContext.Provider>
|
||||
{/* @ts-expect-error */}
|
||||
</DocumentInfoProvider>
|
||||
</Drawer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditMany
|
||||
|
||||
@@ -87,7 +87,7 @@ export const FieldSelect: React.FC<Props> = ({ fields, setSelected }) => {
|
||||
setSelected(selected.map(({ value }) => value))
|
||||
}
|
||||
// remove deselected values from form state
|
||||
if (selected === null || Object.keys(activeFields).length > selected.length) {
|
||||
if (selected === null || Object.keys(activeFields || []).length > selected.length) {
|
||||
Object.keys(activeFields).forEach((path) => {
|
||||
if (
|
||||
selected === null ||
|
||||
@@ -96,8 +96,8 @@ export const FieldSelect: React.FC<Props> = ({ fields, setSelected }) => {
|
||||
})
|
||||
) {
|
||||
dispatchFields({
|
||||
path,
|
||||
type: 'REMOVE',
|
||||
path,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -12,13 +12,13 @@ import { useSearchParams } from '../../providers/SearchParams'
|
||||
import { useTranslation } from '../../providers/Translation'
|
||||
import { Button } from '../Button'
|
||||
import ColumnSelector from '../ColumnSelector'
|
||||
import DeleteMany from '../DeleteMany'
|
||||
import EditMany from '../EditMany'
|
||||
import { DeleteMany } from '../DeleteMany'
|
||||
import { EditMany } from '../EditMany'
|
||||
import Pill from '../Pill'
|
||||
import PublishMany from '../PublishMany'
|
||||
import { PublishMany } from '../PublishMany'
|
||||
import SearchFilter from '../SearchFilter'
|
||||
import SortComplex from '../SortComplex'
|
||||
import UnpublishMany from '../UnpublishMany'
|
||||
import { UnpublishMany } from '../UnpublishMany'
|
||||
import WhereBuilder from '../WhereBuilder'
|
||||
import validateWhereQuery from '../WhereBuilder/validateWhereQuery'
|
||||
import './index.scss'
|
||||
@@ -32,8 +32,7 @@ const baseClass = 'list-controls'
|
||||
*/
|
||||
export const ListControls: React.FC<Props> = (props) => {
|
||||
const {
|
||||
collectionPluralLabel,
|
||||
collectionSlug,
|
||||
collectionConfig,
|
||||
enableColumns = true,
|
||||
enableSort = false,
|
||||
handleSearchChange,
|
||||
@@ -71,10 +70,10 @@ export const ListControls: React.FC<Props> = (props) => {
|
||||
<div className={`${baseClass}__buttons-wrap`}>
|
||||
{!smallBreak && (
|
||||
<React.Fragment>
|
||||
{/* <EditMany resetParams={resetParams} />
|
||||
<PublishMany resetParams={resetParams} />
|
||||
<UnpublishMany resetParams={resetParams} />
|
||||
<DeleteMany resetParams={resetParams} /> */}
|
||||
<EditMany collection={collectionConfig} />
|
||||
<PublishMany collection={collectionConfig} />
|
||||
<UnpublishMany collection={collectionConfig} />
|
||||
<DeleteMany collection={collectionConfig} />
|
||||
</React.Fragment>
|
||||
)}
|
||||
{enableColumns && (
|
||||
@@ -126,7 +125,7 @@ export const ListControls: React.FC<Props> = (props) => {
|
||||
height={visibleDrawer === 'columns' ? 'auto' : 0}
|
||||
id={`${baseClass}-columns`}
|
||||
>
|
||||
<ColumnSelector collectionSlug={collectionSlug} />
|
||||
<ColumnSelector collectionSlug={collectionConfig.slug} />
|
||||
</AnimateHeight>
|
||||
)}
|
||||
<AnimateHeight
|
||||
@@ -135,8 +134,8 @@ export const ListControls: React.FC<Props> = (props) => {
|
||||
id={`${baseClass}-where`}
|
||||
>
|
||||
<WhereBuilder
|
||||
collectionPluralLabel={collectionPluralLabel}
|
||||
collectionSlug={collectionSlug}
|
||||
collectionPluralLabel={collectionConfig?.labels?.plural}
|
||||
collectionSlug={collectionConfig.slug}
|
||||
handleChange={handleWhereChange}
|
||||
modifySearchQuery={modifySearchQuery}
|
||||
/>
|
||||
|
||||
@@ -3,8 +3,7 @@ import type { FieldAffectingData, SanitizedCollectionConfig, Where } from 'paylo
|
||||
import type { Column } from '../Table/types'
|
||||
|
||||
export type Props = {
|
||||
collectionPluralLabel: SanitizedCollectionConfig['labels']['plural']
|
||||
collectionSlug: SanitizedCollectionConfig['slug']
|
||||
collectionConfig: SanitizedCollectionConfig
|
||||
enableColumns?: boolean
|
||||
enableSort?: boolean
|
||||
handleSearchChange?: (search: string) => void
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
'use client'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
import { SelectAllStatus, useSelection } from '../../providers/SelectionProvider'
|
||||
@@ -9,7 +10,7 @@ const baseClass = 'list-selection'
|
||||
type Props = {
|
||||
label: string
|
||||
}
|
||||
const ListSelection: React.FC<Props> = ({ label }) => {
|
||||
export const ListSelection: React.FC<Props> = ({ label }) => {
|
||||
const { count, selectAll, toggleAll, totalDocs } = useSelection()
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -37,5 +38,3 @@ const ListSelection: React.FC<Props> = ({ label }) => {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ListSelection
|
||||
|
||||
@@ -19,7 +19,7 @@ import './index.scss'
|
||||
|
||||
const baseClass = 'publish-many'
|
||||
|
||||
const PublishMany: React.FC<Props> = (props) => {
|
||||
export const PublishMany: React.FC<Props> = (props) => {
|
||||
const { collection: { slug, labels: { plural }, versions } = {} } = props
|
||||
|
||||
const {
|
||||
@@ -130,5 +130,3 @@ const PublishMany: React.FC<Props> = (props) => {
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default PublishMany
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
'use client'
|
||||
import { CheckboxInput, SelectAllStatus, useSelection, useTranslation } from '@payloadcms/ui'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import { CheckboxInput, SelectAllStatus, useSelection, useTranslation } from '../..'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'select-all'
|
||||
|
||||
const SelectAll: React.FC = () => {
|
||||
export const SelectAll: React.FC = () => {
|
||||
const { selectAll, toggleAll } = useSelection()
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
@@ -22,11 +23,9 @@ const SelectAll: React.FC = () => {
|
||||
}
|
||||
className={`${baseClass}__checkbox`}
|
||||
id="select-all"
|
||||
onChange={() => toggleAll()}
|
||||
name="select-all"
|
||||
onToggle={() => toggleAll()}
|
||||
partialChecked={selectAll === SelectAllStatus.Some}
|
||||
path="select-all"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default SelectAll
|
||||
@@ -1,21 +1,20 @@
|
||||
'use client'
|
||||
import { CheckboxInput, useSelection } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
import { CheckboxInput, useSelection, useTableCell } from '../..'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'select-row'
|
||||
|
||||
const SelectRow: React.FC<{ id: number | string }> = ({ id }) => {
|
||||
export const SelectRow: React.FC = () => {
|
||||
const { selected, setSelection } = useSelection()
|
||||
const { rowData } = useTableCell()
|
||||
|
||||
return (
|
||||
<CheckboxInput
|
||||
checked={selected[id]}
|
||||
// onToggle={() => setSelection(id)}
|
||||
checked={selected?.[rowData?.id]}
|
||||
className={`${baseClass}__checkbox`}
|
||||
onToggle={() => setSelection(rowData.id)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default SelectRow
|
||||
@@ -1,17 +1,28 @@
|
||||
import type { CellProps, SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import type { Column } from '../../elements/Table/types'
|
||||
import type { ColumnPreferences } from '../../providers/ListInfo/types'
|
||||
import type { FieldMap } from '../../utilities/buildComponentMap/types'
|
||||
import type { Column } from '../Table/types'
|
||||
|
||||
import { SelectAll } from '../SelectAll'
|
||||
import { SelectRow } from '../SelectRow'
|
||||
|
||||
export const buildColumns = (args: {
|
||||
cellProps: Partial<CellProps>[]
|
||||
columnPreferences: ColumnPreferences
|
||||
defaultColumns?: string[]
|
||||
enableRowSelections: boolean
|
||||
fieldMap: FieldMap
|
||||
useAsTitle: SanitizedCollectionConfig['admin']['useAsTitle']
|
||||
}): Column[] => {
|
||||
const { cellProps, columnPreferences, defaultColumns, fieldMap, useAsTitle } = args
|
||||
const {
|
||||
cellProps,
|
||||
columnPreferences,
|
||||
defaultColumns,
|
||||
enableRowSelections,
|
||||
fieldMap,
|
||||
useAsTitle,
|
||||
} = args
|
||||
|
||||
let sortedFieldMap = fieldMap
|
||||
|
||||
@@ -32,7 +43,7 @@ export const buildColumns = (args: {
|
||||
|
||||
let numberOfActiveColumns = 0
|
||||
|
||||
return sortedFieldMap.reduce((acc, field, index) => {
|
||||
const sorted = sortedFieldMap.reduce((acc, field, index) => {
|
||||
const columnPreference = columnPreferences?.find(
|
||||
(preference) => preference.accessor === field.name,
|
||||
)
|
||||
@@ -56,7 +67,10 @@ export const buildColumns = (args: {
|
||||
name: field.name,
|
||||
accessor: field.name,
|
||||
active,
|
||||
cellProps: cellProps?.[index],
|
||||
cellProps: {
|
||||
...cellProps?.[index],
|
||||
link: (numberOfActiveColumns === 1 && active && enableRowSelections) || undefined,
|
||||
},
|
||||
components: {
|
||||
Cell: field.Cell,
|
||||
Heading: field.Heading,
|
||||
@@ -69,4 +83,19 @@ export const buildColumns = (args: {
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
if (enableRowSelections) {
|
||||
sorted.unshift({
|
||||
name: '',
|
||||
accessor: '_select',
|
||||
active: true,
|
||||
components: {
|
||||
Cell: <SelectRow />,
|
||||
Heading: <SelectAll />,
|
||||
},
|
||||
label: null,
|
||||
})
|
||||
}
|
||||
|
||||
return sorted
|
||||
}
|
||||
@@ -34,8 +34,9 @@ export const TableColumnsProvider: React.FC<{
|
||||
cellProps?: Partial<CellProps>[]
|
||||
children: React.ReactNode
|
||||
collectionSlug: string
|
||||
enableRowSelections?: boolean
|
||||
listPreferences: ListPreferences
|
||||
}> = ({ cellProps, children, collectionSlug, listPreferences }) => {
|
||||
}> = ({ cellProps, children, collectionSlug, enableRowSelections = false, listPreferences }) => {
|
||||
const config = useConfig()
|
||||
|
||||
const { componentMap } = useComponentMap()
|
||||
@@ -60,6 +61,7 @@ export const TableColumnsProvider: React.FC<{
|
||||
cellProps,
|
||||
columnPreferences: listPreferences?.columns,
|
||||
defaultColumns,
|
||||
enableRowSelections,
|
||||
fieldMap,
|
||||
useAsTitle,
|
||||
})
|
||||
@@ -87,6 +89,7 @@ export const TableColumnsProvider: React.FC<{
|
||||
cellProps,
|
||||
columnPreferences: currentPreferences?.columns,
|
||||
defaultColumns,
|
||||
enableRowSelections: true,
|
||||
fieldMap,
|
||||
useAsTitle,
|
||||
}),
|
||||
@@ -159,14 +162,17 @@ export const TableColumnsProvider: React.FC<{
|
||||
[dispatchTableColumns],
|
||||
)
|
||||
|
||||
const toggleColumn = useCallback((column: string) => {
|
||||
dispatchTableColumns({
|
||||
type: 'toggle',
|
||||
payload: {
|
||||
column,
|
||||
},
|
||||
})
|
||||
}, [])
|
||||
const toggleColumn = useCallback(
|
||||
(column: string) => {
|
||||
dispatchTableColumns({
|
||||
type: 'toggle',
|
||||
payload: {
|
||||
column,
|
||||
},
|
||||
})
|
||||
},
|
||||
[dispatchTableColumns],
|
||||
)
|
||||
|
||||
return (
|
||||
<TableColumnContext.Provider
|
||||
|
||||
@@ -8,18 +8,19 @@ import type { Props } from './types'
|
||||
|
||||
import { useAuth } from '../../providers/Auth'
|
||||
import { useConfig } from '../../providers/Config'
|
||||
import { useSearchParams } from '../../providers/SearchParams'
|
||||
import { SelectAllStatus, useSelection } from '../../providers/SelectionProvider'
|
||||
import { useTranslation } from '../../providers/Translation'
|
||||
// import { requests } from '../../../api'
|
||||
import { MinimalTemplate } from '../../templates/Minimal'
|
||||
import { requests } from '../../utilities/api'
|
||||
import { Button } from '../Button'
|
||||
import Pill from '../Pill'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'unpublish-many'
|
||||
|
||||
const UnpublishMany: React.FC<Props> = (props) => {
|
||||
const { collection: { labels: { plural }, slug, versions } = {}, resetParams } = props
|
||||
export const UnpublishMany: React.FC<Props> = (props) => {
|
||||
const { collection: { slug, labels: { plural }, versions } = {} } = props
|
||||
|
||||
const {
|
||||
routes: { api },
|
||||
@@ -28,8 +29,9 @@ const UnpublishMany: React.FC<Props> = (props) => {
|
||||
const { permissions } = useAuth()
|
||||
const { toggleModal } = useModal()
|
||||
const { i18n, t } = useTranslation()
|
||||
const { count, getQueryParams, selectAll } = useSelection()
|
||||
const { getQueryParams, selectAll } = useSelection()
|
||||
const [submitted, setSubmitted] = useState(false)
|
||||
const { dispatchSearchParams } = useSearchParams()
|
||||
|
||||
const collectionPermissions = permissions?.collections?.[slug]
|
||||
const hasPermission = collectionPermissions?.update?.permission
|
||||
@@ -40,45 +42,49 @@ const UnpublishMany: React.FC<Props> = (props) => {
|
||||
toast.error(t('error:unknown'))
|
||||
}, [t])
|
||||
|
||||
const handleUnpublish = useCallback(() => {
|
||||
const handleUnpublish = useCallback(async () => {
|
||||
setSubmitted(true)
|
||||
// requests
|
||||
// .patch(`${serverURL}${api}/${slug}${getQueryParams({ _status: { not_equals: 'draft' } })}`, {
|
||||
// body: JSON.stringify({
|
||||
// _status: 'draft',
|
||||
// }),
|
||||
// headers: {
|
||||
// 'Accept-Language': i18n.language,
|
||||
// 'Content-Type': 'application/json',
|
||||
// },
|
||||
// })
|
||||
// .then(async (res) => {
|
||||
// try {
|
||||
// const json = await res.json()
|
||||
// toggleModal(modalSlug)
|
||||
// if (res.status < 400) {
|
||||
// toast.success(t('general:updatedSuccessfully'))
|
||||
// resetParams({ page: selectAll ? 1 : undefined })
|
||||
// return null
|
||||
// }
|
||||
await requests
|
||||
.patch(`${serverURL}${api}/${slug}${getQueryParams({ _status: { not_equals: 'draft' } })}`, {
|
||||
body: JSON.stringify({
|
||||
_status: 'draft',
|
||||
}),
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(async (res) => {
|
||||
try {
|
||||
const json = await res.json()
|
||||
toggleModal(modalSlug)
|
||||
if (res.status < 400) {
|
||||
toast.success(t('general:updatedSuccessfully'))
|
||||
dispatchSearchParams({
|
||||
type: 'set',
|
||||
browserHistory: 'replace',
|
||||
params: { page: selectAll ? '1' : undefined },
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
// if (json.errors) {
|
||||
// json.errors.forEach((error) => toast.error(error.message))
|
||||
// } else {
|
||||
// addDefaultError()
|
||||
// }
|
||||
// return false
|
||||
// } catch (e) {
|
||||
// return addDefaultError()
|
||||
// }
|
||||
// })
|
||||
if (json.errors) {
|
||||
json.errors.forEach((error) => toast.error(error.message))
|
||||
} else {
|
||||
addDefaultError()
|
||||
}
|
||||
return false
|
||||
} catch (e) {
|
||||
return addDefaultError()
|
||||
}
|
||||
})
|
||||
}, [
|
||||
addDefaultError,
|
||||
api,
|
||||
dispatchSearchParams,
|
||||
getQueryParams,
|
||||
i18n.language,
|
||||
modalSlug,
|
||||
resetParams,
|
||||
selectAll,
|
||||
serverURL,
|
||||
slug,
|
||||
@@ -121,5 +127,3 @@ const UnpublishMany: React.FC<Props> = (props) => {
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default UnpublishMany
|
||||
|
||||
@@ -2,5 +2,4 @@ import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
export type Props = {
|
||||
collection: SanitizedCollectionConfig
|
||||
resetParams: () => void
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export { HydrateClientUser } from '../elements/HydrateClientUser'
|
||||
export { LeaveWithoutSaving } from '../elements/LeaveWithoutSaving'
|
||||
export { ListControls } from '../elements/ListControls'
|
||||
export { useListDrawer } from '../elements/ListDrawer'
|
||||
export { ListSelection } from '../elements/ListSelection'
|
||||
export { LoadingOverlayToggle } from '../elements/Loading'
|
||||
export { FormLoadingOverlayToggle } from '../elements/Loading'
|
||||
export { LoadingOverlay } from '../elements/Loading'
|
||||
|
||||
@@ -23,7 +23,7 @@ export { default as Submit } from '../forms/Submit'
|
||||
export { fieldTypes } from '../forms/fields'
|
||||
export { default as SectionTitle } from '../forms/fields/Blocks/SectionTitle'
|
||||
export { default as Checkbox } from '../forms/fields/Checkbox'
|
||||
export { default as CheckboxInput } from '../forms/fields/Checkbox'
|
||||
export { CheckboxInput } from '../forms/fields/Checkbox/Input'
|
||||
export { default as ConfirmPassword } from '../forms/fields/ConfirmPassword'
|
||||
export { default as Email } from '../forms/fields/Email'
|
||||
export { default as HiddenInput } from '../forms/fields/HiddenInput'
|
||||
|
||||
74
packages/ui/src/forms/fields/Checkbox/Input.tsx
Normal file
74
packages/ui/src/forms/fields/Checkbox/Input.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
|
||||
import { Check, Line } from '../../..'
|
||||
|
||||
type Props = {
|
||||
AfterInput?: React.ReactNode
|
||||
BeforeInput?: React.ReactNode
|
||||
Label?: React.ReactNode
|
||||
checked?: boolean
|
||||
className?: string
|
||||
id?: string
|
||||
inputRef?: React.RefObject<HTMLInputElement>
|
||||
name?: string
|
||||
onToggle: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
partialChecked?: boolean
|
||||
readOnly?: boolean
|
||||
required?: boolean
|
||||
}
|
||||
|
||||
export const inputBaseClass = 'checkbox-input'
|
||||
|
||||
export const CheckboxInput: React.FC<Props> = ({
|
||||
id,
|
||||
name,
|
||||
AfterInput,
|
||||
BeforeInput,
|
||||
Label,
|
||||
checked,
|
||||
className,
|
||||
inputRef,
|
||||
onToggle,
|
||||
partialChecked,
|
||||
readOnly,
|
||||
required,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
className,
|
||||
inputBaseClass,
|
||||
checked && `${inputBaseClass}--checked`,
|
||||
readOnly && `${inputBaseClass}--read-only`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
<div className={`${inputBaseClass}__input`}>
|
||||
{BeforeInput}
|
||||
<input
|
||||
aria-label=""
|
||||
defaultChecked={Boolean(checked)}
|
||||
disabled={readOnly}
|
||||
id={id}
|
||||
name={name}
|
||||
onInput={onToggle}
|
||||
ref={inputRef}
|
||||
required={required}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
className={[`${inputBaseClass}__icon`, !checked && partialChecked ? 'check' : 'partial']
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{checked && <Check />}
|
||||
{!checked && partialChecked && <Line />}
|
||||
</span>
|
||||
{AfterInput}
|
||||
</div>
|
||||
{Label}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -5,18 +5,15 @@ import React, { useCallback } from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { Check } from '../../../icons/Check'
|
||||
import { Line } from '../../../icons/Line'
|
||||
import LabelComp from '../../Label'
|
||||
import useField from '../../useField'
|
||||
import { withCondition } from '../../withCondition'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import { CheckboxInput } from './Input'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'checkbox'
|
||||
|
||||
export const inputBaseClass = 'checkbox-input'
|
||||
|
||||
const Checkbox: React.FC<Props> = (props) => {
|
||||
const {
|
||||
id,
|
||||
@@ -86,40 +83,19 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
}}
|
||||
>
|
||||
<div className={`${baseClass}__error-wrap`}>{Error}</div>
|
||||
<div
|
||||
className={[
|
||||
inputBaseClass,
|
||||
checked && `${inputBaseClass}--checked`,
|
||||
readOnly && `${inputBaseClass}--read-only`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
<div className={`${inputBaseClass}__input`}>
|
||||
{BeforeInput}
|
||||
<input
|
||||
aria-label=""
|
||||
defaultChecked={Boolean(checked)}
|
||||
disabled={readOnly}
|
||||
id={fieldID}
|
||||
name={path}
|
||||
onInput={onToggle}
|
||||
required={required}
|
||||
// ref={inputRef}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
className={[`${inputBaseClass}__icon`, !value && partialChecked ? 'check' : 'partial']
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{value && <Check />}
|
||||
{!value && partialChecked && <Line />}
|
||||
</span>
|
||||
{AfterInput}
|
||||
</div>
|
||||
{Label}
|
||||
</div>
|
||||
<CheckboxInput
|
||||
AfterInput={AfterInput}
|
||||
BeforeInput={BeforeInput}
|
||||
Label={Label}
|
||||
checked={checked}
|
||||
id={fieldID}
|
||||
inputRef={null}
|
||||
name={path}
|
||||
onToggle={onToggle}
|
||||
partialChecked={partialChecked}
|
||||
readOnly={readOnly}
|
||||
required={required}
|
||||
/>
|
||||
{Description}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { TFunction } from '@payloadcms/translations'
|
||||
import type { User } from 'payload/auth'
|
||||
import type { Locale } from 'payload/config'
|
||||
import type { Data, DocumentPreferences, Field as FieldSchema } from 'payload/types'
|
||||
import type { Data, Field as FieldSchema } from 'payload/types'
|
||||
|
||||
import type { FormState } from '../../Form/types'
|
||||
|
||||
@@ -22,9 +22,10 @@ type Args = {
|
||||
}
|
||||
|
||||
export type BuildFormStateArgs = {
|
||||
collectionSlug?: string
|
||||
data?: Data
|
||||
docPreferences: DocumentPreferences
|
||||
formState?: FormState
|
||||
globalSlug?: string
|
||||
id?: number | string
|
||||
operation?: 'create' | 'update'
|
||||
schemaPath: string
|
||||
|
||||
@@ -18,6 +18,13 @@ const Context = createContext({} as DocumentInfoContext)
|
||||
|
||||
export const useDocumentInfo = (): DocumentInfoContext => useContext(Context)
|
||||
|
||||
/**
|
||||
* To initialize documentInfo from the server
|
||||
* use the <SetDocumentInfo /> within a RSC component
|
||||
* to hydrate the documentInfo on the first render.
|
||||
*
|
||||
* Otherwise pass props to initialize the documentInfo.
|
||||
**/
|
||||
export const DocumentInfoProvider: React.FC<
|
||||
DocumentInfoProps & {
|
||||
children: React.ReactNode
|
||||
@@ -53,6 +60,13 @@ export const DocumentInfoProvider: React.FC<
|
||||
|
||||
const [title, setTitle] = useState<string>('')
|
||||
|
||||
const setDocumentTitle = useCallback<DocumentInfoContext['setDocumentTitle']>(
|
||||
(title) => {
|
||||
setTitle(title || id?.toString() || '[untitled]')
|
||||
},
|
||||
[id],
|
||||
)
|
||||
|
||||
const baseURL = `${serverURL}${api}`
|
||||
let slug: string
|
||||
let pluralType: 'collections' | 'globals'
|
||||
@@ -259,19 +273,26 @@ export const DocumentInfoProvider: React.FC<
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
getVersions()
|
||||
void getVersions()
|
||||
}, [getVersions])
|
||||
|
||||
useEffect(() => {
|
||||
getDocPermissions()
|
||||
}, [getDocPermissions])
|
||||
const loadDocPermissions = async () => {
|
||||
const docPermissions: DocumentPermissions = rest.docPermissions
|
||||
if (!docPermissions) await getDocPermissions()
|
||||
else setDocPermissions(docPermissions)
|
||||
}
|
||||
void loadDocPermissions()
|
||||
}, [getDocPermissions, rest.docPermissions, setDocPermissions])
|
||||
|
||||
const setDocumentTitle = useCallback<DocumentInfoContext['setDocumentTitle']>(
|
||||
(title) => {
|
||||
setTitle(title || id?.toString() || '[untitled]')
|
||||
},
|
||||
[id],
|
||||
)
|
||||
useEffect(() => {
|
||||
const loadDocPreferences = async () => {
|
||||
let docPreferences: DocumentPreferences = rest.docPreferences
|
||||
if (!docPreferences) docPreferences = await getDocPreferences()
|
||||
void setPreference(preferencesKey, docPreferences)
|
||||
}
|
||||
void loadDocPreferences()
|
||||
}, [getDocPreferences, preferencesKey, rest.docPreferences, setPreference])
|
||||
|
||||
const value: DocumentInfoContext = {
|
||||
...documentInfo,
|
||||
|
||||
@@ -43,7 +43,7 @@ export type DocumentInfo = DocumentInfoProps & {
|
||||
versionsCount?: PaginatedDocs<TypeWithVersion<any>>
|
||||
}
|
||||
|
||||
export type DocumentInfoContext = DocumentInfo & {
|
||||
export type DocumentInfoContext = Omit<DocumentInfo, 'docPreferences'> & {
|
||||
getDocPermissions: () => Promise<void>
|
||||
getDocPreferences: () => Promise<{ [key: string]: unknown }>
|
||||
getVersions: () => Promise<void>
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -520,6 +520,9 @@ importers:
|
||||
'@faceless-ui/modal':
|
||||
specifier: 2.0.1
|
||||
version: 2.0.1(react-dom@18.2.0)(react@18.2.0)
|
||||
'@faceless-ui/window-info':
|
||||
specifier: 2.1.1
|
||||
version: 2.1.1(react-dom@18.2.0)(react@18.2.0)
|
||||
'@payloadcms/graphql':
|
||||
specifier: workspace:*
|
||||
version: link:../graphql
|
||||
|
||||
@@ -56,8 +56,8 @@ export const CollectionArchive: React.FC<Props> = props => {
|
||||
docs: (populateBy === 'collection'
|
||||
? populatedDocs
|
||||
: populateBy === 'selection'
|
||||
? selectedDocs
|
||||
: []
|
||||
? selectedDocs
|
||||
: []
|
||||
)?.map(doc => doc.value),
|
||||
hasNextPage: false,
|
||||
hasPrevPage: false,
|
||||
|
||||
@@ -56,8 +56,8 @@ export const CollectionArchive: React.FC<Props> = props => {
|
||||
docs: (populateBy === 'collection'
|
||||
? populatedDocs
|
||||
: populateBy === 'selection'
|
||||
? selectedDocs
|
||||
: []
|
||||
? selectedDocs
|
||||
: []
|
||||
)?.map(doc => doc.value),
|
||||
hasNextPage: false,
|
||||
hasPrevPage: false,
|
||||
|
||||
@@ -3,9 +3,9 @@ import * as AWS from '@aws-sdk/client-s3'
|
||||
import path from 'path'
|
||||
|
||||
import type { Payload } from '../../packages/payload/src'
|
||||
import { describeIfInCIOrHasLocalstack } from '../helpers'
|
||||
|
||||
import { getPayload } from '../../packages/payload/src'
|
||||
import { describeIfInCIOrHasLocalstack } from '../helpers'
|
||||
import { startMemoryDB } from '../startMemoryDB'
|
||||
import configPromise from './config'
|
||||
|
||||
@@ -16,103 +16,104 @@ describe('@payloadcms/plugin-cloud-storage', () => {
|
||||
const config = await startMemoryDB(configPromise)
|
||||
payload = await getPayload({ config })
|
||||
})
|
||||
const TEST_BUCKET = 'payload-bucket'
|
||||
const TEST_BUCKET = 'payload-bucket'
|
||||
|
||||
let client: AWS.S3Client
|
||||
describeIfInCIOrHasLocalstack()('plugin-cloud-storage', () => {
|
||||
describe('S3', () => {
|
||||
beforeAll(async () => {
|
||||
client = new AWS.S3({
|
||||
endpoint: 'http://localhost:4566',
|
||||
region: 'us-east-1',
|
||||
forcePathStyle: true, // required for localstack
|
||||
let client: AWS.S3Client
|
||||
describeIfInCIOrHasLocalstack()('plugin-cloud-storage', () => {
|
||||
describe('S3', () => {
|
||||
beforeAll(async () => {
|
||||
client = new AWS.S3({
|
||||
endpoint: 'http://localhost:4566',
|
||||
region: 'us-east-1',
|
||||
forcePathStyle: true, // required for localstack
|
||||
})
|
||||
|
||||
await createTestBucket()
|
||||
})
|
||||
|
||||
await createTestBucket()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await clearTestBucket()
|
||||
})
|
||||
|
||||
it('can upload', async () => {
|
||||
const upload = await payload.create({
|
||||
collection: 'media',
|
||||
data: {},
|
||||
filePath: path.resolve(__dirname, '../uploads/image.png'),
|
||||
afterEach(async () => {
|
||||
await clearTestBucket()
|
||||
})
|
||||
|
||||
expect(upload.id).toBeTruthy()
|
||||
it('can upload', async () => {
|
||||
const upload = await payload.create({
|
||||
collection: 'media',
|
||||
data: {},
|
||||
filePath: path.resolve(__dirname, '../uploads/image.png'),
|
||||
})
|
||||
|
||||
await verifyUploads(upload.id)
|
||||
expect(upload.id).toBeTruthy()
|
||||
|
||||
await verifyUploads(upload.id)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Azure', () => {
|
||||
it.todo('can upload')
|
||||
})
|
||||
|
||||
describe('GCS', () => {
|
||||
it.todo('can upload')
|
||||
})
|
||||
|
||||
describe('R2', () => {
|
||||
it.todo('can upload')
|
||||
})
|
||||
|
||||
async function createTestBucket() {
|
||||
const makeBucketRes = await client.send(new AWS.CreateBucketCommand({ Bucket: TEST_BUCKET }))
|
||||
|
||||
if (makeBucketRes.$metadata.httpStatusCode !== 200) {
|
||||
throw new Error(`Failed to create bucket. ${makeBucketRes.$metadata.httpStatusCode}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function clearTestBucket() {
|
||||
const listedObjects = await client.send(
|
||||
new AWS.ListObjectsV2Command({
|
||||
Bucket: TEST_BUCKET,
|
||||
}),
|
||||
)
|
||||
|
||||
if (!listedObjects?.Contents?.length) return
|
||||
|
||||
const deleteParams = {
|
||||
Bucket: TEST_BUCKET,
|
||||
Delete: { Objects: [] },
|
||||
}
|
||||
|
||||
listedObjects.Contents.forEach(({ Key }) => {
|
||||
deleteParams.Delete.Objects.push({ Key })
|
||||
describe('Azure', () => {
|
||||
it.todo('can upload')
|
||||
})
|
||||
|
||||
const deleteResult = await client.send(new AWS.DeleteObjectsCommand(deleteParams))
|
||||
if (deleteResult.Errors?.length) {
|
||||
throw new Error(JSON.stringify(deleteResult.Errors))
|
||||
}
|
||||
}
|
||||
describe('GCS', () => {
|
||||
it.todo('can upload')
|
||||
})
|
||||
|
||||
async function verifyUploads(uploadId: number | string) {
|
||||
try {
|
||||
const uploadData = (await payload.findByID({
|
||||
collection: 'media',
|
||||
id: uploadId,
|
||||
})) as unknown as { filename: string; sizes: Record<string, { filename: string }> }
|
||||
describe('R2', () => {
|
||||
it.todo('can upload')
|
||||
})
|
||||
|
||||
const fileKeys = Object.keys(uploadData.sizes).map((key) => uploadData.sizes[key].filename)
|
||||
fileKeys.push(uploadData.filename)
|
||||
async function createTestBucket() {
|
||||
const makeBucketRes = await client.send(new AWS.CreateBucketCommand({ Bucket: TEST_BUCKET }))
|
||||
|
||||
for (const key of fileKeys) {
|
||||
const { $metadata } = await client.send(
|
||||
new AWS.HeadObjectCommand({ Bucket: TEST_BUCKET, Key: key }),
|
||||
)
|
||||
|
||||
// Verify each size was properly uploaded
|
||||
expect($metadata.httpStatusCode).toBe(200)
|
||||
if (makeBucketRes.$metadata.httpStatusCode !== 200) {
|
||||
throw new Error(`Failed to create bucket. ${makeBucketRes.$metadata.httpStatusCode}`)
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.error('Error verifying uploads:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function clearTestBucket() {
|
||||
const listedObjects = await client.send(
|
||||
new AWS.ListObjectsV2Command({
|
||||
Bucket: TEST_BUCKET,
|
||||
}),
|
||||
)
|
||||
|
||||
if (!listedObjects?.Contents?.length) return
|
||||
|
||||
const deleteParams = {
|
||||
Bucket: TEST_BUCKET,
|
||||
Delete: { Objects: [] },
|
||||
}
|
||||
|
||||
listedObjects.Contents.forEach(({ Key }) => {
|
||||
deleteParams.Delete.Objects.push({ Key })
|
||||
})
|
||||
|
||||
const deleteResult = await client.send(new AWS.DeleteObjectsCommand(deleteParams))
|
||||
if (deleteResult.Errors?.length) {
|
||||
throw new Error(JSON.stringify(deleteResult.Errors))
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyUploads(uploadId: number | string) {
|
||||
try {
|
||||
const uploadData = (await payload.findByID({
|
||||
collection: 'media',
|
||||
id: uploadId,
|
||||
})) as unknown as { filename: string; sizes: Record<string, { filename: string }> }
|
||||
|
||||
const fileKeys = Object.keys(uploadData.sizes).map((key) => uploadData.sizes[key].filename)
|
||||
fileKeys.push(uploadData.filename)
|
||||
|
||||
for (const key of fileKeys) {
|
||||
const { $metadata } = await client.send(
|
||||
new AWS.HeadObjectCommand({ Bucket: TEST_BUCKET, Key: key }),
|
||||
)
|
||||
|
||||
// Verify each size was properly uploaded
|
||||
expect($metadata.httpStatusCode).toBe(200)
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.error('Error verifying uploads:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -20,9 +20,8 @@ let id: string
|
||||
let payload: Payload
|
||||
|
||||
describe('SEO Plugin', () => {
|
||||
|
||||
beforeAll(async ({ browser }) => {
|
||||
const { serverURL } = await initPayloadE2E({config, dirname: __dirname })
|
||||
const { serverURL } = await initPayloadE2E({ config, dirname: __dirname })
|
||||
url = new AdminUrlUtil(serverURL, 'pages')
|
||||
|
||||
const context = await browser.newContext()
|
||||
@@ -38,7 +37,7 @@ describe('SEO Plugin', () => {
|
||||
file,
|
||||
})
|
||||
|
||||
const createdPage = await payload.create({
|
||||
const createdPage = (await payload.create({
|
||||
collection: 'pages',
|
||||
data: {
|
||||
slug: 'test-page',
|
||||
@@ -50,7 +49,7 @@ describe('SEO Plugin', () => {
|
||||
},
|
||||
title: 'Test Page',
|
||||
},
|
||||
}) as unknown as Promise<PayloadPage>
|
||||
})) as unknown as Promise<PayloadPage>
|
||||
id = createdPage.id
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user