diff --git a/package-lock.json b/package-lock.json index df20fb8b1..c5e902cbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,7 @@ "chai-as-promised": "7.1.1", "chromatic": "6.17.4", "concurrently": "8.0.1", - "cypress": "^12.14.0", + "cypress": "12.14.0", "cypress-vite": "1.4.0", "eslint": "8.33.0", "eslint-config-prettier": "8.6.0", diff --git a/package.json b/package.json index e35945415..b265a88aa 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "chai-as-promised": "7.1.1", "chromatic": "6.17.4", "concurrently": "8.0.1", - "cypress": "^12.14.0", + "cypress": "12.14.0", "cypress-vite": "1.4.0", "eslint": "8.33.0", "eslint-config-prettier": "8.6.0", diff --git a/src/dataset/domain/useCases/createDataset.ts b/src/dataset/domain/useCases/createDataset.ts index 14105b6fd..4034df93a 100644 --- a/src/dataset/domain/useCases/createDataset.ts +++ b/src/dataset/domain/useCases/createDataset.ts @@ -1,15 +1,52 @@ -export interface CreateDatasetFormData { +export interface CreateDatasetFormFields { createDatasetTitle: string } -export class CreateDataset { - submitDataset = async (formData: CreateDatasetFormData): Promise => { - console.log('Submitting dataset:', formData) - return Promise.resolve('Form submitted successfully!') +export interface FormValidationResult { + isValid: boolean + errors: Record +} + +export interface FormValidationService { + validateForm: (fields: CreateDatasetFormFields) => FormValidationResult +} + +export const formValidationService: FormValidationService = { + validateForm: (fields: CreateDatasetFormFields): FormValidationResult => { + const errors: Record = { + createDatasetTitle: undefined + } + + if (!fields.createDatasetTitle) { + errors.createDatasetTitle = 'Name is required' + } + + return { + isValid: Object.values(errors).every((error) => error === undefined), + errors + } } +} + +// formSubmissionService +export interface FormSubmissionService { + submitFormData: (fields: CreateDatasetFormFields) => Promise +} +export const formSubmissionService: FormSubmissionService = { + // If there's an asynchronous operation, use `await` here. + submitFormData: async (fields: CreateDatasetFormFields): Promise => { + // For example, if submitting data to a server: + console.log('Submitting form data:', fields) + const sendDataMock = (fields: CreateDatasetFormFields) => { + // Simulate a network delay, for example, 2 seconds + const delay = 2000 - validateCreateDatasetFormData = (formData: CreateDatasetFormData): boolean => { - // Add validation logic here - return formData.createDatasetTitle.trim() !== '' + return new Promise((resolve) => { + setTimeout(() => { + resolve('Form Data Submitted: ' + JSON.stringify(fields)) + }, delay) + }) + } + await sendDataMock(fields) } } diff --git a/src/sections/create-dataset/CreateDatasetContext.tsx b/src/sections/create-dataset/CreateDatasetContext.tsx index 9927b1613..d6e7e0b80 100644 --- a/src/sections/create-dataset/CreateDatasetContext.tsx +++ b/src/sections/create-dataset/CreateDatasetContext.tsx @@ -1,57 +1,30 @@ -import React, { createContext, useContext, ReactNode } from 'react' -import CreateDatasetFormPresenter from './CreateDatasetForm' -import { useTranslation } from 'react-i18next' -import { CreateDataset, CreateDatasetFormData } from '../../dataset/domain/useCases/createDataset' -import styles from '/src/sections/dataset/Dataset.module.scss' -import { SeparationLine } from '../../components/layout/SeparationLine/SeparationLine' +import { createContext, useContext } from 'react' +import { CreateDatasetFormFields } from '../../dataset/domain/useCases/createDataset' // Importing the FormFields type -interface CreateDatasetFormProps { - submitDataset: (formData: CreateDatasetFormData) => Promise - validateCreateDatasetFormData: (formData: CreateDatasetFormData) => boolean +// Define the context and its interface +interface FormContextInterface { + formState: CreateDatasetFormFields + updateFormState: (newState: CreateDatasetFormFields) => void } -const DatasetContext = createContext({} as CreateDatasetFormProps) -interface DatasetProviderProps { - children: ReactNode +// Define default values for the context +const defaultFormState: CreateDatasetFormFields = { + // Initialize with default values for your form fields + createDatasetTitle: '' } -export const DatasetProvider: React.FC = ({ children }) => { - const createDatasetUseCase = new CreateDataset() - return ( - - {children} - - ) +const defaultContext: FormContextInterface = { + formState: defaultFormState, + updateFormState: () => { + // This is a no-op function since the default context shouldn't update anything + } } -// Hook for easy consumption of the context -export const useDataset = () => useContext(DatasetContext) - -export function CreateDatasetContainer() { - const { t } = useTranslation('createDataset') - return ( - <> -
-
-

{t('pageTitle')}

-
- -
- -
-
- - ) -} - -export const DatasetCreateMaster: React.FC = () => { - return ( - - - - ) +export const FormContext = createContext(defaultContext) +// Custom hook to use the form context +export const useFormContext = () => { + const context = useContext(FormContext) + if (!context) { + throw new Error('useFormContext must be used within a FormProvider') + } + return context } -export default DatasetCreateMaster diff --git a/src/sections/create-dataset/CreateDatasetFactory.tsx b/src/sections/create-dataset/CreateDatasetFactory.tsx index 53af6378d..0edbfa0ce 100644 --- a/src/sections/create-dataset/CreateDatasetFactory.tsx +++ b/src/sections/create-dataset/CreateDatasetFactory.tsx @@ -1,8 +1,21 @@ +import { CreateDatasetFormPresenter } from './CreateDatasetForm' +import { CreateDatasetProvider } from './CreateDatasetProvider' +import { formValidationService } from '../../dataset/domain/useCases/createDataset' +import { formSubmissionService } from '../../dataset/domain/useCases/createDataset' import { ReactElement } from 'react' -import { DatasetCreateMaster } from './CreateDatasetContext' +export const CreateDatasetForm: React.FC = () => { + return ( + + + + ) +} export class CreateDatasetFactory { static create(): ReactElement { - return + return } } diff --git a/src/sections/create-dataset/CreateDatasetForm.tsx b/src/sections/create-dataset/CreateDatasetForm.tsx index f8fd4c6ee..35c078150 100644 --- a/src/sections/create-dataset/CreateDatasetForm.tsx +++ b/src/sections/create-dataset/CreateDatasetForm.tsx @@ -1,56 +1,79 @@ -import React, { useState } from 'react' +import React, { FormEvent, ChangeEvent, useState } from 'react' import { Alert, Button, Col, Form, Row } from '@iqss/dataverse-design-system' import { useTranslation } from 'react-i18next' import { RequiredFieldText } from '../../components/form/RequiredFieldText/RequiredFieldText' import { SeparationLine } from '../../components/layout/SeparationLine/SeparationLine' -import { useDataset } from './CreateDatasetContext' -import { FormInputElement } from '@iqss/dataverse-design-system/src/lib/components/form/form-group/form-element/FormInput' +import { useFormContext } from './CreateDatasetContext' +import { + CreateDatasetFormFields, + FormValidationService, + FormValidationResult +} from '../../dataset/domain/useCases/createDataset' +import { FormSubmissionService } from '../../dataset/domain/useCases/createDataset' -const CreateDatasetFormPresenter: React.FC = () => { +type CreateDatasetFormProps = { + formValidationService: FormValidationService + formSubmissionService: FormSubmissionService +} + +enum SubmissionStatus { + Unsubmitted, + Submitted, + Errored +} + +export const CreateDatasetFormPresenter: React.FC = ({ + formValidationService, + formSubmissionService +}) => { + const { formState, updateFormState } = useFormContext() + const [submissionStatus, setSubmissionStatus] = React.useState( + SubmissionStatus.Unsubmitted + ) + const [isSubmitting, setIsSubmitting] = React.useState(false) + const [formErrors, setFormErrors] = useState< + Record + >({ + createDatasetTitle: undefined + }) const { t } = useTranslation('createDataset') - const [formData, setFormData] = useState({ createDatasetTitle: '' }) - const { submitDataset, validateCreateDatasetFormData } = useDataset() - const [isSubmitting, setIsSubmitting] = useState(false) - const [submitSuccess, setSubmitSuccess] = useState(false) - const handleCreateDatasetFieldChange = (event: React.ChangeEvent): void => { + const handleCreateDatasetFieldChange = (event: ChangeEvent) => { const { name, value } = event.target - setFormData((formData) => ({ - ...formData, - [name]: value - })) + updateFormState({ ...formState, [name]: value }) } - const handleCreateDatasetSubmit = async (event: React.FormEvent) => { + const handleCreateDatasetSubmit = (event: FormEvent) => { event.preventDefault() - setSubmitSuccess(false) - if (validateCreateDatasetFormData(formData)) { - setIsSubmitting(true) - try { - await submitDataset(formData) - // setFormData({ createDatasetTitle: '') // Reset form fields - setSubmitSuccess(true) - } catch (error) { - console.error('Error during submission:', error) - // Optionally handle the error state here - } finally { - setIsSubmitting(false) - } + setIsSubmitting(true) + setSubmissionStatus(SubmissionStatus.Unsubmitted) + + const validationResult: FormValidationResult = formValidationService.validateForm(formState) + + if (validationResult.isValid) { + formSubmissionService + .submitFormData(formState) + .then(() => setSubmissionStatus(SubmissionStatus.Submitted)) + .catch(() => setSubmissionStatus(SubmissionStatus.Errored)) + .finally(() => setIsSubmitting(false)) } else { - console.error('Validation failed: Title is required.') - // Optionally handle the validation error state here + setFormErrors(validationResult.errors) + setSubmissionStatus(SubmissionStatus.Errored) + setIsSubmitting(false) } } return ( <> + {isSubmitting &&

Submitting...

} + {submissionStatus === SubmissionStatus.Submitted &&

Form submitted successfully!

} + {submissionStatus === SubmissionStatus.Errored &&

Error: Submission failed.

}
{ - void handleCreateDatasetSubmit + onSubmit={(event) => { + handleCreateDatasetSubmit(event) }} className={'create-dataset-form'}> - {submitSuccess &&
Form Submitted!
} @@ -64,13 +87,16 @@ const CreateDatasetFormPresenter: React.FC = () => { withinMultipleFieldsGroup={false} /> + {formErrors.createDatasetTitle && {formErrors.createDatasetTitle}} {t('metadataTip.content')} - + @@ -78,4 +104,3 @@ const CreateDatasetFormPresenter: React.FC = () => { ) } -export default CreateDatasetFormPresenter diff --git a/src/sections/create-dataset/CreateDatasetProvider.tsx b/src/sections/create-dataset/CreateDatasetProvider.tsx new file mode 100644 index 000000000..b67f98bf7 --- /dev/null +++ b/src/sections/create-dataset/CreateDatasetProvider.tsx @@ -0,0 +1,38 @@ +import { PropsWithChildren, useState, ReactNode } from 'react' +import { useTranslation } from 'react-i18next' +import styles from '/src/sections/dataset/Dataset.module.scss' +import { SeparationLine } from '../../components/layout/SeparationLine/SeparationLine' +import { FormContext } from './CreateDatasetContext' +import { CreateDatasetFormFields } from '../../dataset/domain/useCases/createDataset' +// Add a type for the children prop +type FormProviderProps = { + children: ReactNode +} +// Context Provider Component +export const CreateDatasetProvider: React.FC = ({ + children +}: PropsWithChildren) => { + const [formState, setFormState] = useState({ + createDatasetTitle: '' + }) + const { t } = useTranslation('createDataset') + const updateFormState = (newState: CreateDatasetFormFields) => { + setFormState((prevState) => ({ ...prevState, ...newState })) + } + + return ( + <> +
+
+

{t('pageTitle')}

+
+ +
+ + {children} + +
+
+ + ) +} diff --git a/src/stories/dataset/dataset-create/DatasetCreate.stories.tsx b/src/stories/dataset/dataset-create/DatasetCreate.stories.tsx index e96a00ffa..be80bb2c3 100644 --- a/src/stories/dataset/dataset-create/DatasetCreate.stories.tsx +++ b/src/stories/dataset/dataset-create/DatasetCreate.stories.tsx @@ -1,18 +1,17 @@ -import type { Meta, StoryObj } from '@storybook/react' -import { WithI18next } from '../../WithI18next' +import type { StoryObj, Meta } from '@storybook/react' +import { CreateDatasetForm } from '../../../sections/create-dataset/CreateDatasetFactory' import { WithLayout } from '../../WithLayout' -import DatasetCreateMaster from '../../../sections/create-dataset/CreateDatasetContext' +import { WithI18next } from '../../WithI18next' -const meta: Meta = { +const meta: Meta = { title: 'Pages/Create Dataset', - component: DatasetCreateMaster, + component: CreateDatasetForm, decorators: [WithI18next, WithLayout] } - export default meta -type Story = StoryObj +type Story = StoryObj export const Default: Story = { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - render: () => + decorators: [WithLayout, WithI18next], + render: () => } diff --git a/tests/component/views/create-dataset/CreateDataset.spec.tsx b/tests/component/views/create-dataset/CreateDataset.spec.tsx index 3e0e9f7a2..c2aaee207 100644 --- a/tests/component/views/create-dataset/CreateDataset.spec.tsx +++ b/tests/component/views/create-dataset/CreateDataset.spec.tsx @@ -1,14 +1,13 @@ -import DatasetCreateMaster from '../../../../src/sections/create-dataset/CreateDatasetContext' +// import DatasetCreate from '../../../../src/sections/create-dataset/CreateDatasetContext' +import { CreateDatasetForm } from '../../../../src/sections/create-dataset/CreateDatasetFactory' import { mount } from 'cypress/react18' describe('Form component', () => { it('renders the Create Dataset page and its contents', () => { - mount() + mount() cy.findByText(/Create Dataset/i).should('exist') - cy.findByText(/Title/i).should('exist') - - cy.get('input[name="createDatasetTitle"]').should('exist') + cy.findByLabelText(/Title/i).should('exist') cy.findByText(/Save Dataset/i).should('exist') @@ -17,14 +16,14 @@ describe('Form component', () => { it('can submit a valid form', () => { cy.log('Submit form') - mount() - cy.get('input[name="createDatasetTitle"]') + mount() + cy.findByLabelText(/Title/i) .should('exist') .type('Test Dataset Title') .should('have.attr', 'required', 'required') .and('have.value', 'Test Dataset Title') cy.findByText(/Save Dataset/i).click() - cy.findByText('Form Submitted!') + cy.findByText('Form submitted successfully!') }) })