fix: issues creating the first user (#5986)
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
This commit is contained in:
@@ -6,16 +6,16 @@ import { useComponentMap } from '@payloadcms/ui/providers/ComponentMap'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export const CreateFirstUserFields: React.FC<{
|
export const CreateFirstUserFields: React.FC<{
|
||||||
createFirstUserFieldMap: FieldMap
|
baseAuthFieldMap: FieldMap
|
||||||
userSlug: string
|
userSlug: string
|
||||||
}> = ({ createFirstUserFieldMap, userSlug }) => {
|
}> = ({ baseAuthFieldMap, userSlug }) => {
|
||||||
const { getFieldMap } = useComponentMap()
|
const { getFieldMap } = useComponentMap()
|
||||||
|
|
||||||
const fieldMap = getFieldMap({ collectionSlug: userSlug })
|
const fieldMap = getFieldMap({ collectionSlug: userSlug })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RenderFields
|
<RenderFields
|
||||||
fieldMap={[...(createFirstUserFieldMap || []), ...(fieldMap || [])]}
|
fieldMap={[...(baseAuthFieldMap || []), ...(fieldMap || [])]}
|
||||||
operation="create"
|
operation="create"
|
||||||
path=""
|
path=""
|
||||||
readOnly={false}
|
readOnly={false}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
|
|||||||
},
|
},
|
||||||
} = initPageResult
|
} = initPageResult
|
||||||
|
|
||||||
const fields: Field[] = [
|
const baseAuthFields: Field[] = [
|
||||||
{
|
{
|
||||||
name: 'email',
|
name: 'email',
|
||||||
type: 'email',
|
type: 'email',
|
||||||
@@ -52,16 +52,29 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const ssrAuthFields = [...baseAuthFields]
|
||||||
|
|
||||||
const WithServerSideProps: WithServerSidePropsType = ({ Component, ...rest }) => {
|
const WithServerSideProps: WithServerSidePropsType = ({ Component, ...rest }) => {
|
||||||
return <WithServerSidePropsGeneric Component={Component} payload={payload} {...rest} />
|
return <WithServerSidePropsGeneric Component={Component} payload={payload} {...rest} />
|
||||||
}
|
}
|
||||||
|
|
||||||
const createFirstUserFieldMap = mapFields({
|
const userFieldSchema = config.collections.find((c) => c.slug === userSlug)
|
||||||
|
ssrAuthFields.push(...userFieldSchema.fields)
|
||||||
|
|
||||||
|
const formState = await buildStateFromSchema({
|
||||||
|
fieldSchema: ssrAuthFields,
|
||||||
|
operation: 'create',
|
||||||
|
preferences: {
|
||||||
|
fields: undefined
|
||||||
|
},
|
||||||
|
req,
|
||||||
|
})
|
||||||
|
|
||||||
|
const baseAuthFieldMap = mapFields({
|
||||||
WithServerSideProps,
|
WithServerSideProps,
|
||||||
config,
|
config,
|
||||||
fieldSchema: fields,
|
fieldSchema: baseAuthFields,
|
||||||
i18n,
|
i18n,
|
||||||
parentPath: userSlug,
|
|
||||||
}).map((field) => {
|
}).map((field) => {
|
||||||
// Transform field types for the password and confirm-password fields
|
// Transform field types for the password and confirm-password fields
|
||||||
if (field.name === 'password') {
|
if (field.name === 'password') {
|
||||||
@@ -83,13 +96,6 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
|
|||||||
return field
|
return field
|
||||||
})
|
})
|
||||||
|
|
||||||
const formState = await buildStateFromSchema({
|
|
||||||
fieldSchema: fields,
|
|
||||||
operation: 'create',
|
|
||||||
preferences: { fields: {} },
|
|
||||||
req,
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<h1>{req.t('general:welcome')}</h1>
|
<h1>{req.t('general:welcome')}</h1>
|
||||||
@@ -101,10 +107,7 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
|
|||||||
redirect={adminRoute}
|
redirect={adminRoute}
|
||||||
validationOperation="create"
|
validationOperation="create"
|
||||||
>
|
>
|
||||||
<CreateFirstUserFields
|
<CreateFirstUserFields baseAuthFieldMap={baseAuthFieldMap} userSlug={userSlug} />
|
||||||
createFirstUserFieldMap={createFirstUserFieldMap}
|
|
||||||
userSlug={userSlug}
|
|
||||||
/>
|
|
||||||
<FormSubmit>{req.t('general:create')}</FormSubmit>
|
<FormSubmit>{req.t('general:create')}</FormSubmit>
|
||||||
</Form>
|
</Form>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export const Auth: React.FC<Props> = (props) => {
|
|||||||
name="password"
|
name="password"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<ConfirmPassword disabled={readOnly} />
|
<ConfirmPassword disabled={readOnly} name="confirm-password" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export const ResetPassword: React.FC<AdminViewProps> = ({ initPageResult, params
|
|||||||
name="password"
|
name="password"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<ConfirmPassword />
|
<ConfirmPassword name="confirm-password" />
|
||||||
<HiddenInput forceUsePathFromProps name="token" value={token} />
|
<HiddenInput forceUsePathFromProps name="token" value={token} />
|
||||||
<FormSubmit>{i18n.t('authentication:resetPassword')}</FormSubmit>
|
<FormSubmit>{i18n.t('authentication:resetPassword')}</FormSubmit>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FormField } from 'payload/types'
|
import type { Description, FormField, Validate } from 'payload/types'
|
||||||
|
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
|
|
||||||
|
import type { FormFieldBase } from '../shared/index.js'
|
||||||
|
|
||||||
import { FieldError } from '../../forms/FieldError/index.js'
|
import { FieldError } from '../../forms/FieldError/index.js'
|
||||||
import { FieldLabel } from '../../forms/FieldLabel/index.js'
|
import { FieldLabel } from '../../forms/FieldLabel/index.js'
|
||||||
import { useFormFields } from '../../forms/Form/context.js'
|
import { useFormFields } from '../../forms/Form/context.js'
|
||||||
@@ -10,13 +12,36 @@ import { useField } from '../../forms/useField/index.js'
|
|||||||
import { useTranslation } from '../../providers/Translation/index.js'
|
import { useTranslation } from '../../providers/Translation/index.js'
|
||||||
import { fieldBaseClass } from '../shared/index.js'
|
import { fieldBaseClass } from '../shared/index.js'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
export type ConfirmPasswordFieldProps = FormFieldBase & {
|
||||||
export type ConfirmPasswordFieldProps = {
|
autoComplete?: string
|
||||||
|
className?: string
|
||||||
|
description?: Description
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
label?: string
|
||||||
|
name: string
|
||||||
|
path?: string
|
||||||
|
required?: boolean
|
||||||
|
style?: React.CSSProperties
|
||||||
|
validate?: Validate
|
||||||
|
width?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConfirmPassword: React.FC<ConfirmPasswordFieldProps> = (props) => {
|
export const ConfirmPassword: React.FC<ConfirmPasswordFieldProps> = (props) => {
|
||||||
const { disabled } = props
|
const {
|
||||||
|
name,
|
||||||
|
CustomError,
|
||||||
|
CustomLabel,
|
||||||
|
autoComplete,
|
||||||
|
className,
|
||||||
|
disabled,
|
||||||
|
errorProps,
|
||||||
|
label,
|
||||||
|
labelProps,
|
||||||
|
path: pathFromProps,
|
||||||
|
required,
|
||||||
|
style,
|
||||||
|
width,
|
||||||
|
} = props
|
||||||
|
|
||||||
const password = useFormFields<FormField>(([fields]) => fields.password)
|
const password = useFormFields<FormField>(([fields]) => fields.password)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -36,11 +61,8 @@ export const ConfirmPassword: React.FC<ConfirmPasswordFieldProps> = (props) => {
|
|||||||
[password, t],
|
[password, t],
|
||||||
)
|
)
|
||||||
|
|
||||||
const path = 'confirm-password'
|
const { formProcessing, path, setValue, showError, value } = useField({
|
||||||
|
path: pathFromProps || name,
|
||||||
const { setValue, showError, value } = useField({
|
|
||||||
disableFormData: true,
|
|
||||||
path,
|
|
||||||
validate,
|
validate,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -58,9 +80,9 @@ export const ConfirmPassword: React.FC<ConfirmPasswordFieldProps> = (props) => {
|
|||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
disabled={!!disabled}
|
disabled={formProcessing || disabled}
|
||||||
id="field-confirm-password"
|
id={`field-${path.replace(/\./g, '__')}`}
|
||||||
name="confirm-password"
|
name={path}
|
||||||
onChange={setValue}
|
onChange={setValue}
|
||||||
type="password"
|
type="password"
|
||||||
value={(value as string) || ''}
|
value={(value as string) || ''}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ const EmailField: React.FC<EmailFieldProps> = (props) => {
|
|||||||
|
|
||||||
const { path, setValue, showError, value } = useField({
|
const { path, setValue, showError, value } = useField({
|
||||||
path: pathFromContext || pathFromProps || name,
|
path: pathFromContext || pathFromProps || name,
|
||||||
validate: memoizedValidate,
|
validate: typeof validate === 'function' ? memoizedValidate : undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -216,29 +216,31 @@ export default buildConfigWithDefaults({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
onInit: async (payload) => {
|
onInit: async (payload) => {
|
||||||
await payload.create({
|
if (process.env.SKIP_ON_INIT !== 'true') {
|
||||||
collection: 'users',
|
await payload.create({
|
||||||
data: {
|
collection: 'users',
|
||||||
custom: 'Hello, world!',
|
data: {
|
||||||
email: devUser.email,
|
custom: 'Hello, world!',
|
||||||
password: devUser.password,
|
email: devUser.email,
|
||||||
},
|
password: devUser.password,
|
||||||
})
|
},
|
||||||
|
})
|
||||||
|
|
||||||
await payload.create({
|
await payload.create({
|
||||||
collection: 'api-keys',
|
collection: 'api-keys',
|
||||||
data: {
|
data: {
|
||||||
apiKey: uuid(),
|
apiKey: uuid(),
|
||||||
enableAPIKey: true,
|
enableAPIKey: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await payload.create({
|
await payload.create({
|
||||||
collection: 'api-keys',
|
collection: 'api-keys',
|
||||||
data: {
|
data: {
|
||||||
apiKey: uuid(),
|
apiKey: uuid(),
|
||||||
enableAPIKey: true,
|
enableAPIKey: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import type { Page } from '@playwright/test'
|
import type { Page } from '@playwright/test'
|
||||||
|
|
||||||
import { expect, test } from '@playwright/test'
|
import { expect, test } from '@playwright/test'
|
||||||
|
import { devUser } from 'credentials.js'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import { wait } from 'payload/utilities'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
import { v4 as uuid } from 'uuid'
|
||||||
|
|
||||||
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
|
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
|
||||||
import type { Config } from './payload-types.js'
|
import type { Config } from './payload-types.js'
|
||||||
|
|
||||||
import { initPageConsoleErrorCatch, login, saveDocAndAssert } from '../helpers.js'
|
import { initPageConsoleErrorCatch, saveDocAndAssert } from '../helpers.js'
|
||||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
||||||
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
||||||
import { POLL_TOPASS_TIMEOUT } from '../playwright.config.js'
|
import { POLL_TOPASS_TIMEOUT } from '../playwright.config.js'
|
||||||
@@ -18,25 +21,37 @@ const dirname = path.dirname(filename)
|
|||||||
|
|
||||||
let payload: PayloadTestSDK<Config>
|
let payload: PayloadTestSDK<Config>
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: Auth
|
|
||||||
* create first user
|
|
||||||
* unlock
|
|
||||||
* log out
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { beforeAll, describe } = test
|
const { beforeAll, describe } = test
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createFirstUser = async ({ page, serverURL }: { page: Page; serverURL: string }) => {
|
||||||
|
await page.goto(serverURL + '/admin/create-first-user')
|
||||||
|
await page.locator('#field-email').fill(devUser.email)
|
||||||
|
await page.locator('#field-password').fill(devUser.password)
|
||||||
|
await page.locator('#field-confirm-password').fill(devUser.password)
|
||||||
|
await page.locator('#field-custom').fill('Hello, world!')
|
||||||
|
|
||||||
|
await wait(500)
|
||||||
|
|
||||||
|
await page.locator('.form-submit > button').click()
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT })
|
||||||
|
.not.toContain('create-first-user')
|
||||||
|
}
|
||||||
|
|
||||||
describe('auth', () => {
|
describe('auth', () => {
|
||||||
let page: Page
|
let page: Page
|
||||||
let url: AdminUrlUtil
|
let url: AdminUrlUtil
|
||||||
let serverURL: string
|
let serverURL: string
|
||||||
let apiURL: string
|
let apiURL: string
|
||||||
|
|
||||||
|
// Allows for testing create-first-user
|
||||||
|
process.env.SKIP_ON_INIT = 'true'
|
||||||
|
|
||||||
beforeAll(async ({ browser }) => {
|
beforeAll(async ({ browser }) => {
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({ dirname }))
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({ dirname }))
|
||||||
apiURL = `${serverURL}/api`
|
apiURL = `${serverURL}/api`
|
||||||
@@ -46,11 +61,22 @@ describe('auth', () => {
|
|||||||
page = await context.newPage()
|
page = await context.newPage()
|
||||||
initPageConsoleErrorCatch(page)
|
initPageConsoleErrorCatch(page)
|
||||||
|
|
||||||
//await delayNetwork({ context, page, delay: 'Slow 4G' })
|
await createFirstUser({ page, serverURL })
|
||||||
|
|
||||||
await login({
|
await payload.create({
|
||||||
page,
|
collection: 'api-keys',
|
||||||
serverURL,
|
data: {
|
||||||
|
apiKey: uuid(),
|
||||||
|
enableAPIKey: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await payload.create({
|
||||||
|
collection: 'api-keys',
|
||||||
|
data: {
|
||||||
|
apiKey: uuid(),
|
||||||
|
enableAPIKey: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -383,9 +383,11 @@ describe('fields - relationship', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await page.goto(url.create)
|
await page.goto(url.create)
|
||||||
|
// wait for relationship options to load
|
||||||
|
const relationFilterOptionsReq = page.waitForResponse(/api\/relation-filter-true/)
|
||||||
// select relationshipMany field that relies on siblingData field above
|
// select relationshipMany field that relies on siblingData field above
|
||||||
await page.locator('#field-relationshipManyFiltered .rs__control').click()
|
await page.locator('#field-relationshipManyFiltered .rs__control').click()
|
||||||
|
await relationFilterOptionsReq
|
||||||
|
|
||||||
const options = page.locator('#field-relationshipManyFiltered .rs__menu')
|
const options = page.locator('#field-relationshipManyFiltered .rs__menu')
|
||||||
await expect(options).toContainText('truth')
|
await expect(options).toContainText('truth')
|
||||||
|
|||||||
Reference in New Issue
Block a user