diff --git a/libs/ui/src/components/RecordEdition/EditRecord/EditRecord.test.tsx b/libs/ui/src/components/RecordEdition/EditRecord/EditRecord.test.tsx index cb380ed3c4..68ac363c7f 100644 --- a/libs/ui/src/components/RecordEdition/EditRecord/EditRecord.test.tsx +++ b/libs/ui/src/components/RecordEdition/EditRecord/EditRecord.test.tsx @@ -9,6 +9,7 @@ import {mockRecord} from '_ui/__mocks__/common/record'; import {IUseCanEditRecordHook} from '../../../hooks/useCanEditRecord/useCanEditRecord'; import {render, screen} from '../../../_tests/testUtils'; import {EditRecord} from './EditRecord'; +import {Form} from 'antd'; jest.mock('../EditRecordContent', () => { return function EditRecordContent() { @@ -20,6 +21,12 @@ jest.mock('hooks/useCanEditRecord/useCanEditRecord', () => ({ useCanEditRecord: (): IUseCanEditRecordHook => ({loading: false, canEdit: true, isReadOnly: false}) })); +const EditRecordWithForm = props => { + const [form] = Form.useForm(); + + return ; +}; + describe('EditRecord', () => { const commonMocks = [ { @@ -52,22 +59,17 @@ describe('EditRecord', () => { }); test('Display form', async () => { - const _handleClose = jest.fn(); - const CompWithButtons = () => { const closeButtonRef = useRef(null); return ( <> ; - ); @@ -79,35 +81,4 @@ describe('EditRecord', () => { expect(screen.getByText('EditRecordContent')).toBeVisible(); }); - - test('Close form', async () => { - const _handleClose = jest.fn(); - - const CompWithButtons = () => { - const closeButtonRef = useRef(null); - - return ( - <> - ; - - - ); - }; - - render(, { - mocks: commonMocks - }); - - await user.click(screen.getByRole('button', {name: /Close/})); - - expect(_handleClose).toBeCalled(); - }); }); diff --git a/libs/ui/src/components/RecordEdition/EditRecord/EditRecord.tsx b/libs/ui/src/components/RecordEdition/EditRecord/EditRecord.tsx index f6ecd81615..1104f02800 100644 --- a/libs/ui/src/components/RecordEdition/EditRecord/EditRecord.tsx +++ b/libs/ui/src/components/RecordEdition/EditRecord/EditRecord.tsx @@ -44,11 +44,12 @@ import editRecordReducer, {EditRecordReducerActionsTypes, initialState} from '.. import {EditRecordReducerContext} from '../editRecordReducer/editRecordReducerContext'; import EditRecordSidebar from '../EditRecordSidebar'; import CreationErrorContext, {ICreationErrorByField} from './creationErrorContext'; +import {FormInstance} from 'antd/lib/form/Form'; interface IEditRecordProps { + antdForm: FormInstance; record: RecordIdentityFragment['whoAmI'] | null; library: string; - onClose: () => void; onCreate?: (newRecord: RecordIdentityFragment['whoAmI']) => void; valuesVersion?: IValueVersion; showSidebar?: boolean; @@ -57,7 +58,6 @@ interface IEditRecordProps { // Here we're not in charge of buttons position. It might on a modal footer or pretty much anywhere. // We're using refs to still be able to handle the click on the buttons buttonsRefs: { - close?: React.RefObject; refresh?: React.RefObject; valuesVersions?: React.RefObject; }; @@ -94,9 +94,9 @@ const Sidebar = styled.div` `; export const EditRecord: FunctionComponent = ({ + antdForm, record, library, - onClose, onCreate, valuesVersion, showSidebar = false, @@ -424,7 +424,6 @@ export const EditRecord: FunctionComponent = ({ }; const listenersByButtonsName: Record void> = { - close: onClose, refresh: () => { dispatch({ type: EditRecordReducerActionsTypes.REQUEST_REFRESH @@ -448,6 +447,7 @@ export const EditRecord: FunctionComponent = ({ ) : canEdit ? ( { return function StandardField() { @@ -14,6 +15,12 @@ jest.mock('./uiElements/StandardField', () => { }; }); +const EditRecordContentWithForm = props => { + const [form] = Form.useForm(); + + return ; +}; + describe('EditRecordContent', () => { const mocks = [ { @@ -86,7 +93,7 @@ describe('EditRecordContent', () => { })); render( - { })); render( - void; @@ -34,6 +34,7 @@ interface IEditRecordContentProps { } function EditRecordContent({ + antdForm, record, library, onRecordSubmit, @@ -45,7 +46,6 @@ function EditRecordContent({ const formId = record ? 'edition' : 'creation'; const {t} = useSharedTranslation(); const {state, dispatch} = useEditRecordReducer(); - const [antForm] = useForm(); useRecordsConsultationHistory(record?.library?.id ?? null, record?.id ?? null); @@ -171,7 +171,7 @@ function EditRecordContent({ return (
diff --git a/libs/ui/src/components/RecordEdition/EditRecordModal/EditRecordModal.test.tsx b/libs/ui/src/components/RecordEdition/EditRecordModal/EditRecordModal.test.tsx index 6c14b95441..053a3a4ca2 100644 --- a/libs/ui/src/components/RecordEdition/EditRecordModal/EditRecordModal.test.tsx +++ b/libs/ui/src/components/RecordEdition/EditRecordModal/EditRecordModal.test.tsx @@ -5,92 +5,140 @@ import userEvent from '@testing-library/user-event'; import {screen, render} from '_ui/_tests/testUtils'; import {mockRecord} from '_ui/__mocks__/common/record'; import {EditRecordModal} from './EditRecordModal'; +import {Form} from 'antd'; + +let user!: ReturnType; jest.mock('../EditRecord', () => ({ - EditRecord: ({record, onCreate}) => { + EditRecord: ({antdForm, record, onCreate}) => { + const fields = [{name: 'leonbloum', value: record ? 'EditRecord' : 'CreateRecord'}]; return ( -
- {record ? 'EditRecord' : 'CreateRecord'} + + + + -
+ ); } })); describe('EditRecordModal', () => { - let user: ReturnType; beforeEach(() => { user = userEvent.setup(); }); - test('Display modal in create mode', async () => { - render(); + describe('create mode', () => { + test('Display modal in create mode', async () => { + render(); - expect(screen.getByText('CreateRecord')).toBeInTheDocument(); - expect(screen.getByRole('button', {name: /cancel/})).toBeInTheDocument(); - expect(screen.getByRole('button', {name: /create$/})).toBeInTheDocument(); - }); + expect(screen.getByDisplayValue('CreateRecord')).toBeInTheDocument(); + expect(screen.getByRole('button', {name: /cancel/})).toBeInTheDocument(); + expect(screen.getByRole('button', {name: /create$/})).toBeInTheDocument(); + }); - test('Display page in create mode with all submit buttons', async () => { - render( - - ); + test('Display modal in create mode with all submit buttons', async () => { + render( + + ); - expect(screen.getByRole('button', {name: /cancel/})).toBeInTheDocument(); - expect(screen.getByRole('button', {name: /create$/})).toBeInTheDocument(); - expect(screen.getByRole('button', {name: /create_and_edit/})).toBeInTheDocument(); - }); + expect(screen.getByRole('button', {name: /cancel/})).toBeInTheDocument(); + expect(screen.getByRole('button', {name: /create$/})).toBeInTheDocument(); + expect(screen.getByRole('button', {name: /create_and_edit/})).toBeInTheDocument(); + }); - test('Display page in create mode with "create and edit" button only', async () => { - render( - - ); + test('Display modal in create mode with "create and edit" button only', async () => { + render( + + ); - expect(screen.getByRole('button', {name: /cancel/})).toBeInTheDocument(); - expect(screen.queryByRole('button', {name: /create$/})).not.toBeInTheDocument(); - expect(screen.getByRole('button', {name: /create_and_edit/})).toBeInTheDocument(); - }); + expect(screen.getByRole('button', {name: /cancel/})).toBeInTheDocument(); + expect(screen.queryByRole('button', {name: /create$/})).not.toBeInTheDocument(); + expect(screen.getByRole('button', {name: /create_and_edit/})).toBeInTheDocument(); + }); - test('Refresh form in edit mode after "create and edit"', async () => { - const onCreateAndEdit = jest.fn(); - const onCreate = jest.fn(); - render( - - ); + test('Should call onClose on click on cancel if antd fields are not touched', async () => { + const mockOnClose = jest.fn(); + render(); + + await userEvent.click(screen.getByRole('button', {name: 'global.cancel'})); + expect(mockOnClose).toHaveBeenCalledTimes(1); + }); - expect(screen.getByText('CreateRecord')).toBeInTheDocument(); + test('Should open modal and call onClose on click on confirm if antd fields are touched', async () => { + const mockOnClose = jest.fn(); + render(); - await user.click(screen.getByText('simulate_create_record')); + expect( + screen.queryByRole('heading', {level: 2, name: 'record_edition.confirm_modal_title'}) + ).not.toBeInTheDocument(); + await userEvent.type(screen.getByDisplayValue('CreateRecord'), 'Something'); + await userEvent.click(screen.getByRole('button', {name: 'global.cancel'})); - expect(await screen.findByText('EditRecord')).toBeInTheDocument(); + expect(screen.queryByText('record_edition.confirm_modal_title')).toBeInTheDocument(); + expect(mockOnClose).not.toHaveBeenCalled(); + + await userEvent.click(screen.queryByText('global.confirm')); + expect(screen.queryByText('record_edition.confirm_modal_title')).not.toBeInTheDocument(); + expect(mockOnClose).toHaveBeenCalled(); + }); }); - test('Display modal in edit mode', async () => { - render(); + describe('edit mode', () => { + test('Display modal in edit mode', async () => { + render(); + + expect(screen.getByDisplayValue('EditRecord')).toBeInTheDocument(); + expect(screen.getByRole('button', {name: /close/})).toBeInTheDocument(); + expect(screen.queryByRole('button', {name: /submit/})).not.toBeInTheDocument(); + }); + + test('Refresh form in edit mode after "create and edit"', async () => { + const onCreateAndEdit = jest.fn(); + const onCreate = jest.fn(); + render( + + ); + + expect(screen.getByDisplayValue('CreateRecord')).toBeInTheDocument(); + + await user.click(screen.getByText('simulate_create_record')); + + expect(screen.getByDisplayValue('EditRecord')).toBeInTheDocument(); + }); + + test('Should not open modal on click on close', async () => { + const mockOnClose = jest.fn(); + render(); + + expect( + screen.queryByRole('heading', {level: 2, name: 'record_edition.confirm_modal_title'}) + ).not.toBeInTheDocument(); + await userEvent.type(screen.getByDisplayValue('EditRecord'), 'Something'); + await userEvent.click(screen.getByRole('button', {name: 'global.close'})); - expect(screen.getByText('EditRecord')).toBeInTheDocument(); - expect(screen.getByRole('button', {name: /close/})).toBeInTheDocument(); - expect(screen.queryByRole('button', {name: /create$/})).not.toBeInTheDocument(); - expect(screen.queryByRole('button', {name: /create_and_edit$/})).not.toBeInTheDocument(); + expect(screen.queryByText('record_edition.confirm_modal_title')).not.toBeInTheDocument(); + expect(mockOnClose).toHaveBeenCalled(); + }); }); }); diff --git a/libs/ui/src/components/RecordEdition/EditRecordModal/EditRecordModal.tsx b/libs/ui/src/components/RecordEdition/EditRecordModal/EditRecordModal.tsx index c0cbe19eba..86864828a4 100644 --- a/libs/ui/src/components/RecordEdition/EditRecordModal/EditRecordModal.tsx +++ b/libs/ui/src/components/RecordEdition/EditRecordModal/EditRecordModal.tsx @@ -2,8 +2,8 @@ // This file is released under LGPL V3 // License text available at https://www.gnu.org/licenses/lgpl-3.0.txt import {Modal} from 'antd'; -import {KitButton, KitSpace, KitTypography} from 'aristid-ds'; import {FunctionComponent, useRef, useState} from 'react'; +import {KitButton, KitModal, KitSpace, KitTypography} from 'aristid-ds'; import styled from 'styled-components'; import {themeVars} from '_ui/antdTheme'; import {useSharedTranslation} from '_ui/hooks/useSharedTranslation'; @@ -14,6 +14,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {faXmark, faRotateRight, faLayerGroup} from '@fortawesome/free-solid-svg-icons'; import {possibleSubmitButtons, submitButtonsName} from '../_types'; import {useGetSubmitButtons} from '../hooks/useGetSubmitButtons'; +import {useForm} from 'antd/lib/form/Form'; interface IEditRecordModalProps { open: boolean; @@ -74,9 +75,27 @@ export const EditRecordModal: FunctionComponent = ({ }; const displayedSubmitButtons = useGetSubmitButtons(submitButtons, isInCreateMode, _handleClickSubmit); + const [antdForm] = useForm(); + + const _handleClose = () => { + if (isInCreateMode && antdForm.isFieldsTouched()) { + return KitModal.confirm({ + title: t('record_edition.confirm_modal_title'), + content: t('record_edition.confirm_modal_content'), + icon: false, + showSecondaryCta: true, + showCloseIcon: true, + dangerConfirm: true, + type: 'confirm', + okText: t('global.confirm'), + onOk: onClose + }); + } + + return onClose(); + }; // Create refs for the buttons to pass them to the EditRecord component - const closeButtonRef = useRef(null); const refreshButtonRef = useRef(null); const valuesVersionsButtonRef = useRef(null); @@ -86,8 +105,8 @@ export const EditRecordModal: FunctionComponent = ({ } + onClick={_handleClose} > {closeButtonLabel} , @@ -117,7 +136,7 @@ export const EditRecordModal: FunctionComponent = ({ return ( = ({ ; jest.mock('../EditRecord', () => ({ - EditRecord: ({record, onCreate}) => { + EditRecord: ({antdForm, record, onCreate}) => { + const fields = [{name: 'jeanjau', value: record ? 'EditRecord' : 'CreateRecord'}]; return ( -
- {record ? 'EditRecord' : 'CreateRecord'} +
+ + + -
+ ); } })); describe('EditRecordPage', () => { - let user: ReturnType; beforeEach(() => { user = userEvent.setup(); }); - test('Display page in create mode', async () => { - render(); - - expect(screen.getByText('CreateRecord')).toBeInTheDocument(); - expect(screen.getByText(/new_record/)).toBeInTheDocument(); - expect(screen.getByLabelText('refresh')).toBeInTheDocument(); - expect(screen.getByRole('button', {name: /cancel/})).toBeInTheDocument(); - expect(screen.getByRole('button', {name: /create$/})).toBeInTheDocument(); - expect(screen.queryByRole('button', {name: /create_and_edit/})).not.toBeInTheDocument(); - }); - - test('Display page in create mode with all submit buttons', async () => { - render( - - ); - - expect(screen.getByLabelText('refresh')).toBeInTheDocument(); - expect(screen.getByRole('button', {name: /cancel/})).toBeInTheDocument(); - expect(screen.getByRole('button', {name: /create$/})).toBeInTheDocument(); - expect(screen.getByRole('button', {name: /create_and_edit/})).toBeInTheDocument(); + describe('create mode', () => { + test('Display page in create mode', async () => { + render(); + + expect(screen.getByDisplayValue('CreateRecord')).toBeInTheDocument(); + expect(screen.getByText(/new_record/)).toBeInTheDocument(); + expect(screen.getByLabelText('refresh')).toBeInTheDocument(); + expect(screen.getByRole('button', {name: /cancel/})).toBeInTheDocument(); + expect(screen.getByRole('button', {name: /create$/})).toBeInTheDocument(); + expect(screen.queryByRole('button', {name: /create_and_edit/})).not.toBeInTheDocument(); + }); + + test('Display page in create mode with all submit buttons', async () => { + render( + + ); + + expect(screen.getByLabelText('refresh')).toBeInTheDocument(); + expect(screen.getByRole('button', {name: /cancel/})).toBeInTheDocument(); + expect(screen.getByRole('button', {name: /create$/})).toBeInTheDocument(); + expect(screen.getByRole('button', {name: /create_and_edit/})).toBeInTheDocument(); + }); + + test('Display page in create mode with "create and edit" button only', async () => { + render( + + ); + + expect(screen.getByLabelText('refresh')).toBeInTheDocument(); + expect(screen.getByRole('button', {name: /cancel/})).toBeInTheDocument(); + expect(screen.queryByRole('button', {name: /create$/})).not.toBeInTheDocument(); + expect(screen.getByRole('button', {name: /create_and_edit/})).toBeInTheDocument(); + }); + + test('Should call onClose on click on cancel if antd fields are not touched', async () => { + const mockOnClose = jest.fn(); + render(); + + await userEvent.click(screen.getByRole('button', {name: 'global.cancel'})); + expect(mockOnClose).toHaveBeenCalledTimes(1); + }); + + test('Should open modal and call onClose on click on confirm if antd fields are touched', async () => { + const mockOnClose = jest.fn(); + render(); + + expect( + screen.queryByRole('heading', {level: 2, name: 'record_edition.confirm_modal_title'}) + ).not.toBeInTheDocument(); + await userEvent.type(screen.getByDisplayValue('CreateRecord'), 'Something'); + await userEvent.click(screen.getByRole('button', {name: 'global.cancel'})); + + expect(screen.queryByText('record_edition.confirm_modal_title')).toBeInTheDocument(); + expect(mockOnClose).not.toHaveBeenCalled(); + + await userEvent.click(screen.queryByText('global.confirm')); + expect(screen.queryByText('record_edition.confirm_modal_title')).not.toBeInTheDocument(); + expect(mockOnClose).toHaveBeenCalled(); + }); }); - test('Display page in create mode with "create and edit" button only', async () => { - render( - - ); - - expect(screen.getByLabelText('refresh')).toBeInTheDocument(); - expect(screen.getByRole('button', {name: /cancel/})).toBeInTheDocument(); - expect(screen.queryByRole('button', {name: /create$/})).not.toBeInTheDocument(); - expect(screen.getByRole('button', {name: /create_and_edit/})).toBeInTheDocument(); - }); - - test('Refresh form in edit mode after "create and edit"', async () => { - const onCreateAndEdit = jest.fn(); - const onCreate = jest.fn(); - render( - - ); - - expect(screen.getByLabelText('refresh')).toBeInTheDocument(); - expect(screen.getByText('CreateRecord')).toBeInTheDocument(); - - await user.click(screen.getByText('simulate_create_record')); - - expect(screen.getByText('EditRecord')).toBeInTheDocument(); - }); - - test('Display page in edit mode', async () => { - render(); - - expect(screen.getByText('EditRecord')).toBeInTheDocument(); - expect(screen.getByText(mockRecord.label)).toBeInTheDocument(); - expect(screen.getByLabelText('refresh')).toBeInTheDocument(); - expect(screen.getByRole('button', {name: /close/})).toBeInTheDocument(); - expect(screen.queryByRole('button', {name: /create$/})).not.toBeInTheDocument(); - }); - - test('Should display a custom title', async () => { - render(); - - expect(screen.getByText('Custom title')).toBeInTheDocument(); - }); - - test('Should hide refresh button if showRefreshButton is set to false', async () => { - render( - - ); - - expect(screen.queryByLabelText('refresh')).not.toBeInTheDocument(); + describe('edit mode', () => { + test('Display page in edit mode', async () => { + render(); + + expect(screen.getByDisplayValue('EditRecord')).toBeInTheDocument(); + expect(screen.getByText(mockRecord.label)).toBeInTheDocument(); + expect(screen.getByLabelText('refresh')).toBeInTheDocument(); + expect(screen.getByRole('button', {name: /close/})).toBeInTheDocument(); + expect(screen.queryByRole('button', {name: /submit/})).not.toBeInTheDocument(); + }); + + test('Should display a custom title', async () => { + render( + + ); + + expect(screen.getByText('Custom title')).toBeInTheDocument(); + }); + + test('Should hide refresh button if showRefreshButton is set to false', async () => { + render( + + ); + + expect(screen.queryByLabelText('refresh')).not.toBeInTheDocument(); + }); + + test('Refresh form in edit mode after "create and edit"', async () => { + const onCreateAndEdit = jest.fn(); + const onCreate = jest.fn(); + render( + + ); + + expect(screen.getByLabelText('refresh')).toBeInTheDocument(); + expect(screen.getByDisplayValue('CreateRecord')).toBeInTheDocument(); + + await user.click(screen.getByText('simulate_create_record')); + + expect(await screen.findByDisplayValue('EditRecord')).toBeInTheDocument(); + }); }); test('Should hide refresh button if showRefreshButton is set to false', async () => { diff --git a/libs/ui/src/components/RecordEdition/EditRecordPage/EditRecordPage.tsx b/libs/ui/src/components/RecordEdition/EditRecordPage/EditRecordPage.tsx index 62780e54eb..4148729f39 100644 --- a/libs/ui/src/components/RecordEdition/EditRecordPage/EditRecordPage.tsx +++ b/libs/ui/src/components/RecordEdition/EditRecordPage/EditRecordPage.tsx @@ -1,8 +1,8 @@ // Copyright LEAV Solutions 2017 // This file is released under LGPL V3 // License text available at https://www.gnu.org/licenses/lgpl-3.0.txt -import {KitButton, KitDivider, KitSpace, KitTypography} from 'aristid-ds'; import {FunctionComponent, ReactNode, useRef, useState} from 'react'; +import {KitButton, KitDivider, KitModal, KitSpace, KitTypography} from 'aristid-ds'; import styled from 'styled-components'; import {useSharedTranslation} from '_ui/hooks/useSharedTranslation'; import {IValueVersion} from '_ui/types'; @@ -12,6 +12,8 @@ import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {faXmark, faRotateRight} from '@fortawesome/free-solid-svg-icons'; import {possibleSubmitButtons, submitButtonsName} from '../_types'; import {useGetSubmitButtons} from '../hooks/useGetSubmitButtons'; +import {EDIT_OR_CREATE_RECORD_FORM_ID} from '../EditRecordContent/formConstants'; +import {useForm} from 'antd/lib/form/Form'; interface IEditRecordPageProps { record: RecordIdentityFragment['whoAmI'] | null; @@ -59,9 +61,27 @@ export const EditRecordPage: FunctionComponent = ({ }; const displayedSubmitButtons = useGetSubmitButtons(submitButtons, isInCreateMode, _handleClickSubmit); + const [antdForm] = useForm(); + + const _handleClose = () => { + if (isInCreateMode && antdForm.isFieldsTouched()) { + return KitModal.confirm({ + title: t('record_edition.confirm_modal_title'), + content: t('record_edition.confirm_modal_content'), + icon: false, + showSecondaryCta: true, + showCloseIcon: true, + dangerConfirm: true, + type: 'confirm', + okText: t('global.confirm'), + onOk: onClose + }); + } + + return onClose(); + }; // Create refs for the buttons to pass them to the EditRecord component - const closeButtonRef = useRef(null); const refreshButtonRef = useRef(null); const closeButtonLabel = isInCreateMode ? t('global.cancel') : t('global.close'); @@ -100,7 +120,7 @@ export const EditRecordPage: FunctionComponent = ({ icon={} /> )} - }> + }> {closeButtonLabel} {displayedSubmitButtons} @@ -108,12 +128,12 @@ export const EditRecordPage: FunctionComponent = ({ diff --git a/libs/ui/src/locales/en/shared.json b/libs/ui/src/locales/en/shared.json index d5ef4068e4..3e4447494a 100644 --- a/libs/ui/src/locales/en/shared.json +++ b/libs/ui/src/locales/en/shared.json @@ -19,7 +19,7 @@ "apply": "Apply", "processing": "Processing", "done": "Done", - "clear": "Effacer", + "clear": "Clear", "copy": "Copy", "feature_not_available": "This feature will be available soon!", "available_soon": "Soon", @@ -29,7 +29,8 @@ "no": "No", "create": "Create", "ok": "Ok", - "max_length": "Max length" + "max_length": "Max length", + "confirm": "Confirm" }, "errors": { "default_language_required": "The default language is required", @@ -446,7 +447,11 @@ }, "date_range_value": "From {{from}} to {{to}}", "external_update_warning": "This record has been updated by {{modifiers}}", - "field_external_update": "This field has been updated" + "field_external_update": "This field has been updated", + "create": "Create", + "create_and_edit": "Create and edit", + "confirm_modal_title": "Confirm abort?", + "confirm_modal_content": "You're about to give up creating. Are you sure?" }, "record_summary": { "preview_title": "Click here to visualize preview.", diff --git a/libs/ui/src/locales/fr/shared.json b/libs/ui/src/locales/fr/shared.json index 3be75f0c58..511941e2ff 100644 --- a/libs/ui/src/locales/fr/shared.json +++ b/libs/ui/src/locales/fr/shared.json @@ -29,7 +29,8 @@ "no": "Non", "create": "Créer", "ok": "Ok", - "max_length": "Longueur maximale" + "max_length": "Longueur maximale", + "confirm": "Confirmer" }, "errors": { "default_language_required": "La langue par défaut est requise", @@ -448,7 +449,9 @@ "external_update_warning": "Cet élément a été modifié par {{modifiers}}", "field_external_update": "Ce champ a été modifié", "create": "Créer", - "create_and_edit": "Créer et éditer" + "create_and_edit": "Créer et éditer", + "confirm_modal_title": "Confirmer l'abandon ?", + "confirm_modal_content": "Vous êtes sur le point d'abandonner la création.
Êtes-vous sûr(e) ?" }, "record_summary": { "preview_title": "Cliquez ici pour voir l’aperçu.",