From 11543e64cba271216fbbb5dced2d120a92a03da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Mon, 8 Jul 2024 09:08:45 -0300 Subject: [PATCH 01/31] feat: initial routing and navigation --- package-lock.json | 27 +++++++++++++------ package.json | 2 +- src/Router.tsx | 5 ++++ src/sections/Route.enum.ts | 8 +++++- .../create-collection/CreateCollection.tsx | 14 ++++++++++ .../CreateCollectionFactory.tsx | 26 ++++++++++++++++++ src/sections/layout/header/Header.tsx | 6 +++-- .../add-data-actions/AddDataActionsButton.tsx | 9 ++++--- 8 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 src/sections/create-collection/CreateCollection.tsx create mode 100644 src/sections/create-collection/CreateCollectionFactory.tsx diff --git a/package-lock.json b/package-lock.json index 004f5610f..60e03d13a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-pr141.153a56a", + "@iqss/dataverse-client-javascript": "2.0.0-pr155.be01a3f", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", @@ -3610,14 +3610,14 @@ }, "node_modules/@iqss/dataverse-client-javascript": { "name": "@IQSS/dataverse-client-javascript", - "version": "2.0.0-pr141.153a56a", - "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-pr141.153a56a/cb16d9477207d284110ca3c348b6540274509cc9", - "integrity": "sha512-urN99q1ll1Xiqr2wDsG9wvLcAjrxsOZmLXmCgFp6zTFoFHaVZ8c07k0EqQZk4jUv6CwgQY8j+jhmW+T+ABuj6A==", + "version": "2.0.0-pr155.be01a3f", + "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-pr155.be01a3f/dea4dac3ef3aab1802b444ccc08326843c530d80", + "integrity": "sha512-Q2KhE3JGu3BaTD1TB3l0iQ6yZIvpUOkds8Zk0nezNymm4pL8gjdISomJDUXFFW9tQuAZ0PkXuOEQGmVfzEUKNA==", "license": "MIT", "dependencies": { "@types/node": "^18.15.11", "@types/turndown": "^5.0.1", - "axios": "^1.3.4", + "axios": "^1.7.2", "turndown": "^7.1.2", "typescript": "^4.9.5" } @@ -3627,6 +3627,16 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.19.tgz", "integrity": "sha512-+pMhShR3Or5GR0/sp4Da7FnhVmTalWm81M6MkEldbwjETSaPalw138Z4KdpQaistvqQxLB7Cy4xwYdxpbSOs9Q==" }, + "node_modules/@iqss/dataverse-client-javascript/node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/@iqss/dataverse-design-system": { "resolved": "packages/design-system", "link": true @@ -17927,6 +17937,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dev": true, "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -23534,9 +23545,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", diff --git a/package.json b/package.json index 7aece5b0d..5e770a5e7 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-pr141.153a56a", + "@iqss/dataverse-client-javascript": "2.0.0-pr155.be01a3f", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", diff --git a/src/Router.tsx b/src/Router.tsx index f7c2feaba..0376afdf7 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -8,6 +8,7 @@ import { FileFactory } from './sections/file/FileFactory' import { CollectionFactory } from './sections/collection/CollectionFactory' import { UploadDatasetFilesFactory } from './sections/upload-dataset-files/UploadDatasetFilesFactory' import { DatasetNonNumericVersion } from './dataset/domain/models/Dataset' +import { CreateCollectionFactory } from './sections/create-collection/CreateCollectionFactory' const router = createBrowserRouter( [ @@ -24,6 +25,10 @@ const router = createBrowserRouter( path: Route.COLLECTIONS, element: CollectionFactory.create() }, + { + path: Route.CREATE_COLLECTION, + element: CreateCollectionFactory.create() + }, { path: Route.DATASETS, element: DatasetFactory.create() diff --git a/src/sections/Route.enum.ts b/src/sections/Route.enum.ts index bdce8e57d..dbbfec6dc 100644 --- a/src/sections/Route.enum.ts +++ b/src/sections/Route.enum.ts @@ -7,5 +7,11 @@ export enum Route { CREATE_DATASET = '/datasets/create', UPLOAD_DATASET_FILES = '/datasets/upload-files', FILES = '/files', - COLLECTIONS = '/collections' + COLLECTIONS = '/collections', + CREATE_COLLECTION = '/collections/create/:ownerCollectionId' +} + +export const RouteWithParams = { + CREATE_COLLECTION: (ownerCollectionId?: string) => + `/collections/create/${ownerCollectionId ?? 'root'}` } diff --git a/src/sections/create-collection/CreateCollection.tsx b/src/sections/create-collection/CreateCollection.tsx new file mode 100644 index 000000000..5fd8b6dd6 --- /dev/null +++ b/src/sections/create-collection/CreateCollection.tsx @@ -0,0 +1,14 @@ +interface CreateCollectionProps { + ownerCollectionId?: string +} + +export function CreateCollection({ ownerCollectionId }: CreateCollectionProps) { + return ( +
+

CreateCollection

+

Owner Collection ID: {ownerCollectionId ?? 'root'}

+
+ ) +} + +export default CreateCollection diff --git a/src/sections/create-collection/CreateCollectionFactory.tsx b/src/sections/create-collection/CreateCollectionFactory.tsx new file mode 100644 index 000000000..9669e4c3b --- /dev/null +++ b/src/sections/create-collection/CreateCollectionFactory.tsx @@ -0,0 +1,26 @@ +import { ReactElement } from 'react' +import { useParams } from 'react-router-dom' +import { CollectionJSDataverseRepository } from '../../collection/infrastructure/repositories/CollectionJSDataverseRepository' +import CreateCollection from './CreateCollection' + +const collectionRepository = new CollectionJSDataverseRepository() + +export class CreateCollectionFactory { + static create(): ReactElement { + return + } +} + +function CreateCollectionWithParams() { + const { ownerCollectionId = 'root' } = useParams<{ ownerCollectionId: string }>() + + // TODO:ME Maybe assert that collection with ownerCollectionId exists first, could be root or a specific collection + // TODO:ME What roles can create a collection, what checks to do? + + return ( + + ) +} diff --git a/src/sections/layout/header/Header.tsx b/src/sections/layout/header/Header.tsx index e65ae69d5..2c24ddf97 100644 --- a/src/sections/layout/header/Header.tsx +++ b/src/sections/layout/header/Header.tsx @@ -1,7 +1,7 @@ import dataverse_logo from '../../../assets/dataverse_brand_icon.svg' import { useTranslation } from 'react-i18next' import { Navbar } from '@iqss/dataverse-design-system' -import { Route } from '../../Route.enum' +import { Route, RouteWithParams } from '../../Route.enum' import { useSession } from '../../session/SessionContext' import { useNavigate } from 'react-router-dom' import { BASE_URL } from '../../../config' @@ -18,6 +18,8 @@ export function Header() { }) } + const createCollectionRoute = RouteWithParams.CREATE_COLLECTION() + return ( - + {t('navigation.newCollection')} diff --git a/src/sections/shared/add-data-actions/AddDataActionsButton.tsx b/src/sections/shared/add-data-actions/AddDataActionsButton.tsx index 8c7377804..5cd8ccb37 100644 --- a/src/sections/shared/add-data-actions/AddDataActionsButton.tsx +++ b/src/sections/shared/add-data-actions/AddDataActionsButton.tsx @@ -3,7 +3,7 @@ import { Dropdown } from 'react-bootstrap' import { Link, useSearchParams } from 'react-router-dom' import { DropdownButton } from '@iqss/dataverse-design-system' import { PlusLg } from 'react-bootstrap-icons' -import { Route } from '../../Route.enum' +import { Route, RouteWithParams } from '../../Route.enum' import styles from './AddDataActionsButton.module.scss' export default function AddDataActionsButton() { @@ -15,16 +15,18 @@ export default function AddDataActionsButton() { ? `${Route.CREATE_DATASET}?collectionId=${collectionId}` : Route.CREATE_DATASET + const createCollectionRoute = RouteWithParams.CREATE_COLLECTION(collectionId) + return ( }> - + {t('navigation.newCollection')} - + {t('navigation.newDataset')} @@ -32,4 +34,3 @@ export default function AddDataActionsButton() { } // TODO: AddData Dropdown item needs proper permissions checking, see Spike #318 -// TODO: Add page for "New Collection", see Issue #319 From a1c1fb70c693722cbdf6ad89b7c9caea4d1f11b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Mon, 8 Jul 2024 10:51:11 -0300 Subject: [PATCH 02/31] feat(design system): refactor NavBarDropdownItem to accept as prop and correspondent props --- packages/design-system/CHANGELOG.md | 1 + .../navbar-dropdown/NavbarDropdownItem.tsx | 22 ++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/design-system/CHANGELOG.md b/packages/design-system/CHANGELOG.md index 80b0b57b0..a29a90841 100644 --- a/packages/design-system/CHANGELOG.md +++ b/packages/design-system/CHANGELOG.md @@ -39,6 +39,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline - **Select Multiple:** add is-invalid classname if isInvalid prop is true. - **Card:** NEW card element to show header and body. - **ProgressBar:** NEW progress bar element to show progress. +- **NavbarDropdownItem:** Now accepts `as` prop and takes `as` Element props. # [1.1.0](https://github.com/IQSS/dataverse-frontend/compare/@iqss/dataverse-design-system@1.0.1...@iqss/dataverse-design-system@1.1.0) (2024-03-12) diff --git a/packages/design-system/src/lib/components/navbar/navbar-dropdown/NavbarDropdownItem.tsx b/packages/design-system/src/lib/components/navbar/navbar-dropdown/NavbarDropdownItem.tsx index e658d74ce..f80364051 100644 --- a/packages/design-system/src/lib/components/navbar/navbar-dropdown/NavbarDropdownItem.tsx +++ b/packages/design-system/src/lib/components/navbar/navbar-dropdown/NavbarDropdownItem.tsx @@ -1,20 +1,26 @@ import { NavDropdown } from 'react-bootstrap' -import { PropsWithChildren } from 'react' +import { ComponentPropsWithoutRef, ElementType, PropsWithChildren } from 'react' -interface NavbarDropdownItemProps { - href: string +type NavbarDropdownItemProps = { + href?: string onClick?: () => void disabled?: boolean -} + as?: T +} & (T extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[T] : ComponentPropsWithoutRef) -export function NavbarDropdownItem({ +export function NavbarDropdownItem({ href, onClick, disabled, - children -}: PropsWithChildren) { + children, + as, + ...props +}: PropsWithChildren>) { + /* eslint-disable @typescript-eslint/no-explicit-any */ + const Component: ElementType = as || 'a' + return ( - + {children} ) From 2af3e3588fb93302bfd20404e66fb5f968dede8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Mon, 8 Jul 2024 10:51:42 -0300 Subject: [PATCH 03/31] feat: items as Links to not refresh page --- src/sections/layout/header/Header.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sections/layout/header/Header.tsx b/src/sections/layout/header/Header.tsx index 2c24ddf97..a0ff8429c 100644 --- a/src/sections/layout/header/Header.tsx +++ b/src/sections/layout/header/Header.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next' import { Navbar } from '@iqss/dataverse-design-system' import { Route, RouteWithParams } from '../../Route.enum' import { useSession } from '../../session/SessionContext' -import { useNavigate } from 'react-router-dom' +import { Link, useNavigate } from 'react-router-dom' import { BASE_URL } from '../../../config' const currentPage = 0 @@ -30,10 +30,10 @@ export function Header() { {user ? ( <> - + {t('navigation.newCollection')} - + {t('navigation.newDataset')} From 16b4b529ef5f87a393f788161bf9238c3539fb9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Mon, 8 Jul 2024 15:26:14 -0300 Subject: [PATCH 04/31] feat(design system): hasValidation prop --- packages/design-system/CHANGELOG.md | 1 + .../form-group/form-input-group/FormInputGroup.tsx | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/design-system/CHANGELOG.md b/packages/design-system/CHANGELOG.md index a29a90841..4e6e4a122 100644 --- a/packages/design-system/CHANGELOG.md +++ b/packages/design-system/CHANGELOG.md @@ -40,6 +40,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline - **Card:** NEW card element to show header and body. - **ProgressBar:** NEW progress bar element to show progress. - **NavbarDropdownItem:** Now accepts `as` prop and takes `as` Element props. +- **FormInputGroup:** extend Props Interface to accept `hasValidation` prop to properly show rounded corners in an with validation # [1.1.0](https://github.com/IQSS/dataverse-frontend/compare/@iqss/dataverse-design-system@1.0.1...@iqss/dataverse-design-system@1.1.0) (2024-03-12) diff --git a/packages/design-system/src/lib/components/form/form-group/form-input-group/FormInputGroup.tsx b/packages/design-system/src/lib/components/form/form-group/form-input-group/FormInputGroup.tsx index 3d6ed63dc..9694502f0 100644 --- a/packages/design-system/src/lib/components/form/form-group/form-input-group/FormInputGroup.tsx +++ b/packages/design-system/src/lib/components/form/form-group/form-input-group/FormInputGroup.tsx @@ -2,8 +2,17 @@ import { ReactNode } from 'react' import { InputGroup } from 'react-bootstrap' import { FormInputGroupText } from './FormInputGroupText' -function FormInputGroup({ children }: { children: ReactNode }) { - return {children} +interface FormInputGroupProps { + children: ReactNode + hasValidation?: boolean +} + +function FormInputGroup({ children, hasValidation }: FormInputGroupProps) { + return ( + + {children} + + ) } FormInputGroup.Text = FormInputGroupText From a9af06694a127917f05f89b7d03292bb5744be76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Mon, 8 Jul 2024 17:06:50 -0300 Subject: [PATCH 05/31] feat: initial fields and form setup --- .../form-group/form-element/FormTextArea.tsx | 4 +- public/locales/en/createCollection.json | 42 ++++ .../domain/useCases/DTOs/CollectionDTO.ts | 28 +++ .../create-collection/CreateCollection.tsx | 69 +++++- .../CreateCollectionFactory.tsx | 2 +- .../CollectionForm.module.scss | 5 + .../collection-form/ContactsField.tsx | 107 +++++++++ .../collection-form/CoreFieldsSection.tsx | 219 ++++++++++++++++++ .../collection-form/index.tsx | 69 ++++++ 9 files changed, 537 insertions(+), 8 deletions(-) create mode 100644 public/locales/en/createCollection.json create mode 100644 src/collection/domain/useCases/DTOs/CollectionDTO.ts create mode 100644 src/sections/create-collection/collection-form/CollectionForm.module.scss create mode 100644 src/sections/create-collection/collection-form/ContactsField.tsx create mode 100644 src/sections/create-collection/collection-form/CoreFieldsSection.tsx create mode 100644 src/sections/create-collection/collection-form/index.tsx diff --git a/packages/design-system/src/lib/components/form/form-group/form-element/FormTextArea.tsx b/packages/design-system/src/lib/components/form/form-group/form-element/FormTextArea.tsx index e1c0a22f7..0fdb994b5 100644 --- a/packages/design-system/src/lib/components/form/form-group/form-element/FormTextArea.tsx +++ b/packages/design-system/src/lib/components/form/form-group/form-element/FormTextArea.tsx @@ -7,10 +7,11 @@ export interface FormTextAreaProps extends Omit} {...props} /> diff --git a/public/locales/en/createCollection.json b/public/locales/en/createCollection.json new file mode 100644 index 000000000..92b187b32 --- /dev/null +++ b/public/locales/en/createCollection.json @@ -0,0 +1,42 @@ +{ + "pageTitle": "Create Collection", + "fields": { + "hostCollection": { + "label": "Host Collection", + "description": "The collection which contains this data.", + "required": "Host Collection is required" + }, + "name": { + "label": "Dataverse Name", + "description": "The project, department, university, professor, or journal this collection will contain data for.", + "required": "Dataverse Name is required" + }, + "affiliation": { + "label": "Affiliation", + "description": "The organization with which this collection is affiliated." + }, + "alias": { + "label": "Identifier", + "description": "Short name used for the URL of this collection.", + "required": "Identifier is required" + }, + "storage": { + "label": "Storage", + "description": "A storage service to be used for datasets in this collection." + }, + "type": { + "label": "Category", + "description": "The type that most closely reflects this collection.", + "required": "Category is required" + }, + "description": { + "label": "Description", + "description": "A summary describing the purpose, nature or scope of this collection." + }, + "contacts": { + "label": "Email", + "description": "The email address(es) of the contact(s) for the collection.", + "required": "Email is required" + } + } +} diff --git a/src/collection/domain/useCases/DTOs/CollectionDTO.ts b/src/collection/domain/useCases/DTOs/CollectionDTO.ts new file mode 100644 index 000000000..98cc2efff --- /dev/null +++ b/src/collection/domain/useCases/DTOs/CollectionDTO.ts @@ -0,0 +1,28 @@ +export interface CollectionDTO { + alias: string + name: string + contacts: string[] + type: CollectionType +} + +export const collectionTypeOptions = { + RESEARCHERS: 'Researchers', + RESEARCH_PROJECTS: 'Research Projects', + JOURNALS: 'Journals', + ORGANIZATIONS_INSTITUTIONS: 'Organizations/Institutions', + TEACHING_COURSES: 'Teaching Courses', + UNCATEGORIZED: 'Uncategorized', + LABORATORY: 'Laboratory', + RESEARCH_GROUP: 'Research Group', + DEPARTMENT: 'Department' +} as const + +export type CollectionType = (typeof collectionTypeOptions)[keyof typeof collectionTypeOptions] + +export const collectionStorageOptions = { + LOCAL_DEFAULT: 'Local (Default)', + LOCAL: 'Local' +} as const + +export type CollectionStorage = + (typeof collectionStorageOptions)[keyof typeof collectionStorageOptions] diff --git a/src/sections/create-collection/CreateCollection.tsx b/src/sections/create-collection/CreateCollection.tsx index 5fd8b6dd6..29561da9a 100644 --- a/src/sections/create-collection/CreateCollection.tsx +++ b/src/sections/create-collection/CreateCollection.tsx @@ -1,13 +1,70 @@ +import { useCollection } from '../collection/useCollection' +import { CollectionRepository } from '../../collection/domain/repositories/CollectionRepository' +import { BreadcrumbsGenerator } from '../shared/hierarchy/BreadcrumbsGenerator' +import { useTranslation } from 'react-i18next' +import { RequiredFieldText } from '../shared/form/RequiredFieldText/RequiredFieldText' +import { CollectionForm, CollectionFormProps } from './collection-form' +import { useEffect } from 'react' +import { useLoading } from '../loading/LoadingContext' + interface CreateCollectionProps { - ownerCollectionId?: string + ownerCollectionId: string + collectionRepository: CollectionRepository } -export function CreateCollection({ ownerCollectionId }: CreateCollectionProps) { +export function CreateCollection({ + ownerCollectionId, + collectionRepository +}: CreateCollectionProps) { + const { t } = useTranslation('createCollection') + const { isLoading, setIsLoading } = useLoading() + + const { collection, isLoading: isLoadingCollection } = useCollection( + collectionRepository, + ownerCollectionId + ) + + console.log({ collection, isLoadingCollection }) + + // TODO:ME If is not loading and collection is not found, show a message and navigate to the root collection + // One good thing would be add toastify to show messages on top of the page for this kind of things + + useEffect(() => { + if (!isLoadingCollection) { + setIsLoading(false) + } + }, [isLoading, isLoadingCollection, setIsLoading]) + + if (!isLoading && !collection) { + return

Owner Collection not found

+ } + + // TODO:ME Create Skeleton + if (isLoading || !collection) { + return

Loading...

+ } + // TODO:ME name = user name, affiliation = user affiliation, first email = user email + const formDefaultValues: CollectionFormProps['defaultValues'] = { + parentCollectionName: collection.name, + name: 'The Nameeeee', + alias: 'Some Alias', + type: 'Department', + contacts: [{ value: '' }], + affiliation: 'Some Affi', + storage: 'Local (Default)', + description: 'Some Description' + } + return ( -
-

CreateCollection

-

Owner Collection ID: {ownerCollectionId ?? 'root'}

-
+
+ + + +
) } diff --git a/src/sections/create-collection/CreateCollectionFactory.tsx b/src/sections/create-collection/CreateCollectionFactory.tsx index 9669e4c3b..21cdd8361 100644 --- a/src/sections/create-collection/CreateCollectionFactory.tsx +++ b/src/sections/create-collection/CreateCollectionFactory.tsx @@ -20,7 +20,7 @@ function CreateCollectionWithParams() { return ( ) } diff --git a/src/sections/create-collection/collection-form/CollectionForm.module.scss b/src/sections/create-collection/collection-form/CollectionForm.module.scss new file mode 100644 index 000000000..2d7519b6b --- /dev/null +++ b/src/sections/create-collection/collection-form/CollectionForm.module.scss @@ -0,0 +1,5 @@ +.identifier-field-group { + :global .input-group-text { + font-size: 14px; + } +} diff --git a/src/sections/create-collection/collection-form/ContactsField.tsx b/src/sections/create-collection/collection-form/ContactsField.tsx new file mode 100644 index 000000000..a63677ec6 --- /dev/null +++ b/src/sections/create-collection/collection-form/ContactsField.tsx @@ -0,0 +1,107 @@ +import { Col, Form, Row } from '@iqss/dataverse-design-system' +import { useCallback, useMemo } from 'react' +import { Controller, useFieldArray, useFormContext } from 'react-hook-form' +import { useTranslation } from 'react-i18next' + +// TODO:ME Create reusable DynamicFieldsButtons component inside shared Form when merged with issue 422 + +export const ContactsField = () => { + const { t } = useTranslation('createCollection') + const { control } = useFormContext() + + const { + fields: fieldsArray, + insert, + remove + } = useFieldArray({ + name: 'contacts', + control: control + }) + + const builtFieldNameWithIndex = useCallback((fieldIndex: number) => { + return `contacts.${fieldIndex}.value` + }, []) + + // We give the label the same ID as the first field, so that clicking on the label focuses the first field only + const controlID = useMemo(() => builtFieldNameWithIndex(0), [builtFieldNameWithIndex]) + + const handleOnAddField = (index: number) => { + insert( + index + 1, + { value: '' }, + { + shouldFocus: true, + focusName: builtFieldNameWithIndex(index + 1) + } + ) + } + + const handleOnRemoveField = (index: number) => remove(index) + + return ( + + + {t('fields.contacts.label')} + + + {(fieldsArray as { id: string; value: string }[]).map((field, index) => ( + + ( + <> + + + + {error?.message} + + {index === 0 ? ( + <> + + + ) : ( + <> + + + + )} + {/* + handleOnAddField(index)} + onRemoveButtonClick={() => handleOnRemoveField(index)} + originalField={index === 0} + /> + */} + + )} + /> + + ))} + + ) +} diff --git a/src/sections/create-collection/collection-form/CoreFieldsSection.tsx b/src/sections/create-collection/collection-form/CoreFieldsSection.tsx new file mode 100644 index 000000000..c4aa86af4 --- /dev/null +++ b/src/sections/create-collection/collection-form/CoreFieldsSection.tsx @@ -0,0 +1,219 @@ +import { Controller, useFormContext } from 'react-hook-form' +import { useTranslation } from 'react-i18next' +import { Col, Form, Row } from '@iqss/dataverse-design-system' +import { + collectionTypeOptions, + collectionStorageOptions +} from '../../../collection/domain/useCases/DTOs/CollectionDTO' +import { ContactsField } from './ContactsField' +import styles from './CollectionForm.module.scss' + +const CoreFieldsSection = () => { + const { t } = useTranslation('createCollection') + const { control } = useFormContext() + + return ( +
+ {/* Host Collection */} + + + + {t('fields.hostCollection.label')} + + ( + + + {error?.message} + + )} + /> + + + + {/* Name & Affiliation */} + + + + {t('fields.name.label')} + + ( + + + {error?.message} + + )} + /> + + + + {t('fields.affiliation.label')} + + ( + + + {error?.message} + + )} + /> + + + + {/* Identifier(alias) & Storage */} + + + + {t('fields.alias.label')} + + ( + + + + {window.location.origin}/spa/collections/ + + + {error?.message} + + + )} + /> + + + + {t('fields.storage.label')} + + ( + + + {/* TODO:ME What are this options? do they come from a configuration? */} + + {Object.values(collectionStorageOptions).map((type) => ( + + ))} + + {error?.message} + + )} + /> + + + + {/* Category (type) & Description */} + + + + {t('fields.type.label')} + + ( + + + + {Object.values(collectionTypeOptions).map((type) => ( + + ))} + + {error?.message} + + )} + /> + + + + + {t('fields.description.label')} + + ( + + + {error?.message} + + )} + /> + + + + {/* Email (contacts) */} + + + +
+ ) +} + +export default CoreFieldsSection diff --git a/src/sections/create-collection/collection-form/index.tsx b/src/sections/create-collection/collection-form/index.tsx new file mode 100644 index 000000000..afe0521a2 --- /dev/null +++ b/src/sections/create-collection/collection-form/index.tsx @@ -0,0 +1,69 @@ +import { useRef } from 'react' +import { FormProvider, useForm } from 'react-hook-form' +import CoreFieldsSection from './CoreFieldsSection' +import { + CollectionType, + CollectionStorage +} from '../../../collection/domain/useCases/DTOs/CollectionDTO' + +export interface CollectionFormProps { + defaultValues: { + parentCollectionName: string + name: string + affiliation?: string + alias: string + storage?: CollectionStorage + type: CollectionType + description?: string + contacts: { value: string }[] + } +} + +export const CollectionForm = ({ defaultValues }: CollectionFormProps) => { + const formContainerRef = useRef(null) + + const form = useForm({ + mode: 'onChange', + defaultValues + }) + + const preventEnterSubmit = (e: React.KeyboardEvent) => { + // When pressing Enter, only submit the form if the user is focused on the submit button itself + if (e.key !== 'Enter') return + + const isButton = e.target instanceof HTMLButtonElement + const isButtonTypeSubmit = isButton ? (e.target as HTMLButtonElement).type === 'submit' : false + + if (!isButton && !isButtonTypeSubmit) e.preventDefault() + } + + const submitForm = (formValues: any) => { + // console.log({ formValues }) + } + + function onSubmittedCollectionError() { + if (formContainerRef.current) { + formContainerRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' }) + } + } + // TODO:ME Apply max width to container + return ( +
+ +
+ + + {/* Metadata Fields Section here 👇 */} + + {/* Browse/Search Facets Section here 👇 */} + +
+
+ ) +} From 8eabb49aff2d86cdf1898b8037faa5a2d2156e5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Wed, 10 Jul 2024 09:47:31 -0300 Subject: [PATCH 06/31] feat: field validations --- public/locales/en/createCollection.json | 9 ++- .../CollectionForm.module.scss | 18 +++++ .../collection-form/index.tsx | 7 +- .../ContactsField.tsx | 81 +++++++++++++------ .../index.tsx} | 65 ++++++++++++--- src/shared/helpers/Validator.ts | 12 +++ 6 files changed, 152 insertions(+), 40 deletions(-) rename src/sections/create-collection/collection-form/{ => top-fields-section}/ContactsField.tsx (59%) rename src/sections/create-collection/collection-form/{CoreFieldsSection.tsx => top-fields-section/index.tsx} (80%) create mode 100644 src/shared/helpers/Validator.ts diff --git a/public/locales/en/createCollection.json b/public/locales/en/createCollection.json index 92b187b32..2c701b242 100644 --- a/public/locales/en/createCollection.json +++ b/public/locales/en/createCollection.json @@ -18,7 +18,11 @@ "alias": { "label": "Identifier", "description": "Short name used for the URL of this collection.", - "required": "Identifier is required" + "required": "Identifier is required", + "invalid": { + "format": "Identifier is not a valid identifier. Valid characters are a-Z, 0-9, '_', and '-'.", + "maxLength": "Identifier must be at most {{maxLength}} characters." + } }, "storage": { "label": "Storage", @@ -36,7 +40,8 @@ "contacts": { "label": "Email", "description": "The email address(es) of the contact(s) for the collection.", - "required": "Email is required" + "required": "Email is required", + "invalid": "Email is not a valid email" } } } diff --git a/src/sections/create-collection/collection-form/CollectionForm.module.scss b/src/sections/create-collection/collection-form/CollectionForm.module.scss index 2d7519b6b..e67184332 100644 --- a/src/sections/create-collection/collection-form/CollectionForm.module.scss +++ b/src/sections/create-collection/collection-form/CollectionForm.module.scss @@ -3,3 +3,21 @@ font-size: 14px; } } + +.dynamic-fields-button-container { + margin-top: calc(24px + 8px); // 24px text label height & 8px its margin bottom + + &.on-composed-multiple { + @media screen and (max-width: 575px) { + margin-top: 0; + } + } + + &.on-primitive-multiple { + margin-top: 0; + + @media screen and (max-width: 575px) { + margin-top: 1rem; + } + } +} diff --git a/src/sections/create-collection/collection-form/index.tsx b/src/sections/create-collection/collection-form/index.tsx index afe0521a2..f91cb2703 100644 --- a/src/sections/create-collection/collection-form/index.tsx +++ b/src/sections/create-collection/collection-form/index.tsx @@ -1,10 +1,11 @@ import { useRef } from 'react' import { FormProvider, useForm } from 'react-hook-form' -import CoreFieldsSection from './CoreFieldsSection' +import { TopFieldsSection } from './top-fields-section' import { CollectionType, CollectionStorage } from '../../../collection/domain/useCases/DTOs/CollectionDTO' +import { SeparationLine } from '../../shared/layout/SeparationLine/SeparationLine' export interface CollectionFormProps { defaultValues: { @@ -57,7 +58,9 @@ export const CollectionForm = ({ defaultValues }: CollectionFormProps) => { onSubmit={form.handleSubmit(submitForm)} onKeyDown={preventEnterSubmit} noValidate={true}> - + + + {/* Metadata Fields Section here 👇 */} diff --git a/src/sections/create-collection/collection-form/ContactsField.tsx b/src/sections/create-collection/collection-form/top-fields-section/ContactsField.tsx similarity index 59% rename from src/sections/create-collection/collection-form/ContactsField.tsx rename to src/sections/create-collection/collection-form/top-fields-section/ContactsField.tsx index a63677ec6..1c32ac9dd 100644 --- a/src/sections/create-collection/collection-form/ContactsField.tsx +++ b/src/sections/create-collection/collection-form/top-fields-section/ContactsField.tsx @@ -1,11 +1,19 @@ import { Col, Form, Row } from '@iqss/dataverse-design-system' import { useCallback, useMemo } from 'react' -import { Controller, useFieldArray, useFormContext } from 'react-hook-form' +import { Controller, UseControllerProps, useFieldArray, useFormContext } from 'react-hook-form' import { useTranslation } from 'react-i18next' +import cn from 'classnames' +import styles from '../CollectionForm.module.scss' +// TODO:ME This imports are only used in the DynamicFieldsButtons component (temporal) +import { MouseEvent } from 'react' +import { Button, Tooltip } from '@iqss/dataverse-design-system' +import { Dash, Plus } from 'react-bootstrap-icons' -// TODO:ME Create reusable DynamicFieldsButtons component inside shared Form when merged with issue 422 +interface ContactsFieldProps { + rules: UseControllerProps['rules'] +} -export const ContactsField = () => { +export const ContactsField = ({ rules }: ContactsFieldProps) => { const { t } = useTranslation('createCollection') const { control } = useFormContext() @@ -52,7 +60,7 @@ export const ContactsField = () => { ( <> @@ -68,35 +76,19 @@ export const ContactsField = () => { {error?.message} - {index === 0 ? ( - <> - - - ) : ( - <> - - - - )} - {/* handleOnAddField(index)} onRemoveButtonClick={() => handleOnRemoveField(index)} originalField={index === 0} /> - */} + )} /> @@ -105,3 +97,46 @@ export const ContactsField = () => { ) } + +// TODO:ME Create reusable DynamicFieldsButtons component inside shared Form when merged with issue 422 +// TODO:ME This component here is temporal, it will be moved to the shared form folder +interface DynamicFieldsButtonsProps { + fieldName: string + originalField?: boolean + onAddButtonClick: (event: MouseEvent) => void + onRemoveButtonClick: (event: MouseEvent) => void +} + +const DynamicFieldsButtons = ({ + fieldName, + originalField, + onAddButtonClick, + onRemoveButtonClick +}: DynamicFieldsButtonsProps) => { + return ( +
+ + + + {!originalField && ( + + + + )} +
+ ) +} diff --git a/src/sections/create-collection/collection-form/CoreFieldsSection.tsx b/src/sections/create-collection/collection-form/top-fields-section/index.tsx similarity index 80% rename from src/sections/create-collection/collection-form/CoreFieldsSection.tsx rename to src/sections/create-collection/collection-form/top-fields-section/index.tsx index c4aa86af4..ff9dfffb0 100644 --- a/src/sections/create-collection/collection-form/CoreFieldsSection.tsx +++ b/src/sections/create-collection/collection-form/top-fields-section/index.tsx @@ -1,29 +1,64 @@ -import { Controller, useFormContext } from 'react-hook-form' +import { Controller, UseControllerProps, useFormContext } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { Col, Form, Row } from '@iqss/dataverse-design-system' import { collectionTypeOptions, collectionStorageOptions -} from '../../../collection/domain/useCases/DTOs/CollectionDTO' +} from '../../../../collection/domain/useCases/DTOs/CollectionDTO' import { ContactsField } from './ContactsField' -import styles from './CollectionForm.module.scss' +import styles from '../CollectionForm.module.scss' +import { Validator } from '../../../../shared/helpers/Validator' -const CoreFieldsSection = () => { +export const TopFieldsSection = () => { const { t } = useTranslation('createCollection') const { control } = useFormContext() + const hostCollectionRules: UseControllerProps['rules'] = { + required: t('fields.hostCollection.required') + } + + const nameRules: UseControllerProps['rules'] = { + required: t('fields.name.required') + } + + const aliasRules: UseControllerProps['rules'] = { + required: t('fields.alias.required'), + maxLength: { + value: 60, + message: t('fields.alias.invalid.maxLength', { maxLength: 60 }) + }, + validate: (value: string) => { + if (!Validator.isValidIdentifier(value)) { + return t('fields.alias.invalid.format') + } + return true + } + } + + const typeRules: UseControllerProps['rules'] = { required: t('fields.type.required') } + + const contactsRules: UseControllerProps['rules'] = { + required: t('fields.contacts.required'), + validate: (value: string) => { + if (!Validator.isValidEmail(value)) { + return t('fields.contacts.invalid') + } + return true + } + } + return (
{/* Host Collection */} - + {t('fields.hostCollection.label')} ( { aria-required={true} ref={ref} disabled + // readOnly + // plaintext + // TODO:ME Check this, modify ds component? /> {error?.message} @@ -51,7 +89,7 @@ const CoreFieldsSection = () => { ( { ( { ( @@ -133,6 +172,7 @@ const CoreFieldsSection = () => { ( { ( { ( { {/* Email (contacts) */} - +
) } - -export default CoreFieldsSection diff --git a/src/shared/helpers/Validator.ts b/src/shared/helpers/Validator.ts new file mode 100644 index 000000000..bdbc8e6fb --- /dev/null +++ b/src/shared/helpers/Validator.ts @@ -0,0 +1,12 @@ +export class Validator { + static isValidEmail(email: string): boolean { + const EMAIL_REGEX = + /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])/ + return EMAIL_REGEX.test(email) + } + + static isValidIdentifier(input: string): boolean { + const IDENTIFIER_REGEX = /^[a-zA-Z0-9_-]+$/ + return IDENTIFIER_REGEX.test(input) + } +} From c3340a7c6f17bef7a4aae3c806482c5f6feb3351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Wed, 10 Jul 2024 16:57:08 -0300 Subject: [PATCH 07/31] feat(design system): add size prop to Button component --- packages/design-system/CHANGELOG.md | 1 + .../src/lib/components/button/Button.tsx | 4 ++++ .../src/lib/stories/button/Button.stories.tsx | 14 ++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/packages/design-system/CHANGELOG.md b/packages/design-system/CHANGELOG.md index 4e6e4a122..e1ddb67e8 100644 --- a/packages/design-system/CHANGELOG.md +++ b/packages/design-system/CHANGELOG.md @@ -41,6 +41,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline - **ProgressBar:** NEW progress bar element to show progress. - **NavbarDropdownItem:** Now accepts `as` prop and takes `as` Element props. - **FormInputGroup:** extend Props Interface to accept `hasValidation` prop to properly show rounded corners in an with validation +- **Button:** extend Props Interface to accept `size` prop. # [1.1.0](https://github.com/IQSS/dataverse-frontend/compare/@iqss/dataverse-design-system@1.0.1...@iqss/dataverse-design-system@1.1.0) (2024-03-12) diff --git a/packages/design-system/src/lib/components/button/Button.tsx b/packages/design-system/src/lib/components/button/Button.tsx index 6d4360e6b..130e80e40 100644 --- a/packages/design-system/src/lib/components/button/Button.tsx +++ b/packages/design-system/src/lib/components/button/Button.tsx @@ -4,10 +4,12 @@ import { Button as ButtonBS } from 'react-bootstrap' import { IconName } from '../icon/IconName' import { Icon } from '../icon/Icon' +type ButtonSize = 'sm' | 'lg' type ButtonVariant = 'primary' | 'secondary' | 'link' type ButtonType = 'button' | 'reset' | 'submit' interface ButtonProps extends HTMLAttributes { + size?: ButtonSize variant?: ButtonVariant disabled?: boolean onClick?: (event: MouseEvent) => void @@ -18,6 +20,7 @@ interface ButtonProps extends HTMLAttributes { } export function Button({ + size, variant = 'primary', disabled = false, onClick, @@ -29,6 +32,7 @@ export function Button({ }: ButtonProps) { return ( ( + <> + + + + + ) +} + export const Disabled: Story = { render: () => ( <> From 371be4446ef1e3d42086bcd59266adad2b402b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Wed, 10 Jul 2024 17:06:03 -0300 Subject: [PATCH 08/31] feat: default values and enhanced identifier/alias field --- public/locales/en/createCollection.json | 7 +- .../create-collection/CreateCollection.tsx | 30 +++---- .../CollectionForm.module.scss | 29 +++++++ .../collection-form/index.tsx | 30 +++---- .../top-fields-section/ContactsField.tsx | 2 +- .../top-fields-section/IdentifierField.tsx | 81 +++++++++++++++++++ .../top-fields-section/index.tsx | 51 ++---------- 7 files changed, 152 insertions(+), 78 deletions(-) create mode 100644 src/sections/create-collection/collection-form/top-fields-section/IdentifierField.tsx diff --git a/public/locales/en/createCollection.json b/public/locales/en/createCollection.json index 2c701b242..8d14585af 100644 --- a/public/locales/en/createCollection.json +++ b/public/locales/en/createCollection.json @@ -7,9 +7,9 @@ "required": "Host Collection is required" }, "name": { - "label": "Dataverse Name", + "label": "Collection Name", "description": "The project, department, university, professor, or journal this collection will contain data for.", - "required": "Dataverse Name is required" + "required": "Collection Name is required" }, "affiliation": { "label": "Affiliation", @@ -22,7 +22,8 @@ "invalid": { "format": "Identifier is not a valid identifier. Valid characters are a-Z, 0-9, '_', and '-'.", "maxLength": "Identifier must be at most {{maxLength}} characters." - } + }, + "suggestion": "Psst... try this" }, "storage": { "label": "Storage", diff --git a/src/sections/create-collection/CreateCollection.tsx b/src/sections/create-collection/CreateCollection.tsx index 29561da9a..32eef981e 100644 --- a/src/sections/create-collection/CreateCollection.tsx +++ b/src/sections/create-collection/CreateCollection.tsx @@ -1,11 +1,12 @@ +import { useEffect } from 'react' +import { useTranslation } from 'react-i18next' import { useCollection } from '../collection/useCollection' import { CollectionRepository } from '../../collection/domain/repositories/CollectionRepository' -import { BreadcrumbsGenerator } from '../shared/hierarchy/BreadcrumbsGenerator' -import { useTranslation } from 'react-i18next' -import { RequiredFieldText } from '../shared/form/RequiredFieldText/RequiredFieldText' -import { CollectionForm, CollectionFormProps } from './collection-form' -import { useEffect } from 'react' import { useLoading } from '../loading/LoadingContext' +import { useSession } from '../session/SessionContext' +import { RequiredFieldText } from '../shared/form/RequiredFieldText/RequiredFieldText' +import { BreadcrumbsGenerator } from '../shared/hierarchy/BreadcrumbsGenerator' +import { CollectionForm, CollectionFormData } from './collection-form' interface CreateCollectionProps { ownerCollectionId: string @@ -18,14 +19,13 @@ export function CreateCollection({ }: CreateCollectionProps) { const { t } = useTranslation('createCollection') const { isLoading, setIsLoading } = useLoading() + const { user } = useSession() const { collection, isLoading: isLoadingCollection } = useCollection( collectionRepository, ownerCollectionId ) - console.log({ collection, isLoadingCollection }) - // TODO:ME If is not loading and collection is not found, show a message and navigate to the root collection // One good thing would be add toastify to show messages on top of the page for this kind of things @@ -44,15 +44,15 @@ export function CreateCollection({ return

Loading...

} // TODO:ME name = user name, affiliation = user affiliation, first email = user email - const formDefaultValues: CollectionFormProps['defaultValues'] = { - parentCollectionName: collection.name, - name: 'The Nameeeee', - alias: 'Some Alias', - type: 'Department', - contacts: [{ value: '' }], - affiliation: 'Some Affi', + const formDefaultValues: Partial = { + hostCollection: collection.name, + name: user?.displayName ? `${user?.displayName} Collection` : '', + alias: '', + type: undefined, + contacts: [{ value: user?.email ?? '' }], + affiliation: user?.affiliation ?? '', storage: 'Local (Default)', - description: 'Some Description' + description: '' } return ( diff --git a/src/sections/create-collection/collection-form/CollectionForm.module.scss b/src/sections/create-collection/collection-form/CollectionForm.module.scss index e67184332..a2fca1ed7 100644 --- a/src/sections/create-collection/collection-form/CollectionForm.module.scss +++ b/src/sections/create-collection/collection-form/CollectionForm.module.scss @@ -2,6 +2,35 @@ :global .input-group-text { font-size: 14px; } + + .suggestion-container { + display: flex; + gap: 0.5rem; + align-items: center; + width: 100%; + margin-top: -0.75rem; + margin-bottom: 1rem; + padding-top: 0.25rem; + + :global .form-text { + margin-top: 0; + } + + .apply-suggestion-btn { + display: grid; + place-items: center; + padding: 3px; + border-radius: 50%; + } + } +} + +.contact-row { + margin-bottom: 1rem; + + &:last-child { + margin-bottom: 0; + } } .dynamic-fields-button-container { diff --git a/src/sections/create-collection/collection-form/index.tsx b/src/sections/create-collection/collection-form/index.tsx index f91cb2703..613de998c 100644 --- a/src/sections/create-collection/collection-form/index.tsx +++ b/src/sections/create-collection/collection-form/index.tsx @@ -1,29 +1,31 @@ import { useRef } from 'react' import { FormProvider, useForm } from 'react-hook-form' -import { TopFieldsSection } from './top-fields-section' import { CollectionType, CollectionStorage } from '../../../collection/domain/useCases/DTOs/CollectionDTO' import { SeparationLine } from '../../shared/layout/SeparationLine/SeparationLine' +import { TopFieldsSection } from './top-fields-section' export interface CollectionFormProps { - defaultValues: { - parentCollectionName: string - name: string - affiliation?: string - alias: string - storage?: CollectionStorage - type: CollectionType - description?: string - contacts: { value: string }[] - } + defaultValues: Partial +} + +export type CollectionFormData = { + hostCollection: string + name: string + affiliation?: string + alias: string + storage?: CollectionStorage + type: CollectionType + description?: string + contacts: { value: string }[] } export const CollectionForm = ({ defaultValues }: CollectionFormProps) => { const formContainerRef = useRef(null) - const form = useForm({ + const form = useForm({ mode: 'onChange', defaultValues }) @@ -38,8 +40,8 @@ export const CollectionForm = ({ defaultValues }: CollectionFormProps) => { if (!isButton && !isButtonTypeSubmit) e.preventDefault() } - const submitForm = (formValues: any) => { - // console.log({ formValues }) + const submitForm = (formValues: CollectionFormData) => { + console.log({ formValues }) } function onSubmittedCollectionError() { diff --git a/src/sections/create-collection/collection-form/top-fields-section/ContactsField.tsx b/src/sections/create-collection/collection-form/top-fields-section/ContactsField.tsx index 1c32ac9dd..c53cdf5f2 100644 --- a/src/sections/create-collection/collection-form/top-fields-section/ContactsField.tsx +++ b/src/sections/create-collection/collection-form/top-fields-section/ContactsField.tsx @@ -56,7 +56,7 @@ export const ContactsField = ({ rules }: ContactsFieldProps) => { {(fieldsArray as { id: string; value: string }[]).map((field, index) => ( - + { + const { t } = useTranslation('createCollection') + const { control, setValue } = useFormContext() + const nameFieldValue = useWatch({ name: 'name' }) as string + + const collectionNameToAlias = (name: string) => { + if (!name) return '' + + return name + .toLowerCase() // Convert to lowercase + .trim() // Remove leading/trailing whitespace + .replace(/[^\w\s-]/g, '') // Remove non-alphanumeric characters except for spaces and hyphens + .replace(/[\s_-]+/g, '-') // Replace spaces and underscores with hyphens + .replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens + } + + const aliasSuggestion = useMemo(() => collectionNameToAlias(nameFieldValue), [nameFieldValue]) + + const applyAliasSuggestion = () => + setValue('alias', aliasSuggestion, { shouldValidate: true, shouldDirty: true }) + + return ( + + + {t('fields.alias.label')} + + + ( + + + {window.location.origin}/spa/collections/ + + {error?.message} + + + {aliasSuggestion !== '' && value !== aliasSuggestion && !invalid && ( +
+ + {t('fields.alias.suggestion')} 👉 {aliasSuggestion} + + +
+ )} + + )} + /> +
+ ) +} diff --git a/src/sections/create-collection/collection-form/top-fields-section/index.tsx b/src/sections/create-collection/collection-form/top-fields-section/index.tsx index ff9dfffb0..7ae71f389 100644 --- a/src/sections/create-collection/collection-form/top-fields-section/index.tsx +++ b/src/sections/create-collection/collection-form/top-fields-section/index.tsx @@ -5,9 +5,9 @@ import { collectionTypeOptions, collectionStorageOptions } from '../../../../collection/domain/useCases/DTOs/CollectionDTO' -import { ContactsField } from './ContactsField' -import styles from '../CollectionForm.module.scss' import { Validator } from '../../../../shared/helpers/Validator' +import { ContactsField } from './ContactsField' +import { IdentifierField } from './IdentifierField' export const TopFieldsSection = () => { const { t } = useTranslation('createCollection') @@ -69,9 +69,6 @@ export const TopFieldsSection = () => { aria-required={true} ref={ref} disabled - // readOnly - // plaintext - // TODO:ME Check this, modify ds component? /> {error?.message} @@ -112,7 +109,6 @@ export const TopFieldsSection = () => { ( { {/* Identifier(alias) & Storage */} - - - {t('fields.alias.label')} - - ( - - - - {window.location.origin}/spa/collections/ - - - {error?.message} - - - )} - /> - + {t('fields.storage.label')} @@ -172,7 +135,6 @@ export const TopFieldsSection = () => { ( { {/* Category (type) & Description */} - + {t('fields.type.label')} { @@ -226,20 +189,18 @@ export const TopFieldsSection = () => { - + {t('fields.description.label')} ( {error?.message} From 42af6af9702d8e06aa93169d28f32f06a81f8b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Thu, 11 Jul 2024 08:58:50 -0300 Subject: [PATCH 09/31] chore: change naming from create to new --- .../en/{createCollection.json => newCollection.json} | 2 +- src/Router.tsx | 6 +++--- src/sections/Route.enum.ts | 5 ++--- src/sections/layout/header/Header.tsx | 2 +- .../NewCollection.tsx} | 11 +++-------- .../NewCollectionFactory.tsx} | 10 +++++----- .../collection-form/CollectionForm.module.scss | 0 .../collection-form/index.tsx | 0 .../top-fields-section/ContactsField.tsx | 2 +- .../top-fields-section/IdentifierField.tsx | 3 ++- .../collection-form/top-fields-section/index.tsx | 2 +- .../shared/add-data-actions/AddDataActionsButton.tsx | 2 +- 12 files changed, 20 insertions(+), 25 deletions(-) rename public/locales/en/{createCollection.json => newCollection.json} (97%) rename src/sections/{create-collection/CreateCollection.tsx => new-collection/NewCollection.tsx} (90%) rename src/sections/{create-collection/CreateCollectionFactory.tsx => new-collection/NewCollectionFactory.tsx} (78%) rename src/sections/{create-collection => new-collection}/collection-form/CollectionForm.module.scss (100%) rename src/sections/{create-collection => new-collection}/collection-form/index.tsx (100%) rename src/sections/{create-collection => new-collection}/collection-form/top-fields-section/ContactsField.tsx (98%) rename src/sections/{create-collection => new-collection}/collection-form/top-fields-section/IdentifierField.tsx (96%) rename src/sections/{create-collection => new-collection}/collection-form/top-fields-section/index.tsx (99%) diff --git a/public/locales/en/createCollection.json b/public/locales/en/newCollection.json similarity index 97% rename from public/locales/en/createCollection.json rename to public/locales/en/newCollection.json index 8d14585af..679f85fb0 100644 --- a/public/locales/en/createCollection.json +++ b/public/locales/en/newCollection.json @@ -1,5 +1,5 @@ { - "pageTitle": "Create Collection", + "pageTitle": "New Collection", "fields": { "hostCollection": { "label": "Host Collection", diff --git a/src/Router.tsx b/src/Router.tsx index 0376afdf7..6350d8056 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -8,7 +8,7 @@ import { FileFactory } from './sections/file/FileFactory' import { CollectionFactory } from './sections/collection/CollectionFactory' import { UploadDatasetFilesFactory } from './sections/upload-dataset-files/UploadDatasetFilesFactory' import { DatasetNonNumericVersion } from './dataset/domain/models/Dataset' -import { CreateCollectionFactory } from './sections/create-collection/CreateCollectionFactory' +import { NewCollectionFactory } from './sections/new-collection/NewCollectionFactory' const router = createBrowserRouter( [ @@ -26,8 +26,8 @@ const router = createBrowserRouter( element: CollectionFactory.create() }, { - path: Route.CREATE_COLLECTION, - element: CreateCollectionFactory.create() + path: Route.NEW_COLLECTION, + element: NewCollectionFactory.create() }, { path: Route.DATASETS, diff --git a/src/sections/Route.enum.ts b/src/sections/Route.enum.ts index dbbfec6dc..78280eaae 100644 --- a/src/sections/Route.enum.ts +++ b/src/sections/Route.enum.ts @@ -8,10 +8,9 @@ export enum Route { UPLOAD_DATASET_FILES = '/datasets/upload-files', FILES = '/files', COLLECTIONS = '/collections', - CREATE_COLLECTION = '/collections/create/:ownerCollectionId' + NEW_COLLECTION = '/collections/new/:ownerCollectionId' } export const RouteWithParams = { - CREATE_COLLECTION: (ownerCollectionId?: string) => - `/collections/create/${ownerCollectionId ?? 'root'}` + NEW_COLLECTION: (ownerCollectionId?: string) => `/collections/new/${ownerCollectionId ?? 'root'}` } diff --git a/src/sections/layout/header/Header.tsx b/src/sections/layout/header/Header.tsx index a0ff8429c..a4309f64b 100644 --- a/src/sections/layout/header/Header.tsx +++ b/src/sections/layout/header/Header.tsx @@ -18,7 +18,7 @@ export function Header() { }) } - const createCollectionRoute = RouteWithParams.CREATE_COLLECTION() + const createCollectionRoute = RouteWithParams.NEW_COLLECTION() return ( ) } - -export default CreateCollection diff --git a/src/sections/create-collection/CreateCollectionFactory.tsx b/src/sections/new-collection/NewCollectionFactory.tsx similarity index 78% rename from src/sections/create-collection/CreateCollectionFactory.tsx rename to src/sections/new-collection/NewCollectionFactory.tsx index 21cdd8361..f9c248495 100644 --- a/src/sections/create-collection/CreateCollectionFactory.tsx +++ b/src/sections/new-collection/NewCollectionFactory.tsx @@ -1,24 +1,24 @@ import { ReactElement } from 'react' import { useParams } from 'react-router-dom' import { CollectionJSDataverseRepository } from '../../collection/infrastructure/repositories/CollectionJSDataverseRepository' -import CreateCollection from './CreateCollection' +import { NewCollection } from './NewCollection' const collectionRepository = new CollectionJSDataverseRepository() -export class CreateCollectionFactory { +export class NewCollectionFactory { static create(): ReactElement { - return + return } } -function CreateCollectionWithParams() { +function NewCollectionWithParams() { const { ownerCollectionId = 'root' } = useParams<{ ownerCollectionId: string }>() // TODO:ME Maybe assert that collection with ownerCollectionId exists first, could be root or a specific collection // TODO:ME What roles can create a collection, what checks to do? return ( - diff --git a/src/sections/create-collection/collection-form/CollectionForm.module.scss b/src/sections/new-collection/collection-form/CollectionForm.module.scss similarity index 100% rename from src/sections/create-collection/collection-form/CollectionForm.module.scss rename to src/sections/new-collection/collection-form/CollectionForm.module.scss diff --git a/src/sections/create-collection/collection-form/index.tsx b/src/sections/new-collection/collection-form/index.tsx similarity index 100% rename from src/sections/create-collection/collection-form/index.tsx rename to src/sections/new-collection/collection-form/index.tsx diff --git a/src/sections/create-collection/collection-form/top-fields-section/ContactsField.tsx b/src/sections/new-collection/collection-form/top-fields-section/ContactsField.tsx similarity index 98% rename from src/sections/create-collection/collection-form/top-fields-section/ContactsField.tsx rename to src/sections/new-collection/collection-form/top-fields-section/ContactsField.tsx index c53cdf5f2..86195f53c 100644 --- a/src/sections/create-collection/collection-form/top-fields-section/ContactsField.tsx +++ b/src/sections/new-collection/collection-form/top-fields-section/ContactsField.tsx @@ -14,7 +14,7 @@ interface ContactsFieldProps { } export const ContactsField = ({ rules }: ContactsFieldProps) => { - const { t } = useTranslation('createCollection') + const { t } = useTranslation('newCollection') const { control } = useFormContext() const { diff --git a/src/sections/create-collection/collection-form/top-fields-section/IdentifierField.tsx b/src/sections/new-collection/collection-form/top-fields-section/IdentifierField.tsx similarity index 96% rename from src/sections/create-collection/collection-form/top-fields-section/IdentifierField.tsx rename to src/sections/new-collection/collection-form/top-fields-section/IdentifierField.tsx index 76dd5d9b1..ff78d96a2 100644 --- a/src/sections/create-collection/collection-form/top-fields-section/IdentifierField.tsx +++ b/src/sections/new-collection/collection-form/top-fields-section/IdentifierField.tsx @@ -10,7 +10,7 @@ interface IdentifierFieldProps { } export const IdentifierField = ({ rules }: IdentifierFieldProps) => { - const { t } = useTranslation('createCollection') + const { t } = useTranslation('newCollection') const { control, setValue } = useFormContext() const nameFieldValue = useWatch({ name: 'name' }) as string @@ -23,6 +23,7 @@ export const IdentifierField = ({ rules }: IdentifierFieldProps) => { .replace(/[^\w\s-]/g, '') // Remove non-alphanumeric characters except for spaces and hyphens .replace(/[\s_-]+/g, '-') // Replace spaces and underscores with hyphens .replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens + .slice(0, 60) // Limit to 60 characters } const aliasSuggestion = useMemo(() => collectionNameToAlias(nameFieldValue), [nameFieldValue]) diff --git a/src/sections/create-collection/collection-form/top-fields-section/index.tsx b/src/sections/new-collection/collection-form/top-fields-section/index.tsx similarity index 99% rename from src/sections/create-collection/collection-form/top-fields-section/index.tsx rename to src/sections/new-collection/collection-form/top-fields-section/index.tsx index 7ae71f389..7575eb3c0 100644 --- a/src/sections/create-collection/collection-form/top-fields-section/index.tsx +++ b/src/sections/new-collection/collection-form/top-fields-section/index.tsx @@ -10,7 +10,7 @@ import { ContactsField } from './ContactsField' import { IdentifierField } from './IdentifierField' export const TopFieldsSection = () => { - const { t } = useTranslation('createCollection') + const { t } = useTranslation('newCollection') const { control } = useFormContext() const hostCollectionRules: UseControllerProps['rules'] = { diff --git a/src/sections/shared/add-data-actions/AddDataActionsButton.tsx b/src/sections/shared/add-data-actions/AddDataActionsButton.tsx index 5cd8ccb37..7ca8c92a6 100644 --- a/src/sections/shared/add-data-actions/AddDataActionsButton.tsx +++ b/src/sections/shared/add-data-actions/AddDataActionsButton.tsx @@ -15,7 +15,7 @@ export default function AddDataActionsButton() { ? `${Route.CREATE_DATASET}?collectionId=${collectionId}` : Route.CREATE_DATASET - const createCollectionRoute = RouteWithParams.CREATE_COLLECTION(collectionId) + const createCollectionRoute = RouteWithParams.NEW_COLLECTION(collectionId) return ( Date: Thu, 11 Jul 2024 09:08:37 -0300 Subject: [PATCH 10/31] feat(design system): add autoFocus prop --- packages/design-system/CHANGELOG.md | 3 +++ .../form/form-group/form-element/FormInput.tsx | 3 +++ .../form/form-group/form-element/FormSelect.tsx | 11 ++++++++++- .../form/form-group/form-element/FormTextArea.tsx | 4 +++- 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/design-system/CHANGELOG.md b/packages/design-system/CHANGELOG.md index e1ddb67e8..c3b94e90a 100644 --- a/packages/design-system/CHANGELOG.md +++ b/packages/design-system/CHANGELOG.md @@ -42,6 +42,9 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline - **NavbarDropdownItem:** Now accepts `as` prop and takes `as` Element props. - **FormInputGroup:** extend Props Interface to accept `hasValidation` prop to properly show rounded corners in an with validation - **Button:** extend Props Interface to accept `size` prop. +- **FormInput:** extend Props Interface to accept `autoFocus` prop. +- **FormTextArea:** extend Props Interface to accept `autoFocus` prop. +- **FormSelect:** extend Props Interface to accept `autoFocus` prop. # [1.1.0](https://github.com/IQSS/dataverse-frontend/compare/@iqss/dataverse-design-system@1.0.1...@iqss/dataverse-design-system@1.1.0) (2024-03-12) diff --git a/packages/design-system/src/lib/components/form/form-group/form-element/FormInput.tsx b/packages/design-system/src/lib/components/form/form-group/form-element/FormInput.tsx index cf404cd24..106a77110 100644 --- a/packages/design-system/src/lib/components/form/form-group/form-element/FormInput.tsx +++ b/packages/design-system/src/lib/components/form/form-group/form-element/FormInput.tsx @@ -12,6 +12,7 @@ export interface FormInputProps extends React.HTMLAttributes { disabled?: boolean value?: string | number required?: boolean + autoFocus?: boolean } export const FormInput = React.forwardRef(function FormInput( @@ -24,6 +25,7 @@ export const FormInput = React.forwardRef(function FormInput( disabled, value, required, + autoFocus, ...props }: FormInputProps, ref @@ -39,6 +41,7 @@ export const FormInput = React.forwardRef(function FormInput( disabled={disabled} value={value} required={required} + autoFocus={autoFocus} ref={ref as React.ForwardedRef} {...props} /> diff --git a/packages/design-system/src/lib/components/form/form-group/form-element/FormSelect.tsx b/packages/design-system/src/lib/components/form/form-group/form-element/FormSelect.tsx index fb99ce748..32fa9fee0 100644 --- a/packages/design-system/src/lib/components/form/form-group/form-element/FormSelect.tsx +++ b/packages/design-system/src/lib/components/form/form-group/form-element/FormSelect.tsx @@ -8,10 +8,18 @@ export interface FormSelectProps isInvalid?: boolean isValid?: boolean disabled?: boolean + autoFocus?: boolean } export const FormSelect = React.forwardRef(function FormSelect( - { isInvalid, isValid, disabled, children, ...props }: PropsWithChildren, + { + isInvalid, + isValid, + disabled, + autoFocus, + children, + ...props + }: PropsWithChildren, ref ) { return ( @@ -19,6 +27,7 @@ export const FormSelect = React.forwardRef(function FormSelect( isInvalid={isInvalid} isValid={isValid} disabled={disabled} + autoFocus={autoFocus} ref={ref as React.ForwardedRef} {...props}> {children} diff --git a/packages/design-system/src/lib/components/form/form-group/form-element/FormTextArea.tsx b/packages/design-system/src/lib/components/form/form-group/form-element/FormTextArea.tsx index 0fdb994b5..b18448dc5 100644 --- a/packages/design-system/src/lib/components/form/form-group/form-element/FormTextArea.tsx +++ b/packages/design-system/src/lib/components/form/form-group/form-element/FormTextArea.tsx @@ -8,10 +8,11 @@ export interface FormTextAreaProps extends Omit} {...props} /> From 06c0275cf5ad06077f8bae70fb53675fc7c06918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Thu, 11 Jul 2024 11:22:49 -0300 Subject: [PATCH 11/31] feat (design system): new Stack component --- packages/design-system/CHANGELOG.md | 1 + .../src/lib/components/stack/Stack.tsx | 26 ++++ packages/design-system/src/lib/index.ts | 1 + .../src/lib/stories/stack/Stack.stories.tsx | 120 ++++++++++++++++++ .../tests/component/stack/Stack.spec.tsx | 38 ++++++ 5 files changed, 186 insertions(+) create mode 100644 packages/design-system/src/lib/components/stack/Stack.tsx create mode 100644 packages/design-system/src/lib/stories/stack/Stack.stories.tsx create mode 100644 packages/design-system/tests/component/stack/Stack.spec.tsx diff --git a/packages/design-system/CHANGELOG.md b/packages/design-system/CHANGELOG.md index c3b94e90a..580561b86 100644 --- a/packages/design-system/CHANGELOG.md +++ b/packages/design-system/CHANGELOG.md @@ -45,6 +45,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline - **FormInput:** extend Props Interface to accept `autoFocus` prop. - **FormTextArea:** extend Props Interface to accept `autoFocus` prop. - **FormSelect:** extend Props Interface to accept `autoFocus` prop. +- **Stack:** NEW Stack element to manage layouts. # [1.1.0](https://github.com/IQSS/dataverse-frontend/compare/@iqss/dataverse-design-system@1.0.1...@iqss/dataverse-design-system@1.1.0) (2024-03-12) diff --git a/packages/design-system/src/lib/components/stack/Stack.tsx b/packages/design-system/src/lib/components/stack/Stack.tsx new file mode 100644 index 000000000..c7e871b70 --- /dev/null +++ b/packages/design-system/src/lib/components/stack/Stack.tsx @@ -0,0 +1,26 @@ +import { ComponentPropsWithoutRef, ElementType } from 'react' +import { Stack as StackBS } from 'react-bootstrap' + +type StackProps = { + direction?: 'horizontal' | 'vertical' + gap?: 0 | 1 | 2 | 3 | 4 | 5 + as?: T + children: React.ReactNode +} & (T extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[T] : ComponentPropsWithoutRef) + +export function Stack({ + direction = 'vertical', + gap = 3, + as, + children, + ...rest +}: StackProps) { + /* eslint-disable @typescript-eslint/no-explicit-any */ + const Component: ElementType = as || 'div' + + return ( + + {children} + + ) +} diff --git a/packages/design-system/src/lib/index.ts b/packages/design-system/src/lib/index.ts index fbac72d65..28240a8af 100644 --- a/packages/design-system/src/lib/index.ts +++ b/packages/design-system/src/lib/index.ts @@ -27,3 +27,4 @@ export { RequiredInputSymbol } from './components/form/required-input-symbol/Req export { SelectMultiple } from './components/select-multiple/SelectMultiple' export { Card } from './components/card/Card' export { ProgressBar } from './components/progress-bar/ProgressBar' +export { Stack } from './components/stack/Stack' diff --git a/packages/design-system/src/lib/stories/stack/Stack.stories.tsx b/packages/design-system/src/lib/stories/stack/Stack.stories.tsx new file mode 100644 index 000000000..e466129b5 --- /dev/null +++ b/packages/design-system/src/lib/stories/stack/Stack.stories.tsx @@ -0,0 +1,120 @@ +import { CSSProperties } from 'react' +import type { Meta, StoryObj } from '@storybook/react' +import { Stack } from '../../components/stack/Stack' +import { Col } from '../../components/grid/Col' + +/** + * ## Description + * Stacks are vertical by default and stacked items are full-width by default. Use the gap prop to add space between items. + * + * Use direction="horizontal" for horizontal layouts. Stacked items are vertically centered by default and only take up their necessary width. + * + * Use the gap prop to add space between items. + */ +const meta: Meta = { + tags: ['autodocs'], + title: 'Stack', + component: Stack +} + +export default meta +type Story = StoryObj + +const inlineStyles: CSSProperties = { + backgroundColor: '#337AB7', + color: 'white', + padding: '0.5rem' +} + +export const VerticalStack: Story = { + render: () => ( + +
Item 1
+
Item 2
+
Item 3
+
+ ) +} + +/** + * Use direction="horizontal" for horizontal layouts. + * Stacked items are vertically centered by default and only take up their necessary width. + */ +export const HorizontalStack: Story = { + render: () => ( + +
Item 1
+
Item 2
+
Item 3
+
+ ) +} +/** + * By using Columns as childrens of the Stack, you can create a layout with columns that are full-width by default. + */ +export const HorizontalStackWithColumns: Story = { + render: () => ( + + Item 1 + Item 2 + Item 3 + + ) +} +/** + * Gap 0 = 0 + * + * Gap 1 = 0.25rem (4px) + * + * Gap 2 = 0.5rem (8px) + * + * Gap 3 = 1rem (16px) + * + * Gap 4 = 1.5rem (24px) + * + * Gap 5 = 3rem (48px) + */ +export const AllGaps: Story = { + render: () => ( + + + Item 1 + Item 2 + + + Item 1 + Item 2 + + + Item 1 + Item 2 + + + Item 1 + Item 2 + + + Item 1 + Item 2 + + + Item 1 + Item 2 + + + ) +} + +/** + * Use `as` prop to render the Stack as a different element. + * If you inspect the rendered HTML, you will see that the Stack is rendered as a section element. + */ +export const StackAsSection: Story = { + render: () => ( + +
Item 1
+
Item 2
+
Item 3
+
+ ) +} diff --git a/packages/design-system/tests/component/stack/Stack.spec.tsx b/packages/design-system/tests/component/stack/Stack.spec.tsx new file mode 100644 index 000000000..b32e65882 --- /dev/null +++ b/packages/design-system/tests/component/stack/Stack.spec.tsx @@ -0,0 +1,38 @@ +import { Stack } from '../../../src/lib/components/stack/Stack' + +describe('Stack', () => { + it('renders vertically by default', () => { + cy.mount( + +
Item 1
+
Item 2
+
Item 3
+
+ ) + + cy.findByTestId('vertical-by-default').should('have.css', 'flex-direction', 'column') + }) + + it('renders horizontally when direction="horizontal"', () => { + cy.mount( + +
Item 1
+
Item 2
+
Item 3
+
+ ) + + cy.findByTestId('horizontal').should('have.css', 'flex-direction', 'row') + }) + + it('renders with the correct gap', () => { + cy.mount( + +
Item 1
+
Item 2
+
+ ) + + cy.findByTestId('gap').should('have.css', 'gap', '24px') + }) +}) From 6ac67ad919c8f5701a81eb7f3c402b4c549a316c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Thu, 11 Jul 2024 11:44:48 -0300 Subject: [PATCH 12/31] feat: temporary sections and improvements --- public/locales/en/newCollection.json | 12 ++++ src/sections/new-collection/NewCollection.tsx | 7 ++ .../browse-search-facets-section/index.tsx | 22 ++++++ .../new-collection/collection-form/index.tsx | 55 ++++++++++++++- .../metadata-fields-section/index.tsx | 22 ++++++ .../top-fields-section/ContactsField.tsx | 2 +- .../top-fields-section/IdentifierField.tsx | 6 +- .../top-fields-section/index.tsx | 67 +++++++++---------- 8 files changed, 152 insertions(+), 41 deletions(-) create mode 100644 src/sections/new-collection/collection-form/browse-search-facets-section/index.tsx create mode 100644 src/sections/new-collection/collection-form/metadata-fields-section/index.tsx diff --git a/public/locales/en/newCollection.json b/public/locales/en/newCollection.json index 679f85fb0..34118dc05 100644 --- a/public/locales/en/newCollection.json +++ b/public/locales/en/newCollection.json @@ -43,6 +43,18 @@ "description": "The email address(es) of the contact(s) for the collection.", "required": "Email is required", "invalid": "Email is not a valid email" + }, + "metadataFields": { + "label": "Metadata Fields", + "helperText": "Choose the metadata fields to use in dataset templates and when adding a dataset to this dataverse." + }, + "browseSearchFacets": { + "label": "Browse/Search Facets", + "helperText": "Choose the metadata fields to use as facets for browsing datasets and dataverses in this dataverse." } + }, + "formButtons": { + "save": "Create Collection", + "cancel": "Cancel" } } diff --git a/src/sections/new-collection/NewCollection.tsx b/src/sections/new-collection/NewCollection.tsx index ad0e1477e..deb1e2088 100644 --- a/src/sections/new-collection/NewCollection.tsx +++ b/src/sections/new-collection/NewCollection.tsx @@ -7,6 +7,7 @@ import { useSession } from '../session/SessionContext' import { RequiredFieldText } from '../shared/form/RequiredFieldText/RequiredFieldText' import { BreadcrumbsGenerator } from '../shared/hierarchy/BreadcrumbsGenerator' import { CollectionForm, CollectionFormData } from './collection-form' +import { SeparationLine } from '../shared/layout/SeparationLine/SeparationLine' interface NewCollectionProps { ownerCollectionId: string @@ -59,6 +60,12 @@ export function NewCollection({ ownerCollectionId, collectionRepository }: NewCo withActionItem actionItemText={t('pageTitle')} /> +
+

New Collection

+
+ + + diff --git a/src/sections/new-collection/collection-form/browse-search-facets-section/index.tsx b/src/sections/new-collection/collection-form/browse-search-facets-section/index.tsx new file mode 100644 index 000000000..f0152a27a --- /dev/null +++ b/src/sections/new-collection/collection-form/browse-search-facets-section/index.tsx @@ -0,0 +1,22 @@ +import { useTranslation } from 'react-i18next' +import { Alert, Col, Form, Row } from '@iqss/dataverse-design-system' + +export const BrowseSearchFacetsSection = () => { + const { t } = useTranslation('newCollection') + + return ( + + + {t('fields.browseSearchFacets.label')} + + + {t('fields.browseSearchFacets.helperText')} + + + Work in progress + + + + + ) +} diff --git a/src/sections/new-collection/collection-form/index.tsx b/src/sections/new-collection/collection-form/index.tsx index 613de998c..5dd4d6aa2 100644 --- a/src/sections/new-collection/collection-form/index.tsx +++ b/src/sections/new-collection/collection-form/index.tsx @@ -1,11 +1,16 @@ -import { useRef } from 'react' +import { MouseEvent, useRef } from 'react' import { FormProvider, useForm } from 'react-hook-form' +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' +import { Button, Card, Stack } from '@iqss/dataverse-design-system' import { CollectionType, CollectionStorage } from '../../../collection/domain/useCases/DTOs/CollectionDTO' import { SeparationLine } from '../../shared/layout/SeparationLine/SeparationLine' import { TopFieldsSection } from './top-fields-section' +import { MetadataFieldsSection } from './metadata-fields-section' +import { BrowseSearchFacetsSection } from './browse-search-facets-section' export interface CollectionFormProps { defaultValues: Partial @@ -24,12 +29,16 @@ export type CollectionFormData = { export const CollectionForm = ({ defaultValues }: CollectionFormProps) => { const formContainerRef = useRef(null) + const { t } = useTranslation('newCollection') + const navigate = useNavigate() const form = useForm({ mode: 'onChange', defaultValues }) + const { formState } = form + const preventEnterSubmit = (e: React.KeyboardEvent) => { // When pressing Enter, only submit the form if the user is focused on the submit button itself if (e.key !== 'Enter') return @@ -49,6 +58,19 @@ export const CollectionForm = ({ defaultValues }: CollectionFormProps) => { formContainerRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' }) } } + + const handleCancel = (event: MouseEvent) => { + event.preventDefault() + navigate(-1) + } + + // const disableSubmitButton = useMemo(() => { + // return ( + // // submissionStatus === SubmissionStatus.IsSubmitting || + // !formState.isDirty + // ) + // }, [submissionStatus, formState.isDirty]) + // TODO:ME Apply max width to container return (
{ - {/* Metadata Fields Section here 👇 */} + + + + + + + + + + + + + - {/* Browse/Search Facets Section here 👇 */} + + + +
diff --git a/src/sections/new-collection/collection-form/metadata-fields-section/index.tsx b/src/sections/new-collection/collection-form/metadata-fields-section/index.tsx new file mode 100644 index 000000000..a9d72b0ed --- /dev/null +++ b/src/sections/new-collection/collection-form/metadata-fields-section/index.tsx @@ -0,0 +1,22 @@ +import { useTranslation } from 'react-i18next' +import { Alert, Col, Form, Row } from '@iqss/dataverse-design-system' + +export const MetadataFieldsSection = () => { + const { t } = useTranslation('newCollection') + + return ( + + + {t('fields.metadataFields.label')} + + + {t('fields.metadataFields.helperText')} + + + Work in progress + + + + + ) +} diff --git a/src/sections/new-collection/collection-form/top-fields-section/ContactsField.tsx b/src/sections/new-collection/collection-form/top-fields-section/ContactsField.tsx index 86195f53c..9a31e2932 100644 --- a/src/sections/new-collection/collection-form/top-fields-section/ContactsField.tsx +++ b/src/sections/new-collection/collection-form/top-fields-section/ContactsField.tsx @@ -47,7 +47,7 @@ export const ContactsField = ({ rules }: ContactsFieldProps) => { const handleOnRemoveField = (index: number) => remove(index) return ( - + { diff --git a/src/sections/new-collection/collection-form/top-fields-section/IdentifierField.tsx b/src/sections/new-collection/collection-form/top-fields-section/IdentifierField.tsx index 3125b25fe..be0af60f3 100644 --- a/src/sections/new-collection/collection-form/top-fields-section/IdentifierField.tsx +++ b/src/sections/new-collection/collection-form/top-fields-section/IdentifierField.tsx @@ -57,7 +57,7 @@ export const IdentifierField = ({ rules }: IdentifierFieldProps) => { {error?.message} - {aliasSuggestion !== '' && value !== aliasSuggestion && !invalid && ( + {aliasSuggestion !== '' && value !== aliasSuggestion && (
{t('fields.alias.suggestion')} 👉 {aliasSuggestion} diff --git a/src/sections/new-collection/collection-form/useSubmitCollection.ts b/src/sections/new-collection/collection-form/useSubmitCollection.ts new file mode 100644 index 000000000..6232f62ff --- /dev/null +++ b/src/sections/new-collection/collection-form/useSubmitCollection.ts @@ -0,0 +1,84 @@ +import { useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { useTranslation } from 'react-i18next' +import { WriteError } from '@iqss/dataverse-client-javascript' +import { createCollection } from '../../../collection/domain/useCases/createCollection' +import { CollectionRepository } from '../../../collection/domain/repositories/CollectionRepository' +import { CollectionDTO } from '../../../collection/domain/useCases/DTOs/CollectionDTO' +import { CollectionFormData } from '.' +import { Route } from '../../Route.enum' +import { JSDataverseWriteErrorHandler } from '../../../shared/helpers/JSDataverseWriteErrorHandler' + +export enum SubmissionStatus { + NotSubmitted = 'NotSubmitted', + IsSubmitting = 'IsSubmitting', + SubmitComplete = 'SubmitComplete', + Errored = 'Errored' +} + +type UseSubmitCollectionReturnType = + | { + submissionStatus: + | SubmissionStatus.NotSubmitted + | SubmissionStatus.IsSubmitting + | SubmissionStatus.SubmitComplete + submitForm: (formData: CollectionFormData) => void + submitError: null + } + | { + submissionStatus: SubmissionStatus.Errored + submitForm: (formData: CollectionFormData) => void + submitError: string + } + +export function useSubmitCollection( + collectionRepository: CollectionRepository, + onSubmitErrorCallback: () => void +): UseSubmitCollectionReturnType { + const navigate = useNavigate() + const { t } = useTranslation('newCollection') + + const [submissionStatus, setSubmissionStatus] = useState( + SubmissionStatus.NotSubmitted + ) + const [submitError, setSubmitError] = useState(null) + + const submitForm = (formData: CollectionFormData): void => { + setSubmissionStatus(SubmissionStatus.IsSubmitting) + + const newCollection: CollectionDTO = { + name: formData.name, + alias: formData.alias, + type: formData.type, + contacts: formData.contacts.map((contact) => contact.value) + } + + const hostCollection = formData.hostCollection + + createCollection(collectionRepository, newCollection, hostCollection) + .then(() => { + setSubmitError(null) + setSubmissionStatus(SubmissionStatus.SubmitComplete) + + navigate(`${Route.COLLECTIONS}?id=${newCollection.alias}`, { + state: { created: true } + }) + return + }) + .catch((err: WriteError) => { + const error = new JSDataverseWriteErrorHandler(err) + const formattedError = error.getReasonWithoutStatusCode() ?? error.getErrorMessage() + + setSubmitError(formattedError) + setSubmissionStatus(SubmissionStatus.Errored) + + onSubmitErrorCallback() + }) + } + + return { + submissionStatus, + submitForm, + submitError + } as UseSubmitCollectionReturnType +} diff --git a/src/shared/helpers/JSDataverseWriteErrorHandler.ts b/src/shared/helpers/JSDataverseWriteErrorHandler.ts new file mode 100644 index 000000000..8b5b88d6e --- /dev/null +++ b/src/shared/helpers/JSDataverseWriteErrorHandler.ts @@ -0,0 +1,37 @@ +import { WriteError } from '@iqss/dataverse-client-javascript' + +export class JSDataverseWriteErrorHandler { + private error: WriteError + + constructor(error: WriteError) { + this.error = error + } + + public getErrorMessage(): string { + return this.error.message + } + + public getReason(): string | null { + // Reason comes after "Reason was: " + const reasonMatch = this.error.message.match(/Reason was: (.*)/) + console.log({ reasonMatch }) + return reasonMatch ? reasonMatch[1] : null + } + + public getStatusCode(): number | null { + // Status code comes inside [] brackets + const statusCodeMatch = this.error.message.match(/\[(\d+)\]/) + return statusCodeMatch ? parseInt(statusCodeMatch[1]) : null + } + + public getReasonWithoutStatusCode(): string | null { + const reason = this.getReason() + if (!reason) return null + + const statusCode = this.getStatusCode() + if (statusCode === null) return reason + + // Remove status code from reason + return reason.replace(`[${statusCode}]`, '').trim() + } +} From 4ddabd395af4ee28dbb7ef27fd9d9c8f1a9b01f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Thu, 11 Jul 2024 15:19:11 -0300 Subject: [PATCH 15/31] feat: show error in alerts and scroll to it --- public/locales/en/newCollection.json | 3 +++ src/assets/variables.scss | 3 ++- .../collection-form/CollectionForm.module.scss | 6 ++++++ .../new-collection/collection-form/index.tsx | 17 ++++++++++++++--- .../top-fields-section/index.tsx | 8 +++++++- .../helpers/JSDataverseWriteErrorHandler.ts | 1 - 6 files changed, 32 insertions(+), 6 deletions(-) diff --git a/public/locales/en/newCollection.json b/public/locales/en/newCollection.json index 34118dc05..f98f6c852 100644 --- a/public/locales/en/newCollection.json +++ b/public/locales/en/newCollection.json @@ -53,6 +53,9 @@ "helperText": "Choose the metadata fields to use as facets for browsing datasets and dataverses in this dataverse." } }, + "submitStatus": { + "success": "Collection created successfully." + }, "formButtons": { "save": "Create Collection", "cancel": "Cancel" diff --git a/src/assets/variables.scss b/src/assets/variables.scss index 3d244d437..45b6480c6 100644 --- a/src/assets/variables.scss +++ b/src/assets/variables.scss @@ -1,2 +1,3 @@ $footer-height: 256px; -$body-available-height: calc(100vh - $footer-height); \ No newline at end of file +$body-available-height: calc(100vh - $footer-height); +$header-aproximate-height: 62px; diff --git a/src/sections/new-collection/collection-form/CollectionForm.module.scss b/src/sections/new-collection/collection-form/CollectionForm.module.scss index a2fca1ed7..b0dcb21cb 100644 --- a/src/sections/new-collection/collection-form/CollectionForm.module.scss +++ b/src/sections/new-collection/collection-form/CollectionForm.module.scss @@ -1,3 +1,9 @@ +@import 'src/assets/variables'; + +.form-container { + scroll-margin-top: calc(#{$header-aproximate-height} + 1rem); +} + .identifier-field-group { :global .input-group-text { font-size: 14px; diff --git a/src/sections/new-collection/collection-form/index.tsx b/src/sections/new-collection/collection-form/index.tsx index 1f65aef62..ac7ac9fbd 100644 --- a/src/sections/new-collection/collection-form/index.tsx +++ b/src/sections/new-collection/collection-form/index.tsx @@ -2,17 +2,18 @@ import { MouseEvent, useMemo, useRef } from 'react' import { FormProvider, useForm } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' -import { Button, Card, Stack } from '@iqss/dataverse-design-system' +import { Alert, Button, Card, Stack } from '@iqss/dataverse-design-system' import { CollectionRepository } from '../../../collection/domain/repositories/CollectionRepository' import { CollectionType, CollectionStorage } from '../../../collection/domain/useCases/DTOs/CollectionDTO' import { SeparationLine } from '../../shared/layout/SeparationLine/SeparationLine' +import { SubmissionStatus, useSubmitCollection } from './useSubmitCollection' import { TopFieldsSection } from './top-fields-section' import { MetadataFieldsSection } from './metadata-fields-section' import { BrowseSearchFacetsSection } from './browse-search-facets-section' -import { SubmissionStatus, useSubmitCollection } from './useSubmitCollection' +import styles from './CollectionForm.module.scss' export interface CollectionFormProps { collectionRepository: CollectionRepository @@ -74,7 +75,17 @@ export const CollectionForm = ({ collectionRepository, defaultValues }: Collecti // TODO:ME Apply max width to container return ( -
+
+ {submissionStatus === SubmissionStatus.Errored && ( + + {submitError} + + )} + {submissionStatus === SubmissionStatus.SubmitComplete && ( + + {t('submitStatus.success')} + + )} { )} /> + {t('fields.affiliation.label')} @@ -118,6 +119,7 @@ export const TopFieldsSection = () => { onChange={onChange} isInvalid={invalid} ref={ref} + disabled /> {error?.message} @@ -129,6 +131,7 @@ export const TopFieldsSection = () => { {/* Identifier(alias) & Storage */} + {t('fields.storage.label')} @@ -142,7 +145,8 @@ export const TopFieldsSection = () => { onChange={onChange} value={value as string} isInvalid={invalid} - ref={ref}> + ref={ref} + disabled> {/* TODO:ME What are this options? do they come from a configuration? */} {Object.values(collectionStorageOptions).map((type) => ( @@ -192,6 +196,7 @@ export const TopFieldsSection = () => { + {t('fields.description.label')} @@ -206,6 +211,7 @@ export const TopFieldsSection = () => { onChange={onChange} isInvalid={invalid} ref={ref} + disabled /> {error?.message} diff --git a/src/shared/helpers/JSDataverseWriteErrorHandler.ts b/src/shared/helpers/JSDataverseWriteErrorHandler.ts index 8b5b88d6e..c0e1263a9 100644 --- a/src/shared/helpers/JSDataverseWriteErrorHandler.ts +++ b/src/shared/helpers/JSDataverseWriteErrorHandler.ts @@ -14,7 +14,6 @@ export class JSDataverseWriteErrorHandler { public getReason(): string | null { // Reason comes after "Reason was: " const reasonMatch = this.error.message.match(/Reason was: (.*)/) - console.log({ reasonMatch }) return reasonMatch ? reasonMatch[1] : null } From 399af849f65d6c379b3f57cf6a480c0721b11231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Thu, 11 Jul 2024 16:12:57 -0300 Subject: [PATCH 16/31] test: fix role button --- src/sections/layout/header/Header.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sections/layout/header/Header.tsx b/src/sections/layout/header/Header.tsx index a4309f64b..c09908ba7 100644 --- a/src/sections/layout/header/Header.tsx +++ b/src/sections/layout/header/Header.tsx @@ -6,7 +6,6 @@ import { useSession } from '../../session/SessionContext' import { Link, useNavigate } from 'react-router-dom' import { BASE_URL } from '../../../config' -const currentPage = 0 export function Header() { const { t } = useTranslation('header') const { user, logout } = useSession() @@ -14,7 +13,8 @@ export function Header() { const onLogoutClick = () => { void logout().then(() => { - navigate(currentPage) + navigate(Route.HOME) + window.location.reload() }) } @@ -38,7 +38,7 @@ export function Header() { - + {t('logOut')} From dd80bf69519280b537bca344c3171d62dd56cd91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Thu, 11 Jul 2024 16:13:33 -0300 Subject: [PATCH 17/31] chore: remove unused useTranslation --- .../new-collection/collection-form/useSubmitCollection.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sections/new-collection/collection-form/useSubmitCollection.ts b/src/sections/new-collection/collection-form/useSubmitCollection.ts index 6232f62ff..78a2c63ba 100644 --- a/src/sections/new-collection/collection-form/useSubmitCollection.ts +++ b/src/sections/new-collection/collection-form/useSubmitCollection.ts @@ -1,6 +1,5 @@ import { useState } from 'react' import { useNavigate } from 'react-router-dom' -import { useTranslation } from 'react-i18next' import { WriteError } from '@iqss/dataverse-client-javascript' import { createCollection } from '../../../collection/domain/useCases/createCollection' import { CollectionRepository } from '../../../collection/domain/repositories/CollectionRepository' @@ -36,7 +35,6 @@ export function useSubmitCollection( onSubmitErrorCallback: () => void ): UseSubmitCollectionReturnType { const navigate = useNavigate() - const { t } = useTranslation('newCollection') const [submissionStatus, setSubmissionStatus] = useState( SubmissionStatus.NotSubmitted From bdc54359cfddb8bccf7765b4a57ae29958172ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Fri, 12 Jul 2024 08:39:20 -0300 Subject: [PATCH 18/31] test: undefined as fallback as prop --- .../navbar/navbar-dropdown/NavbarDropdownItem.tsx | 2 +- src/sections/layout/header/Header.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/design-system/src/lib/components/navbar/navbar-dropdown/NavbarDropdownItem.tsx b/packages/design-system/src/lib/components/navbar/navbar-dropdown/NavbarDropdownItem.tsx index f80364051..39c5fe64b 100644 --- a/packages/design-system/src/lib/components/navbar/navbar-dropdown/NavbarDropdownItem.tsx +++ b/packages/design-system/src/lib/components/navbar/navbar-dropdown/NavbarDropdownItem.tsx @@ -17,7 +17,7 @@ export function NavbarDropdownItem({ ...props }: PropsWithChildren>) { /* eslint-disable @typescript-eslint/no-explicit-any */ - const Component: ElementType = as || 'a' + const Component: ElementType | undefined = as return ( diff --git a/src/sections/layout/header/Header.tsx b/src/sections/layout/header/Header.tsx index c09908ba7..a4309f64b 100644 --- a/src/sections/layout/header/Header.tsx +++ b/src/sections/layout/header/Header.tsx @@ -6,6 +6,7 @@ import { useSession } from '../../session/SessionContext' import { Link, useNavigate } from 'react-router-dom' import { BASE_URL } from '../../../config' +const currentPage = 0 export function Header() { const { t } = useTranslation('header') const { user, logout } = useSession() @@ -13,8 +14,7 @@ export function Header() { const onLogoutClick = () => { void logout().then(() => { - navigate(Route.HOME) - window.location.reload() + navigate(currentPage) }) } @@ -38,7 +38,7 @@ export function Header() { - + {t('logOut')} From d8b1a9157db83b1aeb325fdc2926ea9a2dd6e4da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Fri, 12 Jul 2024 11:26:47 -0300 Subject: [PATCH 19/31] feat: default values and not found page --- src/sections/new-collection/NewCollection.tsx | 12 +++++------- .../new-collection/NewCollectionFactory.tsx | 1 - .../new-collection/collection-form/index.tsx | 19 ++++++++++--------- .../collection-form/useSubmitCollection.ts | 4 ++-- 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/sections/new-collection/NewCollection.tsx b/src/sections/new-collection/NewCollection.tsx index 3aa117f6d..961a26639 100644 --- a/src/sections/new-collection/NewCollection.tsx +++ b/src/sections/new-collection/NewCollection.tsx @@ -8,6 +8,7 @@ import { RequiredFieldText } from '../shared/form/RequiredFieldText/RequiredFiel import { BreadcrumbsGenerator } from '../shared/hierarchy/BreadcrumbsGenerator' import { CollectionForm, CollectionFormData } from './collection-form' import { SeparationLine } from '../shared/layout/SeparationLine/SeparationLine' +import { PageNotFound } from '../page-not-found/PageNotFound' interface NewCollectionProps { ownerCollectionId: string @@ -24,9 +25,6 @@ export function NewCollection({ ownerCollectionId, collectionRepository }: NewCo ownerCollectionId ) - // TODO:ME If is not loading and collection is not found, show a message and navigate to the root collection - // One good thing would be add toastify to show messages on top of the page for this kind of things - useEffect(() => { if (!isLoadingCollection) { setIsLoading(false) @@ -34,19 +32,19 @@ export function NewCollection({ ownerCollectionId, collectionRepository }: NewCo }, [isLoading, isLoadingCollection, setIsLoading]) if (!isLoading && !collection) { - return

Owner Collection not found

+ return } // TODO:ME Create Skeleton if (isLoading || !collection) { return

Loading...

} - // TODO:ME name = user name, affiliation = user affiliation, first email = user email - const formDefaultValues: Partial = { + + const formDefaultValues: CollectionFormData = { hostCollection: collection.name, name: user?.displayName ? `${user?.displayName} Collection` : '', alias: '', - type: undefined, + type: '', contacts: [{ value: user?.email ?? '' }], affiliation: user?.affiliation ?? '', storage: 'Local (Default)', diff --git a/src/sections/new-collection/NewCollectionFactory.tsx b/src/sections/new-collection/NewCollectionFactory.tsx index f9c248495..d948bcac6 100644 --- a/src/sections/new-collection/NewCollectionFactory.tsx +++ b/src/sections/new-collection/NewCollectionFactory.tsx @@ -14,7 +14,6 @@ export class NewCollectionFactory { function NewCollectionWithParams() { const { ownerCollectionId = 'root' } = useParams<{ ownerCollectionId: string }>() - // TODO:ME Maybe assert that collection with ownerCollectionId exists first, could be root or a specific collection // TODO:ME What roles can create a collection, what checks to do? return ( diff --git a/src/sections/new-collection/collection-form/index.tsx b/src/sections/new-collection/collection-form/index.tsx index ac7ac9fbd..404529f76 100644 --- a/src/sections/new-collection/collection-form/index.tsx +++ b/src/sections/new-collection/collection-form/index.tsx @@ -17,19 +17,23 @@ import styles from './CollectionForm.module.scss' export interface CollectionFormProps { collectionRepository: CollectionRepository - defaultValues: Partial + defaultValues: CollectionFormData } export type CollectionFormData = { hostCollection: string name: string - affiliation?: string + affiliation: string alias: string - storage?: CollectionStorage - type: CollectionType - description?: string + storage: CollectionStorage + type: CollectionType | '' + description: string contacts: { value: string }[] } +// On the submit function callback, type is CollectionType as type field is required and wont never be "" +export type CollectionFormValuesOnSubmit = Omit & { + type: CollectionType +} export const CollectionForm = ({ collectionRepository, defaultValues }: CollectionFormProps) => { const formContainerRef = useRef(null) @@ -110,10 +114,7 @@ export const CollectionForm = ({ collectionRepository, defaultValues }: Collecti -