mirror of
https://github.com/strapi/strapi.git
synced 2025-12-12 07:27:46 +00:00
I18N settings modal, creation (#10749)
This commit is contained in:
parent
81aa2d8096
commit
b77feb9764
@ -17,7 +17,6 @@ import { Form } from '@strapi/helper-plugin';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Formik } from 'formik';
|
||||
|
||||
import { Column, LayoutContent } from '../../../../layouts/UnauthenticatedLayout';
|
||||
import Logo from '../Logo';
|
||||
import FieldActionWrapper from '../FieldActionWrapper';
|
||||
|
||||
@ -605,5 +605,6 @@
|
||||
"notification.version.update.message": "A new version of Strapi is available!",
|
||||
"or": "OR",
|
||||
"request.error.model.unknown": "This model doesn't exist",
|
||||
"skipToContent": "Skip to content"
|
||||
"skipToContent": "Skip to content",
|
||||
"clearLabel": "Clear"
|
||||
}
|
||||
|
||||
@ -40,8 +40,8 @@
|
||||
"@fortawesome/react-fontawesome": "^0.1.14",
|
||||
"@strapi/babel-plugin-switch-ee-ce": "1.0.0",
|
||||
"@strapi/helper-plugin": "3.6.8",
|
||||
"@strapi/icons": "0.0.1-alpha.22",
|
||||
"@strapi/parts": "0.0.1-alpha.22",
|
||||
"@strapi/icons": "0.0.1-alpha.23",
|
||||
"@strapi/parts": "0.0.1-alpha.23",
|
||||
"@strapi/utils": "3.6.8",
|
||||
"axios": "^0.21.1",
|
||||
"babel-loader": "8.2.2",
|
||||
|
||||
@ -0,0 +1,75 @@
|
||||
<!--- EmptyStateLayout.stories.mdx --->
|
||||
|
||||
import { Meta, ArgsTable, Canvas, Story } from '@storybook/addon-docs';
|
||||
import { Main, Row } from '@strapi/parts';
|
||||
import { TextInput } from '@strapi/parts/TextInput';
|
||||
import { Button } from '@strapi/parts/Button';
|
||||
import { Box } from '@strapi/parts/Box';
|
||||
import { Text } from '@strapi/parts/Text';
|
||||
import { Formik } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import Form from './';
|
||||
|
||||
<Meta title="components/Form" />
|
||||
|
||||
# Form
|
||||
|
||||
This component is used to display a Formik form with additionnal error handling on fields
|
||||
|
||||
## Per field error
|
||||
|
||||
<Canvas>
|
||||
<Story name="Per field error">
|
||||
<Main>
|
||||
<Formik initialValues={{ email: '' }} validationSchema={Yup.object().shape({ email: Yup.string().email('Invalid email').required('Required')})}>
|
||||
{({ handleSubmit, values, errors, handleChange }) => (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<TextInput
|
||||
name="email"
|
||||
label="Email"
|
||||
hint="Enter a valid email"
|
||||
value={values.email}
|
||||
error={errors.email}
|
||||
value={values.displayName}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Button type="submit">Submit</Button>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</Main>
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
## Per form error
|
||||
|
||||
Make sure to run this story in detached mode. The embeded iframe breaks the focus behaviour.
|
||||
|
||||
<Canvas>
|
||||
<Story name="Per form error">
|
||||
<Main>
|
||||
<Formik initialValues={{ email: '' }} validationSchema={Yup.object().shape({ email: Yup.string().email('Invalid email').required('Required')})} validateOnChange={false}>
|
||||
{({ handleSubmit, values, errors, handleChange }) => (
|
||||
<Form onSubmit={handleSubmit} noValidate>
|
||||
{errors.email ? <Box padding={3} hasRadius background="danger100" color="danger600" tabIndex={-1} id="global-form-error">
|
||||
<Text>The email is invalid</Text>
|
||||
</Box> : null}
|
||||
<TextInput
|
||||
name="email"
|
||||
label="Email"
|
||||
hint="Enter a valid email"
|
||||
value={values.email}
|
||||
value={values.displayName}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Button type="submit">Submit</Button>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</Main>
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
### Props
|
||||
|
||||
<ArgsTable of={EmptyStateLayout} />
|
||||
37
packages/core/helper-plugin/lib/src/components/Form/index.js
Normal file
37
packages/core/helper-plugin/lib/src/components/Form/index.js
Normal file
@ -0,0 +1,37 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Form, useFormikContext } from 'formik';
|
||||
|
||||
const FormWithFocus = props => {
|
||||
const formRef = useRef(null);
|
||||
const { isSubmitting, isValidating, errors, touched } = useFormikContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (isSubmitting && !isValidating) {
|
||||
const errorsInForm = formRef.current.querySelectorAll('[data-strapi-field-error]');
|
||||
|
||||
if (errorsInForm.length > 0) {
|
||||
const firstError = errorsInForm[0];
|
||||
const describingId = firstError.getAttribute('id');
|
||||
const formElementInError = formRef.current.querySelector(
|
||||
`[aria-describedby="${describingId}"]`
|
||||
);
|
||||
|
||||
if (formElementInError) {
|
||||
formElementInError.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isSubmitting && !isValidating && Object.keys(errors).length) {
|
||||
const el = document.getElementById('global-form-error');
|
||||
|
||||
if (el) {
|
||||
el.focus();
|
||||
}
|
||||
}
|
||||
}, [errors, isSubmitting, isValidating, touched]);
|
||||
|
||||
return <Form ref={formRef} {...props} noValidate />;
|
||||
};
|
||||
|
||||
export default FormWithFocus;
|
||||
@ -23,7 +23,6 @@ export { default as HeaderSearch } from './old/components/HeaderSearch';
|
||||
export { default as IcoContainer } from './old/components/IcoContainer';
|
||||
export { default as InputAddon } from './old/components/InputAddon';
|
||||
export { default as EmptyState } from './old/components/EmptyState';
|
||||
export { default as Form } from './old/components/Form';
|
||||
export * from './old/components/Tabs';
|
||||
export * from './old/components/Select';
|
||||
|
||||
@ -191,6 +190,7 @@ export { default as LoadingIndicatorPage } from './components/LoadingIndicatorPa
|
||||
export { default as SettingsPageTitle } from './components/SettingsPageTitle';
|
||||
export { default as Search } from './components/Search';
|
||||
export { default as Status } from './components/Status';
|
||||
export { default as Form } from './components/Form';
|
||||
|
||||
// New icons
|
||||
export { default as SortIcon } from './icons/SortIcon';
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Form, useFormikContext, getIn } from 'formik';
|
||||
|
||||
// TODO move the components folder
|
||||
const FormWithFocus = props => {
|
||||
const { isSubmitting, isValidating, errors, touched } = useFormikContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (isSubmitting && !isValidating) {
|
||||
const errorNames = Object.keys(touched).filter(error => getIn(errors, error));
|
||||
|
||||
if (errorNames.length) {
|
||||
let errorEl;
|
||||
|
||||
// Does not handle field arrays
|
||||
errorNames.forEach(errorKey => {
|
||||
const selector = `[name="${errorKey}"]`;
|
||||
|
||||
if (!errorEl) {
|
||||
errorEl = document.querySelector(selector);
|
||||
}
|
||||
});
|
||||
|
||||
errorEl?.focus();
|
||||
}
|
||||
}
|
||||
if (!isSubmitting && !isValidating && Object.keys(errors).length) {
|
||||
const el = document.getElementById('global-form-error');
|
||||
|
||||
el?.focus();
|
||||
}
|
||||
}, [errors, isSubmitting, isValidating, touched]);
|
||||
|
||||
return <Form {...props} noValidate />;
|
||||
};
|
||||
|
||||
export default FormWithFocus;
|
||||
@ -39,6 +39,7 @@
|
||||
"peerDependencies": {
|
||||
"immer": "9.0.5",
|
||||
"qs": "6.10.1",
|
||||
"formik": "^2.2.6",
|
||||
"react-select": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -56,8 +57,8 @@
|
||||
"@storybook/builder-webpack5": "^6.3.7",
|
||||
"@storybook/manager-webpack5": "^6.3.7",
|
||||
"@storybook/react": "^6.3.7",
|
||||
"@strapi/icons": "0.0.1-alpha.22",
|
||||
"@strapi/parts": "0.0.1-alpha.22",
|
||||
"@strapi/icons": "0.0.1-alpha.23",
|
||||
"@strapi/parts": "0.0.1-alpha.23",
|
||||
"babel-loader": "^8.2.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"enzyme": "^3.8.0",
|
||||
|
||||
@ -48,27 +48,21 @@ const LocaleList = ({ canUpdateLocale, canDeleteLocale, onToggleCreateModal, isC
|
||||
onEditLocale={handleEditLocale}
|
||||
/>
|
||||
) : (
|
||||
<ContentLayout>
|
||||
<EmptyStateLayout
|
||||
icon={<EmptyStateDocument width={undefined} height={undefined} />}
|
||||
content={formatMessage({ id: getTrad('Settings.list.empty.title') })}
|
||||
action={
|
||||
onToggleCreateModal ? (
|
||||
<Button variant="secondary" startIcon={<AddIcon />} onClick={onToggleCreateModal}>
|
||||
{formatMessage({ id: getTrad('Settings.list.actions.add') })}
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
</ContentLayout>
|
||||
<EmptyStateLayout
|
||||
icon={<EmptyStateDocument width={undefined} height={undefined} />}
|
||||
content={formatMessage({ id: getTrad('Settings.list.empty.title') })}
|
||||
action={
|
||||
onToggleCreateModal ? (
|
||||
<Button variant="secondary" startIcon={<AddIcon />} onClick={onToggleCreateModal}>
|
||||
{formatMessage({ id: getTrad('Settings.list.actions.add') })}
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</ContentLayout>
|
||||
|
||||
<ModalCreate
|
||||
isOpened={isCreating}
|
||||
onClose={onToggleCreateModal}
|
||||
alreadyUsedLocales={locales}
|
||||
/>
|
||||
{isCreating && <ModalCreate onClose={onToggleCreateModal} />}
|
||||
<ModalDelete localeToDelete={localeToDelete} onClose={closeModalToDelete} />
|
||||
<ModalEdit localeToEdit={localeToEdit} onClose={closeModalToEdit} locales={locales} />
|
||||
</Main>
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Select, Option } from '@strapi/parts/Select';
|
||||
import { Loader } from '@strapi/parts/Loader';
|
||||
import { useIntl } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import useLocales from '../../hooks/useLocales';
|
||||
import useDefaultLocales from '../../hooks/useDefaultLocales';
|
||||
import { getTrad } from '../../utils';
|
||||
|
||||
const SmallLoader = styled(Loader)`
|
||||
img {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* The component is memoized and needs a useCallback over the onLocaleChange and
|
||||
* onClear props to prevent the Select from re-rendering N times when typing on a specific
|
||||
* key in a formik form
|
||||
*/
|
||||
const LocaleSelect = React.memo(({ value, onLocaleChange, error, onClear }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { defaultLocales, isLoading } = useDefaultLocales();
|
||||
const { locales } = useLocales();
|
||||
|
||||
const options = (defaultLocales || [])
|
||||
.map(locale => ({
|
||||
label: locale.name,
|
||||
value: locale.code,
|
||||
}))
|
||||
.filter(({ value: v }) => {
|
||||
const foundLocale = locales.find(({ code }) => code === v);
|
||||
|
||||
return !foundLocale;
|
||||
});
|
||||
|
||||
const computedValue = value || '';
|
||||
|
||||
return (
|
||||
<Select
|
||||
startIcon={isLoading ? <SmallLoader>Loading the locales...</SmallLoader> : undefined}
|
||||
aria-busy={isLoading}
|
||||
label={formatMessage({
|
||||
id: getTrad('Settings.locales.modal.locales.label'),
|
||||
defaultMessage: 'Locales',
|
||||
})}
|
||||
onClear={value ? onClear : undefined}
|
||||
clearLabel={formatMessage({
|
||||
id: 'clearLabel',
|
||||
defaultMessage: 'Clear',
|
||||
})}
|
||||
error={error}
|
||||
value={computedValue}
|
||||
onChange={selectedLocaleKey => {
|
||||
const selectedLocale = options.find(locale => locale.value === selectedLocaleKey);
|
||||
|
||||
if (selectedLocale) {
|
||||
onLocaleChange({ code: selectedLocale.value, displayName: selectedLocale.label });
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isLoading
|
||||
? null
|
||||
: options.map(option => (
|
||||
<Option value={option.value} key={option.value}>
|
||||
{option.label}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
});
|
||||
|
||||
LocaleSelect.defaultProps = {
|
||||
error: undefined,
|
||||
value: undefined,
|
||||
onClear: () => undefined,
|
||||
};
|
||||
|
||||
LocaleSelect.propTypes = {
|
||||
error: PropTypes.string,
|
||||
onClear: PropTypes.func,
|
||||
onLocaleChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.string,
|
||||
};
|
||||
|
||||
export default LocaleSelect;
|
||||
@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Text, Checkbox, Padded } from '@buffetjs/core';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { BaselineAlignment } from '@strapi/helper-plugin';
|
||||
import { Checkbox } from '@strapi/parts/Checkbox';
|
||||
import { getTrad } from '../../utils';
|
||||
|
||||
const AdvancedForm = () => {
|
||||
@ -10,35 +9,19 @@ const AdvancedForm = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<BaselineAlignment top size="2px" />
|
||||
<Padded bottom size="sm">
|
||||
<Text color="grey" textTransform="uppercase">
|
||||
{formatMessage({
|
||||
id: getTrad('Settings.locales.modal.advanced.settings'),
|
||||
})}
|
||||
</Text>
|
||||
</Padded>
|
||||
|
||||
<BaselineAlignment top size="10px" />
|
||||
<Checkbox
|
||||
id="default-checkbox"
|
||||
name="default-checkbox"
|
||||
onChange={() => setFieldValue('isDefault', !values.isDefault)}
|
||||
message={formatMessage({
|
||||
id: getTrad('Settings.locales.modal.advanced.setAsDefault'),
|
||||
})}
|
||||
someChecked={false}
|
||||
value={values.isDefault}
|
||||
htmlFor="default-checkbox"
|
||||
/>
|
||||
|
||||
<Text color="grey" fontSize="sm">
|
||||
{formatMessage({
|
||||
id: getTrad('Settings.locales.modal.advanced.setAsDefault.hint'),
|
||||
})}
|
||||
</Text>
|
||||
</div>
|
||||
<Checkbox
|
||||
hint={formatMessage({
|
||||
id: getTrad('Settings.locales.modal.advanced.setAsDefault.hint'),
|
||||
defaultMessage: 'One default locale is required, change it by selecting another one',
|
||||
})}
|
||||
onChange={() => setFieldValue('isDefault', !values.isDefault)}
|
||||
value={values.isDefault}
|
||||
>
|
||||
{formatMessage({
|
||||
id: getTrad('Settings.locales.modal.advanced.setAsDefault'),
|
||||
defaultMessage: 'Set as default locale',
|
||||
})}
|
||||
</Checkbox>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,103 +1,74 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Label } from '@buffetjs/core';
|
||||
import { Inputs } from '@buffetjs/custom';
|
||||
import Select, { createFilter } from 'react-select';
|
||||
import { Col, Row } from 'reactstrap';
|
||||
import React, { useCallback } from 'react';
|
||||
import { Grid, GridItem } from '@strapi/parts/Grid';
|
||||
import { TextInput } from '@strapi/parts/TextInput';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useTheme } from 'styled-components';
|
||||
import { BaselineAlignment, selectStyles, DropdownIndicator } from '@strapi/helper-plugin';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { getTrad } from '../../utils';
|
||||
import LocaleSelect from '../LocaleSelect';
|
||||
|
||||
const reactSelectLocaleFilter = createFilter({
|
||||
ignoreCase: true,
|
||||
ignoreAccents: true,
|
||||
matchFrom: 'start',
|
||||
});
|
||||
|
||||
const BaseForm = ({ options, defaultOption }) => {
|
||||
const theme = useTheme();
|
||||
const BaseForm = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { values, handleChange, setFieldValue } = useFormikContext();
|
||||
const { values, handleChange, setFieldValue, errors } = useFormikContext();
|
||||
|
||||
const styles = selectStyles(theme);
|
||||
/**
|
||||
* This is needed because the LocaleSelect component is a memoized component
|
||||
* since it renders ~500 locales and that formik would trigger a re-render on it without
|
||||
* it
|
||||
*/
|
||||
const handleLocaleChange = useCallback(
|
||||
nextLocale => {
|
||||
setFieldValue('displayName', nextLocale.displayName);
|
||||
setFieldValue('code', nextLocale.code);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
/**
|
||||
* This is needed because the LocaleSelect component is a memoized component
|
||||
* since it renders ~500 locales and that formik would trigger a re-render on it without
|
||||
* it
|
||||
*/
|
||||
const handleClear = useCallback(() => {
|
||||
setFieldValue('displayName', '');
|
||||
setFieldValue('code', '');
|
||||
}, [setFieldValue]);
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col>
|
||||
<span id="locale-code">
|
||||
<Label htmlFor="">
|
||||
{formatMessage({
|
||||
id: getTrad('Settings.locales.modal.locales.label'),
|
||||
})}
|
||||
</Label>
|
||||
</span>
|
||||
|
||||
<BaselineAlignment top size="5px" />
|
||||
|
||||
<Select
|
||||
aria-labelledby="locale-code"
|
||||
options={options}
|
||||
defaultValue={defaultOption}
|
||||
filterOption={reactSelectLocaleFilter}
|
||||
onChange={selection => {
|
||||
setFieldValue('displayName', selection.value);
|
||||
setFieldValue('code', selection.label);
|
||||
}}
|
||||
components={{ DropdownIndicator }}
|
||||
styles={{
|
||||
...styles,
|
||||
control: (base, state) => ({ ...base, ...styles.control(base, state), height: '34px' }),
|
||||
indicatorsContainer: (base, state) => ({
|
||||
...base,
|
||||
...styles.indicatorsContainer(base, state),
|
||||
height: '32px',
|
||||
}),
|
||||
}}
|
||||
<Grid gap={4}>
|
||||
<GridItem col={6}>
|
||||
<LocaleSelect
|
||||
error={errors.code}
|
||||
value={values.code}
|
||||
onLocaleChange={handleLocaleChange}
|
||||
onClear={handleClear}
|
||||
/>
|
||||
</Col>
|
||||
</GridItem>
|
||||
|
||||
<Col>
|
||||
<BaselineAlignment top size="2px" />
|
||||
|
||||
<Inputs
|
||||
<GridItem col={6}>
|
||||
<TextInput
|
||||
name="displayName"
|
||||
label={formatMessage({
|
||||
id: getTrad('Settings.locales.modal.locales.displayName'),
|
||||
defaultMessage: 'Locale display name',
|
||||
})}
|
||||
name="displayName"
|
||||
description={formatMessage({
|
||||
hint={formatMessage({
|
||||
id: getTrad('Settings.locales.modal.locales.displayName.description'),
|
||||
defaultMessage: 'Locale will be displayed under that name in the administration panel',
|
||||
})}
|
||||
type="text"
|
||||
error={
|
||||
errors.displayName
|
||||
? formatMessage({
|
||||
id: getTrad('Settings.locales.modal.locales.displayName.error'),
|
||||
defaultMessage: 'The locale display name can only be less than 50 characters.',
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
value={values.displayName}
|
||||
onChange={handleChange}
|
||||
validations={{
|
||||
max: 50,
|
||||
}}
|
||||
translatedErrors={{
|
||||
max: formatMessage({
|
||||
id: getTrad('Settings.locales.modal.locales.displayName.error'),
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
BaseForm.defaultProps = {
|
||||
defaultOption: undefined,
|
||||
};
|
||||
|
||||
BaseForm.propTypes = {
|
||||
options: PropTypes.arrayOf(
|
||||
PropTypes.exact({ value: PropTypes.string.isRequired, label: PropTypes.string.isRequired })
|
||||
).isRequired,
|
||||
defaultOption: PropTypes.exact({
|
||||
value: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
}),
|
||||
};
|
||||
|
||||
export default BaseForm;
|
||||
|
||||
@ -1,136 +1,120 @@
|
||||
import React, { useRef } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Modal, ModalFooter, TabPanel, useRBACProvider } from '@strapi/helper-plugin';
|
||||
import { useRBACProvider, Form } from '@strapi/helper-plugin';
|
||||
import { ModalLayout, ModalHeader, ModalBody, ModalFooter } from '@strapi/parts/ModalLayout';
|
||||
import { TabGroup, Tabs, Tab, TabPanels, TabPanel } from '@strapi/parts/Tabs';
|
||||
import { Button } from '@strapi/parts/Button';
|
||||
import { ButtonText, H2 } from '@strapi/parts/Text';
|
||||
import { Divider } from '@strapi/parts/Divider';
|
||||
import { Box } from '@strapi/parts/Box';
|
||||
import { Row } from '@strapi/parts/Row';
|
||||
import CheckIcon from '@strapi/icons/CheckIcon';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Button } from '@buffetjs/core';
|
||||
import { Formik } from 'formik';
|
||||
import localeFormSchema from '../../schemas';
|
||||
import { getTrad } from '../../utils';
|
||||
import SettingsModal from '../SettingsModal';
|
||||
import useDefaultLocales from '../../hooks/useDefaultLocales';
|
||||
import useAddLocale from '../../hooks/useAddLocale';
|
||||
import BaseForm from './BaseForm';
|
||||
import AdvancedForm from './AdvancedForm';
|
||||
|
||||
const ModalCreate = ({ alreadyUsedLocales, onClose, isOpened }) => {
|
||||
const { defaultLocales, isLoading } = useDefaultLocales();
|
||||
const initialFormValues = {
|
||||
code: '',
|
||||
displayName: '',
|
||||
isDefault: false,
|
||||
};
|
||||
|
||||
const ModalCreate = ({ onClose }) => {
|
||||
const { isAdding, addLocale } = useAddLocale();
|
||||
const { formatMessage } = useIntl();
|
||||
const { refetchPermissions } = useRBACProvider();
|
||||
|
||||
const shouldUpdatePermissions = useRef(false);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
{formatMessage({ id: getTrad('Settings.locales.modal.create.defaultLocales.loading') })}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleClosed = async () => {
|
||||
if (shouldUpdatePermissions.current) {
|
||||
await refetchPermissions();
|
||||
}
|
||||
|
||||
shouldUpdatePermissions.current = false;
|
||||
};
|
||||
|
||||
const options = (defaultLocales || [])
|
||||
.map(locale => ({
|
||||
label: locale.code,
|
||||
value: locale.name,
|
||||
}))
|
||||
.filter(({ label }) => {
|
||||
const foundLocale = alreadyUsedLocales.find(({ code }) => code === label);
|
||||
|
||||
return !foundLocale;
|
||||
/**
|
||||
* No need to explicitly call the onClose prop here
|
||||
* since the all tree (from the root of the page) is destroyed and re-mounted
|
||||
* because of the RBAC refreshing and the potential move of the default locale
|
||||
*/
|
||||
const handleLocaleAdd = async values => {
|
||||
await addLocale({
|
||||
code: values.code,
|
||||
name: values.displayName,
|
||||
isDefault: values.isDefault,
|
||||
});
|
||||
|
||||
const defaultOption = options[0];
|
||||
|
||||
if (!defaultOption) {
|
||||
return null;
|
||||
}
|
||||
await refetchPermissions();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpened} onToggle={onClose} withoverflow="true" onClosed={handleClosed}>
|
||||
<ModalLayout onClose={onClose} labelledBy="add-locale-title">
|
||||
<Formik
|
||||
initialValues={{
|
||||
code: defaultOption.label,
|
||||
displayName: defaultOption.value,
|
||||
isDefault: false,
|
||||
}}
|
||||
onSubmit={values =>
|
||||
addLocale({
|
||||
code: values.code,
|
||||
name: values.displayName,
|
||||
isDefault: values.isDefault,
|
||||
})
|
||||
.then(() => {
|
||||
shouldUpdatePermissions.current = true;
|
||||
})
|
||||
.then(() => {
|
||||
onClose();
|
||||
})}
|
||||
initialValues={initialFormValues}
|
||||
onSubmit={handleLocaleAdd}
|
||||
validationSchema={localeFormSchema}
|
||||
validateOnChange={false}
|
||||
>
|
||||
{({ handleSubmit, errors }) => (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<SettingsModal
|
||||
title={formatMessage({
|
||||
id: getTrad('Settings.locales.modal.title'),
|
||||
})}
|
||||
breadCrumb={[formatMessage({ id: getTrad('Settings.list.actions.add') })]}
|
||||
tabsAriaLabel={formatMessage({
|
||||
id: getTrad('Settings.locales.modal.create.tab.label'),
|
||||
})}
|
||||
tabsId="i18n-settings-tabs-create"
|
||||
>
|
||||
<TabPanel>
|
||||
<BaseForm
|
||||
options={options}
|
||||
defaultOption={defaultOption}
|
||||
alreadyUsedLocales={alreadyUsedLocales}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<AdvancedForm />
|
||||
</TabPanel>
|
||||
</SettingsModal>
|
||||
{({ handleSubmit }) => (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<ModalHeader>
|
||||
<ButtonText textColor="neutral800" as="h2" id="add-locale-title">
|
||||
{formatMessage({ id: getTrad('Settings.list.actions.add') })}
|
||||
</ButtonText>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<TabGroup label="Some stuff for the label" id="tabs" variant="simple">
|
||||
<Row justifyContent="space-between">
|
||||
<H2>
|
||||
{formatMessage({
|
||||
id: getTrad('Settings.locales.modal.title'),
|
||||
})}
|
||||
</H2>
|
||||
<Tabs>
|
||||
<Tab>
|
||||
{formatMessage({
|
||||
id: getTrad('Settings.locales.modal.base'),
|
||||
})}
|
||||
</Tab>
|
||||
<Tab>
|
||||
{formatMessage({
|
||||
id: getTrad('Settings.locales.modal.advanced'),
|
||||
})}
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Row>
|
||||
|
||||
<ModalFooter>
|
||||
<section>
|
||||
<Button type="button" color="cancel" onClick={onClose}>
|
||||
<Divider />
|
||||
|
||||
<Box paddingTop={7} paddingBottom={7}>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<BaseForm />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<AdvancedForm />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Box>
|
||||
</TabGroup>
|
||||
</ModalBody>
|
||||
<ModalFooter
|
||||
startActions={
|
||||
<Button variant="tertiary" onClick={onClose}>
|
||||
{formatMessage({ id: 'app.components.Button.cancel' })}
|
||||
</Button>
|
||||
<Button
|
||||
color="success"
|
||||
type="submit"
|
||||
isLoading={isAdding}
|
||||
disabled={Object.keys(errors).length > 0}
|
||||
>
|
||||
{formatMessage({ id: getTrad('Settings.locales.modal.create.confirmation') })}
|
||||
}
|
||||
endActions={
|
||||
<Button type="submit" startIcon={<CheckIcon />} disabled={isAdding}>
|
||||
{formatMessage({ id: 'app.components.Button.save' })}
|
||||
</Button>
|
||||
</section>
|
||||
</ModalFooter>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</Modal>
|
||||
</ModalLayout>
|
||||
);
|
||||
};
|
||||
|
||||
ModalCreate.defaultProps = {
|
||||
alreadyUsedLocales: [],
|
||||
};
|
||||
|
||||
ModalCreate.propTypes = {
|
||||
alreadyUsedLocales: PropTypes.array,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
isOpened: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default ModalCreate;
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import { useQuery } from 'react-query';
|
||||
import { request, useNotification } from '@strapi/helper-plugin';
|
||||
import { useNotifyAT } from '@strapi/parts/LiveRegions';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { getTrad } from '../../utils';
|
||||
|
||||
const fetchDefaultLocalesList = async toggleNotification => {
|
||||
try {
|
||||
@ -19,9 +22,20 @@ const fetchDefaultLocalesList = async toggleNotification => {
|
||||
};
|
||||
|
||||
const useDefaultLocales = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { notifyStatus } = useNotifyAT();
|
||||
const toggleNotification = useNotification();
|
||||
const { isLoading, data } = useQuery('default-locales', () =>
|
||||
fetchDefaultLocalesList(toggleNotification)
|
||||
fetchDefaultLocalesList(toggleNotification).then(data => {
|
||||
notifyStatus(
|
||||
formatMessage({
|
||||
id: getTrad('Settings.locales.modal.locales.loaded'),
|
||||
defaultMessage: 'The locales have been successfully loaded.',
|
||||
})
|
||||
);
|
||||
|
||||
return data;
|
||||
})
|
||||
);
|
||||
|
||||
return { defaultLocales: data, isLoading };
|
||||
|
||||
@ -1,23 +1,16 @@
|
||||
import React from 'react';
|
||||
import { useRBAC } from '@strapi/helper-plugin';
|
||||
import { useIntl } from 'react-intl';
|
||||
import LocaleSettingsPage from './LocaleSettingsPage';
|
||||
import i18nPermissions from '../../permissions';
|
||||
import { getTrad } from '../../utils';
|
||||
|
||||
const ProtectedLocaleSettingsPage = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const {
|
||||
isLoading,
|
||||
allowedActions: { canRead, canUpdate, canCreate, canDelete },
|
||||
} = useRBAC(i18nPermissions);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div>
|
||||
<p>{formatMessage({ id: getTrad('Settings.permissions.loading') })}</p>
|
||||
</div>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import { object, string } from 'yup';
|
||||
import { translatedErrors as errorsTrads } from '@strapi/helper-plugin';
|
||||
|
||||
const localeFormSchema = object().shape({
|
||||
displayName: string().max(50, 'Settings.locales.modal.locales.displayName.error'),
|
||||
code: string().required(),
|
||||
displayName: string()
|
||||
.max(50, 'Settings.locales.modal.locales.displayName.error')
|
||||
.required(errorsTrads.required),
|
||||
});
|
||||
|
||||
export default localeFormSchema;
|
||||
|
||||
@ -26,7 +26,6 @@
|
||||
"Settings.locales.modal.advanced.settings": "Settings",
|
||||
"Settings.locales.modal.base": "Base settings",
|
||||
"Settings.locales.modal.create.alreadyExist": "This locale already exists",
|
||||
"Settings.locales.modal.create.confirmation": "Add locale",
|
||||
"Settings.locales.modal.create.defaultLocales.loading": "Loading the available locales...",
|
||||
"Settings.locales.modal.create.success": "Locale successfully added",
|
||||
"Settings.locales.modal.create.tab.label": "Navigating between the I18N base settings and advanced settings",
|
||||
@ -42,6 +41,7 @@
|
||||
"Settings.locales.modal.locales.displayName.description": "Locale will be displayed under that name in the administration panel",
|
||||
"Settings.locales.modal.locales.displayName.error": "The locale display name can only be less than 50 characters.",
|
||||
"Settings.locales.modal.locales.label": "Locales",
|
||||
"Settings.locales.modal.locales.loaded": "The locales have been successfully loaded.",
|
||||
"Settings.locales.modal.title": "Configurations",
|
||||
"Settings.locales.row.id": "ID",
|
||||
"Settings.locales.row.displayName": "Display name",
|
||||
|
||||
16
yarn.lock
16
yarn.lock
@ -4910,15 +4910,15 @@
|
||||
resolve-from "^5.0.0"
|
||||
store2 "^2.12.0"
|
||||
|
||||
"@strapi/icons@0.0.1-alpha.22":
|
||||
version "0.0.1-alpha.22"
|
||||
resolved "https://registry.yarnpkg.com/@strapi/icons/-/icons-0.0.1-alpha.22.tgz#d5e0dbdff6b1c45319aa3afcbb68381e116689d3"
|
||||
integrity sha512-GsuRrCNebIP7HZ4r3vget/G1PScqAqG5Uq/fAnI1EUHo3iPrT+1soRRXT9ZrOjK5nmkLminG2JieStNRjvZPOQ==
|
||||
"@strapi/icons@0.0.1-alpha.23":
|
||||
version "0.0.1-alpha.23"
|
||||
resolved "https://registry.yarnpkg.com/@strapi/icons/-/icons-0.0.1-alpha.23.tgz#e2474d8c431b2c270c1bbcd0c15d53e644686d82"
|
||||
integrity sha512-6le+sn4yhJqdqzVjMTEGSQWecx79alTDoHEVTKLimea0cGN/vhSyGrOD9k0ef+8DCz+OkOgcu3RwnPQHj34iwA==
|
||||
|
||||
"@strapi/parts@0.0.1-alpha.22":
|
||||
version "0.0.1-alpha.22"
|
||||
resolved "https://registry.yarnpkg.com/@strapi/parts/-/parts-0.0.1-alpha.22.tgz#23b7ba76cf9ecab01bd9051d0d19f5bab93c2d75"
|
||||
integrity sha512-K7vxDgdugXKGCd2xKHfKVkJ1WL3E3iqCw7/Jn9TNIQSz48GxfNJ7w9jZkH6TdwDUyiX3ugG2GYaFHS7oEBTQAA==
|
||||
"@strapi/parts@0.0.1-alpha.23":
|
||||
version "0.0.1-alpha.23"
|
||||
resolved "https://registry.yarnpkg.com/@strapi/parts/-/parts-0.0.1-alpha.23.tgz#0822079fab14fd0795e51d634b3314079d2b0c86"
|
||||
integrity sha512-ESmmJJu+uJLnPSmbaS5Xs73Ak0MOfQ5NZjczuI+ZQrnR3Hq7C/Pnw2id6MpbwtmIGCuIAH7bKEJciDIXvNpL3Q==
|
||||
dependencies:
|
||||
"@internationalized/number" "^3.0.2"
|
||||
compute-scroll-into-view "^1.0.17"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user