diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4f650ecb..7c0f04f3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -35,6 +35,7 @@ jobs: requirements-level: [pypi] db-service: [postgresql10, postgresql13] search-service: [opensearch2,elasticsearch7] + node-version: [16.x] exclude: - python-version: 3.7 db-service: postgresql13 @@ -55,6 +56,13 @@ jobs: - name: Checkout uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Run eslint test + run: ./run-js-linter.sh -i + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: @@ -82,3 +90,15 @@ jobs: - name: Run tests run: | ./run-tests.sh + + - name: Install deps for frontend tests + working-directory: ./invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies + run: npm install + + - name: Install deps for frontend tests - translations + working-directory: ./invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies + run: npm install + + - name: Run frontend tests + working-directory: ./invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies + run: npm test diff --git a/MANIFEST.in b/MANIFEST.in index 2f4fd341..46507706 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -26,6 +26,8 @@ recursive-include invenio_vocabularies *.html recursive-include invenio_vocabularies *.json recursive-include invenio_vocabularies *.py recursive-include invenio_vocabularies/translations *.po *.pot *.mo +recursive-include invenio_vocabularies *.js *.prettierrc *.yml +recursive-include invenio_vocabularies *.png recursive-include tests *.json recursive-include tests *.py recursive-include tests *.yaml diff --git a/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/.eslintrc.yml b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/.eslintrc.yml new file mode 100644 index 00000000..6afc3c23 --- /dev/null +++ b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/.eslintrc.yml @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Invenio. +# Copyright (C) 2023 CERN. +# +# Invenio is free software; you can redistribute it and/or modify +# it under the terms of the MIT License; see LICENSE file for more details. + +extends: + - '@inveniosoftware/eslint-config-invenio' + - '@inveniosoftware/eslint-config-invenio/prettier' diff --git a/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/.prettierrc b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/.prettierrc new file mode 100644 index 00000000..057ddf29 --- /dev/null +++ b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/.prettierrc @@ -0,0 +1 @@ +"@inveniosoftware/eslint-config-invenio/prettier-config.js" \ No newline at end of file diff --git a/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/index.js b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/index.js new file mode 100644 index 00000000..6f1ac340 --- /dev/null +++ b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/index.js @@ -0,0 +1,7 @@ +// This file is part of InvenioVocabularies +// Copyright (C) 2023 CERN. +// +// Invenio is free software; you can redistribute it and/or modify it +// under the terms of the MIT License; see LICENSE file for more details. + +export * from "./src"; diff --git a/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/package.json b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/package.json new file mode 100644 index 00000000..e927f1f9 --- /dev/null +++ b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/package.json @@ -0,0 +1,31 @@ +{ + "@comment": [ + "This package.json is needed to run the JS tests, locally and CI." + ], + "scripts": { + "test": "react-scripts test" + }, + "devDependencies": { + "@testing-library/jest-dom": "^4.2.0", + "@testing-library/react": "^9.5.0", + "@testing-library/user-event": "^7.2.0", + "axios": "^0.21.0", + "coveralls": "^3.0.0", + "enzyme": "^3.10.0", + "enzyme-adapter-react-16": "^1.15.0", + "enzyme-to-json": "^3.4.0", + "expect": "^26.0.0", + "lodash": "^4.17.0", + "luxon": "^1.23.0", + "react": "^16.13.0", + "react-dom": "^16.13.0", + "react-scripts": "^5.0.1", + "semantic-ui-react": "^2.1.0", + "react-overridable": "^0.0.3" + }, + "jest": { + "snapshotSerializers": [ + "enzyme-to-json/serializer" + ] + } +} diff --git a/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/AwardResults.js b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/AwardResults.js new file mode 100644 index 00000000..57cf579d --- /dev/null +++ b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/AwardResults.js @@ -0,0 +1,95 @@ +// This file is part of InvenioVocabularies +// Copyright (C) 2021-2023 CERN. +// Copyright (C) 2021 Northwestern University. +// +// Invenio is free software; you can redistribute it and/or modify it +// under the terms of the MIT License; see LICENSE file for more details. + +import React from "react"; +import PropTypes from "prop-types"; +import _get from "lodash/get"; +import { Item, Header, Radio, Label, Icon } from "semantic-ui-react"; +import { withState } from "react-searchkit"; +import { FastField } from "formik"; + +export const AwardResults = withState( + ({ + currentResultsState: results, + deserializeAward, + deserializeFunder, + computeFundingContents, + }) => { + return ( + + {({ form: { values, setFieldValue } }) => { + return ( + + {results.data.hits.map((award) => { + let funder = award?.funder; + const deserializedAward = deserializeAward(award); + const deserializedFunder = deserializeFunder(funder); + const funding = { + award: deserializedAward, + funder: deserializedFunder, + }; + let { headerContent, descriptionContent, awardOrFunder } = + computeFundingContents(funding); + + return ( + setFieldValue("selectedFunding", funding)} + className="license-item" + > + setFieldValue("selectedFunding", funding)} + /> + +
+ {headerContent} + {awardOrFunder === "award" + ? award.number && ( + + ) + : ""} + {awardOrFunder === "award" + ? award.url && ( + + + + ) + : ""} +
+ + {descriptionContent} + +
+
+ ); + })} +
+ ); + }} +
+ ); + } +); + +AwardResults.propTypes = { + deserializeAward: PropTypes.func.isRequired, + deserializeFunder: PropTypes.func.isRequired, + computeFundingContents: PropTypes.func.isRequired, +}; diff --git a/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/CustomAwardForm.js b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/CustomAwardForm.js new file mode 100644 index 00000000..5abb6f45 --- /dev/null +++ b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/CustomAwardForm.js @@ -0,0 +1,122 @@ +// This file is part of InvenioVocabularies +// Copyright (C) 2021-2023 CERN. +// Copyright (C) 2021 Northwestern University. +// +// Invenio is free software; you can redistribute it and/or modify it +// under the terms of the MIT License; see LICENSE file for more details. + +import PropTypes from "prop-types"; +import React from "react"; +import { Form, Header } from "semantic-ui-react"; +import { TextField, RemoteSelectField } from "react-invenio-forms"; +import { i18next } from "@translations/invenio_rdm_records/i18next"; +import _isEmpty from "lodash/isEmpty"; + +function CustomAwardForm({ deserializeFunder, selectedFunding }) { + function deserializeFunderToDropdown(funderItem) { + let funderName = null; + let funderPID = null; + + if (funderItem.name) { + funderName = funderItem.name; + } + + if (funderItem.pid) { + funderPID = funderItem.pid; + } + + if (!funderName && !funderPID) { + return {}; + } + + return { + text: funderName || funderPID, + value: funderItem.id, + key: funderItem.id, + ...(funderName && { name: funderName }), + ...(funderPID && { pid: funderPID }), + }; + } + + function serializeFunderFromDropdown(funderDropObject) { + return { + id: funderDropObject.key, + ...(funderDropObject.name && { name: funderDropObject.name }), + ...(funderDropObject.pid && { pid: funderDropObject.pid }), + }; + } + + return ( +
+ { + return funders.map((funder) => + deserializeFunderToDropdown(deserializeFunder(funder)) + ); + }} + searchInput={{ + autoFocus: _isEmpty(selectedFunding), + }} + label={i18next.t("Funder")} + noQueryMessage={i18next.t("Search for funder...")} + clearable + allowAdditions={false} + multiple={false} + selectOnBlur={false} + selectOnNavigation={false} + required + search={(options) => options} + onValueChange={({ formikProps }, selectedFundersArray) => { + if (selectedFundersArray.length === 1) { + const selectedFunder = selectedFundersArray[0]; + if (selectedFunder) { + const deserializedFunder = serializeFunderFromDropdown(selectedFunder); + formikProps.form.setFieldValue( + "selectedFunding.funder", + deserializedFunder + ); + } + } + }} + /> + +
+ {i18next.t("Award information")} ({i18next.t("optional")}) +
+ + + + + + + ); +} + +CustomAwardForm.propTypes = { + deserializeFunder: PropTypes.func.isRequired, + selectedFunding: PropTypes.object, +}; + +CustomAwardForm.defaultProps = { + selectedFunding: undefined, +}; + +export default CustomAwardForm; diff --git a/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FunderDropdown.js b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FunderDropdown.js new file mode 100644 index 00000000..0b26678f --- /dev/null +++ b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FunderDropdown.js @@ -0,0 +1,87 @@ +// This file is part of InvenioVocabularies +// Copyright (C) 2021-2023 CERN. +// Copyright (C) 2021 Northwestern University. +// +// Invenio is free software; you can redistribute it and/or modify it +// under the terms of the MIT License; see LICENSE file for more details. + +import React from "react"; + +import { Dropdown } from "semantic-ui-react"; +import { withState } from "react-searchkit"; +import { i18next } from "@translations/invenio_rdm_records/i18next"; + +export const FunderDropdown = withState( + ({ currentResultsState: awardsList, updateQueryState, currentQueryState }) => { + const [fundersFromFacets] = useFundersFromFacets(awardsList); + + /** + * Trigger on funder selection. + * Updated the query state to filter by the selected funder. + * + * @param {*} event + * @param {*} data + */ + function onFunderSelect(event, data) { + let newFilter = []; + + if (data && data.value !== "") { + newFilter = ["funders", data.value]; + } + updateQueryState({ ...currentQueryState, filters: newFilter, page: 1 }); + } + + /** + * Custom hook, triggered when the awards list changes. + * It retrieves funders from new award's facets. + * + * @param {object} awards + * + * @returns {object[]} an array of objects, each representing a facetted funder. + */ + function useFundersFromFacets(awards) { + const [result, setResult] = React.useState([]); + React.useEffect(() => { + /** + * Retrieves funders from awards facets and sets the result in state 'result'. + */ + function getFundersFromAwardsFacet() { + if (awards.loading) { + setResult([]); + return; + } + + const funders = awards.data.aggregations?.funders?.buckets.map((agg) => { + return { + key: agg.key, + value: agg.key, + text: agg.label, + }; + }); + setResult(funders); + } + + getFundersFromAwardsFacet(); + }, [awards]); + + return [result]; + } + + return ( + + ); + } +); diff --git a/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.js b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.js new file mode 100644 index 00000000..830863a0 --- /dev/null +++ b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.js @@ -0,0 +1,215 @@ +// This file is part of InvenioVocabularies +// Copyright (C) 2021-2023 CERN. +// Copyright (C) 2021 Northwestern University. +// +// Invenio is free software; you can redistribute it and/or modify it +// under the terms of the MIT License; see LICENSE file for more details. + +import React from "react"; +import PropTypes from "prop-types"; +import { FieldArray, getIn } from "formik"; +import { HTML5Backend } from "react-dnd-html5-backend"; +import { DndProvider } from "react-dnd"; +import { Button, Form, Icon, List } from "semantic-ui-react"; +import { FieldLabel } from "react-invenio-forms"; + +import { FundingFieldItem } from "./FundingFieldItem"; +import FundingModal from "./FundingModal"; + +import { i18next } from "@translations/invenio_rdm_records/i18next"; + +function FundingFieldForm(props) { + const { + label, + labelIcon, + fieldPath, + form: { values }, + move: formikArrayMove, + push: formikArrayPush, + remove: formikArrayRemove, + replace: formikArrayReplace, + required, + deserializeAward: deserializeAwardFunc, + deserializeFunder: deserializeFunderFunc, + computeFundingContents: computeFundingContentsFunc, + searchConfig, + } = props; + + const deserializeAward = deserializeAwardFunc + ? deserializeAwardFunc + : (award) => ({ + title: award?.title_l10n, + number: award.number, + funder: award.funder ?? "", + id: award.id, + ...(award.identifiers && { identifiers: award.identifiers }), + ...(award.acronym && { acronym: award.acronym }), + }); + + const deserializeFunder = deserializeFunderFunc + ? deserializeFunderFunc + : (funder) => ({ + id: funder.id, + name: funder.name, + ...(funder.pid && { pid: funder.pid }), + ...(funder.country && { country: funder.country }), + ...(funder.identifiers && { identifiers: funder.identifiers }), + }); + + const computeFundingContents = computeFundingContentsFunc + ? computeFundingContentsFunc + : (funding) => { + let headerContent, + descriptionContent = ""; + let awardOrFunder = "award"; + if (funding.award) { + headerContent = funding.award.title; + } + + if (funding.funder) { + const funderName = + funding?.funder?.name ?? funding.funder?.title ?? funding?.funder?.id ?? ""; + descriptionContent = funderName; + if (!headerContent) { + awardOrFunder = "funder"; + headerContent = funderName; + descriptionContent = ""; + } + } + + return { headerContent, descriptionContent, awardOrFunder }; + }; + return ( + + + + + {getIn(values, fieldPath, []).map((value, index) => { + const key = `${fieldPath}.${index}`; + // if award does not exist or has no id, it's a custom one + const awardType = value?.award?.id ? "standard" : "custom"; + return ( + + ); + })} + + + {i18next.t("Add award")} + + } + onAwardChange={(selectedFunding) => { + formikArrayPush(selectedFunding); + }} + mode="standard" + action="add" + deserializeAward={deserializeAward} + deserializeFunder={deserializeFunder} + computeFundingContents={computeFundingContents} + /> + + + {i18next.t("Add custom")} + + } + onAwardChange={(selectedFunding) => { + formikArrayPush(selectedFunding); + }} + mode="custom" + action="add" + deserializeAward={deserializeAward} + deserializeFunder={deserializeFunder} + computeFundingContents={computeFundingContents} + /> + + + + ); +} + +FundingFieldForm.propTypes = { + label: PropTypes.node, + labelIcon: PropTypes.node, + fieldPath: PropTypes.string.isRequired, + form: PropTypes.object, + move: PropTypes.func, + push: PropTypes.func, + remove: PropTypes.func, + replace: PropTypes.func, + required: PropTypes.bool, + deserializeAward: PropTypes.func, + deserializeFunder: PropTypes.func, + computeFundingContents: PropTypes.func, + searchConfig: PropTypes.object, +}; + +FundingFieldForm.defaultProps = { + label: undefined, + labelIcon: undefined, + form: undefined, + move: undefined, + push: undefined, + remove: undefined, + replace: undefined, + required: undefined, + deserializeAward: undefined, + deserializeFunder: undefined, + computeFundingContents: undefined, + searchConfig: undefined, +}; + +export function FundingField(props) { + const { fieldPath } = props; + return ( + } + /> + ); +} + +FundingField.propTypes = { + fieldPath: PropTypes.string.isRequired, + label: PropTypes.string, + labelIcon: PropTypes.string, + searchConfig: PropTypes.object.isRequired, + required: PropTypes.bool, + deserializeAward: PropTypes.func, + deserializeFunder: PropTypes.func, + computeFundingContents: PropTypes.func, +}; + +FundingField.defaultProps = { + label: "Awards", + labelIcon: "money bill alternate outline", + required: false, + deserializeAward: undefined, + deserializeFunder: undefined, + computeFundingContents: undefined, +}; diff --git a/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.test.js b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.test.js new file mode 100644 index 00000000..2729edae --- /dev/null +++ b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.test.js @@ -0,0 +1 @@ +it("can contain tests", () => {}); diff --git a/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingFieldItem.js b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingFieldItem.js new file mode 100644 index 00000000..e5c6e312 --- /dev/null +++ b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingFieldItem.js @@ -0,0 +1,152 @@ +// This file is part of InvenioVocabularies +// Copyright (C) 2021-2023 CERN. +// Copyright (C) 2021 Northwestern University. +// Copyright (C) 2021 Graz University of Technology. +// +// Invenio is free software; you can redistribute it and/or modify it +// under the terms of the MIT License; see LICENSE file for more details. + +import { i18next } from "@translations/invenio_rdm_records/i18next"; +import React from "react"; +import { useDrag, useDrop } from "react-dnd"; +import { Button, Icon, Label, List, Ref } from "semantic-ui-react"; + +import FundingModal from "./FundingModal"; +import PropTypes from "prop-types"; + +export const FundingFieldItem = ({ + compKey, + index, + fundingItem, + awardType, + moveFunding, + replaceFunding, + removeFunding, + searchConfig, + deserializeAward, + deserializeFunder, + computeFundingContents, +}) => { + const dropRef = React.useRef(null); + // eslint-disable-next-line no-unused-vars + const [_, drag, preview] = useDrag({ + item: { index, type: "award" }, + }); + const [{ hidden }, drop] = useDrop({ + accept: "award", + hover(item, monitor) { + if (!dropRef.current) { + return; + } + const dragIndex = item.index; + const hoverIndex = index; + + // Don't replace items with themselves + if (dragIndex === hoverIndex) { + return; + } + + if (monitor.isOver({ shallow: true })) { + moveFunding(dragIndex, hoverIndex); + item.index = hoverIndex; + } + }, + collect: (monitor) => ({ + hidden: monitor.isOver({ shallow: true }), + }), + }); + + let { headerContent, descriptionContent, awardOrFunder } = + computeFundingContents(fundingItem); + + // Initialize the ref explicitely + drop(dropRef); + return ( + + + + { + replaceFunding(index, selectedFunding); + }} + mode={awardType} + action="edit" + trigger={ + + } + deserializeAward={deserializeAward} + deserializeFunder={deserializeFunder} + computeFundingContents={computeFundingContents} + initialFunding={fundingItem} + /> + + + + + + + + + + <> + {headerContent} + + {awardOrFunder === "award" + ? fundingItem?.award?.number && ( + + ) + : ""} + {awardOrFunder === "award" + ? fundingItem?.award?.url && ( + + + + ) + : ""} + + + + {descriptionContent ? descriptionContent :
} +
+
+
+
+
+ ); +}; + +FundingFieldItem.propTypes = { + compKey: PropTypes.any, + index: PropTypes.number, + fundingItem: PropTypes.object, + awardType: PropTypes.string, + moveFunding: PropTypes.func.isRequired, + replaceFunding: PropTypes.func.isRequired, + removeFunding: PropTypes.func.isRequired, + searchConfig: PropTypes.object.isRequired, + deserializeAward: PropTypes.func.isRequired, + deserializeFunder: PropTypes.func.isRequired, + computeFundingContents: PropTypes.func.isRequired, +}; + +FundingFieldItem.defaultProps = { + compKey: undefined, + index: undefined, + fundingItem: undefined, + awardType: undefined, +}; diff --git a/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingModal.js b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingModal.js new file mode 100644 index 00000000..0baf00dc --- /dev/null +++ b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingModal.js @@ -0,0 +1,272 @@ +// This file is part of InvenioVocabularies +// Copyright (C) 2021-2023 CERN. +// Copyright (C) 2021 Northwestern University. +// +// Invenio is free software; you can redistribute it and/or modify it +// under the terms of the MIT License; see LICENSE file for more details. + +import { i18next } from "@translations/invenio_rdm_records/i18next"; +import { Formik } from "formik"; +import PropTypes from "prop-types"; +import React, { useState } from "react"; +import { + EmptyResults, + Error, + InvenioSearchApi, + Pagination, + ReactSearchKit, + ResultsLoader, + SearchBar, +} from "react-searchkit"; +import { Grid, Modal, Container, Button } from "semantic-ui-react"; +import * as Yup from "yup"; +import { AwardResults } from "./AwardResults"; +import CustomAwardForm from "./CustomAwardForm"; +import { FunderDropdown } from "./FunderDropdown"; +import { NoAwardResults } from "./NoAwardResults"; + +const ModalTypes = { + STANDARD: "standard", + CUSTOM: "custom", +}; + +const ModalActions = { + ADD: "add", + EDIT: "edit", +}; + +const StandardSchema = Yup.object().shape({ + selectedFunding: Yup.object().shape({ + funder: Yup.object().shape({ + id: Yup.string().required(), + }), + award: Yup.object().shape({ + id: Yup.string().required(), + }), + }), +}); + +const CustomFundingSchema = Yup.object().shape({ + selectedFunding: Yup.object().shape({ + funder: Yup.object().shape({ + id: Yup.string().required(i18next.t("Funder is required.")), + }), + award: Yup.object().shape({ + title: Yup.string().test({ + name: "testTitle", + message: i18next.t("Title must be set alongside number."), + test: function testTitle(value) { + const { number } = this.parent; + + if (number && !value) { + return false; + } + + return true; + }, + }), + number: Yup.string().test({ + name: "testNumber", + message: i18next.t("Number must be set alongside title."), + test: function testNumber(value) { + const { title } = this.parent; + + if (title && !value) { + return false; + } + + return true; + }, + }), + url: Yup.string() + .url(i18next.t("URL must be valid.")) + .test({ + name: "validateUrlDependencies", + message: i18next.t("URL must be set alongside title and number."), + test: function testUrl(value) { + const { title, number } = this.parent; + + if (value && value !== "" && !title && !number) { + return false; + } + + return true; + }, + }), + }), + }), +}); + +function FundingModal({ + action, + mode: initialMode, + trigger, + onAwardChange, + searchConfig, + deserializeAward, + deserializeFunder, + computeFundingContents, + ...props +}) { + const [open, setOpen] = useState(false); + const [mode, setMode] = useState(initialMode); + const openModal = () => setOpen(true); + const closeModal = () => { + setMode(initialMode); + setOpen(false); + }; + const onSubmit = (values, formikBag) => { + formikBag.setSubmitting(false); + formikBag.resetForm(); + setMode(initialMode); + closeModal(); + onAwardChange(values.selectedFunding); + }; + + const searchApi = new InvenioSearchApi(searchConfig.searchApi); + const customObject = mode === ModalTypes.CUSTOM ? props.initialFunding : {}; + const initialFunding = { + selectedFunding: action === ModalActions.EDIT ? customObject : {}, + }; + + const FundingSchema = + mode === ModalTypes.CUSTOM ? CustomFundingSchema : StandardSchema; + + return ( + + {({ values, resetForm, handleSubmit }) => ( + + + {mode === "standard" + ? i18next.t("Add standard award") + : i18next.t("Add custom award")} + + + {mode === ModalTypes.STANDARD && ( + + + + + + + + + + + + + + + + + + + + + + + + { + resetForm(); + setMode(ModalTypes.CUSTOM); + }} + /> + + + + )} + {mode === ModalTypes.CUSTOM && ( + + )} + + +