diff --git a/packages/sanity/src/core/bundles/components/dialog/BundleForm.tsx b/packages/sanity/src/core/bundles/components/dialog/BundleForm.tsx index 6f0485b2cf8..d4e9a9bcfd7 100644 --- a/packages/sanity/src/core/bundles/components/dialog/BundleForm.tsx +++ b/packages/sanity/src/core/bundles/components/dialog/BundleForm.tsx @@ -1,8 +1,14 @@ /* eslint-disable i18next/no-literal-string */ import {CalendarIcon} from '@sanity/icons' -import {Box, Button, Card, Flex, Popover, Stack, Text, TextArea, TextInput} from '@sanity/ui' +import {Box, Button, Flex, Popover, Stack, Text, TextArea, TextInput} from '@sanity/ui' import {useCallback, useMemo, useState} from 'react' -import {useDateTimeFormat, useTranslation} from 'sanity' +import { + FormFieldHeaderText, + type FormNodeValidation, + useBundles, + useDateTimeFormat, + useTranslation, +} from 'sanity' import speakingurl from 'speakingurl' import {type CalendarLabels} from '../../../form/inputs/DateInputs/base/calendar/types' @@ -14,16 +20,24 @@ import {BundleIconEditorPicker, type BundleIconEditorPickerValue} from './Bundle export function BundleForm(props: { onChange: (params: Partial) => void + onError: (errorsExist: boolean) => void value: Partial }): JSX.Element { - const {onChange, value} = props + const {onChange, onError, value} = props const {title, description, icon, hue, publishAt} = value const dateFormatter = useDateTimeFormat() - const [showTitleValidation, setShowTitleValidation] = useState(false) const [showDateValidation, setShowDateValidation] = useState(false) const [showDatePicker, setShowDatePicker] = useState(false) + const [showBundleExists, setShowBundleExists] = useState(false) + const [showIsDraftPublishError, setShowIsDraftPublishError] = useState(false) + + const [isInitialRender, setIsInitialRender] = useState(true) + const {data} = useBundles() + + const [titleErrors, setTitleErrors] = useState([]) + const [dateErrors, setDateErrors] = useState([]) const publishAtDisplayValue = useMemo(() => { if (!publishAt) return '' @@ -45,16 +59,44 @@ export function BundleForm(props: { const handleBundleTitleChange = useCallback( (event: React.ChangeEvent) => { const pickedTitle = event.target.value - - if (isDraftOrPublished(pickedTitle)) { - setShowTitleValidation(true) + const pickedNameExists = + data && data.find((bundle) => bundle.name === speakingurl(pickedTitle)) + const isEmptyTitle = pickedTitle.trim() === '' && !isInitialRender + + if ( + isDraftOrPublished(pickedTitle) || + pickedNameExists || + (isEmptyTitle && !isInitialRender) + ) { + if (isEmptyTitle && !isInitialRender) { + // if the title is empty and it's not the first opening of the dialog, show an error + // TODO localize text + + setTitleErrors([{level: 'error', message: 'Bundle needs a name', path: []}]) + } + if (isDraftOrPublished(pickedTitle)) { + // if the title is 'drafts' or 'published', show an error + // TODO localize text + setTitleErrors([ + {level: 'error', message: "Title cannot be 'drafts' or 'published'", path: []}, + ]) + } + if (pickedNameExists) { + // if the bundle already exists, show an error + // TODO localize text + setTitleErrors([{level: 'error', message: 'Bundle already exists', path: []}]) + } + + onError(true) } else { - setShowTitleValidation(false) + setTitleErrors([]) + onError(false) } + setIsInitialRender(false) onChange({...value, title: pickedTitle, name: speakingurl(pickedTitle)}) }, - [onChange, value], + [data, isInitialRender, onChange, onError, value], ) const handleBundleDescriptionChange = useCallback( @@ -88,15 +130,25 @@ export function BundleForm(props: { // needs to check that the date is not invalid & not empty // in which case it can update the input value but not the actual bundle value if (new Date(event.target.value).toString() === 'Invalid Date' && dateValue !== '') { - setShowDateValidation(true) + // if the date is invalid, show an error + // TODO localize text + setDateErrors([ + { + level: 'error', + message: 'Should be an empty or valid date', + path: [], + }, + ]) setDisplayDate(dateValue) + onError(true) } else { - setShowDateValidation(false) + setDateErrors([]) setDisplayDate(dateValue) onChange({...value, publishAt: dateValue}) + onError(false) } }, - [onChange, value], + [onChange, value, onError], ) const handleIconValueChange = useCallback( @@ -112,24 +164,13 @@ export function BundleForm(props: { - {showTitleValidation && ( - - - {/* localize & validate copy & UI */} - Title cannot be "drafts" or "published" - - - )} - - {/* TODO ADD CHECK FOR EXISTING NAMES AND AVOID DUPLICATES */} - - {/* localize text */} - Title - + {/* localize text */} + 0 ? 'error' : undefined} value={title} - data-testid="bundle-form-title" /> @@ -146,18 +187,8 @@ export function BundleForm(props: { - - {/* localize text */} - Schedule for publishing at - - {showDateValidation && ( - - - {/* localize & validate copy & UI */} - Should be an empty or valid date - - - )} + {/* localize text */} + 0 ? 'error' : undefined} /> diff --git a/packages/sanity/src/core/bundles/components/dialog/CreateBundleDialog.tsx b/packages/sanity/src/core/bundles/components/dialog/CreateBundleDialog.tsx index 76e60c30c71..1e5d1b77519 100644 --- a/packages/sanity/src/core/bundles/components/dialog/CreateBundleDialog.tsx +++ b/packages/sanity/src/core/bundles/components/dialog/CreateBundleDialog.tsx @@ -5,7 +5,6 @@ import {type FormEvent, useCallback, useState} from 'react' import {type BundleDocument} from '../../../store/bundles/types' import {useBundleOperations} from '../../../store/bundles/useBundleOperations' import {usePerspective} from '../../hooks/usePerspective' -import {isDraftOrPublished} from '../../util/dummyGetters' import {BundleForm} from './BundleForm' interface CreateBundleDialogProps { @@ -16,6 +15,7 @@ interface CreateBundleDialogProps { export function CreateBundleDialog(props: CreateBundleDialogProps): JSX.Element { const {onCancel, onCreate} = props const {createBundle} = useBundleOperations() + const [hasErrors, setHasErrors] = useState(false) const [value, setValue] = useState>({ name: '', @@ -53,6 +53,10 @@ export function CreateBundleDialog(props: CreateBundleDialogProps): JSX.Element setValue(changedValue) }, []) + const handleOnError = useCallback((errorsExist: boolean) => { + setHasErrors(errorsExist) + }, []) + return (
- +