fix: email and username fields rendering in drawers (#7520)

Fixes https://github.com/payloadcms/payload/issues/7428

Now email and username fields are rendered with the RenderFields
component, making them behave similarly to other fields. They now appear
and can respect doc permissions, readOnly settings, etc.
This commit is contained in:
Jarrod Flesch
2024-08-05 20:18:32 -04:00
committed by GitHub
parent 5d1cc760c9
commit 442189ec48
23 changed files with 131 additions and 50 deletions

7
.vscode/launch.json vendored
View File

@@ -56,6 +56,13 @@
"request": "launch",
"type": "node-terminal"
},
{
"command": "node --no-deprecation test/dev.js login-with-username",
"cwd": "${workspaceFolder}",
"name": "Run Dev Login-With-Username",
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm run dev plugin-cloud-storage",
"cwd": "${workspaceFolder}",

View File

@@ -1,45 +1,103 @@
'use client'
import type { LoginWithUsernameOptions } from 'payload'
import type { FieldPermissions, LoginWithUsernameOptions } from 'payload'
import { EmailField, TextField, useTranslation } from '@payloadcms/ui'
import { EmailField, RenderFields, TextField, useTranslation } from '@payloadcms/ui'
import { email, username } from 'payload/shared'
import React from 'react'
type Props = {
loginWithUsername?: LoginWithUsernameOptions | false
}
export const EmailAndUsernameFields: React.FC<Props> = ({ loginWithUsername }) => {
function EmailFieldComponent(props: Props) {
const { loginWithUsername } = props
const { t } = useTranslation()
const requireEmail = !loginWithUsername || (loginWithUsername && loginWithUsername.requireEmail)
const requireUsername = loginWithUsername && loginWithUsername.requireUsername
const showEmailField =
!loginWithUsername || loginWithUsername?.requireEmail || loginWithUsername?.allowEmailLogin
if (showEmailField) {
return (
<EmailField
autoComplete="off"
label={t('general:email')}
name="email"
path="email"
required={requireEmail}
validate={email}
/>
)
}
return null
}
function UsernameFieldComponent(props: Props) {
const { loginWithUsername } = props
const { t } = useTranslation()
const requireUsername = loginWithUsername && loginWithUsername.requireUsername
const showUsernameField = Boolean(loginWithUsername)
return (
<React.Fragment>
{showEmailField && (
<EmailField
autoComplete="email"
label={t('general:email')}
name="email"
path="email"
required={requireEmail}
validate={email}
/>
)}
if (showUsernameField) {
return (
<TextField
label={t('authentication:username')}
name="username"
path="username"
required={requireUsername}
validate={username}
/>
)
}
{showUsernameField && (
<TextField
label={t('authentication:username')}
name="username"
path="username"
required={requireUsername}
validate={username}
/>
)}
</React.Fragment>
return null
}
type RenderEmailAndUsernameFieldsProps = {
className?: string
loginWithUsername?: LoginWithUsernameOptions | false
operation?: 'create' | 'update'
permissions?: {
[fieldName: string]: FieldPermissions
}
readOnly: boolean
}
export function RenderEmailAndUsernameFields(props: RenderEmailAndUsernameFieldsProps) {
const { className, loginWithUsername, operation, permissions, readOnly } = props
return (
<RenderFields
className={className}
fieldMap={[
{
name: 'email',
type: 'text',
CustomField: <EmailFieldComponent loginWithUsername={loginWithUsername} />,
cellComponentProps: null,
fieldComponentProps: { type: 'email', readOnly },
fieldIsPresentational: false,
isFieldAffectingData: true,
localized: false,
},
{
name: 'username',
type: 'text',
CustomField: <UsernameFieldComponent loginWithUsername={loginWithUsername} />,
cellComponentProps: null,
fieldComponentProps: { type: 'text', readOnly },
fieldIsPresentational: false,
isFieldAffectingData: true,
localized: false,
},
]}
forceRender
operation={operation}
path=""
permissions={permissions}
readOnly={readOnly}
schemaPath=""
/>
)
}

View File

@@ -15,7 +15,7 @@ import {
import { getFormState } from '@payloadcms/ui/shared'
import React from 'react'
import { EmailAndUsernameFields } from '../../elements/EmailAndUsername/index.js'
import { RenderEmailAndUsernameFields } from '../../elements/EmailAndUsername/index.js'
export const CreateFirstUserClient: React.FC<{
initialState: FormState
@@ -57,7 +57,12 @@ export const CreateFirstUserClient: React.FC<{
redirect={admin}
validationOperation="create"
>
<EmailAndUsernameFields loginWithUsername={loginWithUsername} />
<RenderEmailAndUsernameFields
className="emailAndUsername"
loginWithUsername={loginWithUsername}
operation="create"
readOnly={false}
/>
<PasswordField
label={t('authentication:newPassword')}
name="password"

View File

@@ -3,3 +3,7 @@
margin-bottom: var(--base);
}
}
.emailAndUsername {
margin-bottom: var(--base);
}

View File

@@ -17,7 +17,7 @@ import { toast } from 'sonner'
import type { Props } from './types.js'
import { EmailAndUsernameFields } from '../../../../elements/EmailAndUsername/index.js'
import { RenderEmailAndUsernameFields } from '../../../../elements/EmailAndUsername/index.js'
import { APIKey } from './APIKey.js'
import './index.scss'
@@ -47,7 +47,7 @@ export const Auth: React.FC<Props> = (props) => {
const dispatchFields = useFormFields((reducer) => reducer[1])
const modified = useFormModified()
const { i18n, t } = useTranslation()
const { isInitializing } = useDocumentInfo()
const { docPermissions, isInitializing } = useDocumentInfo()
const {
routes: { api },
@@ -138,7 +138,12 @@ export const Auth: React.FC<Props> = (props) => {
<div className={[baseClass, className].filter(Boolean).join(' ')}>
{!disableLocalStrategy && (
<React.Fragment>
<EmailAndUsernameFields loginWithUsername={loginWithUsername} />
<RenderEmailAndUsernameFields
loginWithUsername={loginWithUsername}
operation={operation}
permissions={docPermissions?.fields}
readOnly={readOnly}
/>
{(showPasswordFields || requirePassword) && (
<div className={`${baseClass}__changing-password`}>
<PasswordField

View File

@@ -15,7 +15,7 @@ export type ArrayFieldProps = {
name?: string
validate?: ArrayFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type ArrayFieldLabelComponent = LabelComponent<'array'>

View File

@@ -15,7 +15,7 @@ export type BlocksFieldProps = {
slug?: string
validate?: BlockFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type ReducedBlock = {
LabelComponent: Block['admin']['components']['Label']

View File

@@ -12,7 +12,7 @@ export type CheckboxFieldProps = {
path?: string
validate?: CheckboxFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type CheckboxFieldLabelComponent = LabelComponent<'checkbox'>

View File

@@ -10,7 +10,7 @@ export type CodeFieldProps = {
path?: string
validate?: CodeFieldValidation
width: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type CodeFieldLabelComponent = LabelComponent<'code'>

View File

@@ -10,7 +10,7 @@ export type DateFieldProps = {
placeholder?: DateField['admin']['placeholder'] | string
validate?: DateFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type DateFieldLabelComponent = LabelComponent<'date'>

View File

@@ -10,7 +10,7 @@ export type EmailFieldProps = {
placeholder?: EmailField['admin']['placeholder']
validate?: EmailFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type EmailFieldLabelComponent = LabelComponent<'email'>

View File

@@ -10,7 +10,7 @@ export type JSONFieldProps = {
path?: string
validate?: JSONFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type JSONFieldLabelComponent = LabelComponent<'json'>

View File

@@ -15,7 +15,7 @@ export type NumberFieldProps = {
step?: number
validate?: NumberFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type NumberFieldLabelComponent = LabelComponent<'number'>

View File

@@ -9,7 +9,7 @@ export type PointFieldProps = {
step?: number
validate?: PointFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type PointFieldLabelComponent = LabelComponent<'point'>

View File

@@ -12,7 +12,7 @@ export type RadioFieldProps = {
validate?: RadioFieldValidation
value?: string
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type OnChange<T = string> = (value: T) => void

View File

@@ -12,7 +12,7 @@ export type RelationshipFieldProps = {
sortOptions?: RelationshipField['admin']['sortOptions']
validate?: RelationshipFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type RelationshipFieldLabelComponent = LabelComponent<'relationship'>

View File

@@ -8,7 +8,7 @@ export type RichTextComponentProps = {
richTextComponentMap?: Map<string, MappedField[] | React.ReactNode>
validate?: RichTextFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type RichTextFieldLabelComponent = LabelComponent<'richText'>

View File

@@ -14,7 +14,7 @@ export type SelectFieldProps = {
validate?: SelectFieldValidation
value?: string
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type SelectFieldLabelComponent = LabelComponent<'select'>

View File

@@ -16,7 +16,7 @@ export type TextFieldProps = {
placeholder?: TextField['admin']['placeholder']
validate?: TextFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type TextFieldLabelComponent = LabelComponent<'text'>

View File

@@ -12,7 +12,7 @@ export type TextareaFieldProps = {
rows?: number
validate?: TextareaFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type TextareaFieldLabelComponent = LabelComponent<'textarea'>

View File

@@ -15,7 +15,7 @@ export type UploadFieldProps = {
relationTo?: UploadField['relationTo']
validate?: UploadFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type UploadFieldLabelComponent = LabelComponent<'upload'>

View File

@@ -1,13 +1,15 @@
'use client'
import type { TextFieldProps } from 'payload'
import type { SelectFieldValidation, TextFieldProps } from 'payload'
import { SelectField, useForm } from '@payloadcms/ui'
import React, { useEffect, useState } from 'react'
import type { SelectFieldOption } from '../../types.js'
export const DynamicFieldSelector: React.FC<TextFieldProps> = (props) => {
export const DynamicFieldSelector: React.FC<
{ validate: SelectFieldValidation } & TextFieldProps
> = (props) => {
const { fields, getDataByPath } = useForm()
const [options, setOptions] = useState<SelectFieldOption[]>([])

View File

@@ -58,7 +58,7 @@ const RichTextField: React.FC<
richTextComponentMap: Map<string, React.ReactNode>
validate?: RichTextFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
> = (props) => {
const {
name,