Merge branch 'master' into fix/deploy
@ -328,6 +328,171 @@ export default FooPage;
|
||||
|
||||
***
|
||||
|
||||
## OverlayBlocker
|
||||
|
||||
The OverlayBlocker is a React component that is very useful to block user interactions when the strapi server is restarting in order to avoid front-end errors.
|
||||
|
||||
### Usage
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
| -------- | ---- | -------- | ----------- |
|
||||
| children | node | no | Anything that is wrapped inside the OverlayBlocker |
|
||||
| isOpen | bool | no | If set to `true` it will display the component |
|
||||
|
||||
### Example
|
||||
|
||||
In this example we'll have a button that when clicked it will display the OverlayBlocker for 5 seconds thus 'freezes' the admin so the user can't navigate (it simulates a very long server restart).
|
||||
|
||||
**Path -** `./plugins/my-plugin/admin/src/containers/FooPage/constants.js`.
|
||||
```js
|
||||
export const ON_BUTTON_CLICK = 'MyPlugin/FooPage/ON_BUTTON_CLICK';
|
||||
export const RESET_SHOW_OVERLAY_PROP = 'MyPlugin/FooPage/RESET_SHOW_OVERLAY_PROP';
|
||||
```
|
||||
|
||||
**Path -** `./plugins/my-plugin/admin/src/containers/FooPage/actions.js`.
|
||||
```js
|
||||
import { ON_BUTTON_CLICK, RESET_SHOW_OVERLAY_PROP } from './constants';
|
||||
|
||||
export function onButtonClick() {
|
||||
return {
|
||||
type: ON_BUTTON_CLICK,
|
||||
};
|
||||
}
|
||||
|
||||
export function resetShowOverlayProp() {
|
||||
return {
|
||||
type: RESET_SHOW_OVERLAY_PROP,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**Path -** `./plugins/my-plugin/admin/src/containers/FooPage/index.js`.
|
||||
```js
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators, compose } from 'redux';
|
||||
|
||||
// Design
|
||||
import Button from 'components/Button';
|
||||
import OverlayBlocker from 'components/OverlayBlocker';
|
||||
|
||||
// Utils
|
||||
import injectSaga from 'utils/injectSaga';
|
||||
import injectReducer from 'utils/injectReducer';
|
||||
|
||||
// Actions
|
||||
import { onButtonClick } from './actions';
|
||||
|
||||
// Reducer
|
||||
import reducer from './reducer';
|
||||
|
||||
// Saga
|
||||
import saga from './saga';
|
||||
|
||||
// Selectors (see the documentation to see how to use selectors)
|
||||
import makeSelectFooPage from './selectors';
|
||||
|
||||
|
||||
export class FooPage extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<OverlayBlocker isOpen={this.props.showOverlayBlocker}>
|
||||
<div style={{ width: '100px', height: '100px', backgroundColor: '#fff' }}>
|
||||
<h4>The app is now blocked for 5 seconds</h4>
|
||||
</div>
|
||||
</Overlay>
|
||||
<Button onClick={this.props.onButtonClick} primary>Click me</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FooPage.propTypes = {
|
||||
onButtonClick: PropTypes.func.isRequired,
|
||||
showOverlayBlocker: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = makeSelectFooPage();
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(
|
||||
{
|
||||
onButtonClick,
|
||||
},
|
||||
dispatch,
|
||||
);
|
||||
}
|
||||
|
||||
const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
||||
const withReducer = injectReducer({ key: 'fooPage', reducer });
|
||||
const withSaga = injectSaga({ key: 'fooPage', saga });
|
||||
|
||||
export default compose(
|
||||
withReducer,
|
||||
withSaga,
|
||||
withConnect,
|
||||
)(FooPage);
|
||||
|
||||
```
|
||||
**Path -** `./plugins/my-plugin/admin/src/containers/FooPage/reducer.js`.
|
||||
```js
|
||||
import { fromJS } from 'immutable';
|
||||
import { ON_BUTTON_CLICK, RESET_SHOW_OVERLAY_PROP } from './constants';
|
||||
|
||||
const initialState = fromJS({
|
||||
showOverlayBlocker: false,
|
||||
});
|
||||
|
||||
function fooPageReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case ON_BUTTON_CLICK:
|
||||
return state.set('showOverlayBlocker', true);
|
||||
case RESET_SHOW_OVERLAY_PROP:
|
||||
return state.set('showOverlayBlocker', false);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default fooPageReducer;
|
||||
```
|
||||
|
||||
**Path -** `./plugins/my-plugin/admin/src/containers/FooPage/saga.js`.
|
||||
```js
|
||||
import {
|
||||
fork,
|
||||
put,
|
||||
takeLatest,
|
||||
} from 'redux-saga/effects';
|
||||
|
||||
import { resetShowOverlayProp } from './actions';
|
||||
|
||||
import { ON_BUTTON_CLICK } from './constants';
|
||||
|
||||
export function* buttonClicked() {
|
||||
try {
|
||||
// Start the timer
|
||||
yield new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
yield put(resetShowOverlayProp());
|
||||
} catch(err) {
|
||||
yield put(resetShowOverlayProp());
|
||||
}
|
||||
}
|
||||
|
||||
export default function* defaultSaga() {
|
||||
yield fork(takeLatest, ON_BUTTON_CLICK, buttonClicked);
|
||||
}
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## PopUp Warning
|
||||
|
||||
PopUp warning library based on [reactstrap](https://reactstrap.github.io/components/modals/).
|
||||
|
@ -229,6 +229,7 @@ window.strapi = Object.assign(window.strapi || {}, {
|
||||
}),
|
||||
router: history,
|
||||
languages,
|
||||
currentLanguage: window.localStorage.getItem('strapi-admin-language') || window.navigator.language || window.navigator.userLanguage || 'en',
|
||||
});
|
||||
|
||||
const dispatch = store.dispatch;
|
||||
|
19
packages/strapi-admin/admin/src/assets/icons/icon_success.svg
Executable file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 44.1 (41455) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Check</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Pages" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Home-view" transform="translate(-1086.000000, -34.000000)">
|
||||
<g id="Notification-2---Success" transform="translate(1066.000000, 14.000000)">
|
||||
<g id="Check" transform="translate(20.000000, 20.000000)">
|
||||
<g id="check" transform="translate(5.500000, 7.000000)" fill-rule="nonzero" fill="#27B70F">
|
||||
<path d="M9.34239514,1.3983456 C9.34239514,1.55907497 9.28613986,1.69569495 9.1736293,1.80820551 L4.80982666,6.17200815 L3.99010683,6.99172798 C3.87759626,7.10423854 3.74097629,7.16049383 3.58024691,7.16049383 C3.41951753,7.16049383 3.28289756,7.10423854 3.170387,6.99172798 L2.35066717,6.17200815 L0.168765848,3.99010683 C0.0562552826,3.87759626 0,3.74097629 0,3.58024691 C0,3.41951753 0.0562552826,3.28289756 0.168765848,3.170387 L0.98848568,2.35066717 C1.10099625,2.2381566 1.23761622,2.18190132 1.3983456,2.18190132 C1.55907497,2.18190132 1.69569495,2.2381566 1.80820551,2.35066717 L3.58024691,4.12873592 L7.53418963,0.168765848 C7.6467002,0.0562552826 7.78332017,0 7.94404955,0 C8.10477893,0 8.2413989,0.0562552826 8.35390947,0.168765848 L9.1736293,0.98848568 C9.28613986,1.10099625 9.34239514,1.23761622 9.34239514,1.3983456 Z" id="Shape"></path>
|
||||
</g>
|
||||
<rect id="Rectangle-2" stroke="#27B70F" x="0.5" y="0.5" width="19" height="19" rx="9.5"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1,27 @@
|
||||
/*
|
||||
*
|
||||
* DownloadInfo
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Icon from '../../assets/icons/icon_success.svg';
|
||||
import styles from './styles.scss';
|
||||
|
||||
function DownloadInfo() {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.content}>
|
||||
<img src={Icon} alt="info" />
|
||||
<div>
|
||||
<FormattedMessage id="app.components.DownloadInfo.download" />
|
||||
<br />
|
||||
<FormattedMessage id="app.components.DownloadInfo.text" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DownloadInfo;
|
@ -0,0 +1,34 @@
|
||||
.wrapper {
|
||||
width: 372px;
|
||||
height: 159px;
|
||||
border-radius: 2px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 3rem;
|
||||
// color: #F64D0A;
|
||||
text-align: center;
|
||||
font-family: Lato;
|
||||
font-size: 1.3rem;
|
||||
> img {
|
||||
width: 2.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
> div {
|
||||
padding-top: 9px;
|
||||
line-height: 18px;
|
||||
> span:first-child {
|
||||
color: #333740;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
> span {
|
||||
color: #787E8F;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
/*
|
||||
*
|
||||
* InstallPluginPopup
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Modal, ModalHeader, ModalBody } from 'reactstrap';
|
||||
import { map } from 'lodash';
|
||||
import cn from 'classnames';
|
||||
|
||||
import Official from 'components/Official';
|
||||
// import StarsContainer from 'components/StarsContainer';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
class InstallPluginPopup extends React.Component {
|
||||
handleClick = () => {
|
||||
this.props.history.push({ pathname: this.props.history.location.pathname });
|
||||
this.context.downloadPlugin(this.props.plugin.id);
|
||||
}
|
||||
|
||||
toggle = () => {
|
||||
this.props.history.push({
|
||||
pathname: this.props.history.location.pathname,
|
||||
});
|
||||
}
|
||||
|
||||
navLinks = [
|
||||
{
|
||||
content: 'app.components.InstallPluginPopup.navLink.description',
|
||||
name: 'description',
|
||||
},
|
||||
{
|
||||
content: 'app.components.InstallPluginPopup.navLink.screenshots',
|
||||
name: 'screenshots',
|
||||
},
|
||||
{
|
||||
content: 'app.components.InstallPluginPopup.navLink.avis',
|
||||
name: 'avis',
|
||||
},
|
||||
{
|
||||
content: 'app.components.InstallPluginPopup.navLink.faq',
|
||||
name: 'faq',
|
||||
},
|
||||
{
|
||||
content: 'app.components.InstallPluginPopup.navLink.changelog',
|
||||
name: 'changelog',
|
||||
},
|
||||
];
|
||||
|
||||
render() {
|
||||
const descriptions = {
|
||||
short: this.props.plugin.id === 'support-us' ? <FormattedMessage id={this.props.plugin.description.short} /> : this.props.plugin.description.short,
|
||||
long: this.props.plugin.id === 'support-us' ? <FormattedMessage id={this.props.plugin.description.long || this.props.plugin.description.short} /> : this.props.plugin.description.long || this.props.plugin.description.short,
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal isOpen={this.props.isOpen} toggle={this.toggle} className={styles.modalPosition}>
|
||||
<ModalHeader toggle={this.toggle} className={styles.modalHeader} />
|
||||
<ModalBody className={styles.modalBody}>
|
||||
<div className={styles.wrapper}>
|
||||
|
||||
<div className={styles.headerWrapper}>
|
||||
<div className={styles.logo}><img src={`${this.props.plugin.logo}`} alt="icon" /></div>
|
||||
<div className={styles.headerInfo}>
|
||||
<div className={styles.name}>{this.props.plugin.name}</div>
|
||||
<div className={styles.ratings}>
|
||||
{/*}
|
||||
<StarsContainer ratings={this.props.plugin.ratings} />
|
||||
<div>
|
||||
<span style={{ fontWeight: '600', color: '#333740', fontSize: '12px'}}>{this.props.plugin.ratings}</span>
|
||||
<span style={{ fontWeight: '500', color: '#666666', fontSize: '11px' }}>/5</span>
|
||||
</div>
|
||||
*/}
|
||||
<Official style={{ marginTop: '0' }} />
|
||||
</div>
|
||||
<div className={styles.headerDescription}>
|
||||
{descriptions.short}
|
||||
</div>
|
||||
<div className={styles.headerButtonContainer}>
|
||||
<div>
|
||||
<i className={`fa fa-${this.props.plugin.isCompatible ? 'check' : 'times'}`} />
|
||||
<FormattedMessage id={`app.components.PluginCard.compatible${this.props.plugin.id === 'support-us' ? 'Community' : ''}`} />
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
{/*}
|
||||
<span style={{ fontWeight: '600' }}>+{this.props.plugin.downloads_nb}k </span><FormattedMessage id="app.components.InstallPluginPopup.downloads" />
|
||||
*/}
|
||||
</div>
|
||||
<div className={styles.buttonWrapper} onClick={this.handleClick}>
|
||||
<div>
|
||||
<FormattedMessage id="app.components.InstallPluginPopup.downloads" />
|
||||
</div>
|
||||
{/* Uncomment whebn prices are running}
|
||||
<div>{this.props.plugin.price} €</div>
|
||||
*/}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.navContainer}>
|
||||
{map(this.navLinks, link => {
|
||||
const isActive = this.props.history.location.hash.split('::')[1] === link.name;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={link.name}
|
||||
className={cn(isActive ? styles.navLink : '', link.name !== 'description' ? styles.notAllowed : '')}
|
||||
onClick={() => {
|
||||
if (link.name === 'description') {
|
||||
this.props.history.push({ pathname: this.props.history.location.pathname, hash: `${this.props.plugin.id}::${link.name}` });
|
||||
}
|
||||
}}
|
||||
style={isActive ? { paddingTop: '4px'} : { paddingTop: '6px' }}
|
||||
>
|
||||
<FormattedMessage id={link.content} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className={styles.pluginDescription}>
|
||||
{descriptions.long}
|
||||
</div>
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InstallPluginPopup.contextTypes = {
|
||||
downloadPlugin: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
InstallPluginPopup.defaultProps = {
|
||||
description: {
|
||||
short: 'app.Components.InstallPluginPopup.noDescription',
|
||||
},
|
||||
};
|
||||
|
||||
InstallPluginPopup.propTypes = {
|
||||
description: PropTypes.shape({
|
||||
long: PropTypes.string,
|
||||
short: PropTypes.string,
|
||||
}),
|
||||
history: PropTypes.object.isRequired,
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
plugin: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default InstallPluginPopup;
|
@ -0,0 +1,201 @@
|
||||
.buttonWrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
min-width: 136px;
|
||||
height: 30px;
|
||||
margin-left: 20px;
|
||||
border-radius: 0.3rem;
|
||||
background: linear-gradient(315deg, #0097F6 0%, #005EEA 100%);
|
||||
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
|
||||
cursor: pointer;
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
> div {
|
||||
text-align: center;
|
||||
text-transform: capitalize;
|
||||
&:first-child {
|
||||
flex-grow: 2;
|
||||
}
|
||||
}
|
||||
// > div:last-child {
|
||||
// width: 50px;
|
||||
// border-left: 1px solid #0774D9;
|
||||
// }
|
||||
}
|
||||
|
||||
.headerButtonContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-top: 13px;
|
||||
line-height: 29px;
|
||||
|
||||
> div {
|
||||
font-size: 13px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
> div:first-child {
|
||||
> i {
|
||||
margin-right: 10px;
|
||||
}
|
||||
color: #5A9E06;
|
||||
}
|
||||
|
||||
> div:nth-child(2) {
|
||||
display: flex;
|
||||
> span:nth-child(2) {
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.headerDescription {
|
||||
height: 48px;
|
||||
padding-top: 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.headerInfo {
|
||||
flex-grow: 1;
|
||||
padding-top: 7px;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.headerWrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.logo {
|
||||
flex-shrink: 0;
|
||||
width: 144px;
|
||||
height: 144px;
|
||||
border: 1px solid #E8EEFC;
|
||||
border-radius: 3px;
|
||||
background-size: contain;
|
||||
line-height: 144px;
|
||||
text-align: center;
|
||||
> img {
|
||||
min-width: 72px;
|
||||
// max-height: 72px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.modalHeader {
|
||||
margin: 0 1.4rem !important;
|
||||
padding: 1.4rem 0 0 0 !important;
|
||||
border-bottom: 0 !important;
|
||||
> button {
|
||||
margin-right: -1rem !important;
|
||||
color: #C3C5C8;
|
||||
opacity: 1;
|
||||
font-size: 1.8rem;
|
||||
font-weight: 100;
|
||||
z-index: 999;
|
||||
&:hover, &:focus {
|
||||
color: #C3C5C8;
|
||||
opacity: 1;
|
||||
outline: 0!important;
|
||||
}
|
||||
> span {
|
||||
display: none;
|
||||
}
|
||||
&:before {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
content: '\F00d';
|
||||
font-family: 'FontAwesome';
|
||||
font-weight: 400;
|
||||
font-size: 1.2rem;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modalBody {
|
||||
padding: 5px 0px !important;
|
||||
}
|
||||
|
||||
.modalPosition {
|
||||
> div {
|
||||
border:none;
|
||||
border-radius: 2px;
|
||||
width: 74.5rem;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
padding-bottom: 1px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.navContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 36px;
|
||||
|
||||
margin-top: 38px;
|
||||
padding: 0 30px;
|
||||
|
||||
background-color: #FAFAFB;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
text-transform: capitalize;
|
||||
|
||||
> div {
|
||||
flex-grow: 2;
|
||||
text-align: center;
|
||||
line-height: 23px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.navLink {
|
||||
border-top: 2px solid #1C5DE7;
|
||||
}
|
||||
|
||||
.notAllowed {
|
||||
opacity: .3;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
.pluginDescription {
|
||||
padding: 24px 30px;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.ratings {
|
||||
display: flex;
|
||||
line-height: 18px;
|
||||
|
||||
> div:last-child {
|
||||
padding-top: 1px;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.starsContainer {
|
||||
display: flex;
|
||||
> div {
|
||||
color: #EED348;
|
||||
> i {
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
> div:last-child {
|
||||
color: #B3B5B9;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding: 0 30px;
|
||||
}
|
30
packages/strapi-admin/admin/src/components/Official/index.js
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
*
|
||||
* Official
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './styles.scss';
|
||||
|
||||
function Official(props) {
|
||||
|
||||
return (
|
||||
<button className={styles.wrapper} style={props.style}>
|
||||
<i className="fa fa-star" />
|
||||
<FormattedMessage id="app.components.Official" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
Official.defaultProps = {
|
||||
style: {},
|
||||
};
|
||||
|
||||
Official.propTypes = {
|
||||
style: PropTypes.object,
|
||||
};
|
||||
|
||||
export default Official;
|
@ -0,0 +1,29 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
// justify-content: space-between;
|
||||
height: 20px !important;
|
||||
width: 88px;
|
||||
padding: 0 10px;
|
||||
border-radius: 2px;
|
||||
background-color: #EE8948;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
> span {
|
||||
height: 20px;
|
||||
padding: 0!important;
|
||||
color: #fff;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: 600;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
> i {
|
||||
margin-top: 1px;
|
||||
margin-right: 6px;
|
||||
vertical-align: -webkit-baseline-middle;
|
||||
|
||||
color: #FFDC00;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
@ -7,19 +7,25 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import { isEmpty, map, times } from 'lodash';
|
||||
import { isEmpty, replace } from 'lodash';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
// Temporary picture
|
||||
import Button from 'components/Button';
|
||||
import FakeScreenShot from './screenshot.png';
|
||||
import InstallPluginPopup from 'components/InstallPluginPopup';
|
||||
import Official from 'components/Official';
|
||||
// import StarsContainer from 'components/StarsContainer';
|
||||
|
||||
import styles from './styles.scss';
|
||||
import Screenshot from './screenshot.png';
|
||||
|
||||
class PluginCard extends React.Component {
|
||||
state = { isOpen: false };
|
||||
state = { isOpen: false, boostrapCol: 'col-lg-4' };
|
||||
|
||||
componentDidMount() {
|
||||
this.shouldOpenModal(this.props);
|
||||
window.addEventListener('resize', this.setBoostrapCol);
|
||||
this.setBoostrapCol();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
@ -28,14 +34,44 @@ class PluginCard extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.setBoostrapCol);
|
||||
}
|
||||
|
||||
setBoostrapCol = () => {
|
||||
let boostrapCol = 'col-lg-4';
|
||||
|
||||
if (window.innerWidth > 1680) {
|
||||
boostrapCol = 'col-lg-3';
|
||||
}
|
||||
|
||||
if (window.innerWidth > 2300) {
|
||||
boostrapCol = 'col-lg-2';
|
||||
}
|
||||
|
||||
this.setState({ boostrapCol });
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
if (this.props.plugin.id !== 'support-us') {
|
||||
this.props.history.push({
|
||||
pathname: this.props.history.location.pathname,
|
||||
hash: `${this.props.plugin.id}::description`,
|
||||
});
|
||||
} else {
|
||||
this.aTag.click();
|
||||
}
|
||||
}
|
||||
|
||||
handleDownloadPlugin = () => {
|
||||
this.props.downloadPlugin();
|
||||
}
|
||||
|
||||
shouldOpenModal = (props) => {
|
||||
this.setState({ isOpen: !isEmpty(props.history.location.hash) });
|
||||
}
|
||||
|
||||
render() {
|
||||
const stars = Math.round(this.props.plugin.ratings);
|
||||
const coloredStars = times(stars, String);
|
||||
const emptyStars = times(5 - stars, String);
|
||||
const buttonClass = !this.props.isAlreadyInstalled || this.props.showSupportUsButton ? styles.primary : styles.secondary;
|
||||
|
||||
let buttonLabel = this.props.isAlreadyInstalled ? 'app.components.PluginCard.Button.label.install' : 'app.components.PluginCard.Button.label.download';
|
||||
@ -44,49 +80,73 @@ class PluginCard extends React.Component {
|
||||
buttonLabel = 'app.components.PluginCard.Button.label.support';
|
||||
}
|
||||
|
||||
const pluginIcon = this.props.plugin.id !== 'email' ? (
|
||||
<div className={styles.frame}>
|
||||
<span className={styles.helper} />
|
||||
<img src={`${this.props.plugin.logo}`} alt="icon" />
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.iconContainer}><i className={`fa fa-${this.props.plugin.icon}`} /></div>
|
||||
);
|
||||
|
||||
const descriptions = {
|
||||
short: this.props.plugin.id === 'support-us' ? <FormattedMessage id={this.props.plugin.description.short} /> : this.props.plugin.description.short,
|
||||
long: this.props.plugin.id === 'support-us' ? <FormattedMessage id={this.props.plugin.description.long || this.props.plugin.description.short} /> : this.props.plugin.description.long || this.props.plugin.description.short,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn('col-md-4', styles.pluginCard)}>
|
||||
<div className={cn(this.state.boostrapCol, styles.pluginCard)} onClick={this.handleClick}>
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.cardTitle}>
|
||||
<div><i className={`fa fa-${this.props.plugin.icon}`} /></div>
|
||||
{pluginIcon}
|
||||
<div>{this.props.plugin.name}</div>
|
||||
</div>
|
||||
<div className={styles.cardDescription}>
|
||||
<FormattedMessage id={this.props.plugin.description} />
|
||||
{descriptions.short}
|
||||
<FormattedMessage id="app.components.PluginCard.more-details" />
|
||||
</div>
|
||||
<div className={styles.cardScreenshot}>
|
||||
<img src={FakeScreenShot} alt='plugin screenshot' />
|
||||
<div className={styles.cardScreenshot} style={{ backgroundImage: `url(${Screenshot})` }}>
|
||||
|
||||
</div>
|
||||
<div className={styles.cardPrice}>
|
||||
<div>
|
||||
<i className={`fa fa-${this.props.plugin.isCompatible ? 'check' : 'times'}`} />
|
||||
<FormattedMessage id={`app.components.PluginCard.compatible${this.props.plugin.id === 'support-us' ? 'Community' : ''}`} />
|
||||
</div>
|
||||
<div>{this.props.plugin.price !== 0 ? `${this.props.plugin.price}€` : <FormattedMessage id="app.components.PluginCard.price.free" />}</div>
|
||||
<div>{this.props.plugin.price !== 0 ? `${this.props.plugin.price}€` : ''}</div>
|
||||
</div>
|
||||
<div className={styles.cardFooter}>
|
||||
<div className={styles.cardFooter} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.ratings}>
|
||||
<div className={styles.starsContainer}>
|
||||
<div>
|
||||
{map(coloredStars, star => <i key={star} className=" fa fa-star" />)}
|
||||
</div>
|
||||
<div>
|
||||
{map(emptyStars, s => <i key={s} className="fa fa-star" />)}
|
||||
</div>
|
||||
</div>
|
||||
{/*<StarsContainer ratings={this.props.plugin.ratings} />
|
||||
<div>
|
||||
<span style={{ fontWeight: '600', color: '#333740' }}>{this.props.plugin.ratings}</span>
|
||||
<span style={{ fontWeight: '500', color: '#666666' }}>/5</span>
|
||||
</div>
|
||||
*/}
|
||||
<Official />
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
className={cn(buttonClass, styles.button)}
|
||||
label={buttonLabel}
|
||||
onClick={this.handleDownloadPlugin}
|
||||
/>
|
||||
<a
|
||||
href="mailto:hi@strapi.io?subject=I'd like to support Strapi"
|
||||
style={{ display: 'none' }}
|
||||
ref={(a) => { this.aTag = a; }}
|
||||
>
|
||||
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<InstallPluginPopup
|
||||
history={this.props.history}
|
||||
isAlreadyInstalled={this.props.isAlreadyInstalled}
|
||||
isOpen={!isEmpty(this.props.history.location.hash) && replace(this.props.history.location.hash.split('::')[0], '#', '') === this.props.plugin.id}
|
||||
plugin={this.props.plugin}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -97,7 +157,6 @@ PluginCard.defaultProps = {
|
||||
plugin: {
|
||||
description: '',
|
||||
id: '',
|
||||
icon: '',
|
||||
name: '',
|
||||
price: 0,
|
||||
ratings: 5,
|
||||
@ -106,6 +165,7 @@ PluginCard.defaultProps = {
|
||||
};
|
||||
|
||||
PluginCard.propTypes = {
|
||||
downloadPlugin: PropTypes.func.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
isAlreadyInstalled: PropTypes.bool,
|
||||
plugin: PropTypes.object,
|
||||
|
@ -1,20 +1,30 @@
|
||||
.button {
|
||||
height: 26px;
|
||||
min-width: 89px !important;
|
||||
padding-top: 2px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
border-radius: 2px !important;
|
||||
font-size: 13px;
|
||||
font-weight: 500 !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cardDescription {
|
||||
height: 54px;
|
||||
margin-bottom: 19px;
|
||||
padding-top: 10px;
|
||||
font-size: 12px;
|
||||
margin-top: 27px;
|
||||
margin-bottom: 9px;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
|
||||
> span:last-child {
|
||||
color: #1C5DE7;
|
||||
}
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.cardFooter {
|
||||
cursor: initial;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 54px;
|
||||
@ -22,14 +32,18 @@
|
||||
margin-right: -1.5rem;
|
||||
padding: 1.5rem 1.5rem 0 1.5rem;
|
||||
background-color: #FAFAFB;
|
||||
|
||||
&>div:first-of-type {
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.cardPrice {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin-bottom: 2.7rem;
|
||||
padding-top: 1.8rem;
|
||||
margin-bottom: 10px;
|
||||
padding-top: 2rem;
|
||||
> div:first-child {
|
||||
> i {
|
||||
margin-right: 10px;
|
||||
@ -48,9 +62,9 @@
|
||||
}
|
||||
|
||||
.cardScreenshot {
|
||||
> img {
|
||||
width: 100%;
|
||||
}
|
||||
height: 78px;
|
||||
border-radius: 2px;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.cardTitle {
|
||||
@ -58,22 +72,48 @@
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
line-height: 36px;
|
||||
letter-spacing: 0.5px;
|
||||
> div:first-child {
|
||||
height: 36px;
|
||||
width: 70px;
|
||||
margin-right: 14px;
|
||||
border: 1px solid #E8EEFC;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
> div:last-child {
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.frame {
|
||||
width: 70px;
|
||||
height: 36px;
|
||||
margin: auto 0;
|
||||
text-align: center;
|
||||
border: 1px solid #E8EEFC;
|
||||
border-radius: 3px;
|
||||
white-space: nowrap;
|
||||
> img {
|
||||
max-height: 36px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.iconContainer {
|
||||
height: 36px;
|
||||
width: 70px;
|
||||
margin-right: 14px;
|
||||
border: 1px solid #E8EEFC;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
|
||||
.helper {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.pluginCard {
|
||||
|
||||
}
|
||||
@ -89,12 +129,14 @@
|
||||
}
|
||||
|
||||
.ratings {
|
||||
display: flex;
|
||||
|
||||
> div:last-child {
|
||||
padding-top: 1px;
|
||||
font-style: italic;
|
||||
}
|
||||
// display: flex;
|
||||
// padding-top: 1px;
|
||||
// TODO uncomment when ratings
|
||||
// > div:last-child {
|
||||
// padding-top: 4px;
|
||||
// font-size: 12px;
|
||||
// font-style: italic;
|
||||
// }
|
||||
}
|
||||
|
||||
.secondary {
|
||||
@ -119,9 +161,10 @@
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
height: 320px;
|
||||
min-height: 320px;
|
||||
margin-bottom: 4rem;
|
||||
padding: 1.2rem 1.5rem;
|
||||
padding-bottom: 0;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 4px #E3E9F3;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
@ -49,20 +49,12 @@ class Row extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const pluginIcon = this.props.plugin.name !== 'Email' ? (
|
||||
<div className={styles.frame} style={{ marginRight: '30px' }}>
|
||||
<span className={styles.helper} />{this.renderImg()}
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.icoContainer} style={{ marginRight: '30px' }}>
|
||||
<i className={`fa fa-${this.props.plugin.icon}`} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<ListRow>
|
||||
<div className={cn("col-md-11", styles.nameWrapper)}>
|
||||
{pluginIcon}
|
||||
<div className={styles.icoContainer} style={{ marginRight: '30px' }}>
|
||||
<img src={`${this.props.plugin.logo}`} alt="icon" />
|
||||
</div>
|
||||
<div className={styles.pluginContent}>
|
||||
<span>{this.props.plugin.name} — </span>
|
||||
<FormattedMessage id={this.props.plugin.description} />
|
||||
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
*
|
||||
* StarsContainer
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { map, times } from 'lodash';
|
||||
import styles from './styles.scss';
|
||||
|
||||
function StarsContainer({ ratings }) {
|
||||
const stars = Math.round(ratings);
|
||||
const coloredStars = times(stars, String);
|
||||
const emptyStars = times(5 - stars, String);
|
||||
|
||||
return (
|
||||
<div className={styles.starsContainer}>
|
||||
<div>
|
||||
{map(coloredStars, star => <i key={star} className="fa fa-star" />)}
|
||||
</div>
|
||||
<div>
|
||||
{map(emptyStars, s => <i key={s} className="fa fa-star" />)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
StarsContainer.defaultProps = {
|
||||
ratings: 5,
|
||||
};
|
||||
|
||||
StarsContainer.propTypes = {
|
||||
ratings: PropTypes.number,
|
||||
};
|
||||
|
||||
export default StarsContainer;
|
@ -0,0 +1,13 @@
|
||||
.starsContainer {
|
||||
display: flex;
|
||||
margin-right: 10px;
|
||||
> div {
|
||||
color: #EEA348;
|
||||
> i {
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
> div:last-child {
|
||||
color: #B3B5B9;
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ import ComingSoonPage from 'containers/ComingSoonPage';
|
||||
import Content from 'containers/Content';
|
||||
import Header from 'components/Header/index';
|
||||
import HomePage from 'containers/HomePage';
|
||||
// import InstallPluginPage from 'containers/InstallPluginPage';
|
||||
import InstallPluginPage from 'containers/InstallPluginPage';
|
||||
import LeftMenu from 'containers/LeftMenu';
|
||||
import ListPluginsPage from 'containers/ListPluginsPage';
|
||||
import Logout from 'components/Logout';
|
||||
@ -123,7 +123,7 @@ export class AdminPage extends React.Component { // eslint-disable-line react/pr
|
||||
<Route path="/plugins/:pluginId" component={PluginPage} />
|
||||
<Route path="/plugins" component={ComingSoonPage} />
|
||||
<Route path="/list-plugins" component={ListPluginsPage} exact />
|
||||
<Route path="/install-plugin" component={ComingSoonPage} exact />
|
||||
<Route path="/install-plugin" component={InstallPluginPage} exact />
|
||||
<Route path="/configuration" component={ComingSoonPage} exact />
|
||||
<Route path="" component={NotFoundPage} />
|
||||
<Route path="404" component={NotFoundPage} />
|
||||
|
@ -5,11 +5,33 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
DOWNLOAD_PLUGIN,
|
||||
DOWNLOAD_PLUGIN_ERROR,
|
||||
DOWNLOAD_PLUGIN_SUCCEEDED,
|
||||
GET_PLUGINS,
|
||||
GET_PLUGINS_SUCCEEDED,
|
||||
ON_CHANGE,
|
||||
} from './constants';
|
||||
|
||||
export function downloadPlugin(pluginToDownload) {
|
||||
return {
|
||||
type: DOWNLOAD_PLUGIN,
|
||||
pluginToDownload,
|
||||
};
|
||||
}
|
||||
|
||||
export function downloadPluginError() {
|
||||
return {
|
||||
type: DOWNLOAD_PLUGIN_ERROR,
|
||||
};
|
||||
}
|
||||
|
||||
export function downloadPluginSucceeded() {
|
||||
return {
|
||||
type: DOWNLOAD_PLUGIN_SUCCEEDED,
|
||||
};
|
||||
}
|
||||
|
||||
export function getPlugins() {
|
||||
return {
|
||||
type: GET_PLUGINS,
|
||||
|
@ -4,6 +4,9 @@
|
||||
*
|
||||
*/
|
||||
|
||||
export const DOWNLOAD_PLUGIN = 'StrapiAdmin/InstallPluginPage/DOWNLOAD_PLUGIN';
|
||||
export const DOWNLOAD_PLUGIN_ERROR = 'StrapiAdmin/InstallPluginPage/DOWNLOAD_PLUGIN_ERROR';
|
||||
export const DOWNLOAD_PLUGIN_SUCCEEDED = 'StrapiAdmin/InstallPluginPage/DOWNLOAD_PLUGIN_SUCCEEDED';
|
||||
export const GET_PLUGINS = 'StrapiAdmin/InstallPluginPage/GET_PLUGINS';
|
||||
export const GET_PLUGINS_SUCCEEDED = 'StrapiAdmin/InstallPluginPage/GET_PLUGINS_SUCCEEDED';
|
||||
export const ON_CHANGE = 'StrapiAdmin/InstallPluginPage/ON_CHANGE';
|
||||
|
@ -7,7 +7,10 @@
|
||||
"isCompatible": true,
|
||||
"name": "Content Manager",
|
||||
"price": 0,
|
||||
"ratings": 1
|
||||
"ratings": 1,
|
||||
"longDescription": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
||||
"downloads_nb": 3,
|
||||
"logo": "http://blog.sqreen.io/wp-content/uploads/2016/07/Sqreen-Logo-Vertical-1.png"
|
||||
},
|
||||
{
|
||||
"description": "content-type-builder.plugin.description",
|
||||
@ -16,7 +19,10 @@
|
||||
"isCompatible": true,
|
||||
"name": "content type builder",
|
||||
"price": 0,
|
||||
"ratings": 4.4
|
||||
"ratings": 4.4,
|
||||
"longDescription": "lorem",
|
||||
"downloads_nb": 3,
|
||||
"logo": "http://blog.sqreen.io/wp-content/uploads/2016/07/Sqreen-Logo-Vertical-1.png"
|
||||
},
|
||||
{
|
||||
"description": "settings-manager.plugin.description",
|
||||
@ -25,7 +31,10 @@
|
||||
"isCompatible": true,
|
||||
"name": "settings manager",
|
||||
"price": 0,
|
||||
"ratings": 2.6
|
||||
"ratings": 2.6,
|
||||
"longDescription": "lorem",
|
||||
"downloads_nb": 3,
|
||||
"logo": "http://blog.sqreen.io/wp-content/uploads/2016/07/Sqreen-Logo-Vertical-1.png"
|
||||
},
|
||||
{
|
||||
"description": "users-permissions.plugin.description",
|
||||
@ -34,7 +43,10 @@
|
||||
"isCompatible": true,
|
||||
"name": "Auth & Permissions",
|
||||
"price": 0,
|
||||
"ratings": 1
|
||||
"ratings": 1,
|
||||
"longDescription": "lorem",
|
||||
"downloads_nb": 3,
|
||||
"logo": "http://blog.sqreen.io/wp-content/uploads/2016/07/Sqreen-Logo-Vertical-1.png"
|
||||
},
|
||||
{
|
||||
"description": "users-permissions.plugin.description",
|
||||
@ -43,7 +55,10 @@
|
||||
"isCompatible": true,
|
||||
"name": "test",
|
||||
"price": 0,
|
||||
"ratings": 1
|
||||
"ratings": 1,
|
||||
"longDescription": "lorem",
|
||||
"downloads_nb": 3,
|
||||
"logo": "http://blog.sqreen.io/wp-content/uploads/2016/07/Sqreen-Logo-Vertical-1.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ import { get, isUndefined, map } from 'lodash';
|
||||
|
||||
// Design
|
||||
// import Input from 'components/Input';
|
||||
import DownloadInfo from 'components/DownloadInfo';
|
||||
import OverlayBlocker from 'components/OverlayBlocker';
|
||||
import PluginCard from 'components/PluginCard';
|
||||
import PluginHeader from 'components/PluginHeader';
|
||||
|
||||
@ -22,6 +24,7 @@ import injectSaga from 'utils/injectSaga';
|
||||
import injectReducer from 'utils/injectReducer';
|
||||
|
||||
import {
|
||||
downloadPlugin,
|
||||
getPlugins,
|
||||
onChange,
|
||||
} from './actions';
|
||||
@ -33,6 +36,12 @@ import saga from './saga';
|
||||
import styles from './styles.scss';
|
||||
|
||||
export class InstallPluginPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
getChildContext = () => (
|
||||
{
|
||||
downloadPlugin: this.props.downloadPlugin,
|
||||
}
|
||||
);
|
||||
|
||||
componentDidMount() {
|
||||
// Don't fetch the available plugins if it has already been done
|
||||
if (!this.props.didFetchPlugins) {
|
||||
@ -43,6 +52,9 @@ export class InstallPluginPage extends React.Component { // eslint-disable-line
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<OverlayBlocker isOpen={this.props.blockApp}>
|
||||
<DownloadInfo />
|
||||
</OverlayBlocker>
|
||||
<FormattedMessage id="app.components.InstallPluginPage.helmet">
|
||||
{message => (
|
||||
<Helmet>
|
||||
@ -76,7 +88,15 @@ export class InstallPluginPage extends React.Component { // eslint-disable-line
|
||||
key={plugin.id}
|
||||
plugin={plugin}
|
||||
showSupportUsButton={plugin.id === 'support-us'}
|
||||
isAlreadyInstalled={isUndefined(get(this.context.plugins.toJS(), plugin.id))}
|
||||
isAlreadyInstalled={!isUndefined(get(this.context.plugins.toJS(), plugin.id))}
|
||||
downloadPlugin={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (plugin.id !== 'support-us') {
|
||||
this.props.downloadPlugin(plugin.id);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@ -86,13 +106,19 @@ export class InstallPluginPage extends React.Component { // eslint-disable-line
|
||||
}
|
||||
}
|
||||
|
||||
InstallPluginPage.childContextTypes = {
|
||||
downloadPlugin: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
InstallPluginPage.contextTypes = {
|
||||
plugins: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
InstallPluginPage.propTypes = {
|
||||
availablePlugins: PropTypes.array.isRequired,
|
||||
blockApp: PropTypes.bool.isRequired,
|
||||
didFetchPlugins: PropTypes.bool.isRequired,
|
||||
downloadPlugin: PropTypes.func.isRequired,
|
||||
getPlugins: PropTypes.func.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
// onChange: PropTypes.func.isRequired,
|
||||
@ -104,6 +130,7 @@ const mapStateToProps = makeSelectInstallPluginPage();
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(
|
||||
{
|
||||
downloadPlugin,
|
||||
getPlugins,
|
||||
onChange,
|
||||
},
|
||||
|
@ -6,18 +6,35 @@
|
||||
|
||||
import { fromJS, List } from 'immutable';
|
||||
import {
|
||||
DOWNLOAD_PLUGIN,
|
||||
DOWNLOAD_PLUGIN_ERROR,
|
||||
DOWNLOAD_PLUGIN_SUCCEEDED,
|
||||
GET_PLUGINS_SUCCEEDED,
|
||||
ON_CHANGE,
|
||||
} from './constants';
|
||||
|
||||
const initialState = fromJS({
|
||||
availablePlugins: List([]),
|
||||
blockApp: false,
|
||||
didFetchPlugins: false,
|
||||
pluginToDownload: '',
|
||||
search: '',
|
||||
});
|
||||
|
||||
function installPluginPageReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case DOWNLOAD_PLUGIN:
|
||||
return state
|
||||
.set('blockApp', true)
|
||||
.set('pluginToDownload', action.pluginToDownload);
|
||||
case DOWNLOAD_PLUGIN_ERROR:
|
||||
return state
|
||||
.set('blockApp', false)
|
||||
.set('pluginToDownload', '');
|
||||
case DOWNLOAD_PLUGIN_SUCCEEDED:
|
||||
return state
|
||||
.set('blockApp', false)
|
||||
.set('pluginToDownload', '');
|
||||
case GET_PLUGINS_SUCCEEDED:
|
||||
return state
|
||||
.set('didFetchPlugins', true)
|
||||
|
@ -1,27 +1,77 @@
|
||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||
import {
|
||||
// call,
|
||||
call,
|
||||
cancel,
|
||||
fork,
|
||||
put,
|
||||
// select,
|
||||
select,
|
||||
take,
|
||||
takeLatest,
|
||||
} from 'redux-saga/effects';
|
||||
|
||||
// import request from 'utils/request';
|
||||
import fakeData from './fakeData.json';
|
||||
import request from 'utils/request';
|
||||
|
||||
import { getPluginsSucceeded } from './actions';
|
||||
import { GET_PLUGINS } from './constants';
|
||||
import { selectLocale } from '../LanguageProvider/selectors';
|
||||
import {
|
||||
downloadPluginError,
|
||||
downloadPluginSucceeded,
|
||||
getPluginsSucceeded,
|
||||
} from './actions';
|
||||
import { DOWNLOAD_PLUGIN, GET_PLUGINS } from './constants';
|
||||
import { makeSelectPluginToDownload } from './selectors';
|
||||
|
||||
|
||||
export function* pluginDownload() {
|
||||
try {
|
||||
const pluginToDownload = yield select(makeSelectPluginToDownload());
|
||||
const opts = {
|
||||
method: 'POST',
|
||||
body: {
|
||||
plugin: pluginToDownload,
|
||||
port: window.location.port,
|
||||
},
|
||||
};
|
||||
const response = yield call(request, '/admin/plugins/install', opts, true);
|
||||
|
||||
if (response.ok) {
|
||||
yield put(downloadPluginSucceeded());
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 500);
|
||||
}
|
||||
} catch(err) {
|
||||
yield put(downloadPluginError());
|
||||
}
|
||||
}
|
||||
|
||||
export function* pluginsGet() {
|
||||
try {
|
||||
const availablePlugins = fakeData.availablePlugins;
|
||||
// Get current locale.
|
||||
const locale = yield select(selectLocale());
|
||||
|
||||
const opts = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
params: {
|
||||
lang: locale,
|
||||
},
|
||||
};
|
||||
|
||||
// Retrieve plugins list.
|
||||
const availablePlugins = yield call(request, 'https://marketplace.strapi.io/plugins', opts);
|
||||
|
||||
// Add support us card to the plugins list.
|
||||
const supportUs = {
|
||||
description: 'app.components.InstallPluginPage.plugin.support-us.description',
|
||||
description: {
|
||||
short: 'app.components.InstallPluginPage.plugin.support-us.description',
|
||||
long: 'app.components.InstallPluginPage.plugin.support-us.description',
|
||||
},
|
||||
id: 'support-us',
|
||||
icon: '',
|
||||
logo: '',
|
||||
name: 'buy a t-shirt',
|
||||
price: 30,
|
||||
ratings: 5,
|
||||
@ -39,6 +89,7 @@ export function* pluginsGet() {
|
||||
// Individual exports for testing
|
||||
export default function* defaultSaga() {
|
||||
const loadPluginsWatcher = yield fork(takeLatest, GET_PLUGINS, pluginsGet);
|
||||
yield fork(takeLatest, DOWNLOAD_PLUGIN, pluginDownload);
|
||||
|
||||
yield take(LOCATION_CHANGE);
|
||||
|
||||
|
@ -19,7 +19,13 @@ const makeSelectInstallPluginPage = () => createSelector(
|
||||
(substate) => substate.toJS()
|
||||
);
|
||||
|
||||
const makeSelectPluginToDownload = () => createSelector(
|
||||
selectInstallPluginPageDomain(),
|
||||
(substate) => substate.get('pluginToDownload'),
|
||||
);
|
||||
|
||||
export default makeSelectInstallPluginPage;
|
||||
export {
|
||||
selectInstallPluginPageDomain,
|
||||
makeSelectPluginToDownload,
|
||||
};
|
||||
|
@ -23,5 +23,5 @@
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding-top: 2.8rem;
|
||||
padding-top: .3rem;
|
||||
}
|
||||
|
@ -35,6 +35,8 @@ function languageProviderReducer(state = initialState, action) {
|
||||
case CHANGE_LOCALE:
|
||||
// Set user language in local storage.
|
||||
window.localStorage.setItem(localStorageKey, action.locale);
|
||||
strapi.currentLanguage = action.locale;
|
||||
|
||||
return state
|
||||
.set('locale', action.locale);
|
||||
default:
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { get } from 'lodash';
|
||||
import { fork, call, put, select, takeLatest } from 'redux-saga/effects';
|
||||
|
||||
import { pluginDeleted } from 'containers/App/actions';
|
||||
import auth from 'utils/auth';
|
||||
import request from 'utils/request';
|
||||
|
||||
import { selectLocale } from '../LanguageProvider/selectors';
|
||||
import { deletePluginSucceeded, getPluginsSucceeded } from './actions';
|
||||
import { GET_PLUGINS, ON_DELETE_PLUGIN_CONFIRM } from './constants';
|
||||
import { makeSelectPluginToDelete } from './selectors';
|
||||
@ -32,13 +33,34 @@ export function* deletePlugin() {
|
||||
|
||||
export function* pluginsGet() {
|
||||
try {
|
||||
// Fetch plugins.
|
||||
const response = yield call(request, '/admin/plugins', { method: 'GET' });
|
||||
const locale = yield select(selectLocale());
|
||||
|
||||
const opts = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
params: {
|
||||
lang: locale,
|
||||
},
|
||||
};
|
||||
|
||||
// Fetch plugins informations.
|
||||
const availablePlugins = yield call(request, 'https://marketplace.strapi.io/plugins', opts);
|
||||
|
||||
// Add logo URL to object.
|
||||
Object.keys(response.plugins).map(name => {
|
||||
response.plugins[name].logo = get(availablePlugins.find(plugin => plugin.id === name), 'logo', '');
|
||||
});
|
||||
|
||||
yield put(getPluginsSucceeded(response));
|
||||
} catch(err) {
|
||||
strapi.notification.error('notification.error');
|
||||
}
|
||||
}
|
||||
|
||||
// Individual exports for testing
|
||||
export default function* defaultSaga() {
|
||||
yield fork(takeLatest, ON_DELETE_PLUGIN_CONFIRM, deletePlugin);
|
||||
|
@ -33,6 +33,7 @@ export default function configureStore(initialState = {}, history) {
|
||||
// TODO Try to remove when `react-router-redux` is out of beta, LOCATION_CHANGE should not be fired more than once after hot reloading
|
||||
// Prevent recomputing reducers for `replaceReducer`
|
||||
shouldHotReload: false,
|
||||
name: `Strapi - Dashboard`,
|
||||
})
|
||||
: compose;
|
||||
/* eslint-enable */
|
||||
|
@ -2,6 +2,9 @@
|
||||
"app.components.ComingSoonPage.comingSoon": "Coming soon",
|
||||
"app.components.ComingSoonPage.featuresNotAvailable": "This feature is still under active development.",
|
||||
|
||||
"app.components.DownloadInfo.download": "Download in progress...",
|
||||
"app.components.DownloadInfo.text": "It can take a minute. Thanks for your patience.",
|
||||
|
||||
"app.components.HomePage.welcome": "Welcome on board!",
|
||||
"app.components.HomePage.description.part1": "We are happy to have you as one of our users!",
|
||||
"app.components.HomePage.description.part2": "You are now a member of our community which will help you building your dreamed app.",
|
||||
@ -14,6 +17,13 @@
|
||||
"app.components.InstallPluginPage.plugin.support-us.description": "Support us by buying the Strapi t-shirt. It will allow us to keep working on the project and try giving the best possible experience!",
|
||||
"app.components.InstallPluginPage.InputSearch.label": " ",
|
||||
"app.components.InstallPluginPage.InputSearch.placeholder": "Search a plugin... (ex: authentication)",
|
||||
"app.components.InstallPluginPopup.downloads": "download",
|
||||
"app.components.InstallPluginPopup.navLink.description": "Description",
|
||||
"app.components.InstallPluginPopup.navLink.screenshots": "Screenshots",
|
||||
"app.components.InstallPluginPopup.navLink.avis": "avis",
|
||||
"app.components.InstallPluginPopup.navLink.faq": "faq",
|
||||
"app.components.InstallPluginPopup.navLink.changelog": "changelog",
|
||||
"app.components.InstallPluginPopup.noDescription": "No description available",
|
||||
|
||||
"app.components.LeftMenuFooter.poweredBy": "Proudly powered by",
|
||||
"app.components.LeftMenuLinkContainer.configuration": "Configuration",
|
||||
@ -35,12 +45,15 @@
|
||||
"app.components.NotFoundPage.description": "Not Found",
|
||||
"app.components.NotFoundPage.back": "Back to homepage",
|
||||
|
||||
"app.components.Official": "Official",
|
||||
|
||||
"app.components.PluginCard.compatible": "Compatible with your app",
|
||||
"app.components.PluginCard.compatibleCommunity": "Compatible with the community",
|
||||
"app.components.PluginCard.Button.label.download": "Download",
|
||||
"app.components.PluginCard.Button.label.install": "Already installed",
|
||||
"app.components.PluginCard.Button.label.support": "Support us",
|
||||
"app.components.PluginCard.price.free": "Free",
|
||||
"app.components.PluginCard.more-details": "More details",
|
||||
|
||||
"components.AutoReloadBlocker.header": "Reload feature is required for this plugin.",
|
||||
"components.AutoReloadBlocker.description": "Open the following file and enable the feature.",
|
||||
|
@ -2,6 +2,8 @@
|
||||
"app.components.ComingSoonPage.comingSoon": "Bientôt disponible",
|
||||
"app.components.ComingSoonPage.featuresNotAvailable": "Cette fonctionnalité est toujours en cours de développement.",
|
||||
|
||||
"app.components.DownloadInfo.download": "Téléchargement en cours...",
|
||||
"app.components.DownloadInfo.text": "Cela peut prendre une minute. Merci de patienter.",
|
||||
|
||||
"app.components.HomePage.welcome": "Bienvenue à bord!",
|
||||
"app.components.HomePage.description.part1": "Nous somme heureux de vous compter parmi nos utilisateurs",
|
||||
@ -15,6 +17,13 @@
|
||||
"app.components.InstallPluginPage.plugin.support-us.description": "Soutenez-nous en achetant un Strapi tee shirt. Cela nous aidera a continuer de travailler sur le projet pour vous donner la meilleure expérience possible!",
|
||||
"app.components.InstallPluginPage.InputSearch.label": " ",
|
||||
"app.components.InstallPluginPage.InputSearch.placeholder": "Recherchez un plugin... (ex: authentification)",
|
||||
"app.components.InstallPluginPopup.downloads": "téléchargements",
|
||||
"app.components.InstallPluginPopup.navLink.description": "Description",
|
||||
"app.components.InstallPluginPopup.navLink.screenshots": "Screenshots",
|
||||
"app.components.InstallPluginPopup.navLink.avis": "avis",
|
||||
"app.components.InstallPluginPopup.navLink.faq": "faq",
|
||||
"app.components.InstallPluginPopup.navLink.changelog": "changelog",
|
||||
"app.components.InstallPluginPopup.noDescription": "Aucune description disponible",
|
||||
|
||||
"app.components.LeftMenuFooter.poweredBy": "Propulsé par",
|
||||
"app.components.LeftMenuLinkContainer.configuration": "Configuration",
|
||||
@ -36,12 +45,15 @@
|
||||
"app.components.NotFoundPage.description": "Page introuvable",
|
||||
"app.components.NotFoundPage.back": "Retourner à la page d'accueil",
|
||||
|
||||
"app.components.Official": "Officiel",
|
||||
|
||||
"app.components.PluginCard.compatible": "Compatble avec votre app",
|
||||
"app.components.PluginCard.compatibleCommunity": "Compatble avec la communauté",
|
||||
"app.components.PluginCard.Button.label.download": "Télécharger",
|
||||
"app.components.PluginCard.Button.label.install": "Déjà installé",
|
||||
"app.components.PluginCard.Button.label.support": "Nous soutenir",
|
||||
"app.components.PluginCard.price.free": "Gratuit",
|
||||
"app.components.PluginCard.more-details": "Plus de détails",
|
||||
|
||||
"components.AutoReloadBlocker.header": "L'autoReload doit être activé pour ce plugin.",
|
||||
"components.AutoReloadBlocker.description": "Ouvrez le fichier suivant pour activer cette fonctionnalité.",
|
||||
|
@ -8,6 +8,14 @@
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/plugins/install",
|
||||
"handler": "Admin.installPlugin",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/plugins/uninstall/:plugin",
|
||||
|
@ -8,6 +8,26 @@ const exec = require('child_process').execSync;
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
installPlugin: async ctx => {
|
||||
try {
|
||||
const { plugin, port } = ctx.request.body;
|
||||
const strapiBin = path.join(process.cwd(), 'node_modules', 'strapi', 'bin', 'strapi');
|
||||
|
||||
strapi.reload.isWatching = false;
|
||||
|
||||
strapi.log.info(`Installing ${plugin}...`);
|
||||
|
||||
exec(`node ${strapiBin} install ${plugin} ${port === '4000' ? '--dev' : ''}`);
|
||||
|
||||
ctx.send({ ok: true });
|
||||
|
||||
strapi.reload();
|
||||
} catch(err) {
|
||||
strapi.reload.isWatching = true;
|
||||
ctx.badRequest(null, [{ messages: [{ id: 'An error occured' }] }]);
|
||||
}
|
||||
},
|
||||
|
||||
plugins: async ctx => {
|
||||
try {
|
||||
const plugins = Object.keys(strapi.plugins).reduce((acc, key) => {
|
||||
|
@ -53,7 +53,7 @@ const apiUrl = `${strapi.backendURL}/${pluginId}`;
|
||||
const router = strapi.router;
|
||||
|
||||
// Create redux store with Strapi admin history
|
||||
const store = configureStore({}, strapi.router);
|
||||
const store = configureStore({}, strapi.router, pluginName);
|
||||
|
||||
// Define the plugin root component
|
||||
function Comp(props) {
|
||||
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
*
|
||||
* OverlayBlocker
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
class OverlayBlocker extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.overlayContainer = document.createElement('div');
|
||||
document.body.appendChild(this.overlayContainer);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.body.removeChild(this.overlayContainer);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.isOpen) {
|
||||
return ReactDOM.createPortal(
|
||||
<div className={styles.overlay}>
|
||||
<div>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>,
|
||||
this.overlayContainer
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
OverlayBlocker.defaultProps = {
|
||||
children: '',
|
||||
isOpen: false,
|
||||
};
|
||||
|
||||
OverlayBlocker.propTypes = {
|
||||
children: PropTypes.node,
|
||||
isOpen: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default OverlayBlocker;
|
@ -0,0 +1,16 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1040;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
|
||||
> div {
|
||||
position: fixed;
|
||||
top: 17.5rem;
|
||||
left: 50%;
|
||||
margin-left: -24rem;
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import createReducer from './reducers';
|
||||
|
||||
const sagaMiddleware = createSagaMiddleware();
|
||||
|
||||
export default function configureStore(initialState = {}, history) {
|
||||
export default function configureStore(initialState = {}, history, name) {
|
||||
// Create the store with two middlewares
|
||||
// 1. sagaMiddleware: Makes redux-sagas work
|
||||
// 2. routerMiddleware: Syncs the location/URL path to the state
|
||||
@ -33,6 +33,7 @@ export default function configureStore(initialState = {}, history) {
|
||||
// TODO Try to remove when `react-router-redux` is out of beta, LOCATION_CHANGE should not be fired more than once after hot reloading
|
||||
// Prevent recomputing reducers for `replaceReducer`
|
||||
shouldHotReload: false,
|
||||
name: `Plugin - ${name}`,
|
||||
})
|
||||
: compose;
|
||||
/* eslint-enable */
|
||||
|
@ -78,38 +78,38 @@ function serverRestartWatcher(response) {
|
||||
*
|
||||
* @return {object} The response data
|
||||
*/
|
||||
export default function request(url, options = {}, shouldWatchServerRestart = false) {
|
||||
// Set headers
|
||||
options.headers = Object.assign({
|
||||
'Content-Type': 'application/json',
|
||||
}, options.headers, {
|
||||
'X-Forwarded-Host': 'strapi',
|
||||
});
|
||||
export default function request(url, options = {}, shouldWatchServerRestart = false) {
|
||||
// Set headers
|
||||
if (!options.headers) {
|
||||
options.headers = Object.assign({
|
||||
'Content-Type': 'application/json',
|
||||
}, options.headers, {
|
||||
'X-Forwarded-Host': 'strapi',
|
||||
});
|
||||
}
|
||||
|
||||
const token = auth.getToken();
|
||||
const token = auth.getToken();
|
||||
|
||||
if (token) {
|
||||
options.headers = Object.assign({
|
||||
'Authorization': `Bearer ${token}`,
|
||||
}, options.headers);
|
||||
}
|
||||
if (token) {
|
||||
options.headers = Object.assign({
|
||||
'Authorization': `Bearer ${token}`,
|
||||
}, options.headers);
|
||||
}
|
||||
|
||||
// Add parameters to url
|
||||
url = _.startsWith(url, '/')
|
||||
? `${strapi.backendURL}${url}`
|
||||
: url;
|
||||
// Add parameters to url
|
||||
url = _.startsWith(url, '/') ? `${strapi.backendURL}${url}` : url;
|
||||
|
||||
if (options && options.params) {
|
||||
const params = formatQueryParams(options.params);
|
||||
url = `${url}?${params}`;
|
||||
}
|
||||
if (options && options.params) {
|
||||
const params = formatQueryParams(options.params);
|
||||
url = `${url}?${params}`;
|
||||
}
|
||||
|
||||
// Stringify body object
|
||||
if (options && options.body) {
|
||||
options.body = JSON.stringify(options.body);
|
||||
}
|
||||
// Stringify body object
|
||||
if (options && options.body) {
|
||||
options.body = JSON.stringify(options.body);
|
||||
}
|
||||
|
||||
return fetch(url, options)
|
||||
return fetch(url, options)
|
||||
.then(checkStatus)
|
||||
.then(parseJSON)
|
||||
.then((response) => {
|
||||
@ -118,5 +118,5 @@ function serverRestartWatcher(response) {
|
||||
}
|
||||
|
||||
return response;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
1
packages/strapi-plugin-content-manager/admin/src/assets/images/logo.svg
Executable file
@ -0,0 +1 @@
|
||||
<svg width="35" height="24" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M1.148 20.03c.567-7.141 2.974-7.675 7.222-1.604 6.372 9.107 7.533-.667 9.19-2.017" stroke="#FFD2B6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><g fill-rule="nonzero"><path d="M21.313 16.48l1.081-1.082-2.792-2.792-1.081 1.081v1.271h1.52v1.521h1.272zm6.214-11.027c0-.174-.087-.26-.262-.26a.275.275 0 0 0-.202.082l-6.44 6.44a.275.275 0 0 0-.082.202c0 .174.087.261.261.261.08 0 .147-.028.202-.083l6.44-6.44a.275.275 0 0 0 .083-.202zm-.642-2.28l4.943 4.942L21.943 18H17v-4.943l9.885-9.885zM35 4.312c0 .42-.147.776-.44 1.07l-1.972 1.971-4.942-4.942 1.972-1.96A1.412 1.412 0 0 1 30.688 0c.419 0 .78.15 1.08.451l2.792 2.78c.293.31.44.67.44 1.082z" fill="#181D29"/><path d="M35 4.313c0 .42-.147.776-.44 1.07l-1.972 1.971-4.942-4.942 1.972-1.96A1.412 1.412 0 0 1 30.688 0c.419 0 .78.15 1.08.451l2.792 2.78c.293.31.44.67.44 1.082z" fill="#FF84B3"/></g></g></svg>
|
After Width: | Height: | Size: 980 B |
@ -1,5 +1,6 @@
|
||||
{
|
||||
"plugin.description": "Quick way to see, edit and delete the data in your database.",
|
||||
"plugin.description.short": "Quick way to see, edit and delete the data in your database.",
|
||||
"plugin.description.long": "Quick way to see, edit and delete the data in your database.",
|
||||
"containers.Home.pluginHeaderTitle": "Content Manager",
|
||||
"containers.Home.introduction": "To edit your entries go to the specific link in the left menu. This plugin doesn't have a proper way to edit settings and it's still under active development.",
|
||||
"containers.Home.pluginHeaderDescription": "Manage your entries through a powerful and beautiful interface.",
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"plugin.description": "Visualisez, modifiez et supprimez les données de votre base de données.",
|
||||
"plugin.description.short": "Visualisez, modifiez et supprimez les données de votre base de données.",
|
||||
"plugin.description.long": "Visualisez, modifiez et supprimez les données de votre base de données.",
|
||||
"containers.Home.pluginHeaderTitle": "Type de contenu",
|
||||
"containers.Home.pluginHeaderDescription": "Créer et modifier votre type de contenu",
|
||||
"containers.Home.introduction": "Pour éditer du contenu, choisissez un type de données dans le menu de gauche.",
|
||||
|
@ -0,0 +1 @@
|
||||
<svg width="22" height="22" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path d="M8.604.607c.317.623.75 1.155 1.298 1.597.549.443 1.16.754 1.835.934l.012.873c.032 1.745-.494 3.166-1.579 4.264-1.084 1.098-2.5 1.647-4.247 1.647-1 0-1.885-.19-2.657-.571a4.852 4.852 0 0 1-1.858-1.567 7.325 7.325 0 0 1-1.055-2.25A9.927 9.927 0 0 1 0 2.832l.5.369c.276.205.528.387.755.547.228.16.468.309.72.448.251.14.438.21.56.21.333 0 .557-.152.67-.455a6.33 6.33 0 0 1 .701-1.383c.264-.381.547-.692.847-.934.3-.242.658-.436 1.073-.584A6.547 6.547 0 0 1 7.08.736c.422-.062.93-.105 1.523-.13z" id="a"/></defs><g fill="none" fill-rule="evenodd"><g transform="translate(0 12)"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#EABE8D" fill-rule="nonzero" xlink:href="#a"/><path d="M10.18.264c4.086 6.335 2.315 9.584-5.314 9.747-7.629.163-6.185-1.797 4.331-5.88l.982-3.867z" fill="#DB1818" mask="url(#b)"/><path d="M10.564.16C11.422 6.26 8.9 9.507 2.998 9.904c-5.902.397-4.484-1.392 4.255-5.367L10.563.16z" fill="#F1D520" mask="url(#b)"/><path d="M10.196-1.556C9.168 5.281 6.272 8.728 1.507 8.786c-4.764.059-3.209-1.83 4.668-5.663l4.021-4.679z" fill="#32B167" mask="url(#b)"/><path d="M9.506-4.516C9.417 2.713 6.99 6.357 2.226 6.414-2.539 6.474-.784 4.262 7.49-.22l2.016-4.295z" fill="#1279EA" mask="url(#b)"/><path d="M9.449-4.392C8.72 1.871 5.974 5.032 1.209 5.09c-4.764.058-3.398-1.712 4.099-5.311l4.14-4.17z" fill="#4C2DCE" mask="url(#b)"/></g><path d="M19.88 0c.564 0 1.058.186 1.482.56.424.372.635.84.635 1.401 0 .505-.181 1.111-.544 1.817-2.679 5.045-4.555 8.06-5.628 9.047-.783.73-1.662 1.095-2.638 1.095-1.017 0-1.89-.37-2.62-1.113-.73-.742-1.096-1.622-1.096-2.64 0-1.027.371-1.877 1.113-2.551L18.306.65c.476-.433 1-.65 1.573-.65z" fill-rule="nonzero" fill="#553D38"/></g></svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -1,5 +1,6 @@
|
||||
{
|
||||
"plugin.description": "Modelize the data structure of you API.",
|
||||
"plugin.description.short": "Modelize the data structure of you API.",
|
||||
"plugin.description.long": "Modelize the data structure of you API. Create new fields and relations in a minute. The files are automatically created and updated in your project.",
|
||||
"attribute.string": "String",
|
||||
"attribute.text": "Text",
|
||||
"attribute.boolean": "Boolean",
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"plugin.description": "Modelisez la structure de données de votre API.",
|
||||
"plugin.description.short": "Modelisez la structure de données de votre API.",
|
||||
"plugin.description.long": "Modelisez la structure de données de votre API. Créer des nouveaux champs et relations en un instant. Les fichiers se créent et se mettent à jour automatiquement.",
|
||||
"attribute.string": "Chaîne de caractères",
|
||||
"attribute.text": "Text",
|
||||
"attribute.boolean": "Booléen",
|
||||
|
@ -1,3 +1,4 @@
|
||||
{
|
||||
"plugin.description": "Send emails"
|
||||
"plugin.description.short": "Send emails",
|
||||
"plugin.description.long": "Send emails"
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
{
|
||||
"plugin.description": "Envoyez des emails"
|
||||
"plugin.description.short": "Envoyez des emails",
|
||||
"plugin.description.long": "Envoyez des emails"
|
||||
}
|
||||
|
1
packages/strapi-plugin-settings-manager/admin/src/assets/images/logo.svg
Executable file
@ -0,0 +1 @@
|
||||
<svg width="54" height="34" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><g fill-rule="nonzero"><path d="M11.392 30.3a1.797 1.797 0 0 0-.22-1.409 1.797 1.797 0 0 0-1.141-.857 1.797 1.797 0 0 0-1.41.22c-.449.27-.734.65-.857 1.142-.122.492-.049.962.22 1.41.27.449.65.734 1.142.857.491.122.961.049 1.41-.22.448-.27.734-.65.856-1.142zm21.224-7.353L8.464 37.459c-.874.525-1.812.663-2.813.413-.983-.245-1.756-.809-2.318-1.692L1.09 32.37c-.548-.86-.695-1.8-.44-2.82.249-1.002.822-1.772 1.72-2.311l24.117-14.492a14.885 14.885 0 0 0 2.02 5.728 14.885 14.885 0 0 0 4.108 4.472z" fill="#181D29"/><path d="M53.663 15.097c-.184.737-.65 1.684-1.401 2.842-1.52 2.31-3.587 3.978-6.2 5.003-2.615 1.024-5.254 1.204-7.918.54-3.497-.872-6.177-2.86-8.043-5.965-1.865-3.105-2.362-6.405-1.49-9.901.871-3.496 2.86-6.177 5.964-8.043 3.105-1.865 6.405-2.362 9.901-1.49 1.096.273 2.205.715 3.328 1.326 1.122.611 2.028 1.304 2.718 2.078.251.283.336.586.256.907-.08.321-.297.548-.651.68l-9.5 2.72-1.584 6.35 4.715 4.397c.109-.033.97-.305 2.582-.816 1.613-.511 3.084-.957 4.414-1.339 1.33-.38 2.08-.55 2.25-.508.283.071.481.22.595.45.113.229.135.485.064.769z" fill="#A1A1A1"/></g><path stroke="#C6C6C6" fill="#D8D8D8" d="M46.521 5.425l3.657 3.41-1.125 4.872-4.781 1.461-3.657-3.41 1.125-4.871z"/></g></svg>
|
After Width: | Height: | Size: 1.3 KiB |
@ -1,5 +1,6 @@
|
||||
{
|
||||
"plugin.description": "Configure your project in seconds",
|
||||
"plugin.description.short": "Configure your project in seconds",
|
||||
"plugin.description.long": "Configure your project in seconds",
|
||||
"menu.section.global-settings": "Global settings",
|
||||
"menu.item.application": "Application",
|
||||
"menu.item.languages": "Languages",
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"plugin.description": "Configurez votre projet en quelques secondes.",
|
||||
"plugin.description.short": "Configurez votre projet en quelques secondes.",
|
||||
"plugin.description.long": "Configurez votre projet en quelques secondes.",
|
||||
"menu.section.global-settings": "Configuration générale",
|
||||
"menu.item.application": "Application",
|
||||
"menu.item.languages": "Langages",
|
||||
|
2
packages/strapi-plugin-users-permissions/admin/src/assets/images/logo.svg
Normal file → Executable file
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 447.373 124.856"><path d="M59.392 84.949L45.274 93.1m61.279-23.138L87.322 81l.029.049-14.076 7.995-13.925-3.939m-14.004 32.174l.014-24.031-.014 24.031 9.18 5.297 52.05-30.117M23.329 55.09L2.404 43.016m28.999 21.498l-6.961 4.018m11.863 4.472L22.948 80.65m18.287.894l-16.108 9.037m15.234-57.632l-2.886 14.039M86.84 34.679l7.96 4.553-.008 9.219-7.948 4.589-7.939-4.631-.012-9.142zm19.713 35.283l9.824-5.671.065-37.62-32.514-18.81-43.505 24.83M54.38 2.28L2.28 32.425l.124 10.591m35.043 3.923L23.329 55.09" fill="none" stroke="#215cea" stroke-linecap="round" stroke-linejoin="round" stroke-width="4.56"/><path d="M217.23 70.167a16.674 16.674 0 0 1-6.96 13.944 23.419 23.419 0 0 1-7.656 3.587 36.216 36.216 0 0 1-9.628 1.215 51.307 51.307 0 0 1-9.57-.987 44.292 44.292 0 0 1-8.99-2.61l2.32-11.252a47.382 47.382 0 0 0 7.656 2.378 37.023 37.023 0 0 0 8.004.87 16.16 16.16 0 0 0 6.554-1.16 4.19 4.19 0 0 0 2.61-4.176 4.056 4.056 0 0 0-2.553-3.886q-2.552-1.218-6.727-2.959-3.367-1.391-6.496-2.84a23.814 23.814 0 0 1-5.51-3.48 16.169 16.169 0 0 1-3.828-4.757 14.309 14.309 0 0 1-1.45-6.786 16.434 16.434 0 0 1 1.682-7.656 14.719 14.719 0 0 1 4.64-5.336 21.237 21.237 0 0 1 6.96-3.132 34.133 34.133 0 0 1 8.641-1.044 48.31 48.31 0 0 1 9.338.88 31.134 31.134 0 0 1 8.643 3.104l-2.437 11.328a47.347 47.347 0 0 0-7.017-2.494 28.209 28.209 0 0 0-7.366-.986 15.901 15.901 0 0 0-5.394.752 3.048 3.048 0 0 0-2.03 3.183 3.707 3.707 0 0 0 2.435 3.471q2.436 1.158 5.801 2.66 2.436 1.044 5.626 2.373a27.855 27.855 0 0 1 5.974 3.415 20.364 20.364 0 0 1 4.756 5.033 12.945 12.945 0 0 1 1.972 7.348zM219.087 31.26h9.164V16.644h15.891V31.26h16.937v12.412h-16.937v24.245a11.726 11.726 0 0 0 1.45 6.263q1.45 2.322 5.743 2.32a28.445 28.445 0 0 0 8.584-1.276l1.16 11.252a32.326 32.326 0 0 1-6.381 1.74 42.634 42.634 0 0 1-7.655.697 21.308 21.308 0 0 1-8.643-1.567 15.322 15.322 0 0 1-5.8-4.35 17.812 17.812 0 0 1-3.305-6.495 29.903 29.903 0 0 1-1.044-8.121V43.672h-9.164zM303.3 44.832h-2.668a23.137 23.137 0 0 0-9.859 1.856 15.077 15.077 0 0 0-6.265 5.336v35.729h-15.891V31.26h15.311v9.976a15.79 15.79 0 0 1 5.162-7.772q3.538-2.784 9.454-2.784a17.838 17.838 0 0 1 4.756.58zM339.725 81.72a28.045 28.045 0 0 1-7.54 5.104 23.604 23.604 0 0 1-10.208 2.089 17.8 17.8 0 0 1-6.727-1.219 14.668 14.668 0 0 1-5.105-3.363 15.292 15.292 0 0 1-3.248-5.047 17.02 17.02 0 0 1-1.16-6.38q0-7.771 5.22-11.832 5.22-4.06 14.268-4.64 3.48-.231 7.193-.348 3.71-.114 6.727-.232V53.88q0-5.45-2.668-8.294-2.669-2.841-8.351-2.842a32.492 32.492 0 0 0-8.353 1.044 31.835 31.835 0 0 0-6.96 2.668l-3.132-11.136a37.516 37.516 0 0 1 9.744-3.886A47.28 47.28 0 0 1 330.91 30.1q11.251 0 17.69 6.148 6.438 6.15 6.439 18.212v33.293h-15.313zm-11.948-3.596a16.699 16.699 0 0 0 6.265-1.16 20.378 20.378 0 0 0 5.103-2.9v-8.933q-2.436.235-5.22.465-2.784.235-5.104.58a14.924 14.924 0 0 0-5.567 1.914 4.888 4.888 0 0 0-2.088 4.466 4.949 4.949 0 0 0 1.797 4.176 7.65 7.65 0 0 0 4.814 1.392zM366.753 31.26h15.196v7.192a20.668 20.668 0 0 1 6.96-6.09 21.187 21.187 0 0 1 10.208-2.262 20.456 20.456 0 0 1 9.28 2.088 20.833 20.833 0 0 1 7.134 5.858 27.726 27.726 0 0 1 4.582 9.048 39.976 39.976 0 0 1 1.624 11.774 33.714 33.714 0 0 1-2.32 12.933 27.712 27.712 0 0 1-6.264 9.455A25.634 25.634 0 0 1 403.93 87a32.946 32.946 0 0 1-11.31 1.914 34.277 34.277 0 0 1-5.568-.407 37.931 37.931 0 0 1-4.408-.986v24.128h-15.891zm27.028 11.252a13.216 13.216 0 0 0-6.67 1.624 15.954 15.954 0 0 0-4.467 3.596v27.956a16.191 16.191 0 0 0 3.48.87 28.552 28.552 0 0 0 4.176.29q8.119 0 11.832-4.988 3.712-4.986 3.712-12.876a21.71 21.71 0 0 0-2.958-11.774q-2.957-4.698-9.105-4.698zM428.928 14.788a9.048 9.048 0 0 1 2.553-6.496 8.735 8.735 0 0 1 6.611-2.668 9.019 9.019 0 0 1 9.28 9.164 8.996 8.996 0 0 1-2.61 6.438 8.829 8.829 0 0 1-6.67 2.726 8.639 8.639 0 0 1-6.61-2.726 9.101 9.101 0 0 1-2.554-6.438zm1.277 72.965V31.26h15.891v56.493z" fill="#111722"/><path fill="#215cea" d="M54.381 4.916l9.912 5.712 4.62-2.642L55.506.298l-1.125 4.618zM108.854 92.417l-4.57-1.264V79.682l4.56-2.606.01 15.341z"/></svg>
|
||||
<svg width="28" height="26" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero" fill="none"><path d="M8.453 12.772c-1.54.047-2.799.656-3.778 1.824h-1.91c-.779 0-1.435-.192-1.967-.577C.266 13.634 0 13.071 0 12.33c0-3.354.59-5.032 1.768-5.032.057 0 .263.1.62.3.356.2.82.401 1.39.605.57.205 1.135.307 1.696.307.636 0 1.268-.11 1.896-.328a7.106 7.106 0 0 0-.072.94c0 1.322.385 2.538 1.155 3.65zm15.266 9.08c0 1.14-.347 2.04-1.04 2.701-.694.66-1.616.99-2.766.99H7.455c-1.15 0-2.072-.33-2.765-.99-.694-.66-1.04-1.56-1.04-2.701 0-.504.016-.995.049-1.475.033-.48.1-.998.2-1.554s.225-1.072.377-1.547c.152-.475.357-.938.613-1.39.257-.45.551-.836.884-1.154a3.718 3.718 0 0 1 1.219-.763 4.28 4.28 0 0 1 1.59-.285c.094 0 .298.102.612.307.314.204.66.432 1.04.684.38.252.89.48 1.526.684a6.264 6.264 0 0 0 1.924.307c.646 0 1.288-.103 1.925-.307.636-.204 1.145-.432 1.525-.684.38-.252.727-.48 1.04-.684.314-.205.518-.307.613-.307.58 0 1.11.095 1.59.285.48.19.886.445 1.218.763.333.318.628.703.884 1.155.257.45.461.914.613 1.39.152.474.278.99.378 1.546.1.556.166 1.074.2 1.554.033.48.05.971.05 1.475zm3.65-9.522c0 .741-.267 1.304-.799 1.69-.532.384-1.188.576-1.967.576h-1.91c-.979-1.168-2.238-1.777-3.777-1.824.77-1.112 1.154-2.328 1.154-3.65 0-.275-.024-.588-.071-.94.627.219 1.259.328 1.896.328.56 0 1.126-.102 1.696-.307a9.374 9.374 0 0 0 1.39-.605c.356-.2.563-.3.62-.3 1.178 0 1.767 1.678 1.767 5.032z" fill="#553D38"/><path d="M9.123 3.65a3.516 3.516 0 0 1-1.07 2.58 3.516 3.516 0 0 1-2.58 1.068 3.516 3.516 0 0 1-2.58-1.069 3.516 3.516 0 0 1-1.068-2.58c0-1.007.356-1.867 1.069-2.58A3.516 3.516 0 0 1 5.474 0C6.48 0 7.34.356 8.054 1.07a3.516 3.516 0 0 1 1.069 2.58zm10.035 5.473c0 1.51-.535 2.8-1.604 3.87-1.069 1.069-2.359 1.603-3.87 1.603-1.51 0-2.8-.534-3.87-1.603-1.069-1.07-1.603-2.36-1.603-3.87 0-1.511.534-2.801 1.603-3.87 1.07-1.07 2.36-1.604 3.87-1.604 1.511 0 2.801.535 3.87 1.604 1.07 1.069 1.604 2.359 1.604 3.87zm6.386-5.474a3.516 3.516 0 0 1-1.07 2.58 3.516 3.516 0 0 1-2.58 1.07 3.516 3.516 0 0 1-2.58-1.07 3.516 3.516 0 0 1-1.068-2.58c0-1.007.356-1.867 1.069-2.58A3.516 3.516 0 0 1 21.895 0c1.007 0 1.867.356 2.58 1.07a3.516 3.516 0 0 1 1.069 2.58z" fill="#EBBF8E"/><path d="M12.496 19.991h2.393v-.897c0-.33-.117-.612-.35-.846a1.153 1.153 0 0 0-.847-.35c-.33 0-.612.116-.846.35-.233.234-.35.516-.35.846v.897zm3.889.45v2.691c0 .125-.044.231-.131.318a.433.433 0 0 1-.318.131h-4.487a.433.433 0 0 1-.318-.13.433.433 0 0 1-.131-.319V20.44c0-.124.044-.23.13-.318a.433.433 0 0 1 .319-.13h.15v-.898c0-.573.205-1.066.616-1.477A2.015 2.015 0 0 1 13.692 17c.574 0 1.066.206 1.477.617.412.411.617.904.617 1.477v.897h.15c.125 0 .23.044.318.131.087.088.13.194.13.318z" fill="#FFF"/></g></svg>
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 2.6 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 447.373 124.856"><path d="M59.392 84.949L45.274 93.1m61.279-23.138L87.322 81l.029.049-14.076 7.995-13.925-3.939m-14.004 32.174l.014-24.031-.014 24.031 9.18 5.297 52.05-30.117M23.329 55.09L2.404 43.016m28.999 21.498l-6.961 4.018m11.863 4.472L22.948 80.65m18.287.894l-16.108 9.037m15.234-57.632l-2.886 14.039M86.84 34.679l7.96 4.553-.008 9.219-7.948 4.589-7.939-4.631-.012-9.142zm19.713 35.283l9.824-5.671.065-37.62-32.514-18.81-43.505 24.83M54.38 2.28L2.28 32.425l.124 10.591m35.043 3.923L23.329 55.09" fill="none" stroke="#215cea" stroke-linecap="round" stroke-linejoin="round" stroke-width="4.56"/><path d="M217.23 70.167a16.674 16.674 0 0 1-6.96 13.944 23.419 23.419 0 0 1-7.656 3.587 36.216 36.216 0 0 1-9.628 1.215 51.307 51.307 0 0 1-9.57-.987 44.292 44.292 0 0 1-8.99-2.61l2.32-11.252a47.382 47.382 0 0 0 7.656 2.378 37.023 37.023 0 0 0 8.004.87 16.16 16.16 0 0 0 6.554-1.16 4.19 4.19 0 0 0 2.61-4.176 4.056 4.056 0 0 0-2.553-3.886q-2.552-1.218-6.727-2.959-3.367-1.391-6.496-2.84a23.814 23.814 0 0 1-5.51-3.48 16.169 16.169 0 0 1-3.828-4.757 14.309 14.309 0 0 1-1.45-6.786 16.434 16.434 0 0 1 1.682-7.656 14.719 14.719 0 0 1 4.64-5.336 21.237 21.237 0 0 1 6.96-3.132 34.133 34.133 0 0 1 8.641-1.044 48.31 48.31 0 0 1 9.338.88 31.134 31.134 0 0 1 8.643 3.104l-2.437 11.328a47.347 47.347 0 0 0-7.017-2.494 28.209 28.209 0 0 0-7.366-.986 15.901 15.901 0 0 0-5.394.752 3.048 3.048 0 0 0-2.03 3.183 3.707 3.707 0 0 0 2.435 3.471q2.436 1.158 5.801 2.66 2.436 1.044 5.626 2.373a27.855 27.855 0 0 1 5.974 3.415 20.364 20.364 0 0 1 4.756 5.033 12.945 12.945 0 0 1 1.972 7.348zM219.087 31.26h9.164V16.644h15.891V31.26h16.937v12.412h-16.937v24.245a11.726 11.726 0 0 0 1.45 6.263q1.45 2.322 5.743 2.32a28.445 28.445 0 0 0 8.584-1.276l1.16 11.252a32.326 32.326 0 0 1-6.381 1.74 42.634 42.634 0 0 1-7.655.697 21.308 21.308 0 0 1-8.643-1.567 15.322 15.322 0 0 1-5.8-4.35 17.812 17.812 0 0 1-3.305-6.495 29.903 29.903 0 0 1-1.044-8.121V43.672h-9.164zM303.3 44.832h-2.668a23.137 23.137 0 0 0-9.859 1.856 15.077 15.077 0 0 0-6.265 5.336v35.729h-15.891V31.26h15.311v9.976a15.79 15.79 0 0 1 5.162-7.772q3.538-2.784 9.454-2.784a17.838 17.838 0 0 1 4.756.58zM339.725 81.72a28.045 28.045 0 0 1-7.54 5.104 23.604 23.604 0 0 1-10.208 2.089 17.8 17.8 0 0 1-6.727-1.219 14.668 14.668 0 0 1-5.105-3.363 15.292 15.292 0 0 1-3.248-5.047 17.02 17.02 0 0 1-1.16-6.38q0-7.771 5.22-11.832 5.22-4.06 14.268-4.64 3.48-.231 7.193-.348 3.71-.114 6.727-.232V53.88q0-5.45-2.668-8.294-2.669-2.841-8.351-2.842a32.492 32.492 0 0 0-8.353 1.044 31.835 31.835 0 0 0-6.96 2.668l-3.132-11.136a37.516 37.516 0 0 1 9.744-3.886A47.28 47.28 0 0 1 330.91 30.1q11.251 0 17.69 6.148 6.438 6.15 6.439 18.212v33.293h-15.313zm-11.948-3.596a16.699 16.699 0 0 0 6.265-1.16 20.378 20.378 0 0 0 5.103-2.9v-8.933q-2.436.235-5.22.465-2.784.235-5.104.58a14.924 14.924 0 0 0-5.567 1.914 4.888 4.888 0 0 0-2.088 4.466 4.949 4.949 0 0 0 1.797 4.176 7.65 7.65 0 0 0 4.814 1.392zM366.753 31.26h15.196v7.192a20.668 20.668 0 0 1 6.96-6.09 21.187 21.187 0 0 1 10.208-2.262 20.456 20.456 0 0 1 9.28 2.088 20.833 20.833 0 0 1 7.134 5.858 27.726 27.726 0 0 1 4.582 9.048 39.976 39.976 0 0 1 1.624 11.774 33.714 33.714 0 0 1-2.32 12.933 27.712 27.712 0 0 1-6.264 9.455A25.634 25.634 0 0 1 403.93 87a32.946 32.946 0 0 1-11.31 1.914 34.277 34.277 0 0 1-5.568-.407 37.931 37.931 0 0 1-4.408-.986v24.128h-15.891zm27.028 11.252a13.216 13.216 0 0 0-6.67 1.624 15.954 15.954 0 0 0-4.467 3.596v27.956a16.191 16.191 0 0 0 3.48.87 28.552 28.552 0 0 0 4.176.29q8.119 0 11.832-4.988 3.712-4.986 3.712-12.876a21.71 21.71 0 0 0-2.958-11.774q-2.957-4.698-9.105-4.698zM428.928 14.788a9.048 9.048 0 0 1 2.553-6.496 8.735 8.735 0 0 1 6.611-2.668 9.019 9.019 0 0 1 9.28 9.164 8.996 8.996 0 0 1-2.61 6.438 8.829 8.829 0 0 1-6.67 2.726 8.639 8.639 0 0 1-6.61-2.726 9.101 9.101 0 0 1-2.554-6.438zm1.277 72.965V31.26h15.891v56.493z" fill="#111722"/><path fill="#215cea" d="M54.381 4.916l9.912 5.712 4.62-2.642L55.506.298l-1.125 4.618zM108.854 92.417l-4.57-1.264V79.682l4.56-2.606.01 15.341z"/></svg>
|
After Width: | Height: | Size: 4.0 KiB |
@ -55,7 +55,7 @@ class Plugin extends React.Component { // eslint-disable-line react/prefer-state
|
||||
<div>
|
||||
{ icon ? (
|
||||
<div className={styles.iconContainer}>
|
||||
<i className={`fa fa-${icon}`} />
|
||||
<img src={this.props.plugin.information.logo} alt="icon" />
|
||||
</div>
|
||||
) : ''}
|
||||
<div className={styles.name}>{this.props.name}</div>
|
||||
@ -107,6 +107,7 @@ Plugin.defaultProps = {
|
||||
plugin: {
|
||||
description: 'users-permissions.Plugin.permissions.description.empty',
|
||||
controllers: {},
|
||||
information: {},
|
||||
},
|
||||
};
|
||||
|
||||
@ -115,6 +116,9 @@ Plugin.propTypes = {
|
||||
name: PropTypes.string,
|
||||
plugin: PropTypes.shape({
|
||||
description: PropTypes.string,
|
||||
information: PropTypes.shape({
|
||||
logo: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
}),
|
||||
pluginSelected: PropTypes.string.isRequired,
|
||||
};
|
||||
|
@ -45,7 +45,13 @@ export function* fetchUser(action) {
|
||||
|
||||
export function* permissionsGet() {
|
||||
try {
|
||||
const response = yield call(request, '/users-permissions/permissions', { method: 'GET' });
|
||||
const response = yield call(request, '/users-permissions/permissions', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
lang: strapi.currentLanguage,
|
||||
},
|
||||
});
|
||||
|
||||
yield put(getPermissionsSucceeded(response));
|
||||
} catch(err) {
|
||||
strapi.notification.error('users-permissions.EditPage.notification.permissions.error');
|
||||
@ -68,7 +74,12 @@ export function* policiesGet() {
|
||||
|
||||
export function* roleGet(action) {
|
||||
try {
|
||||
const role = yield call(request, `/users-permissions/roles/${action.id}`, { method: 'GET' });
|
||||
const role = yield call(request, `/users-permissions/roles/${action.id}`, {
|
||||
method: 'GET',
|
||||
params: {
|
||||
lang: strapi.currentLanguage,
|
||||
},
|
||||
});
|
||||
|
||||
yield put(getRoleSucceeded(role));
|
||||
} catch(err) {
|
||||
|
@ -5,11 +5,6 @@ import { createSelector } from 'reselect';
|
||||
*/
|
||||
const selectEditPageDomain = () => (state) => state.get('editPage');
|
||||
|
||||
/**
|
||||
* Other specific selectors
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Default selector used by EditPage
|
||||
*/
|
||||
|
@ -108,7 +108,8 @@
|
||||
"notification.info.emailSent": "The email has been sent",
|
||||
"notification.success.delete": "The item has been deleted",
|
||||
|
||||
"plugin.description": "Protect your API with a full-authentication process",
|
||||
"plugin.description.short": "Protect your API with a full-authentication process based on JWT",
|
||||
"plugin.description.long": "Protect your API with a full-authentication process based on JWT. This plugin comes also with an ACL strategy that allows you to manage the permissions between the groups of users.",
|
||||
|
||||
"Plugin.permissions.application.description": "Define the allowed actions in your project.",
|
||||
"Plugin.permissions.plugins.description": "Define the allowed actions in the {name} plugin.",
|
||||
|
@ -108,7 +108,8 @@
|
||||
"notification.info.emailSent": "L'email a été envoyé",
|
||||
"notification.success.delete": "L'élément a bien été supprimé",
|
||||
|
||||
"plugin.description": "Protegez votre API avec un système d'authentification complet",
|
||||
"plugin.description.short": "Protégez votre API avec un système d'authentification complet basé sur JWT",
|
||||
"plugin.description.long": "Protégez votre API avec un système d'authentification complet basé sur JWT (JSON Web Token). Ce plugin ajoute aussi une stratégie ACL (Access Control Layer) qui vous permet de gérer les permissions entre les groupes d'utilisateurs.",
|
||||
|
||||
"Plugin.permissions.application.description": "Définissez les actions autorisées dans votre projet.",
|
||||
"Plugin.permissions.plugins.description": "Définissez les actions autorisées dans le {name} plugin.",
|
||||
|
@ -60,7 +60,9 @@ module.exports = {
|
||||
|
||||
getPermissions: async (ctx) => {
|
||||
try {
|
||||
const permissions = await strapi.plugins['users-permissions'].services.userspermissions.getActions();
|
||||
const { lang } = ctx.query;
|
||||
const plugins = await strapi.plugins['users-permissions'].services.userspermissions.getPlugins(lang);
|
||||
const permissions = await strapi.plugins['users-permissions'].services.userspermissions.getActions(plugins);
|
||||
ctx.send({ permissions });
|
||||
} catch(err) {
|
||||
ctx.badRequest(null, [{ message: [{ id: 'Not Found' }] }]);
|
||||
@ -75,7 +77,9 @@ module.exports = {
|
||||
|
||||
getRole: async (ctx) => {
|
||||
const { id } = ctx.params;
|
||||
const role = await strapi.plugins['users-permissions'].services.userspermissions.getRole(id);
|
||||
const { lang } = ctx.query;
|
||||
const plugins = await strapi.plugins['users-permissions'].services.userspermissions.getPlugins(lang);
|
||||
const role = await strapi.plugins['users-permissions'].services.userspermissions.getRole(id, plugins);
|
||||
|
||||
if (_.isEmpty(role)) {
|
||||
return ctx.badRequest(null, [{ messages: [{ id: `Role don't exist` }] }]);
|
||||
|
@ -26,6 +26,7 @@
|
||||
"dependencies": {
|
||||
"bcryptjs": "^2.4.3",
|
||||
"jsonwebtoken": "^8.1.0",
|
||||
"request": "^2.83.0",
|
||||
"uuid": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -4,6 +4,7 @@ const fs = require('fs')
|
||||
const path = require('path');
|
||||
const stringify = JSON.stringify;
|
||||
const _ = require('lodash');
|
||||
const request = require('request');
|
||||
|
||||
/**
|
||||
* UsersPermissions.js service
|
||||
@ -40,7 +41,25 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
getActions: () => {
|
||||
getPlugins: (plugin, lang = 'en') => {
|
||||
return new Promise((resolve, reject) => {
|
||||
request({
|
||||
uri: `https://marketplace.strapi.io/plugins?lang=${lang}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'cache-control': 'max-age=3600'
|
||||
}
|
||||
}, (err, response, body) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(body);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getActions: (plugins = []) => {
|
||||
const generateActions = (data) => (
|
||||
Object.keys(data).reduce((acc, key) => {
|
||||
acc[key] = { enabled: false, policy: '' };
|
||||
@ -60,7 +79,7 @@ module.exports = {
|
||||
|
||||
return obj;
|
||||
|
||||
}, { controllers: {} });
|
||||
}, { controllers: {}, information: plugins.find(plugin => plugin.id === key) || {} });
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
@ -74,10 +93,17 @@ module.exports = {
|
||||
return _.merge(permissions, pluginsPermissions);;
|
||||
},
|
||||
|
||||
getRole: async (roleId) => {
|
||||
const appRoles = strapi.plugins['users-permissions'].config.roles
|
||||
getRole: async (roleId, plugins) => {
|
||||
const appRoles = strapi.plugins['users-permissions'].config.roles;
|
||||
|
||||
appRoles[roleId].users = await strapi.query('user', 'users-permissions').find(strapi.utils.models.convertParams('user', { role: roleId }));
|
||||
|
||||
Object.keys(appRoles[roleId].permissions)
|
||||
.filter(name => name !== 'application')
|
||||
.map(name => {
|
||||
appRoles[roleId].permissions[name].information = plugins.find(plugin => plugin.id === name) || {};
|
||||
});
|
||||
|
||||
return appRoles[roleId];
|
||||
},
|
||||
|
||||
|
@ -150,7 +150,7 @@ class Strapi extends EventEmitter {
|
||||
// Destroy server and available connections.
|
||||
this.server.destroy();
|
||||
|
||||
if (cluster.isWorker && process.env.NODE_ENV === 'development' && get(this.config, 'currentEnvironment.server.autoReload.enabled') === true) {
|
||||
if (cluster.isWorker && this.config.environment === 'development' && get(this.config, 'currentEnvironment.server.autoReload.enabled', true) === true) {
|
||||
process.send('stop');
|
||||
}
|
||||
|
||||
@ -190,8 +190,10 @@ class Strapi extends EventEmitter {
|
||||
}
|
||||
|
||||
reload() {
|
||||
const reload = function() {
|
||||
if (cluster.isWorker && process.env.NODE_ENV === 'development' && get(this.config, 'currentEnvironment.server.autoReload.enabled') === true) process.send('reload');
|
||||
const reload = function () {
|
||||
if (cluster.isWorker && this.config.environment === 'development' && get(this.config, 'currentEnvironment.server.autoReload.enabled', true) === true) {
|
||||
process.send('reload');
|
||||
}
|
||||
};
|
||||
|
||||
reload.isReloading = false;
|
||||
|
@ -89,4 +89,4 @@
|
||||
},
|
||||
"preferGlobal": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|