chore: adjusts endpoint for buildFormState
This commit is contained in:
@@ -8,4 +8,5 @@ export default ({ params, searchParams }) =>
|
||||
collectionSlug: params.collection,
|
||||
searchParams,
|
||||
config,
|
||||
route: `/${params.collection + '/' + params.segments?.join('/')}`,
|
||||
})
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
import { Dashboard } from '@payloadcms/next/pages/Dashboard'
|
||||
import config from 'payload-config'
|
||||
|
||||
export default async () => Dashboard({ config })
|
||||
export default async ({ searchParams }) => Dashboard({ config, searchParams })
|
||||
|
||||
@@ -22,6 +22,8 @@ export const Account = async ({
|
||||
const { config, payload, permissions, user, i18n, locale } = await initPage({
|
||||
config: configPromise,
|
||||
redirectUnauthenticatedUser: true,
|
||||
searchParams,
|
||||
route: `/account`,
|
||||
})
|
||||
|
||||
const {
|
||||
|
||||
@@ -15,15 +15,19 @@ export const CollectionList = async ({
|
||||
collectionSlug,
|
||||
config: configPromise,
|
||||
searchParams,
|
||||
route,
|
||||
}: {
|
||||
collectionSlug: string
|
||||
config: Promise<SanitizedConfig>
|
||||
searchParams: { [key: string]: string | string[] | undefined }
|
||||
route
|
||||
}) => {
|
||||
const { config, payload, permissions, user, collectionConfig } = await initPage({
|
||||
config: configPromise,
|
||||
redirectUnauthenticatedUser: true,
|
||||
collectionSlug,
|
||||
route,
|
||||
searchParams,
|
||||
})
|
||||
|
||||
let listPreferences: ListPreferences
|
||||
|
||||
@@ -7,12 +7,16 @@ import { DefaultDashboard } from './Default'
|
||||
|
||||
export const Dashboard = async ({
|
||||
config: configPromise,
|
||||
searchParams,
|
||||
}: {
|
||||
config: Promise<SanitizedConfig>
|
||||
searchParams: { [key: string]: string | string[] | undefined }
|
||||
}) => {
|
||||
const { config, user, permissions } = await initPage({
|
||||
config: configPromise,
|
||||
redirectUnauthenticatedUser: true,
|
||||
route: '',
|
||||
searchParams,
|
||||
})
|
||||
|
||||
const CustomDashboardComponent = config.admin.components?.views?.Dashboard
|
||||
|
||||
@@ -43,12 +43,16 @@ export const Document = async ({
|
||||
|
||||
const isEditing = Boolean(globalSlug || (collectionSlug && !!id))
|
||||
|
||||
const route = `/${collectionSlug || globalSlug + '/' + params.segments.join('/')}`
|
||||
|
||||
const { config, payload, permissions, user, collectionConfig, globalConfig, locale, i18n } =
|
||||
await initPage({
|
||||
config: configPromise,
|
||||
redirectUnauthenticatedUser: true,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
searchParams,
|
||||
route,
|
||||
})
|
||||
|
||||
if (!collectionConfig && !globalConfig) {
|
||||
@@ -149,7 +153,7 @@ export const Document = async ({
|
||||
limit: 1,
|
||||
})) as any as { docs: { value: DocumentPreferences }[] }
|
||||
|
||||
const formState = await buildStateFromSchema({
|
||||
const initialState = await buildStateFromSchema({
|
||||
id,
|
||||
data: data || {},
|
||||
fieldSchema: formatFields(fields, isEditing),
|
||||
@@ -176,13 +180,11 @@ export const Document = async ({
|
||||
globalSlug,
|
||||
data,
|
||||
hasSavePermission,
|
||||
formState,
|
||||
initialState,
|
||||
isEditing,
|
||||
docPermissions,
|
||||
docPreferences,
|
||||
updatedAt: data?.updatedAt?.toString(),
|
||||
user,
|
||||
locale,
|
||||
payload,
|
||||
config,
|
||||
searchParams,
|
||||
|
||||
@@ -35,7 +35,7 @@ export const Login: React.FC<{
|
||||
config: Promise<SanitizedConfig>
|
||||
searchParams: { [key: string]: string | string[] | undefined }
|
||||
}> = async ({ config: configPromise, searchParams }) => {
|
||||
const { config, user } = await initPage({ config: configPromise })
|
||||
const { config, user } = await initPage({ config: configPromise, searchParams, route: '/login' })
|
||||
|
||||
const {
|
||||
admin: { components: { afterLogin, beforeLogin } = {}, user: userSlug },
|
||||
|
||||
67
packages/next/src/routes/rest/buildFormState.ts
Normal file
67
packages/next/src/routes/rest/buildFormState.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import {
|
||||
BuildFormStateArgs,
|
||||
FieldSchemaMap,
|
||||
buildFieldSchemaMap,
|
||||
buildStateFromSchema,
|
||||
reduceFieldsToValues,
|
||||
} from '@payloadcms/ui'
|
||||
import { Field, PayloadRequest, SanitizedConfig } from 'payload/types'
|
||||
|
||||
let cached = global._payload_fieldSchemaMap
|
||||
|
||||
if (!cached) {
|
||||
// eslint-disable-next-line no-multi-assign
|
||||
cached = global._payload_fieldSchemaMap = null
|
||||
}
|
||||
|
||||
export const getFieldSchemaMap = (config: SanitizedConfig): FieldSchemaMap => {
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
|
||||
cached = buildFieldSchemaMap(config)
|
||||
|
||||
return cached
|
||||
}
|
||||
|
||||
export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
|
||||
const { data: reqData, user, t, locale } = req
|
||||
|
||||
// TODO: run ADMIN access control for user
|
||||
|
||||
const fieldSchemaMap = getFieldSchemaMap(req.payload.config)
|
||||
|
||||
const { id, operation, docPreferences, formState, schemaPath } = reqData as BuildFormStateArgs
|
||||
const schemaPathSegments = schemaPath.split('.')
|
||||
|
||||
let fieldSchema: Field[]
|
||||
|
||||
if (schemaPathSegments.length === 1) {
|
||||
if (req.payload.collections[schemaPath]) {
|
||||
fieldSchema = req.payload.collections[schemaPath].config.fields
|
||||
} else if (req.payload.globals[schemaPath]) {
|
||||
fieldSchema = req.payload.globals[schemaPath].config.fields
|
||||
}
|
||||
} else if (fieldSchemaMap.has(schemaPath)) {
|
||||
fieldSchema = fieldSchemaMap.get(schemaPath)
|
||||
}
|
||||
|
||||
const data = reduceFieldsToValues(formState, true)
|
||||
|
||||
const result = await buildStateFromSchema({
|
||||
id,
|
||||
data,
|
||||
fieldSchema,
|
||||
locale,
|
||||
operation,
|
||||
preferences: docPreferences,
|
||||
t,
|
||||
user,
|
||||
})
|
||||
|
||||
return Response.json(result, {
|
||||
status: httpStatus.OK,
|
||||
})
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from './types'
|
||||
|
||||
import { RouteError } from './RouteError'
|
||||
import { buildFormState } from './buildFormState'
|
||||
import { endpointsAreDisabled } from './checkEndpoints'
|
||||
|
||||
import { me } from './auth/me'
|
||||
@@ -50,6 +51,9 @@ const endpoints = {
|
||||
GET: {
|
||||
access,
|
||||
},
|
||||
POST: {
|
||||
'form-state': buildFormState,
|
||||
},
|
||||
},
|
||||
collection: {
|
||||
GET: {
|
||||
@@ -295,6 +299,7 @@ export const POST =
|
||||
config,
|
||||
params: { collection: slug1 },
|
||||
})
|
||||
|
||||
collection = req.payload.collections?.[slug1]
|
||||
|
||||
const disableEndpoints = endpointsAreDisabled({
|
||||
@@ -315,6 +320,7 @@ export const POST =
|
||||
payloadRequest: req,
|
||||
endpoints: collection.config.endpoints,
|
||||
})
|
||||
|
||||
if (customEndpointResponse) return customEndpointResponse
|
||||
|
||||
switch (slug.length) {
|
||||
@@ -324,6 +330,7 @@ export const POST =
|
||||
break
|
||||
case 2:
|
||||
if (slug2 in endpoints.collection.POST) {
|
||||
// /:collection/form-state
|
||||
// /:collection/login
|
||||
// /:collection/logout
|
||||
// /:collection/unlock
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { headers as getHeaders } from 'next/headers'
|
||||
import qs from 'qs'
|
||||
|
||||
import { auth } from './auth'
|
||||
|
||||
@@ -22,12 +23,16 @@ export const initPage = async ({
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
localeParam,
|
||||
searchParams,
|
||||
route,
|
||||
}: {
|
||||
config: SanitizedConfig | Promise<SanitizedConfig>
|
||||
redirectUnauthenticatedUser?: boolean
|
||||
collectionSlug?: string
|
||||
globalSlug?: string
|
||||
localeParam?: string
|
||||
searchParams?: { [key: string]: string | string[] | undefined }
|
||||
route?: string
|
||||
}): Promise<{
|
||||
payload: Awaited<ReturnType<typeof getPayload>>
|
||||
permissions: Awaited<ReturnType<typeof auth>>['permissions']
|
||||
@@ -46,15 +51,18 @@ export const initPage = async ({
|
||||
config: configPromise,
|
||||
cookies,
|
||||
})
|
||||
|
||||
const language = getRequestLanguage({ cookies, headers })
|
||||
|
||||
const config = await configPromise
|
||||
|
||||
const { localization, routes, collections, globals } = config
|
||||
|
||||
if (redirectUnauthenticatedUser && !user) {
|
||||
// `redirect(`${payload.config.routes.admin}/unauthorized`)` is not built yet
|
||||
redirect(`${routes.admin}/login`)
|
||||
if (redirectUnauthenticatedUser && !user && route !== '/login') {
|
||||
const stringifiedSearchParams = Object.keys(searchParams ?? {}).length
|
||||
? `?${qs.stringify(searchParams)}`
|
||||
: ''
|
||||
redirect(`${routes.admin}/login?redirect=${routes.admin + route + stringifiedSearchParams}`)
|
||||
}
|
||||
|
||||
const payload = await getPayload({
|
||||
|
||||
@@ -25,6 +25,9 @@ import IDLabel from '../IDLabel'
|
||||
import type { EditViewProps } from '../../views/types'
|
||||
import { DefaultEditView } from '../../views/Edit'
|
||||
import { Gutter } from '../Gutter'
|
||||
import { LoadingOverlay } from '../Loading'
|
||||
import { getFormState } from '../../views/Edit/getFormState'
|
||||
import { useFieldPath } from '../../forms/FieldPathProvider'
|
||||
|
||||
const Content: React.FC<DocumentDrawerProps> = ({ collectionSlug, Header, drawerSlug, onSave }) => {
|
||||
const config = useConfig()
|
||||
@@ -37,7 +40,7 @@ const Content: React.FC<DocumentDrawerProps> = ({ collectionSlug, Header, drawer
|
||||
const { closeModal, modalState, toggleModal } = useModal()
|
||||
const locale = useLocale()
|
||||
const { user } = useAuth()
|
||||
const [internalState, setInternalState] = useState<FormState>()
|
||||
const [initialState, setInitialState] = useState<FormState>()
|
||||
const { i18n, t } = useTranslation()
|
||||
const hasInitializedState = useRef(false)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
@@ -48,6 +51,7 @@ const Content: React.FC<DocumentDrawerProps> = ({ collectionSlug, Header, drawer
|
||||
const { admin: { components: { views: { Edit } = {} } = {} } = {}, fields: fieldsFromConfig } =
|
||||
collectionConfig
|
||||
|
||||
const { schemaPath } = useFieldPath()
|
||||
const { id, docPermissions } = useDocumentInfo()
|
||||
|
||||
// The component definition could come from multiple places in the config
|
||||
@@ -105,7 +109,33 @@ const Content: React.FC<DocumentDrawerProps> = ({ collectionSlug, Header, drawer
|
||||
(isEditing && docPermissions?.update?.permission) ||
|
||||
(!isEditing && (docPermissions as CollectionPermission)?.create?.permission)
|
||||
|
||||
const isLoading = !internalState || !docPermissions || isLoadingDocument
|
||||
useEffect(() => {
|
||||
if (!hasInitializedState.current && data) {
|
||||
const getInitialState = async () => {
|
||||
const result = await getFormState({
|
||||
serverURL,
|
||||
apiRoute: api,
|
||||
body: {
|
||||
id,
|
||||
operation: isEditing ? 'update' : 'create',
|
||||
formState: data,
|
||||
docPreferences: null, // TODO: get this
|
||||
schemaPath,
|
||||
},
|
||||
})
|
||||
|
||||
setInitialState(result)
|
||||
}
|
||||
|
||||
getInitialState()
|
||||
}
|
||||
}, [])
|
||||
|
||||
const isLoading = !initialState || !docPermissions || isLoadingDocument
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingOverlay />
|
||||
}
|
||||
|
||||
const componentProps: EditViewProps = {
|
||||
id,
|
||||
@@ -144,11 +174,10 @@ const Content: React.FC<DocumentDrawerProps> = ({ collectionSlug, Header, drawer
|
||||
onSave,
|
||||
collectionSlug: collectionConfig.slug,
|
||||
docPermissions: docPermissions as CollectionPermission,
|
||||
docPreferences: null,
|
||||
docPreferences: null, // TODO: get this
|
||||
user,
|
||||
updatedAt: data?.updatedAt,
|
||||
locale,
|
||||
initializeFormState: true,
|
||||
initialState,
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -28,3 +28,4 @@ export { default as buildInitialState } from '../forms/Form'
|
||||
export { default as FieldDescription } from '../forms/FieldDescription'
|
||||
export { default as useField } from '../forms/useField'
|
||||
export { default as Error } from '../forms/Error'
|
||||
export type { BuildFormStateArgs } from '../forms/utilities/buildStateFromSchema'
|
||||
|
||||
@@ -5,3 +5,5 @@ export type { EntityToGroup, Group } from '../utilities/groupNavItems'
|
||||
export { EntityType, groupNavItems } from '../utilities/groupNavItems'
|
||||
export { withMergedProps } from '../utilities/withMergedProps'
|
||||
export type { FieldMap, MappedField } from '../utilities/buildComponentMap/types'
|
||||
export { buildFieldSchemaMap } from '../utilities/buildFieldSchemaMap'
|
||||
export type { FieldSchemaMap } from '../utilities/buildFieldSchemaMap/types'
|
||||
|
||||
@@ -16,7 +16,6 @@ export const FieldPathProvider: React.FC<{
|
||||
children: React.ReactNode
|
||||
}> = (props) => {
|
||||
const { children, path, schemaPath } = props
|
||||
|
||||
return (
|
||||
<FieldPathContext.Provider
|
||||
value={{
|
||||
|
||||
@@ -39,7 +39,6 @@ import getSiblingDataFunc from './getSiblingData'
|
||||
import initContextState from './initContextState'
|
||||
import reduceFieldsToValues from './reduceFieldsToValues'
|
||||
import useDebounce from '../../hooks/useDebounce'
|
||||
import { FieldPathProvider } from '../FieldPathProvider'
|
||||
|
||||
const baseClass = 'form'
|
||||
|
||||
@@ -514,7 +513,7 @@ const Form: React.FC<Props> = (props) => {
|
||||
<ProcessingContext.Provider value={processing}>
|
||||
<ModifiedContext.Provider value={modified}>
|
||||
<FormFieldsContext.Provider value={fieldsReducer}>
|
||||
<FieldPathProvider path="">{children}</FieldPathProvider>
|
||||
{children}
|
||||
</FormFieldsContext.Provider>
|
||||
</ModifiedContext.Provider>
|
||||
</ProcessingContext.Provider>
|
||||
|
||||
@@ -39,6 +39,7 @@ type BlockFieldProps = UseDraggableSortableReturn & {
|
||||
path: string
|
||||
labels: Labels
|
||||
permissions: FieldPermissions
|
||||
schemaPath: string
|
||||
}
|
||||
|
||||
export const BlockRow: React.FC<BlockFieldProps> = ({
|
||||
@@ -54,6 +55,7 @@ export const BlockRow: React.FC<BlockFieldProps> = ({
|
||||
listeners,
|
||||
moveRow,
|
||||
path: parentPath,
|
||||
schemaPath,
|
||||
permissions,
|
||||
readOnly,
|
||||
removeRow,
|
||||
@@ -132,7 +134,7 @@ export const BlockRow: React.FC<BlockFieldProps> = ({
|
||||
onToggle={(collapsed) => setCollapse(row.id, collapsed)}
|
||||
>
|
||||
<HiddenInput name={`${path}.id`} value={row.id} />
|
||||
<FieldPathProvider path={path} schemaPath={`${parentPath}.${block.slug}`}>
|
||||
<FieldPathProvider path={path} schemaPath={`${schemaPath}.${block.slug}`}>
|
||||
<RenderFields
|
||||
className={`${baseClass}__fields`}
|
||||
fieldMap={block.subfields}
|
||||
|
||||
@@ -96,6 +96,7 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
valid,
|
||||
value,
|
||||
path,
|
||||
schemaPath,
|
||||
} = useField<number>({
|
||||
hasRows: true,
|
||||
path: pathFromProps || name,
|
||||
@@ -248,6 +249,7 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
labels={labels}
|
||||
moveRow={moveRow}
|
||||
path={path}
|
||||
schemaPath={schemaPath}
|
||||
permissions={permissions}
|
||||
readOnly={readOnly}
|
||||
removeRow={removeRow}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { TFunction } from '@payloadcms/translations'
|
||||
|
||||
import type { User } from 'payload/auth'
|
||||
import type { Field as FieldSchema, Data } from 'payload/types'
|
||||
import type { Field as FieldSchema, Data, DocumentPreferences } from 'payload/types'
|
||||
import type { FormState } from '../../Form/types'
|
||||
|
||||
import { iterateFields } from './iterateFields'
|
||||
@@ -21,6 +21,14 @@ type Args = {
|
||||
user?: User | null
|
||||
}
|
||||
|
||||
export type BuildFormStateArgs = {
|
||||
id?: string | number
|
||||
operation?: 'create' | 'update'
|
||||
docPreferences: DocumentPreferences
|
||||
formState?: FormState
|
||||
schemaPath: string
|
||||
}
|
||||
|
||||
const buildStateFromSchema = async (args: Args): Promise<FormState> => {
|
||||
const { id, data: fullData = {}, fieldSchema, locale, operation, preferences, t, user } = args
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { AuthContext } from './types'
|
||||
import { requests } from '../../utilities/api'
|
||||
import useDebounce from '../../hooks/useDebounce'
|
||||
import { useConfig } from '../Config'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
// import { useLocale } from '../Locale'
|
||||
|
||||
const Context = createContext({} as AuthContext)
|
||||
@@ -19,6 +19,7 @@ const Context = createContext({} as AuthContext)
|
||||
const maxTimeoutTime = 2147483647
|
||||
|
||||
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const searchParams = useSearchParams()
|
||||
const [user, setUser] = useState<User | null>()
|
||||
const [tokenInMemory, setTokenInMemory] = useState<string>()
|
||||
const [tokenExpiration, setTokenExpiration] = useState<number>()
|
||||
@@ -209,6 +210,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
if (autoLoginJson?.token) {
|
||||
setTokenAndExpiration(autoLoginJson)
|
||||
}
|
||||
push(searchParams.get('redirect') || admin)
|
||||
} else {
|
||||
setUser(null)
|
||||
revokeTokenAndExpire()
|
||||
|
||||
25
packages/ui/src/utilities/buildFieldSchemaMap/index.ts
Normal file
25
packages/ui/src/utilities/buildFieldSchemaMap/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { SanitizedConfig } from 'payload/types'
|
||||
import { FieldSchemaMap } from './types'
|
||||
import { traverseFields } from './traverseFields'
|
||||
|
||||
export const buildFieldSchemaMap = (config: SanitizedConfig): FieldSchemaMap => {
|
||||
const result: FieldSchemaMap = new Map()
|
||||
|
||||
config.collections.forEach((collection) => {
|
||||
traverseFields({
|
||||
schemaPath: collection.slug,
|
||||
fields: collection.fields,
|
||||
schemaMap: result,
|
||||
})
|
||||
})
|
||||
|
||||
config.globals.forEach((global) => {
|
||||
traverseFields({
|
||||
schemaPath: global.slug,
|
||||
fields: global.fields,
|
||||
schemaMap: result,
|
||||
})
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Field, tabHasName } from 'payload/types'
|
||||
import { FieldSchemaMap } from './types'
|
||||
|
||||
type Args = {
|
||||
fields: Field[]
|
||||
schemaMap: FieldSchemaMap
|
||||
schemaPath: string
|
||||
}
|
||||
|
||||
export const traverseFields = ({ fields, schemaMap, schemaPath }: Args) => {
|
||||
fields.map((field) => {
|
||||
switch (field.type) {
|
||||
case 'group':
|
||||
case 'array':
|
||||
traverseFields({
|
||||
fields: field.fields,
|
||||
schemaMap,
|
||||
schemaPath: `${schemaPath}.${field.name}`,
|
||||
})
|
||||
break
|
||||
|
||||
case 'collapsible':
|
||||
case 'row':
|
||||
traverseFields({
|
||||
fields: field.fields,
|
||||
schemaMap,
|
||||
schemaPath,
|
||||
})
|
||||
break
|
||||
|
||||
case 'blocks':
|
||||
field.blocks.map((block) => {
|
||||
traverseFields({
|
||||
fields: block.fields,
|
||||
schemaMap,
|
||||
schemaPath: `${schemaPath}.${field.name}.${block.slug}`,
|
||||
})
|
||||
})
|
||||
break
|
||||
|
||||
case 'tabs':
|
||||
field.tabs.map((tab) => {
|
||||
const tabSchemaPath = tabHasName(tab) ? `${schemaPath}.${tab.name}` : schemaPath
|
||||
traverseFields({
|
||||
fields: tab.fields,
|
||||
schemaMap,
|
||||
schemaPath: tabSchemaPath,
|
||||
})
|
||||
})
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
3
packages/ui/src/utilities/buildFieldSchemaMap/types.ts
Normal file
3
packages/ui/src/utilities/buildFieldSchemaMap/types.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Field } from 'payload/types'
|
||||
|
||||
export type FieldSchemaMap = Map<string, Field[]>
|
||||
@@ -1,63 +0,0 @@
|
||||
'use server'
|
||||
|
||||
import { getPayload } from 'payload'
|
||||
import { FormState } from '../../forms/Form/types'
|
||||
import configPromise from 'payload-config'
|
||||
import buildStateFromSchema from '../../forms/utilities/buildStateFromSchema'
|
||||
import { reduceFieldsToValues } from '../..'
|
||||
import { DocumentPreferences } from 'payload/types'
|
||||
import { Locale } from 'payload/config'
|
||||
import { User } from 'payload/auth'
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { translations } from '@payloadcms/translations/api'
|
||||
|
||||
export const getFormStateFromServer = async (
|
||||
args: {
|
||||
collectionSlug: string
|
||||
docPreferences: DocumentPreferences
|
||||
locale: Locale
|
||||
id?: string
|
||||
operation: 'create' | 'update'
|
||||
user: User
|
||||
language: string
|
||||
},
|
||||
{
|
||||
formState,
|
||||
}: {
|
||||
formState: FormState
|
||||
},
|
||||
) => {
|
||||
const { collectionSlug, docPreferences, locale, id, operation, user, language } = args
|
||||
|
||||
const payload = await getPayload({
|
||||
config: configPromise,
|
||||
})
|
||||
|
||||
const collectionConfig = payload.collections[collectionSlug]?.config
|
||||
|
||||
if (!collectionConfig) {
|
||||
throw new Error(`Collection with slug "${collectionSlug}" not found`)
|
||||
}
|
||||
|
||||
const data = reduceFieldsToValues(formState, true)
|
||||
|
||||
const { t } = await initI18n({
|
||||
translations,
|
||||
language: language,
|
||||
config: payload.config.i18n,
|
||||
context: 'api',
|
||||
})
|
||||
|
||||
const result = await buildStateFromSchema({
|
||||
id,
|
||||
data,
|
||||
fieldSchema: collectionConfig.fields,
|
||||
locale: locale.code,
|
||||
operation,
|
||||
preferences: docPreferences,
|
||||
t,
|
||||
user,
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
26
packages/ui/src/views/Edit/getFormState.ts
Normal file
26
packages/ui/src/views/Edit/getFormState.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { SanitizedConfig } from 'payload/types'
|
||||
import { FormState } from '../../forms/Form/types'
|
||||
import { BuildFormStateArgs } from '../..'
|
||||
|
||||
export const getFormState = async (args: {
|
||||
serverURL: SanitizedConfig['serverURL']
|
||||
apiRoute: SanitizedConfig['routes']['api']
|
||||
body: BuildFormStateArgs
|
||||
}): Promise<FormState> => {
|
||||
const { serverURL, apiRoute, body } = args
|
||||
|
||||
const res = await fetch(`${serverURL}${apiRoute}/form-state`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
const json = (await res.json()) as FormState
|
||||
return json
|
||||
}
|
||||
|
||||
return body?.formState
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import React, { Fragment, useCallback, useState } from 'react'
|
||||
import React, { Fragment, useCallback } from 'react'
|
||||
|
||||
import { FormLoadingOverlayToggle } from '../../elements/Loading'
|
||||
import Form from '../../forms/Form'
|
||||
@@ -9,18 +9,19 @@ import { OperationProvider } from '../../providers/OperationProvider'
|
||||
import { DocumentControls } from '../../elements/DocumentControls'
|
||||
import { DocumentFields } from '../../elements/DocumentFields'
|
||||
import { LeaveWithoutSaving } from '../../elements/LeaveWithoutSaving'
|
||||
// import Meta from '../../../../utilities/Meta'
|
||||
import Auth from './Auth'
|
||||
import { SetStepNav } from './SetStepNav'
|
||||
import { EditViewProps } from '../types'
|
||||
import { getFormStateFromServer } from './action'
|
||||
import { Upload } from './Upload'
|
||||
import { useConfig } from '../../providers/Config'
|
||||
import { useTranslation } from '../../providers/Translation'
|
||||
import { useComponentMap } from '../../providers/ComponentMapProvider'
|
||||
import { SetDocumentTitle } from './SetDocumentTitle'
|
||||
import { Props as FormProps, FormState } from '../../forms/Form/types'
|
||||
|
||||
import './index.scss'
|
||||
import { BuildFormStateArgs } from '../..'
|
||||
import { getFormState } from './getFormState'
|
||||
import { FieldPathProvider } from '../../forms/FieldPathProvider'
|
||||
|
||||
const baseClass = 'collection-edit'
|
||||
|
||||
@@ -32,20 +33,22 @@ export const DefaultEditView: React.FC<EditViewProps> = (props) => {
|
||||
AfterDocument,
|
||||
AfterFields,
|
||||
data,
|
||||
formState: initialStateFromProps,
|
||||
initializeFormState,
|
||||
initialState,
|
||||
// isLoading,
|
||||
onSave: onSaveFromProps,
|
||||
docPreferences,
|
||||
docPermissions,
|
||||
docPreferences,
|
||||
user,
|
||||
locale,
|
||||
} = props
|
||||
|
||||
const config = useConfig()
|
||||
const { collections, globals } = config
|
||||
const {
|
||||
serverURL,
|
||||
collections,
|
||||
globals,
|
||||
routes: { api: apiRoute },
|
||||
} = config
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
const { getFieldMap } = useComponentMap()
|
||||
|
||||
const collectionConfig =
|
||||
@@ -55,6 +58,8 @@ export const DefaultEditView: React.FC<EditViewProps> = (props) => {
|
||||
const globalConfig =
|
||||
'globalSlug' in props && globals.find((global) => global.slug === props.globalSlug)
|
||||
|
||||
const [schemaPath] = React.useState(collectionConfig?.slug || globalConfig?.slug)
|
||||
|
||||
const fieldMap = getFieldMap({
|
||||
collectionSlug: collectionConfig?.slug,
|
||||
globalSlug: globalConfig?.slug,
|
||||
@@ -64,22 +69,6 @@ export const DefaultEditView: React.FC<EditViewProps> = (props) => {
|
||||
const isEditing = 'isEditing' in props ? props.isEditing : undefined
|
||||
const operation = isEditing ? 'update' : 'create'
|
||||
|
||||
const [initialState] = useState(() => {
|
||||
if (initializeFormState) {
|
||||
const initializedState = getFormStateFromServer.bind(null, {
|
||||
collectionSlug: collectionConfig?.slug,
|
||||
id: id || undefined,
|
||||
locale,
|
||||
language: i18n.language,
|
||||
operation,
|
||||
docPreferences,
|
||||
user,
|
||||
})({ formState: {} })
|
||||
|
||||
return initializedState
|
||||
} else return initialStateFromProps
|
||||
})
|
||||
|
||||
const auth = collectionConfig ? collectionConfig.auth : undefined
|
||||
const upload = collectionConfig ? collectionConfig.upload : undefined
|
||||
const hasSavePermission = 'hasSavePermission' in props ? props.hasSavePermission : undefined
|
||||
@@ -137,41 +126,56 @@ export const DefaultEditView: React.FC<EditViewProps> = (props) => {
|
||||
// setViewActions(defaultActions)
|
||||
// }, [id, location.pathname, collectionConfig?.admin?.components?.views?.Edit, setViewActions])
|
||||
|
||||
const rebuildFormState = getFormStateFromServer.bind(null, {
|
||||
collectionSlug: collectionConfig?.slug,
|
||||
id: id || undefined,
|
||||
locale,
|
||||
language: i18n.language,
|
||||
operation,
|
||||
docPreferences,
|
||||
user,
|
||||
})
|
||||
const onChange: FormProps['onChange'][0] = useCallback(
|
||||
async ({ formState: prevFormState }) =>
|
||||
getFormState({
|
||||
serverURL,
|
||||
apiRoute,
|
||||
body: {
|
||||
id,
|
||||
operation,
|
||||
formState: prevFormState,
|
||||
docPreferences,
|
||||
schemaPath,
|
||||
},
|
||||
}),
|
||||
[
|
||||
serverURL,
|
||||
apiRoute,
|
||||
collectionConfig,
|
||||
globalConfig,
|
||||
id,
|
||||
operation,
|
||||
docPreferences,
|
||||
schemaPath,
|
||||
],
|
||||
)
|
||||
|
||||
return (
|
||||
<main className={classes}>
|
||||
<OperationProvider operation={operation}>
|
||||
<Form
|
||||
action={action}
|
||||
className={`${baseClass}__form`}
|
||||
disabled={!hasSavePermission}
|
||||
initialState={initialState}
|
||||
method={id ? 'PATCH' : 'POST'}
|
||||
beforeSubmit={[rebuildFormState]}
|
||||
onChange={[rebuildFormState]}
|
||||
onSuccess={onSave}
|
||||
>
|
||||
<FormLoadingOverlayToggle
|
||||
action={operation}
|
||||
// formIsLoading={isLoading}
|
||||
// loadingSuffix={getTranslation(collectionConfig.labels.singular, i18n)}
|
||||
name={`collection-edit--${
|
||||
typeof collectionConfig?.labels?.singular === 'string'
|
||||
? collectionConfig.labels.singular
|
||||
: 'document'
|
||||
}`}
|
||||
type="withoutNav"
|
||||
/>
|
||||
{/* <Meta
|
||||
<FieldPathProvider path="" schemaPath={schemaPath}>
|
||||
<OperationProvider operation={operation}>
|
||||
<Form
|
||||
action={action}
|
||||
className={`${baseClass}__form`}
|
||||
disabled={!hasSavePermission}
|
||||
initialState={initialState}
|
||||
method={id ? 'PATCH' : 'POST'}
|
||||
onChange={[onChange]}
|
||||
onSuccess={onSave}
|
||||
>
|
||||
<FormLoadingOverlayToggle
|
||||
action={operation}
|
||||
// formIsLoading={isLoading}
|
||||
// loadingSuffix={getTranslation(collectionConfig.labels.singular, i18n)}
|
||||
name={`collection-edit--${
|
||||
typeof collectionConfig?.labels?.singular === 'string'
|
||||
? collectionConfig.labels.singular
|
||||
: 'document'
|
||||
}`}
|
||||
type="withoutNav"
|
||||
/>
|
||||
{/* <Meta
|
||||
description={`${isEditing ? t('general:editing') : t('general:creating')} - ${getTranslation(
|
||||
collection.labels.singular,
|
||||
i18n,
|
||||
@@ -182,61 +186,62 @@ export const DefaultEditView: React.FC<EditViewProps> = (props) => {
|
||||
i18n,
|
||||
)}`}
|
||||
/> */}
|
||||
{BeforeDocument}
|
||||
{preventLeaveWithoutSaving && <LeaveWithoutSaving />}
|
||||
<SetStepNav
|
||||
collectionSlug={collectionConfig?.slug}
|
||||
globalSlug={globalConfig?.slug}
|
||||
useAsTitle={collectionConfig?.admin?.useAsTitle}
|
||||
id={id}
|
||||
isEditing={isEditing || false}
|
||||
pluralLabel={collectionConfig?.labels?.plural}
|
||||
/>
|
||||
<SetDocumentTitle
|
||||
config={config}
|
||||
collectionConfig={collectionConfig}
|
||||
globalConfig={globalConfig}
|
||||
/>
|
||||
<DocumentControls
|
||||
apiURL={apiURL}
|
||||
slug={collectionConfig?.slug}
|
||||
data={data}
|
||||
disableActions={disableActions}
|
||||
hasSavePermission={hasSavePermission}
|
||||
id={id}
|
||||
isEditing={isEditing}
|
||||
permissions={docPermissions}
|
||||
/>
|
||||
<DocumentFields
|
||||
BeforeFields={
|
||||
<Fragment>
|
||||
{auth && (
|
||||
<Auth
|
||||
className={`${baseClass}__auth`}
|
||||
collectionSlug={collectionConfig.slug}
|
||||
email={data?.email}
|
||||
operation={operation}
|
||||
readOnly={!hasSavePermission}
|
||||
requirePassword={!isEditing}
|
||||
useAPIKey={auth.useAPIKey}
|
||||
verify={auth.verify}
|
||||
/>
|
||||
)}
|
||||
{upload && (
|
||||
<Upload
|
||||
uploadConfig={upload}
|
||||
collectionSlug={collectionConfig.slug}
|
||||
initialState={initialState}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
}
|
||||
fieldMap={fieldMap}
|
||||
AfterFields={AfterFields}
|
||||
/>
|
||||
{AfterDocument}
|
||||
</Form>
|
||||
</OperationProvider>
|
||||
{BeforeDocument}
|
||||
{preventLeaveWithoutSaving && <LeaveWithoutSaving />}
|
||||
<SetStepNav
|
||||
collectionSlug={collectionConfig?.slug}
|
||||
globalSlug={globalConfig?.slug}
|
||||
useAsTitle={collectionConfig?.admin?.useAsTitle}
|
||||
id={id}
|
||||
isEditing={isEditing || false}
|
||||
pluralLabel={collectionConfig?.labels?.plural}
|
||||
/>
|
||||
<SetDocumentTitle
|
||||
config={config}
|
||||
collectionConfig={collectionConfig}
|
||||
globalConfig={globalConfig}
|
||||
/>
|
||||
<DocumentControls
|
||||
apiURL={apiURL}
|
||||
slug={collectionConfig?.slug}
|
||||
data={data}
|
||||
disableActions={disableActions}
|
||||
hasSavePermission={hasSavePermission}
|
||||
id={id}
|
||||
isEditing={isEditing}
|
||||
permissions={docPermissions}
|
||||
/>
|
||||
<DocumentFields
|
||||
BeforeFields={
|
||||
<Fragment>
|
||||
{auth && (
|
||||
<Auth
|
||||
className={`${baseClass}__auth`}
|
||||
collectionSlug={collectionConfig.slug}
|
||||
email={data?.email}
|
||||
operation={operation}
|
||||
readOnly={!hasSavePermission}
|
||||
requirePassword={!isEditing}
|
||||
useAPIKey={auth.useAPIKey}
|
||||
verify={auth.verify}
|
||||
/>
|
||||
)}
|
||||
{upload && (
|
||||
<Upload
|
||||
uploadConfig={upload}
|
||||
collectionSlug={collectionConfig.slug}
|
||||
initialState={initialState}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
}
|
||||
fieldMap={fieldMap}
|
||||
AfterFields={AfterFields}
|
||||
/>
|
||||
{AfterDocument}
|
||||
</Form>
|
||||
</OperationProvider>
|
||||
</FieldPathProvider>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { CollectionPermission, GlobalPermission, Permissions, User } from 'payload/auth'
|
||||
import type { Document, DocumentPreferences, Payload, SanitizedConfig } from 'payload/types'
|
||||
import type { FormState } from '../forms/Form/types'
|
||||
import type { Locale } from 'payload/config'
|
||||
import { I18n } from '@payloadcms/translations'
|
||||
|
||||
export type EditViewProps = (
|
||||
@@ -22,15 +21,13 @@ export type EditViewProps = (
|
||||
action?: string
|
||||
apiURL: string
|
||||
canAccessAdmin?: boolean
|
||||
data: Document
|
||||
docPreferences: DocumentPreferences
|
||||
data: Document
|
||||
// isLoading: boolean
|
||||
onSave?: (json: any) => void
|
||||
updatedAt: string
|
||||
user: User | null | undefined
|
||||
locale: Locale
|
||||
formState?: FormState
|
||||
initializeFormState?: boolean
|
||||
initialState?: FormState
|
||||
BeforeDocument?: React.ReactNode
|
||||
AfterDocument?: React.ReactNode
|
||||
AfterFields?: React.ReactNode
|
||||
|
||||
Reference in New Issue
Block a user