Merge branch 'feat/next/qs' into feat/next-poc

This commit is contained in:
Jarrod Flesch
2024-03-06 15:40:51 -05:00
24 changed files with 202 additions and 75 deletions

8
.vscode/launch.json vendored
View File

@@ -3,13 +3,7 @@
// Hover to view descriptions of existing attributes.
"configurations": [
{
"command": "pnpm dev",
"name": "Run Dev 3.0",
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm run dev _community",
"command": "pnpm run dev _community -- --no-turbo",
"cwd": "${workspaceFolder}",
"name": "Run Dev Community",
"request": "launch",

View File

@@ -78,6 +78,7 @@
"graphql-http": "^1.22.0",
"graphql-playground-html": "1.6.30",
"path-to-regexp": "^6.2.1",
"qs": "6.11.2",
"react-diff-viewer-continued": "3.2.6",
"react-toastify": "8.2.0",
"sass": "^1.71.1",

View File

@@ -10,6 +10,7 @@ import { translations } from '@payloadcms/translations/api'
import { getAuthenticatedUser } from 'payload/auth'
import { parseCookies } from 'payload/auth'
import { getDataLoader } from 'payload/utilities'
import QueryString from 'qs'
import { URL } from 'url'
import { getDataAndFile } from './getDataAndFile'
@@ -95,6 +96,7 @@ export const createPayloadRequest = async ({
payloadUploadSizes: {},
port: urlProperties.port,
protocol: urlProperties.protocol,
query: urlProperties.search ? QueryString.parse(urlProperties.search) : {},
routeParams: params || {},
search: urlProperties.search,
searchParams: urlProperties.searchParams,

View File

@@ -19,7 +19,6 @@ export { generateAccountMetadata } from './meta'
export const Account: React.FC<AdminViewProps> = async ({ initPageResult, searchParams }) => {
const {
locale,
permissions,
req: {
payload,
@@ -94,8 +93,8 @@ export const Account: React.FC<AdminViewProps> = async ({ initPageResult, search
<HydrateClientUser permissions={permissions} user={user} />
<SetDocumentInfo
AfterFields={<Settings />}
action={`${serverURL}${api}/${userSlug}/${data?.id}?locale=${locale.code}`}
apiURL={`${serverURL}${api}/${userSlug}/${data?.id}?locale=${locale.code}`}
action={`${serverURL}${api}/${userSlug}${data?.id ? `/${data.id}` : ''}`}
apiURL={`${serverURL}${api}/${userSlug}${data?.id ? `/${data.id}` : ''}`}
collectionSlug={userSlug}
docPermissions={collectionPermissions}
docPreferences={docPreferences}

View File

@@ -181,13 +181,6 @@ export const Document: React.FC<AdminViewProps> = async ({
req,
})
const formQueryParams: QueryParamTypes = {
depth: 0,
'fallback-locale': 'null',
locale: locale.code,
uploadEdits: undefined,
}
const serverSideProps: ServerSideEditViewProps = {
initPageResult,
routeSegments: segments,
@@ -204,7 +197,7 @@ export const Document: React.FC<AdminViewProps> = async ({
/>
<HydrateClientUser permissions={permissions} user={user} />
<SetDocumentInfo
action={`${action}?${queryString.stringify(formQueryParams)}`}
action={action}
apiURL={apiURL}
collectionSlug={collectionConfig?.slug}
disableActions={false}
@@ -224,7 +217,14 @@ export const Document: React.FC<AdminViewProps> = async ({
})}
/>
<EditDepthProvider depth={1} key={`${collectionSlug || globalSlug}-${locale.code}`}>
<FormQueryParamsProvider formQueryParams={formQueryParams}>
<FormQueryParamsProvider
initialParams={{
depth: 0,
'fallback-locale': 'null',
locale: locale.code,
uploadEdits: undefined,
}}
>
<RenderCustomComponent
CustomComponent={typeof CustomView === 'function' ? CustomView : undefined}
DefaultComponent={DefaultView}

View File

@@ -6,6 +6,7 @@ import {
useComponentMap,
useConfig,
useDocumentInfo,
useFormQueryParams,
} from '@payloadcms/ui'
import { useRouter } from 'next/navigation'
import React, { Fragment, useEffect } from 'react'
@@ -20,6 +21,7 @@ export const EditViewClient: React.FC = () => {
} = useConfig()
const router = useRouter()
const { dispatchFormQueryParams } = useFormQueryParams()
const { getComponentMap } = useComponentMap()
@@ -38,16 +40,23 @@ export const EditViewClient: React.FC = () => {
if (!isEditing) {
router.push(`${adminRoute}/collections/${collectionSlug}/${json?.doc?.id}`)
} else {
// buildState(json.doc, {
// fieldSchema: collection.fields,
// })
// setFormQueryParams((params) => ({
// ...params,
// uploadEdits: undefined,
// }))
dispatchFormQueryParams({
type: 'SET',
params: {
uploadEdits: null,
},
})
}
},
[getVersions, isEditing, getDocPermissions, collectionSlug, adminRoute, router],
[
adminRoute,
collectionSlug,
dispatchFormQueryParams,
getDocPermissions,
getVersions,
isEditing,
router,
],
)
useEffect(() => {

View File

@@ -54,6 +54,8 @@ export type CustomPayloadRequest<U = any> = {
payloadDataLoader?: DataLoader<string, TypeWithID>
/** Resized versions of the image that was uploaded during this request */
payloadUploadSizes?: Record<string, Buffer>
/** Query params on the request */
query: Record<string, unknown>
/** The route parameters
* @example
* /:collection/:id -> /posts/123

View File

@@ -52,8 +52,7 @@ export const generateFileData = async <T>({
let file = req.file
const { searchParams } = req
const uploadEdits = searchParams.get('uploadEdits') || {}
const uploadEdits = req.query['uploadEdits'] || {}
const { disableLocalStorage, formatOptions, imageSizes, resizeOptions, staticDir, trimOptions } =
collectionConfig.upload

View File

@@ -94,6 +94,7 @@ export const createLocalReq: CreateLocalReq = async (
req.user = user || req?.user || null
req.payloadDataLoader = req?.payloadDataLoader || getDataLoader(req)
req.routeParams = req?.routeParams || {}
req.query = req?.query || {}
if (!req?.url) attachFakeURLProperties(req)

View File

@@ -59,7 +59,7 @@ export const DeleteMany: React.FC<Props> = (props) => {
toast.success(json.message || t('general:deletedSuccessfully'), { autoClose: 3000 })
toggleAll()
dispatchSearchParams({
type: 'set',
type: 'SET',
browserHistory: 'replace',
params: { page: selectAll ? '1' : undefined },
})

View File

@@ -154,7 +154,7 @@ export const EditMany: React.FC<Props> = (props) => {
const onSuccess = () => {
dispatchSearchParams({
type: 'set',
type: 'SET',
browserHistory: 'replace',
params: { page: selectAll === SelectAllStatus.AllAvailable ? '1' : undefined },
})

View File

@@ -36,7 +36,7 @@ export const EditUpload: React.FC<{
}> = ({ fileName, fileSrc, imageCacheTag, showCrop, showFocalPoint }) => {
const { closeModal } = useModal()
const { t } = useTranslation()
const { formQueryParams, setFormQueryParams } = useFormQueryParams()
const { dispatchFormQueryParams, formQueryParams } = useFormQueryParams()
const { uploadEdits } = formQueryParams || {}
const [crop, setCrop] = useState<CropType>({
height: uploadEdits?.crop?.height || 100,
@@ -81,11 +81,16 @@ export const EditUpload: React.FC<{
}
const saveEdits = () => {
setFormQueryParams({
...formQueryParams,
uploadEdits: {
crop: crop || undefined,
focalPoint: pointPosition ? pointPosition : undefined,
dispatchFormQueryParams({
type: 'SET',
params: {
uploadEdits:
crop || pointPosition
? {
crop: crop || null,
focalPoint: pointPosition ? pointPosition : null,
}
: null,
},
})
closeModal(editDrawerSlug)

View File

@@ -49,7 +49,7 @@ const Localizer: React.FC<{
onClick={() => {
close()
dispatchSearchParams({
type: 'set',
type: 'SET',
params: {
locale: searchParams.locale,
},

View File

@@ -64,7 +64,7 @@ export const PublishMany: React.FC<Props> = (props) => {
if (res.status < 400) {
toast.success(t('general:updatedSuccessfully'))
dispatchSearchParams({
type: 'set',
type: 'SET',
browserHistory: 'replace',
params: { page: selectAll ? '1' : undefined },
})

View File

@@ -61,7 +61,7 @@ export const UnpublishMany: React.FC<Props> = (props) => {
if (res.status < 400) {
toast.success(t('general:updatedSuccessfully'))
dispatchSearchParams({
type: 'set',
type: 'SET',
browserHistory: 'replace',
params: { page: selectAll ? '1' : undefined },
})

View File

@@ -9,7 +9,6 @@ import { useFormSubmitted } from '../../forms/Form/context'
import reduceFieldsToValues from '../../forms/Form/reduceFieldsToValues'
import { fieldBaseClass } from '../../forms/fields/shared'
import useField from '../../forms/useField'
import { useClientFunctions } from '../../providers/ClientFunction'
import { useDocumentInfo } from '../../providers/DocumentInfo'
import { useTranslation } from '../../providers/Translation'
import { Button } from '../Button'
@@ -53,7 +52,6 @@ export const UploadActions = ({ canEdit, showSizePreviews }) => {
export const Upload: React.FC<Props> = (props) => {
const { collectionSlug, initialState, onChange, updatedAt, uploadConfig } = props
const clientFunctions = useClientFunctions()
const submitted = useFormSubmitted()
const [replacingFile, setReplacingFile] = useState(false)

View File

@@ -6,11 +6,13 @@ import isDeepEqual from 'deep-equal'
import { useRouter } from 'next/navigation'
import { serialize } from 'object-to-formdata'
import { wait } from 'payload/utilities'
import QueryString from 'qs'
import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react'
import { toast } from 'react-toastify'
import type { Context as FormContextType, GetDataByPath, Props, SubmitOptions } from './types'
import { useFormQueryParams } from '../..'
import { useDebouncedEffect } from '../../hooks/useDebouncedEffect'
import useThrottledEffect from '../../hooks/useThrottledEffect'
import { useAuth } from '../../providers/Auth'
@@ -69,6 +71,7 @@ const Form: React.FC<Props> = (props) => {
const { i18n, t } = useTranslation()
const { refreshCookie, user } = useAuth()
const operation = useOperation()
const { formQueryParams } = useFormQueryParams()
const config = useConfig()
const {
@@ -221,7 +224,8 @@ const Form: React.FC<Props> = (props) => {
let res
if (typeof actionToUse === 'string') {
res = await requests[methodToUse.toLowerCase()](actionToUse, {
const actionEndpoint = `${actionToUse}${QueryString.stringify(formQueryParams, { addQueryPrefix: true })}`
res = await requests[methodToUse.toLowerCase()](actionEndpoint, {
body: formData,
headers: {
'Accept-Language': i18n.language,
@@ -352,6 +356,7 @@ const Form: React.FC<Props> = (props) => {
i18n,
waitForAutocomplete,
beforeSubmit,
formQueryParams,
],
)
@@ -562,9 +567,14 @@ const Form: React.FC<Props> = (props) => {
[fields, dispatchFields, onChange],
)
const actionString =
typeof action === 'string'
? `${action}${QueryString.stringify(formQueryParams, { addQueryPrefix: true })}`
: ''
return (
<form
action={action}
action={method ? actionString : action}
className={classes}
method={method}
noValidate

View File

@@ -1,38 +1,65 @@
'use client'
import type { UploadEdits } from 'payload/types'
import React, { createContext, useContext, useState } from 'react'
import React, { createContext, useContext } from 'react'
export type QueryParamTypes = {
depth: number
'fallback-locale': string
locale: string
uploadEdits?: UploadEdits
}
export const FormQueryParams = createContext(
{} as {
formQueryParams: QueryParamTypes
setFormQueryParams: (params: QueryParamTypes) => void
},
)
import type { Action, FormQueryParamsContext, State } from './types'
import { useLocale } from '../Locale'
export const FormQueryParams = createContext({} as FormQueryParamsContext)
export const FormQueryParamsProvider: React.FC<{
children: React.ReactNode
formQueryParams?: QueryParamTypes
setFormQueryParams?: (params: QueryParamTypes) => void
}> = ({ children, formQueryParams: formQueryParamsFromProps }) => {
const [formQueryParams, setFormQueryParams] = useState(
formQueryParamsFromProps || ({} as QueryParamTypes),
initialParams?: State
}> = ({ children, initialParams: formQueryParamsFromProps }) => {
const [formQueryParams, dispatchFormQueryParams] = React.useReducer(
(state: State, action: Action) => {
const newState = { ...state }
switch (action.type) {
case 'SET':
if (action.params?.uploadEdits === null && newState?.uploadEdits) {
delete newState.uploadEdits
}
if (action.params?.uploadEdits?.crop === null && newState?.uploadEdits?.crop) {
delete newState.uploadEdits.crop
}
if (
action.params?.uploadEdits?.focalPoint === null &&
newState?.uploadEdits?.focalPoint
) {
delete newState.uploadEdits.focalPoint
}
return {
...newState,
...action.params,
}
default:
return state
}
},
formQueryParamsFromProps || ({} as State),
)
const locale = useLocale()
React.useEffect(() => {
dispatchFormQueryParams({
type: 'SET',
params: {
locale: locale.code,
},
})
}, [locale.code])
return (
<FormQueryParams.Provider value={{ formQueryParams, setFormQueryParams }}>
<FormQueryParams.Provider value={{ dispatchFormQueryParams, formQueryParams }}>
{children}
</FormQueryParams.Provider>
)
}
export const useFormQueryParams = (): {
formQueryParams: QueryParamTypes
setFormQueryParams: (params: QueryParamTypes) => void
dispatchFormQueryParams: React.Dispatch<Action>
formQueryParams: State
} => useContext(FormQueryParams)

View File

@@ -0,0 +1,18 @@
import type { UploadEdits } from 'payload/types'
export type FormQueryParamsContext = {
dispatchFormQueryParams: (action: Action) => void
formQueryParams: State
}
export type State = {
depth: number
'fallback-locale': string
locale: string
uploadEdits?: UploadEdits
}
export type Action = {
params: Partial<State>
type: 'SET'
}

View File

@@ -75,7 +75,7 @@ export const LocaleProvider: React.FC<{ children?: React.ReactNode }> = ({ child
useEffect(() => {
if (searchParams?.locale) {
dispatchSearchParams({
type: 'set',
type: 'SET',
params: {
locale: searchParams.locale,
},

View File

@@ -26,16 +26,16 @@ export const SearchParamsProvider: React.FC<{ children?: React.ReactNode }> = ({
let paramsToSet
switch (action.type) {
case 'set':
case 'SET':
paramsToSet = {
...state,
...action.params,
}
break
case 'replace':
case 'REPLACE':
paramsToSet = action.params
break
case 'clear':
case 'CLEAR':
paramsToSet = {}
break
default:

View File

@@ -8,14 +8,14 @@ export type State = qs.ParsedQs
export type Action = (
| {
params: qs.ParsedQs
type: 'replace'
type: 'REPLACE'
}
| {
params: qs.ParsedQs
type: 'set'
type: 'SET'
}
| {
type: 'clear'
type: 'CLEAR'
}
) & {
/**

44
pnpm-lock.yaml generated
View File

@@ -574,6 +574,9 @@ importers:
path-to-regexp:
specifier: ^6.2.1
version: 6.2.1
qs:
specifier: 6.11.2
version: 6.11.2
react-diff-viewer-continued:
specifier: 3.2.6
version: 3.2.6(react-dom@18.2.0)(react@18.2.0)
@@ -1330,7 +1333,7 @@ importers:
version: 2.3.0
next:
specifier: 14.1.1-canary.26
version: 14.1.1-canary.26(@babel/core@7.24.0)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.1)
version: 14.1.1-canary.26(@babel/core@7.24.0)(react-dom@18.2.0)(react@18.2.0)
object-to-formdata:
specifier: 4.5.1
version: 4.5.1
@@ -13512,6 +13515,45 @@ packages:
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
dev: false
/next@14.1.1-canary.26(@babel/core@7.24.0)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-vHj7hCL9qn8AhRXNEC1ujTO55w3IjckEE1tkmxwyqA3ypTH9PtxSnU6eFfC9C67Xf/Q2C5Btug7Yqvw7pxGkhg==}
engines: {node: '>=18.17.0'}
hasBin: true
peerDependencies:
'@opentelemetry/api': ^1.1.0
react: ^18.2.0
react-dom: ^18.2.0
sass: ^1.3.0
peerDependenciesMeta:
'@opentelemetry/api':
optional: true
sass:
optional: true
dependencies:
'@next/env': 14.1.1-canary.26
'@swc/helpers': 0.5.2
busboy: 1.6.0
caniuse-lite: 1.0.30001591
graceful-fs: 4.2.11
postcss: 8.4.31
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
styled-jsx: 5.1.1(@babel/core@7.24.0)(react@18.2.0)
optionalDependencies:
'@next/swc-darwin-arm64': 14.1.1-canary.26
'@next/swc-darwin-x64': 14.1.1-canary.26
'@next/swc-linux-arm64-gnu': 14.1.1-canary.26
'@next/swc-linux-arm64-musl': 14.1.1-canary.26
'@next/swc-linux-x64-gnu': 14.1.1-canary.26
'@next/swc-linux-x64-musl': 14.1.1-canary.26
'@next/swc-win32-arm64-msvc': 14.1.1-canary.26
'@next/swc-win32-ia32-msvc': 14.1.1-canary.26
'@next/swc-win32-x64-msvc': 14.1.1-canary.26
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
dev: false
/next@14.1.1-canary.26(@babel/core@7.24.0)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.1):
resolution: {integrity: sha512-vHj7hCL9qn8AhRXNEC1ujTO55w3IjckEE1tkmxwyqA3ypTH9PtxSnU6eFfC9C67Xf/Q2C5Btug7Yqvw7pxGkhg==}
engines: {node: '>=18.17.0'}

View File

@@ -4,7 +4,27 @@ export const mediaSlug = 'media'
export const MediaCollection: CollectionConfig = {
slug: mediaSlug,
upload: true,
upload: {
crop: true,
focalPoint: true,
imageSizes: [
{
name: 'thumbnail',
width: 200,
height: 200,
},
{
name: 'medium',
width: 800,
height: 800,
},
{
name: 'large',
width: 1200,
height: 1200,
},
],
},
access: {
read: () => true,
create: () => true,