fix: issues creating the first user (#5986)

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
This commit is contained in:
Paul
2024-04-24 12:30:52 -03:00
committed by GitHub
parent b723efdd3b
commit 0ede95f375
9 changed files with 123 additions and 68 deletions

View File

@@ -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}

View File

@@ -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>

View File

@@ -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>
)} )}

View File

@@ -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>

View File

@@ -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) || ''}

View File

@@ -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 (

View File

@@ -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,
}, },
}) })
}
}, },
}) })

View File

@@ -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,
},
}) })
}) })

View File

@@ -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')