Design lang page

This commit is contained in:
soupette 2019-09-11 16:06:24 +02:00
parent 9614a7afe7
commit 4f1b2973fc
15 changed files with 511 additions and 12 deletions

View File

@ -0,0 +1 @@
{}

View File

@ -22,16 +22,16 @@ window.strapi = Object.assign(window.strapi || {}, {
module.exports = {
'strapi-plugin-users-permissions': require('../../../strapi-plugin-users-permissions/admin/src')
.default,
'strapi-plugin-content-manager': require('../../../strapi-plugin-content-manager/admin/src')
.default,
// 'strapi-plugin-content-manager': require('../../../strapi-plugin-content-manager/admin/src')
// .default,
'strapi-plugin-content-type-builder': require('../../../strapi-plugin-content-type-builder/admin/src')
.default,
'strapi-plugin-documentation': require('../../../strapi-plugin-documentation/admin/src')
.default,
// 'strapi-plugin-documentation': require('../../../strapi-plugin-documentation/admin/src')
// .default,
'strapi-plugin-settings-manager': require('../../../strapi-plugin-settings-manager/admin/src')
.default,
'strapi-plugin-email': require('../../../strapi-plugin-email/admin/src')
.default,
'strapi-plugin-upload': require('../../../strapi-plugin-upload/admin/src')
.default,
// 'strapi-plugin-email': require('../../../strapi-plugin-email/admin/src')
// .default,
// 'strapi-plugin-upload': require('../../../strapi-plugin-upload/admin/src')
// .default,
};

View File

@ -6,7 +6,7 @@
import styled from 'styled-components';
import { sizes } from 'strapi-helper-plugin';
import sizes from '../../assets/styles/sizes';
const StyledViewContainer = styled.div`
min-height: calc(100vh - ${sizes.header.height});

View File

@ -93,6 +93,7 @@ export { default as PluginHeader } from './components/PluginHeader';
export { default as PopUpWarning } from './components/PopUpWarning';
export { default as StyledLeftMenu } from './components/StyledLeftMenu';
export { default as TrashButton } from './components/TrashButton';
export { default as ViewContainer } from './components/ViewContainer';
// Utils
export { default as auth } from './utils/auth';

View File

@ -7,10 +7,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import StyledViewContainer from './StyledViewContainer';
// import StyledViewContainer from './StyledViewContainer';
import LeftMenu from '../LeftMenu';
import { PluginHeader, getQueryParameters } from 'strapi-helper-plugin';
import {
PluginHeader,
getQueryParameters,
ViewContainer as StyledViewContainer,
} from 'strapi-helper-plugin';
function ViewContainer({
children,

View File

@ -0,0 +1,67 @@
/**
*
* ListRow
*
*/
import styled from 'styled-components';
import { colors } from 'strapi-helper-plugin';
const ListRow = styled.tr`
background-color: transparent;
p {
margin-bottom: 0;
}
img {
width: 35px;
}
button {
cursor: pointer;
}
td:first-of-type {
padding-left: 3rem;
position: relative;
img {
width: 35px;
height: 20px;
position: absolute;
top: calc(50% - 10px);
left: 3rem;
}
img + p {
width: 237px;
padding-left: calc(3rem + 35px);
}
}
td:nth-child(2) {
width: 25rem;
p {
font-weight: 500;
text-transform: capitalize;
}
}
td:last-child {
text-align: right;
}
&.relation-row {
background: linear-gradient(
135deg,
rgba(28, 93, 231, 0.05),
rgba(239, 243, 253, 0)
);
}
&.clickable {
&:hover {
cursor: pointer;
background-color: ${colors.grey};
& + tr {
&::before {
background-color: transparent;
}
}
}
}
`;
export default ListRow;

View File

@ -0,0 +1,36 @@
import React from 'react';
import PropTypes from 'prop-types';
import { NavLink } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import pluginId from '../../pluginId';
const MenuSection = ({ name, items }) => {
return (
<section>
<h3>
<FormattedMessage id={`${pluginId}.${name}`} />
</h3>
<ul className="menu-list">
{items.map(link => {
return (
<li key={link.slug}>
<NavLink to={`/plugins/${pluginId}/${link.slug}`}>
<p>
<i className={`fa fa-${link.icon}`} />
<FormattedMessage id={`${pluginId}.${link.name}`} />
</p>
</NavLink>
</li>
);
})}
</ul>
</section>
);
};
MenuSection.propTypes = {
items: PropTypes.array.isRequired,
name: PropTypes.string.isRequired,
};
export default MenuSection;

View File

@ -0,0 +1,23 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import pluginId from '../../pluginId';
const Action = ({ isActive }) => {
const trad = isActive ? 'default' : 'set';
const tradId = `${pluginId}.list.languages.${trad}.languages`;
const color = isActive ? '#49515a' : '#1c5de7';
const fontStyle = isActive ? 'italic' : '';
return (
<FormattedMessage id={tradId}>
{msg => <span style={{ fontStyle, color }}>{msg}</span>}
</FormattedMessage>
);
};
Action.propTypes = {
isActive: PropTypes.bool.isRequired,
};
export default Action;

View File

@ -0,0 +1,7 @@
import { createGlobalStyle } from 'styled-components';
const Flags = createGlobalStyle`
@import url('https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.3.0/css/flag-icon.css');
`;
export default Flags;

View File

@ -0,0 +1,155 @@
import React, { useCallback, useEffect, useReducer } from 'react';
// import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { get } from 'lodash';
import {
LoadingIndicatorPage,
PluginHeader,
List,
ListHeader,
ListTitle,
ListWrapper,
} from 'strapi-helper-plugin';
import pluginId from '../../pluginId';
import useFetch from '../../hooks/useFetch';
import ListRow from '../../components/ListRow';
import getFlag, { formatLanguageLocale } from '../../utils/getFlag';
import filterLanguages from './utils/filterLanguages';
import reducer, { initialState } from './reducer';
import Action from './Action';
import Flags from './Flags';
const getTrad = key => `${pluginId}.${key}`;
const LanguagePage = () => {
const [reducerState, dispatch] = useReducer(reducer, initialState);
const { modifiedData, allLanguages } = reducerState.toJS();
const { data, isLoading } = useFetch(['languages', 'i18n']);
const findLangTrad = useCallback(
lang => {
const trad = get(allLanguages, [
'sections',
'0',
'items',
'0',
'items',
]).find(obj => obj.value === formatLanguageLocale(lang).join('_'));
return trad;
},
[allLanguages]
);
useEffect(() => {
if (!isLoading) {
const [{ languages }, result] = data;
dispatch({
type: 'GET_DATA_SUCCEEDED',
languages,
allLanguages: result,
availableLanguages: filterLanguages(languages, result),
});
}
return () => {};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoading]);
if (isLoading) {
return <LoadingIndicatorPage />;
}
// return null;
const buttonProps = {
kind: 'secondaryHotlineAdd',
label: getTrad('list.languages.button.label'),
};
const availableLanguagesLength = modifiedData.length;
const listTitleSuffix = availableLanguagesLength > 1 ? 'plural' : 'singular';
return (
<>
<Flags />
<PluginHeader
description={{ id: getTrad('form.language.description') }}
title={{ id: getTrad('form.language.name') }}
actions={[
{
label: `${pluginId}.form.button.cancel`,
kind: 'secondary',
type: 'button',
},
{
label: `${pluginId}.form.button.save`,
kind: 'primary',
type: 'submit',
id: 'saveData',
},
]}
/>
<ListWrapper>
<ListHeader
button={{ ...buttonProps, style: { right: '15px', top: '18px' } }}
style={{ paddingBottom: '1rem' }}
>
<div className="list-header-title">
<FormattedMessage
id={getTrad(`list.languages.title.${listTitleSuffix}`)}
>
{title => (
<ListTitle>
{availableLanguagesLength}&nbsp;{title}
</ListTitle>
)}
</FormattedMessage>
</div>
</ListHeader>
<List>
<table>
<tbody>
{modifiedData.map(lang => {
const langArray = formatLanguageLocale(lang.name);
const flag = getFlag(langArray);
return (
<ListRow key={lang.name} className="clickable">
<td>
<FormattedMessage
id={getTrad(findLangTrad(lang.name).name)}
>
{msg => (
<p style={{ fontWeight: 500 }}>
<span
className={`flag-icon flag-icon-${flag}`}
style={{ marginRight: 39 }}
/>
{msg}
</p>
)}
</FormattedMessage>
</td>
<td>{lang.name}</td>
<td>
<Action isActive={lang.active} />
</td>
<td>
<button type="button">
<i className="fa fa-trash link-icon" />
</button>
</td>
</ListRow>
);
})}
</tbody>
</table>
</List>
</ListWrapper>
</>
);
};
export default LanguagePage;

View File

@ -0,0 +1,34 @@
import { fromJS } from 'immutable';
import { get } from 'lodash';
const initialState = fromJS({
availableLanguages: {},
allLanguages: {},
initialData: [],
modifiedData: [],
selectOptions: [],
});
const reducer = (state, action) => {
switch (action.type) {
case 'GET_DATA_SUCCEEDED':
return state
.update('allLanguages', () => fromJS(action.allLanguages))
.update('modifiedData', () => fromJS(action.languages))
.update('initialData', () => fromJS(action.languages))
.update('selectOptions', () =>
fromJS(
get(
action.availableLanguages,
['sections', '0', 'items', '0', 'items'],
[]
)
)
);
default:
return state;
}
};
export default reducer;
export { initialState };

View File

@ -0,0 +1,22 @@
import { cloneDeep, get, set } from 'lodash';
const filterLanguages = (appLanguages, availableLanguages) => {
const ret = cloneDeep(availableLanguages);
const filteredLanguages = get(
ret,
['sections', '0', 'items', '0', 'items'],
[]
).filter(lang => {
const i = appLanguages.findIndex(obj => {
return obj.name === lang.value.toLowerCase();
});
return i === -1;
});
set(ret, ['sections', '0', 'items', '0', 'items'], filteredLanguages);
return ret;
};
export default filterLanguages;

View File

@ -0,0 +1,110 @@
import React, { useEffect, useState } from 'react';
import { get } from 'lodash';
import { Switch, Route } from 'react-router-dom';
import PropTypes from 'prop-types';
import {
LoadingIndicatorPage,
request,
StyledLeftMenu,
ViewContainer,
} from 'strapi-helper-plugin';
import pluginId from '../../pluginId';
import MenuSection from '../../components/MenuSection';
import LanguagePage from '../LanguagePage';
/* eslint-disable */
const Main = ({
global: { currentEnvironment },
history: { push },
location: { pathname },
}) => {
const [{ menuSections, environments, isLoading }, setState] = useState({
menuSections: [],
environments: [],
isLoading: true,
});
const abortController = new AbortController();
const { signal } = abortController;
useEffect(() => {
const getData = async () => {
try {
const endPoints = ['menu', 'configurations/environments'];
const [
{ sections: menuSections },
{ environments },
] = await Promise.all(
endPoints.map(endPoint =>
request(`/${pluginId}/${endPoint}`, { method: 'GET', signal })
)
);
// Redirect to first link item
if (pathname.split('/').length === 3) {
const firstLink = get(
menuSections,
[0, 'items', 0, 'slug'],
'application'
);
push(`/plugins/${pluginId}/${firstLink}`);
}
setState({ menuSections, environments, isLoading: false });
} catch (err) {
strapi.notification.error(`${pluginId}.strapi.notification.error`);
}
};
getData();
return () => {
abortController.abort();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (isLoading) {
return <LoadingIndicatorPage />;
}
// console.log({ menuSections });
return (
<ViewContainer>
<div className="container-fluid">
<div className="row">
<StyledLeftMenu className="col-3">
{menuSections.map(section => (
<MenuSection key={section.name} {...section} />
))}
</StyledLeftMenu>
<div className="col-9">
<div className="components-container">
<Switch>
<Route
path={`/plugins/${pluginId}/languages`}
component={LanguagePage}
exact
/>
</Switch>
</div>
</div>
</div>
</div>
</ViewContainer>
);
};
Main.propTypes = {
global: PropTypes.shape({
currentEnvironment: PropTypes.string.isRequired,
}),
history: PropTypes.shape({
push: PropTypes.func.isRequired,
}),
location: PropTypes.shape({
pathname: PropTypes.string.isRequired,
}),
};
export default Main;

View File

@ -0,0 +1,38 @@
import { useEffect, useState } from 'react';
import { request } from 'strapi-helper-plugin';
import pluginId from '../pluginId';
const useFetch = endPoints => {
const abortController = new AbortController();
const { signal } = abortController;
const [state, setState] = useState({ data: {}, isLoading: true });
useEffect(() => {
const getData = async () => {
try {
const data = await Promise.all(
endPoints.map(endPoint =>
request(`/${pluginId}/configurations/${endPoint}`, {
method: 'GET',
signal,
})
)
);
setState({ data, isLoading: false });
} catch (err) {
strapi.notification.error(`${pluginId}.strapi.notification.error`);
}
};
getData();
return () => {
abortController.abort();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return { data: state.data, isLoading: state.isLoading };
};
export default useFetch;

View File

@ -1,7 +1,8 @@
import React from 'react';
import pluginPkg from '../../package.json';
import pluginId from './pluginId';
import App from './containers/App';
// import App from './containers/App';
import App from './containers/Main';
import Initializer from './containers/Initializer';
import lifecycles from './lifecycles';
import trads from './translations';