Merge branch 'master' into patch-1

This commit is contained in:
Jim LAURIE 2019-03-02 14:07:46 +01:00 committed by GitHub
commit 193d2bdc93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
192 changed files with 1725 additions and 595 deletions

View File

@ -26,3 +26,4 @@
- [ ] MongoDB
- [ ] MySQL
- [ ] Postgres
- [ ] SQLite

View File

@ -1,19 +1,48 @@
# Authentication
::: warning
This feature requires the Users & Permissions plugin (installed by default).
:::
This Authentication API requires the Users & Permissions plugin which comes with Strapi, installed by default.
## Token usage
A jwt token may be used for making permission-restricted API requests. To make an API request as a user, place the jwt token into an `Authorization` header of the GET request. A request without a token, will assume the `public` role permissions by default. Modify the permissions of each user's role in admin dashboard. Authentication failures return a 401 (unauthorized) error.
#### Usage
- The `token` variable is the `data.jwt` received when login in or registering.
```js
import axios from 'axios';
const token = 'YOUR_TOKEN_HERE';
// Request API.
axios
.get('http://localhost:1337/posts', {
headers: {
Authorization: `Bearer ${token}`
}
})
.then(response => {
// Handle success.
console.log('Data: ', response.data);
})
.catch(error => {
// Handle error.
console.log('An error occurred:', error);
});
```
## Registration
This route lets you create new users.
Creates a new user in the database with a default role as 'registered'.
#### Usage
```js
import axios from 'axios';
// Request API.
// Request API.
// Add your own code here to customize or restrict how the public can register new users.
axios
.post('http://localhost:1337/auth/local/register', {
username: 'Strapi user',
@ -34,7 +63,7 @@ axios
## Login
This route lets you login your users by getting an authentication token.
Submit the user's identifier and password credentials for authentication. When the authentication is successful, the response data returned will have the users information along with a jwt authentication token.
#### Local
@ -76,12 +105,12 @@ Strapi comes with the following providers:
---
To use the providers authentication, set your credentials in the admin interface (Plugin Users & Permissions > Providers).
Set your providers credentials in the admin interface (Plugin Users & Permissions > Providers).
Then update and enable the provider you want use.
Redirect your user to: `GET /connect/:provider`. eg: `GET /connect/facebook`
To authenticate the user, use the GET method to request the url, `/connect/:provider`. eg: `GET /connect/facebook`
After his approval, he will be redirected to `/auth/:provider/callback`. The `jwt` and `user` data will be available in the body response.
After authentication, create and customize your own redirect callback at `/auth/:provider/callback`. The `jwt` and `user` data will be available in a .json response.
Response payload:
@ -92,36 +121,6 @@ Response payload:
}
```
## Token usage
By default, each API request is identified as `guest` role (see permissions of `guest`'s role in your admin dashboard). To make a request as a user, you have to set the `Authorization` token in your request headers. You receive a 401 error if you are not authorized to make this request or if your authorization header is not correct.
#### Usage
- The `token` variable is the `data.jwt` received when login in or registering.
```js
import axios from 'axios';
const token = 'YOUR_TOKEN_HERE';
// Request API.
axios
.get('http://localhost:1337/posts', {
headers: {
Authorization: `Bearer ${token}`
}
})
.then(response => {
// Handle success.
console.log('Data: ', response.data);
})
.catch(error => {
// Handle error.
console.log('An error occurred:', error);
});
```
## Forgotten password
This action sends an email to a user with the link of you reset password page. This link contains an URL param `code` which is required to reset user password.
@ -220,18 +219,15 @@ packages/strapi-plugin-users-permissions/admin/src/translations/en.json
We will go step by step.
### Configure your Provider Request
First, we need to configure our new provider onto `Provider.js` file.
Configure the new provider in the `Provider.js` file at the `getProfile` function.
Jump onto the `getProfile` function, you will see the list of currently available providers in the form of a `switch...case`.
As you can see, `getProfile` take three params:
The `getProfile` takes three params:
1. provider :: The name of the used provider as a string.
2. query :: The query is the result of the provider callback.
3. callback :: The callback function who will continue the internal Strapi login logic.
Let's take the `discord` one as an example since it's not the easier, it should cover most of the case you
may encounter trying to implement your own provider.
Here is an example that uses the `discord` provider.
#### Configure your oauth generic information
@ -259,14 +255,16 @@ may encounter trying to implement your own provider.
}
```
So here, you can see that we use a module called `Purest`. This module gives us with a generic way to interact
with the REST API.
This code creates a `Purest` object that gives us a generic way to interact with the provider's REST API.
To understand each value usage, and the templating syntax, I invite you to read the [Official Purest Documentation](https://github.com/simov/purest/tree/2.x)
For more specs on using the `Purest` module, please refer to the [Official Purest Documentation](https://github.com/simov/purest/tree/2.x)
You may also want to take a look onto the numerous already made configurations [here](https://github.com/simov/purest-providers/blob/master/config/providers.json).
#### Retrieve your user informations:
#### Retrieve your user's information:
For our discord provider it will look like:
```js
discord.query().get('users/@me').auth(access_token).request((err, res, body) => {
if (err) {
@ -300,9 +298,9 @@ to retrieve your user from the database and log you in.
Now, we need to configure our 'model' for our new provider. That way, our settings can be stored in the database, and
managed from the admin panel.
Into: `packages/strapi-plugin-users-permissions/config/functions/bootstrap.js`
Open the file `packages/strapi-plugin-users-permissions/config/functions/bootstrap.js`
Simply add the fields your provider need into the `grantConfig` object.
Add the fields your provider needs into the `grantConfig` object.
For our discord provider it will look like:
```js
@ -319,31 +317,29 @@ For our discord provider it will look like:
},
```
You have already done the hard part, now, we simply need to make our new provider available from the front
side of our application. So let's do it!
<!-- #### Tests -->
<!-- TODO Add documentation about how to configure unit test for the new provider -->
### Configure frontend for your new provider
First, let's edit: `packages/strapi-plugin-users-permissions/admin/src/components/PopUpForm/index.js`
As for backend, we have a `switch...case` where we need to put our new provider info.
To make the new provider available on the front end of the application,
edit `packages/strapi-plugin-users-permissions/admin/src/components/PopUpForm/index.js`
Add the new provider info. For our discord provider it will look like:
```js
case 'discord':
return `${strapi.backendURL}/connect/discord/callback`;
```
Add the corresponding translation into: `packages/strapi-plugin-users-permissions/admin/src/translations/en.json`
### Add language translation
Add the language translation in `packages/strapi-plugin-users-permissions/admin/src/translations/en.json`
```js
'PopUpForm.Providers.discord.providerConfig.redirectURL': 'The redirect URL to add in your Discord application configurations',
````
These two change will set up the popup message who appear on the UI when we will configure our new provider.
That's it, now you should be able to use your new provider.
These two change will set up the popup message that appears in the UI. That's it, now you should be able to use your new provider.
## Email templates

View File

@ -13,7 +13,7 @@ import 'whatwg-fetch';
import {
getAppPluginsSucceeded,
unsetHasUserPlugin,
} from 'containers/App/actions';
} from './containers/App/actions';
import { basename, store } from './createStore';
import './intlPolyfill';
import './public-path';

View File

@ -6,12 +6,12 @@
*/
import { findIndex } from 'lodash';
import 'babel-polyfill';
import 'sanitize.css/sanitize.css';
import {
getAppPluginsSucceeded,
unsetHasUserPlugin,
} from 'containers/App/actions';
import 'babel-polyfill';
import 'sanitize.css/sanitize.css';
} from './containers/App/actions';
import { store } from './createStore';
import render from './renderApp';
import './intlPolyfill';

View File

@ -11,7 +11,7 @@ import { Modal, ModalHeader, ModalBody } from 'reactstrap';
import { map } from 'lodash';
import cn from 'classnames';
import Official from 'components/Official';
import Official from '../Official';
// import StarsContainer from 'components/StarsContainer';
import styles from './styles.scss';

View File

@ -8,7 +8,7 @@ import React from 'react';
import { defineMessages, FormattedMessage } from 'react-intl';
import { PropTypes } from 'prop-types';
import LeftMenuLink from 'components/LeftMenuLink';
import LeftMenuLink from '../LeftMenuLink';
import styles from './styles.scss';
import messages from './messages.json';

View File

@ -10,7 +10,7 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import en from 'translations/en.json';
import en from '../../translations/en.json';
import styles from './styles.scss';

View File

@ -9,7 +9,7 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { findIndex, get, snakeCase, isEmpty, map, sortBy } from 'lodash';
import LeftMenuLink from 'components/LeftMenuLink';
import LeftMenuLink from '../LeftMenuLink';
import styles from './styles.scss';
import messages from './messages.json';

View File

@ -12,7 +12,7 @@ import { map, size } from 'lodash';
// Design
import Button from 'components/Button';
import Row from 'components/Row';
import Row from '../Row';
import styles from './styles.scss';

View File

@ -8,7 +8,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import Notification from 'components/Notification';
import Notification from '../Notification';
import styles from './styles.scss';

View File

@ -10,7 +10,6 @@ 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" />

View File

@ -0,0 +1,170 @@
/**
*
* OnboardingList
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { Modal, ModalHeader, ModalBody } from 'reactstrap';
import { Player } from 'video-react';
import '../../../../node_modules/video-react/dist/video-react.css';
import styles from './styles.scss';
class OnboardingVideo extends React.Component {
componentDidMount() {
this.hiddenPlayer.current.subscribeToStateChange(
this.handleChangeState,
);
}
hiddenPlayer = React.createRef();
player = React.createRef();
handleChangeState = (state, prevState) => {
const { duration } = state;
const { id } = this.props;
if (duration !== prevState.duration) {
this.props.setVideoDuration(id, duration);
}
};
handleChangeIsPlayingState = (state, prevState) => {
const { isActive } = state;
const { id } = this.props;
if (isActive !== prevState.isActive && isActive) {
this.props.didPlayVideo(id, this.props.video.startTime);
}
};
handleCurrentTimeChange = (curr) => {
this.props.getVideoCurrentTime(this.props.id, curr, this.props.video.duration);
}
handleModalOpen = () => {
this.player.current.subscribeToStateChange(
this.handleChangeIsPlayingState,
);
this.player.current.play();
if (this.props.video.startTime === 0) {
const { player } = this.player.current.getState();
player.isActive = true;
this.props.didPlayVideo(this.props.id, this.props.video.startTime);
} else {
this.player.current.pause();
}
};
handleVideoPause = () => {
const { player } = this.player.current.getState();
const currTime = player.currentTime;
this.handleCurrentTimeChange(currTime);
this.props.didStopVideo(this.props.id, currTime);
};
handleModalClose = () => {
const { player } = this.player.current.getState();
const paused = player.paused;
if (!paused) {
this.handleVideoPause();
}
};
render() {
const { video } = this.props;
return (
<li
key={this.props.id}
onClick={this.props.onClick}
id={this.props.id}
className={cn(styles.listItem, video.end && (styles.finished))}
>
<div className={styles.thumbWrapper}>
<img src={video.preview} alt="preview" />
<div className={styles.overlay} />
<div className={styles.play} />
</div>
<div className={styles.txtWrapper}>
<p className={styles.title}>{video.title}</p>
<p className={styles.time}>{isNaN(video.duration) ? '\xA0' : `${Math.floor(video.duration / 60)}:${Math.floor(video.duration)%60}`}</p>
</div>
<Modal
isOpen={video.isOpen}
toggle={this.props.onClick} // eslint-disable-line react/jsx-handler-names
className={styles.videoModal}
onOpened={this.handleModalOpen}
onClosed={this.handleModalClose}
>
<ModalHeader
toggle={this.props.onClick} // eslint-disable-line react/jsx-handler-names
className={styles.videoModalHeader}
>
{video.title}
</ModalHeader>
<ModalBody className={styles.modalBodyHelper}>
<div>
<Player
ref={this.player}
className={styles.videoPlayer}
poster="/assets/poster.png"
src={video.video}
startTime={video.startTime}
preload="auto"
onPause={this.handleVideoPause}
onplay={this.videoStart}
subscribeToStateChange={this.subscribeToStateChange}
/>
</div>
</ModalBody>
</Modal>
{!this.props.video.duration && (
<div className={cn(styles.hiddenPlayerWrapper)}>
<Player
ref={this.hiddenPlayer}
poster="/assets/poster.png"
src={video.video}
preload="auto"
subscribeToStateChange={this.subscribeToStateChange}
/>
</div>
)}
</li>
);
}
}
OnboardingVideo.defaultProps = {
didPlayVideo: () => {},
didStopVideo: () => {},
getVideoCurrentTime: () => {},
id: 0,
onClick: () => {},
setVideoDuration: () => {},
video: {},
};
OnboardingVideo.propTypes = {
didPlayVideo: PropTypes.func,
didStopVideo: PropTypes.func,
getVideoCurrentTime: PropTypes.func,
id: PropTypes.number,
onClick: PropTypes.func,
setVideoDuration: PropTypes.func,
video: PropTypes.object,
};
export default OnboardingVideo;

View File

@ -0,0 +1,144 @@
li.listItem {
display: block;
padding: 17px 15px;
cursor: pointer;
&:hover {
background-color: #f7f8f8;
.title {
color: #0e7de7;
}
}
.txtWrapper,
.thumbWrapper {
display: inline-block;
vertical-align: middle;
}
.thumbWrapper {
position: relative;
width: 55px;
height: 38px;
background-color: #d8d8d8;
border-radius: 2px;
overflow: hidden;
.overlay {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
background-color: rgba(#0E7DE7, .8);
}
img {
position: relative;
z-index: 0;
width: 100%;
height: 100%;
}
.play {
position: absolute;
top: calc(50% - 10px);
left: calc(50% - 10px);
width: 20px;
height: 20px;
background-color: #0e7de7;
border: 1px solid white;
text-align: center;
line-height: 20px;
border-radius: 50%;
z-index: 2;
&::before {
content: '\f04b';
display: inline-block;
vertical-align: top;
height: 100%;
font-family: 'FontAwesome';
color: white;
font-size: 10px;
margin-left: 3px;
line-height: 18px;
}
}
}
&.finished {
.title {
color: #919bae;
}
.thumbWrapper {
.overlay {
background-color: transparent;
}
img {
opacity: 0.6;
}
.play {
background-color: #5a9e06;
border-color: #5a9e06;
&::before {
content: '\f00c';
margin-left: 0;
font-size: 11px;
line-height: 20px;
}
}
}
}
.txtWrapper {
padding: 0 15px;
p {
font-size: 14px;
line-height: 24px;
font-family: Lato;
font-weight: 600;
}
.time {
color: #919bae;
font-family: Lato;
font-weight: bold;
font-size: 11px;
line-height: 11px;
}
}
}
.hiddenPlayerWrapper {
display: none;
}
.videoModal {
margin-right: auto !important;
margin-left: auto !important;
.videoModalHeader {
padding-bottom: 0;
border-bottom: 0;
> h5 {
font-family: Lato;
font-weight: bold!important;
font-size: 1.8rem!important;
line-height: 3.1rem;
color: #333740;
}
> button {
display: flex;
position: absolute;
right: 0;
top: 0;
margin-top: 0;
margin-right: 0;
padding: 10px;
cursor: pointer;
span {
line-height: 0.6em;
}
}
}
.videoPlayer {
> button {
top: 50%;
margin-top: -0.75em;
left: 50%;
margin-left: -1.5em;
}
}
}

View File

@ -0,0 +1,11 @@
// import OnboardingList from '../index';
import expect from 'expect';
// import { shallow } from 'enzyme';
// import React from 'react';
describe('<OnboardingList />', () => {
it('Expect to have unit tests specified', () => {
expect(true).toEqual(true);
});
});

View File

@ -11,7 +11,7 @@ import { isEmpty, replace } from 'lodash';
import { FormattedMessage } from 'react-intl';
import Button from 'components/Button';
import InstallPluginPopup from 'components/InstallPluginPopup';
import InstallPluginPopup from '../InstallPluginPopup';
import styles from './styles.scss';

View File

@ -6,6 +6,7 @@
import {
GET_ADMIN_DATA,
GET_ADMIN_DATA_SUCCEEDED,
EMIT_EVENT,
} from './constants';
export function getAdminData() {
@ -19,4 +20,12 @@ export function getAdminDataSucceeded(data) {
type: GET_ADMIN_DATA_SUCCEEDED,
data,
};
}
export function emitEvent(event, properties) {
return {
type: EMIT_EVENT,
event,
properties,
};
}

View File

@ -5,4 +5,5 @@
*/
export const GET_ADMIN_DATA = 'app/Admin/GET_ADMIN_DATA';
export const GET_ADMIN_DATA_SUCCEEDED = 'app/Admin/GET_ADMIN_DATA_SUCCEEDED';
export const GET_ADMIN_DATA_SUCCEEDED = 'app/Admin/GET_ADMIN_DATA_SUCCEEDED';
export const EMIT_EVENT = 'app/Admin/EMIT_EVENT';

View File

@ -22,7 +22,14 @@ import {
disableGlobalOverlayBlocker,
enableGlobalOverlayBlocker,
} from 'actions/overlayBlocker';
import { pluginLoaded, updatePlugin } from 'containers/App/actions';
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
import OverlayBlocker from 'components/OverlayBlocker';
// Utils
import auth from 'utils/auth';
import { pluginLoaded, updatePlugin } from '../App/actions';
import {
makeSelectAppPlugins,
makeSelectBlockApp,
@ -31,28 +38,28 @@ import {
makeSelectShowGlobalAppBlocker,
selectHasUserPlugin,
selectPlugins,
} from 'containers/App/selectors';
} from '../App/selectors';
import injectReducer from '../../utils/injectReducer';
import injectSaga from '../../utils/injectSaga';
// Design
import ComingSoonPage from 'containers/ComingSoonPage';
import Content from 'containers/Content';
import LocaleToggle from 'containers/LocaleToggle';
import CTAWrapper from 'components/CtaWrapper';
import Header from 'components/Header/index';
import HomePage from 'containers/HomePage/Loadable';
import Marketplace from 'containers/Marketplace/Loadable';
import LeftMenu from 'containers/LeftMenu';
import ListPluginsPage from 'containers/ListPluginsPage/Loadable';
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
import Logout from 'components/Logout';
import NotFoundPage from 'containers/NotFoundPage/Loadable';
import OverlayBlocker from 'components/OverlayBlocker';
import PluginPage from 'containers/PluginPage';
import FullStory from 'components/FullStory';
// Utils
import auth from 'utils/auth';
import injectReducer from 'utils/injectReducer';
import injectSaga from 'utils/injectSaga';
import { getAdminData } from './actions';
import CTAWrapper from '../../components/CtaWrapper';
import Header from '../../components/Header/index';
import Logout from '../../components/Logout';
import FullStory from '../../components/FullStory';
import ComingSoonPage from '../ComingSoonPage';
import Content from '../Content';
import LocaleToggle from '../LocaleToggle';
import HomePage from '../HomePage/Loadable';
import Marketplace from '../Marketplace/Loadable';
import LeftMenu from '../LeftMenu';
import ListPluginsPage from '../ListPluginsPage/Loadable';
import Onboarding from '../Onboarding';
import NotFoundPage from '../NotFoundPage/Loadable';
import PluginPage from '../PluginPage';
import { emitEvent, getAdminData } from './actions';
import reducer from './reducer';
import saga from './saga';
import selectAdminPage from './selectors';
@ -70,6 +77,7 @@ export class AdminPage extends React.Component {
getChildContext = () => ({
currentEnvironment: this.props.adminPage.currentEnvironment,
disableGlobalOverlayBlocker: this.props.disableGlobalOverlayBlocker,
emitEvent: this.props.emitEvent,
enableGlobalOverlayBlocker: this.props.enableGlobalOverlayBlocker,
plugins: this.props.plugins,
updatePlugin: this.props.updatePlugin,
@ -80,7 +88,6 @@ export class AdminPage extends React.Component {
this.checkLogin(this.props);
ReactGA.initialize('UA-54313258-9');
}
componentDidUpdate(prevProps) {
const {
adminPage: { uuid },
@ -95,7 +102,7 @@ export class AdminPage extends React.Component {
ReactGA.pageview(pathname);
}
}
const hasAdminPath = ['users-permissions', 'hasAdminUser'];
if (
@ -267,7 +274,11 @@ export class AdminPage extends React.Component {
<Route path="/plugins/:pluginId" component={PluginPage} />
<Route path="/plugins" component={ComingSoonPage} />
<Route path="/list-plugins" component={ListPluginsPage} exact />
<Route path="/marketplace" render={this.renderMarketPlace} exact />
<Route
path="/marketplace"
render={this.renderMarketPlace}
exact
/>
<Route path="/configuration" component={ComingSoonPage} exact />
<Route path="" component={NotFoundPage} />
<Route path="404" component={NotFoundPage} />
@ -278,12 +289,14 @@ export class AdminPage extends React.Component {
isOpen={this.props.blockApp && this.props.showGlobalAppBlocker}
{...this.props.overlayBlockerData}
/>
{this.shouldDisplayLogout() && <Onboarding />}
</div>
);
}
}
AdminPage.childContextTypes = {
emitEvent: PropTypes.func,
currentEnvironment: PropTypes.string.isRequired,
disableGlobalOverlayBlocker: PropTypes.func,
enableGlobalOverlayBlocker: PropTypes.func,
@ -308,6 +321,7 @@ AdminPage.propTypes = {
appPlugins: PropTypes.array,
blockApp: PropTypes.bool.isRequired,
disableGlobalOverlayBlocker: PropTypes.func.isRequired,
emitEvent: PropTypes.func.isRequired,
enableGlobalOverlayBlocker: PropTypes.func.isRequired,
getAdminData: PropTypes.func.isRequired,
hasUserPlugin: PropTypes.bool,
@ -336,6 +350,7 @@ function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
disableGlobalOverlayBlocker,
emitEvent,
enableGlobalOverlayBlocker,
getAdminData,
pluginLoaded,

View File

@ -16,6 +16,8 @@ const initialState = fromJS({
isLoading: true,
layout: Map({}),
strapiVersion: '3',
eventName: '',
shouldEmit: true,
});
function adminPageReducer(state = initialState, action) {

View File

@ -1,11 +1,36 @@
import { all, fork, call, put, select, takeLatest } from 'redux-saga/effects';
import auth from 'utils/auth';
import request from 'utils/request';
import { makeSelectAppPlugins } from 'containers/App/selectors';
import { makeSelectAppPlugins } from '../App/selectors';
import {
getAdminDataSucceeded,
} from './actions';
import { GET_ADMIN_DATA } from './constants';
import { makeSelectUuid } from './selectors';
import { EMIT_EVENT, GET_ADMIN_DATA } from './constants';
function* emitter(action) {
try {
const requestURL = 'https://analytics.strapi.io/track';
const uuid = yield select(makeSelectUuid());
const { event, properties } = action;
if (uuid) {
yield call(
fetch, // eslint-disable-line no-undef
requestURL,
{
method: 'POST',
body: JSON.stringify({ event, uuid, properties }),
headers: {
'Content-Type': 'application/json',
},
},
);
}
} catch(err) {
console.log(err); // eslint-disable-line no-console
}
}
function* getData() {
try {
@ -32,6 +57,7 @@ function* getData() {
function* defaultSaga() {
yield all([
fork(takeLatest, GET_ADMIN_DATA, getData),
fork(takeLatest, EMIT_EVENT, emitter),
]);
}

View File

@ -14,9 +14,15 @@ const selectAdminPageDomain = () => state => state.get('adminPage');
* Default selector used by HomePage
*/
const makeSelectUuid = () => createSelector(
selectAdminPageDomain(),
substate => substate.get('uuid'),
);
const selectAdminPage = () => createSelector(
selectAdminPageDomain(),
(substate) => substate.toJS(),
);
export default selectAdminPage;
export { makeSelectUuid };

View File

@ -14,12 +14,15 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Switch, Route } from 'react-router-dom';
import AdminPage from 'containers/AdminPage';
import NotFoundPage from 'containers/NotFoundPage';
import NotificationProvider from 'containers/NotificationProvider';
import AppLoader from 'containers/AppLoader';
// From strapi-helper-plugin
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
import '../../styles/main.scss';
import AdminPage from '../AdminPage';
import NotFoundPage from '../NotFoundPage';
import NotificationProvider from '../NotificationProvider';
import AppLoader from '../AppLoader';
import styles from './styles.scss';
export class App extends React.Component { // eslint-disable-line react/prefer-stateless-function

View File

@ -7,7 +7,7 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import makeSelectApp from 'containers/App/selectors';
import makeSelectApp from '../App/selectors';
class AppLoader extends React.Component {
shouldLoad = () => {
@ -31,4 +31,4 @@ AppLoader.propTypes = {
const mapStateToProps = makeSelectApp();
export default connect(mapStateToProps, null)(AppLoader);
export default connect(mapStateToProps, null)(AppLoader);

View File

@ -9,7 +9,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { selectPlugins } from 'containers/App/selectors';
import { selectPlugins } from '../App/selectors';
import styles from './styles.scss';

View File

@ -14,20 +14,21 @@ import PropTypes from 'prop-types';
import { get, isEmpty, upperFirst } from 'lodash';
import cn from 'classnames';
import Block from 'components/HomePageBlock';
import Button from 'components/Button';
import Sub from 'components/Sub';
import Input from 'components/InputText';
import SupportUsCta from 'components/SupportUsCta';
import SupportUsTitle from 'components/SupportUsTitle';
import { selectPlugins } from 'containers/App/selectors';
import auth from 'utils/auth';
import injectReducer from 'utils/injectReducer';
import injectSaga from 'utils/injectSaga';
import validateInput from 'utils/inputsValidations';
import Block from '../../components/HomePageBlock';
import Sub from '../../components/Sub';
import SupportUsCta from '../../components/SupportUsCta';
import SupportUsTitle from '../../components/SupportUsTitle';
import { selectPlugins } from '../App/selectors';
import injectReducer from '../../utils/injectReducer';
import injectSaga from '../../utils/injectSaga';
import BlockLink from './BlockLink';
import CommunityContent from './CommunityContent';
import CreateContent from './CreateContent';
@ -122,7 +123,11 @@ export class HomePage extends React.PureComponent {
handleSubmit = e => {
e.preventDefault();
const errors = validateInput(this.props.homePage.body.email, { required: true }, 'email');
const errors = validateInput(
this.props.homePage.body.email,
{ required: true },
'email',
);
this.setState({ errors });
if (isEmpty(errors)) {
@ -131,13 +136,18 @@ export class HomePage extends React.PureComponent {
};
showFirstBlock = () =>
get(this.props.plugins.toJS(), 'content-manager.leftMenuSections.0.links', []).length === 0;
get(
this.props.plugins.toJS(),
'content-manager.leftMenuSections.0.links',
[],
).length === 0;
renderButton = () => {
const data = this.showFirstBlock()
? {
className: styles.homePageTutorialButton,
href: 'https://strapi.io/documentation/3.x.x/getting-started/quick-start.html#_3-create-a-content-type',
href:
'https://strapi.io/documentation/getting-started/quick-start.html#_3-create-a-content-type',
id: 'app.components.HomePage.button.quickStart',
primary: true,
}
@ -158,7 +168,9 @@ export class HomePage extends React.PureComponent {
};
render() {
const { homePage: { articles, body } } = this.props;
const {
homePage: { articles, body },
} = this.props;
const WELCOME_AGAIN_BLOCK = [
{
title: {
@ -177,7 +189,12 @@ export class HomePage extends React.PureComponent {
<Block>
{this.showFirstBlock() &&
FIRST_BLOCK.map((value, key) => (
<Sub key={key} {...value} underline={key === 0} bordered={key === 0} />
<Sub
key={key}
{...value}
underline={key === 0}
bordered={key === 0}
/>
))}
{!this.showFirstBlock() &&
WELCOME_AGAIN_BLOCK.concat(articles).map((value, key) => (
@ -191,14 +208,21 @@ export class HomePage extends React.PureComponent {
))}
{this.renderButton()}
<div className={styles.homePageFlex}>
{FIRST_BLOCK_LINKS.map((value, key) => <BlockLink {...value} key={key} />)}
{FIRST_BLOCK_LINKS.map((value, key) => (
<BlockLink {...value} key={key} />
))}
</div>
</Block>
<Block>
<Sub {...SECOND_BLOCK} />
<div className={styles.homePageFlex}>
<div className="row" style={{ width: '100%', marginRight: '0' }}>
{SOCIAL_LINKS.map((value, key) => <SocialLink key={key} {...value} />)}
<div
className="row"
style={{ width: '100%', marginRight: '0' }}
>
{SOCIAL_LINKS.map((value, key) => (
<SocialLink key={key} {...value} />
))}
</div>
<div className={styles.newsLetterWrapper}>
<div>
@ -265,10 +289,17 @@ function mapDispatchToProps(dispatch) {
);
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
);
const withReducer = injectReducer({ key: 'homePage', reducer });
const withSaga = injectSaga({ key: 'homePage', saga });
// export default connect(mapDispatchToProps)(HomePage);
export default compose(withReducer, withSaga, withConnect)(HomePage);
export default compose(
withReducer,
withSaga,
withConnect,
)(HomePage);

View File

@ -1,14 +1,7 @@
import 'whatwg-fetch';
import { dropRight, take } from 'lodash';
import removeMd from 'remove-markdown';
import {
all,
call,
fork,
put,
select,
takeLatest,
} from 'redux-saga/effects';
import { all, call, fork, put, select, takeLatest } from 'redux-saga/effects';
import request from 'utils/request';
import { getArticlesSucceeded, submitSucceeded } from './actions';
import { GET_ARTICLES, SUBMIT } from './constants';
@ -19,7 +12,11 @@ function* getArticles() {
const articles = yield call(fetchArticles);
const posts = articles.posts.reduce((acc, curr) => {
// Limit to 200 characters and remove last word.
const content = dropRight(take(removeMd(curr.markdown), 250).join('').split(' ')).join(' ');
const content = dropRight(
take(removeMd(curr.markdown), 250)
.join('')
.split(' '),
).join(' ');
acc.push({
title: curr.title,
@ -31,17 +28,19 @@ function* getArticles() {
}, []);
yield put(getArticlesSucceeded(posts));
} catch(err) {
} catch (err) {
// Silent
}
}
function* submit() {
try {
const body = yield select(makeSelectBody());
yield call(request, 'https://analytics.strapi.io/register', { method: 'POST', body });
} catch(err) {
yield call(request, 'https://analytics.strapi.io/register', {
method: 'POST',
body,
});
} catch (err) {
// silent
} finally {
strapi.notification.success('HomePage.notification.newsLetter.success');
@ -56,11 +55,12 @@ function* defaultSaga() {
]);
}
function fetchArticles() {
return fetch('https://blog.strapi.io/ghost/api/v0.1/posts/?client_id=ghost-frontend&client_secret=1f260788b4ec&limit=2', {})
.then(resp => {
return resp.json ? resp.json() : resp;
});
return fetch(
'https://blog.strapi.io/ghost/api/v0.1/posts/?client_id=ghost-frontend&client_secret=1f260788b4ec&limit=2',
{},
).then(resp => {
return resp.json ? resp.json() : resp;
});
}
export default defaultSaga;

View File

@ -8,9 +8,9 @@ import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import LeftMenuHeader from 'components/LeftMenuHeader';
import LeftMenuLinkContainer from 'components/LeftMenuLinkContainer';
import LeftMenuFooter from 'components/LeftMenuFooter';
import LeftMenuHeader from '../../components/LeftMenuHeader';
import LeftMenuLinkContainer from '../../components/LeftMenuLinkContainer';
import LeftMenuFooter from '../../components/LeftMenuFooter';
import styles from './styles.scss';

View File

@ -14,11 +14,12 @@ import { FormattedMessage } from 'react-intl';
import cn from 'classnames';
import PluginHeader from 'components/PluginHeader';
import ListPlugins from 'components/ListPlugins';
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
import injectSaga from 'utils/injectSaga';
import injectReducer from 'utils/injectReducer';
import ListPlugins from '../../components/ListPlugins';
import injectSaga from '../../utils/injectSaga';
import injectReducer from '../../utils/injectReducer';
import {
makeSelectCurrentEnv,
makeSelectPluginDeleteAction,

View File

@ -1,9 +1,9 @@
import { LOCATION_CHANGE } from 'react-router-redux';
import { get } from 'lodash';
import { all, fork, call, put, select, takeLatest, take, cancel } from 'redux-saga/effects';
import { pluginDeleted } from 'containers/App/actions';
import auth from 'utils/auth';
import request from 'utils/request';
import { pluginDeleted } from '../App/actions';
import { selectLocale } from '../LanguageProvider/selectors';
import { deletePluginSucceeded, getAppCurrentEnvSucceeded, getPluginsSucceeded } from './actions';
import { GET_PLUGINS, ON_DELETE_PLUGIN_CONFIRM } from './constants';

View File

@ -13,9 +13,9 @@ import cn from 'classnames';
import { ButtonDropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
import { selectLocale } from 'containers/LanguageProvider/selectors';
import { changeLocale } from 'containers/LanguageProvider/actions';
import { languages } from 'i18n';
import { selectLocale } from '../LanguageProvider/selectors';
import { changeLocale } from '../LanguageProvider/actions';
import { languages } from '../../i18n';
import styles from './styles.scss';

View File

@ -12,13 +12,13 @@ import { FormattedMessage } from 'react-intl';
import { bindActionCreators, compose } from 'redux';
import cn from 'classnames';
// Design
import PluginCard from 'components/PluginCard';
import PluginHeader from 'components/PluginHeader';
// Design
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
import PluginCard from '../../components/PluginCard';
import injectSaga from 'utils/injectSaga';
import injectReducer from 'utils/injectReducer';
import injectSaga from '../../utils/injectSaga';
import injectReducer from '../../utils/injectReducer';
import {
downloadPlugin,

View File

@ -9,7 +9,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import NotificationsContainer from 'components/NotificationsContainer';
import NotificationsContainer from '../../components/NotificationsContainer';
import { selectNotifications } from './selectors';
import { hideNotification } from './actions';

View File

@ -0,0 +1,64 @@
/*
*
* Onboarding actions
*
*/
import { GET_VIDEOS, GET_VIDEOS_SUCCEEDED, SHOULD_OPEN_MODAL, ON_CLICK, SET_VIDEOS_DURATION, UPDATE_VIDEO_START_TIME, SET_VIDEO_END, REMOVE_VIDEOS } from './constants';
export function getVideos() {
return {
type: GET_VIDEOS,
};
}
export function getVideosSucceeded(videos) {
return {
type: GET_VIDEOS_SUCCEEDED,
videos,
};
}
export function shouldOpenModal(opened) {
return {
type: SHOULD_OPEN_MODAL,
opened,
};
}
export function onClick(e) {
return {
type: ON_CLICK,
index: parseInt(e.currentTarget.id, 10),
};
}
export function setVideoDuration(index, duration) {
return {
type: SET_VIDEOS_DURATION,
index: parseInt(index, 10),
duration: parseFloat(duration, 10),
};
}
export function updateVideoStartTime(index, startTime) {
return {
type: UPDATE_VIDEO_START_TIME,
index: parseInt(index, 10),
startTime: parseFloat(startTime, 10),
};
}
export function setVideoEnd(index, end) {
return {
type: SET_VIDEO_END,
index: parseInt(index, 10),
end,
};
}
export function removeVideos() {
return {
type: REMOVE_VIDEOS,
};
}

View File

@ -0,0 +1,15 @@
/*
*
* Onboarding constants
*
*/
export const GET_VIDEOS = 'StrapiAdmin/Onboarding/GET_VIDEOS';
export const GET_VIDEOS_SUCCEEDED =
'StrapiAdmin/Onboarding/GET_VIDEOS_SUCCEEDED';
export const SHOULD_OPEN_MODAL = 'StrapiAdmin/Onboarding/SHOULD_OPEN_MODAL';
export const ON_CLICK = 'StrapiAdmin/Onboarding/ON_CLICK';
export const SET_VIDEOS_DURATION = 'StrapiAdmin/Onboarding/SET_VIDEOS_DURATION';
export const UPDATE_VIDEO_START_TIME = 'StrapiAdmin/Onboarding/UPDATE_VIDEO_START_TIME';
export const SET_VIDEO_END = 'StrapiAdmin/Onboarding/SET_VIDEO_END';
export const REMOVE_VIDEOS = 'StrapiAdmin/Onboarding/REMOVE_VIDEOS';

View File

@ -0,0 +1,182 @@
/**
*
* Onboarding
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux';
import { FormattedMessage } from 'react-intl';
import injectSaga from 'utils/injectSaga';
import injectReducer from 'utils/injectReducer';
import OnboardingVideo from 'components/OnboardingVideo';
import { getVideos, onClick, removeVideos, setVideoDuration, setVideoEnd, updateVideoStartTime } from './actions';
import makeSelectOnboarding from './selectors';
import reducer from './reducer';
import saga from './saga';
import styles from './styles.scss';
export class Onboarding extends React.Component {
state = { showVideos: false };
componentDidMount() {
this.props.getVideos();
}
componentDidUpdate(prevProps) {
const { shouldOpenModal } = this.props;
if (shouldOpenModal !== prevProps.shouldOpenModal && shouldOpenModal) {
this.handleOpenModal();
}
}
componentWillUnmount() {
this.props.removeVideos();
}
setVideoEnd = () => {
this.setVideoEnd();
}
didPlayVideo = (index, currTime) => {
const eventName = `didPlay${index}GetStartedVideo`;
this.context.emitEvent(eventName, {timestamp: currTime});
}
didStopVideo = (index, currTime) => {
const eventName = `didStop${index}Video`;
this.context.emitEvent(eventName, {timestamp: currTime});
}
handleOpenModal = () => this.setState({ showVideos: true });
handleVideosToggle = () => {
this.setState(prevState => ({ showVideos: !prevState.showVideos }));
const { showVideos } = this.state;
const eventName = showVideos ? 'didOpenGetStartedVideoContainer' : 'didCloseGetStartedVideoContainer';
this.context.emitEvent(eventName);
};
updateCurrentTime = (index, current, duration) => {
this.props.updateVideoStartTime(index, current);
const percent = current * 100 / duration;
const video = this.props.videos[index];
if (percent >= 80) {
if (video.end === false) {
this.updateEnd(index);
}
}
};
updateEnd = (index) => {
this.props.setVideoEnd(index, true);
};
// eslint-disable-line jsx-handler-names
render() {
const { videos, onClick, setVideoDuration } = this.props;
return (
<div className={cn(styles.videosWrapper, videos.length > 0 ? styles.visible : styles.hidden)}>
<div className={cn(styles.videosContent, this.state.showVideos ? styles.shown : styles.hide)}>
<div className={styles.videosHeader}>
<p><FormattedMessage id="app.components.Onboarding.title" /></p>
{videos.length && (
<p>{Math.floor((videos.filter(v => v.end).length)*100/videos.length)}<FormattedMessage id="app.components.Onboarding.label.completed" /></p>
)}
</div>
<ul className={styles.onboardingList}>
{videos.map((video, i) => {
return (
<OnboardingVideo
key={i}
id={i}
video={video}
onClick={onClick}
setVideoDuration={setVideoDuration}
getVideoCurrentTime={this.updateCurrentTime}
didPlayVideo={this.didPlayVideo}
didStopVideo={this.didStopVideo}
/>
);
})}
</ul>
</div>
<div className={styles.openBtn}>
<button
onClick={this.handleVideosToggle}
className={this.state.showVideos ? styles.active : ''}
>
<i className="fa fa-question" />
<i className="fa fa-times" />
<span />
</button>
</div>
</div>
);
}
}
Onboarding.contextTypes = {
emitEvent: PropTypes.func,
};
Onboarding.defaultProps = {
onClick: () => {},
removeVideos: () => {},
setVideoDuration: () => {},
setVideoEnd: () => {},
shouldOpenModal: false,
videos: [],
updateVideoStartTime: () => {},
};
Onboarding.propTypes = {
getVideos: PropTypes.func.isRequired,
onClick: PropTypes.func,
removeVideos: PropTypes.func,
setVideoDuration: PropTypes.func,
setVideoEnd: PropTypes.func,
shouldOpenModal: PropTypes.bool,
updateVideoStartTime: PropTypes.func,
videos: PropTypes.array,
};
const mapStateToProps = makeSelectOnboarding();
function mapDispatchToProps(dispatch) {
return bindActionCreators({ getVideos, onClick, setVideoDuration, updateVideoStartTime, setVideoEnd, removeVideos }, dispatch);
}
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
);
/* Remove this line if the container doesn't have a route and
* check the documentation to see how to create the container's store
*/
const withReducer = injectReducer({ key: 'onboarding', reducer });
/* Remove the line below the container doesn't have a route and
* check the documentation to see how to create the container's store
*/
const withSaga = injectSaga({ key: 'onboarding', saga });
export default compose(
withReducer,
withSaga,
withConnect,
)(Onboarding);

View File

@ -0,0 +1,69 @@
/*
*
* Onboarding reducer
*
*/
import { fromJS } from 'immutable';
import { GET_VIDEOS_SUCCEEDED, SHOULD_OPEN_MODAL, ON_CLICK, SET_VIDEOS_DURATION, UPDATE_VIDEO_START_TIME, SET_VIDEO_END, REMOVE_VIDEOS } from './constants';
const initialState = fromJS({
videos: fromJS([]),
});
function onboardingReducer(state = initialState, action) {
switch (action.type) {
case GET_VIDEOS_SUCCEEDED:
return state.update('videos', () => fromJS(action.videos));
case SHOULD_OPEN_MODAL:
return state.update('shouldOpenModal', () => action.opened);
case ON_CLICK:
return state.updateIn(['videos'], list => {
return list.reduce((acc, current, index) => {
if (index === action.index) {
return acc.updateIn([index, 'isOpen'], v => !v);
}
return acc.updateIn([index, 'isOpen'], () => false);
}, list);
});
case SET_VIDEOS_DURATION:
return state.updateIn(['videos', action.index, 'duration'], () => action.duration);
case UPDATE_VIDEO_START_TIME: {
const storedVideos = JSON.parse(localStorage.getItem('videos'));
const videos = state.updateIn(['videos'], list => {
return list.reduce((acc, current, index) => {
if (index === action.index) {
storedVideos[index].startTime = action.startTime;
return acc.updateIn([index, 'startTime'], () => action.startTime);
}
storedVideos[index].startTime = 0;
return acc.updateIn([index, 'startTime'], () => 0);
}, list);
});
localStorage.setItem('videos', JSON.stringify(storedVideos));
return videos;
}
case SET_VIDEO_END: {
const storedVideos = JSON.parse(localStorage.getItem('videos'));
storedVideos[action.index].end = action.end;
localStorage.setItem('videos', JSON.stringify(storedVideos));
return state.updateIn(['videos', action.index, 'end'], () => action.end);
}
case REMOVE_VIDEOS:
return initialState;
default:
return state;
}
}
export default onboardingReducer;

View File

@ -0,0 +1,66 @@
import request from 'utils/request';
import { all, call, fork, takeLatest, put } from 'redux-saga/effects';
import { GET_VIDEOS } from './constants';
import { getVideosSucceeded, shouldOpenModal } from './actions';
function* getVideos() {
try {
const data = yield call(request, 'https://strapi.io/videos', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
},
false,
true,
{ noAuth: true },
);
const storedVideo = JSON.parse(localStorage.getItem('videos')) || null;
const videos = data.map(video => {
const { end, startTime } = storedVideo ? storedVideo.find(v => v.order === video.order) : { end: false, startTime: 0};
return {
...video,
duration: null,
end,
isOpen: false,
key: video.order,
startTime,
};
}).sort((a,b) => (a.order - b.order));
localStorage.setItem('videos', JSON.stringify(videos));
yield put(
getVideosSucceeded(videos),
);
const isFirstTime = JSON.parse(localStorage.getItem('onboarding')) || null;
if (isFirstTime === null) {
yield new Promise(resolve => {
setTimeout(() => {
resolve();
}, 500);
});
yield put(
shouldOpenModal(true),
);
localStorage.setItem('onboarding', true);
}
} catch (err) {
console.log(err); // eslint-disable-line no-console
}
}
function* defaultSaga() {
yield all([fork(takeLatest, GET_VIDEOS, getVideos)]);
}
export default defaultSaga;

View File

@ -0,0 +1,25 @@
import { createSelector } from 'reselect';
/**
* Direct selector to the onboarding state domain
*/
const selectOnboardingDomain = () => (state) => state.get('onboarding');
/**
* Other specific selectors
*/
/**
* Default selector used by Onboarding
*/
const makeSelectOnboarding = () => createSelector(
selectOnboardingDomain(),
(substate) => substate.toJS()
);
export default makeSelectOnboarding;
export {
selectOnboardingDomain,
};

View File

@ -0,0 +1,116 @@
.videosWrapper {
position: fixed;
right: 15px;
bottom: 15px;
button,
button:focus,
a {
cursor: pointer;
outline: 0;
}
p {
margin-bottom: 0;
}
.videosHeader {
padding: 25px 15px 0 15px;
p {
display: inline-block;
vertical-align: top;
width: 50%;
font-family: Lato;
font-weight: bold;
font-size: 11px;
color: #5c5f66;
letter-spacing: 0.5px;
text-transform: uppercase;
&:last-of-type {
color: #5a9e06;
text-align: right;
}
}
}
&.visible {
opacity: 1;
}
&.hidden {
opacity: 0;
}
.videosContent {
min-width: 320px;
margin-bottom: 10px;
margin-right: 15px;
background-color: white;
box-shadow: 0 2px 4px 0 #e3e9f3;
border-radius: 3px;
overflow: hidden;
&.shown {
animation: fadeIn 0.5s forwards;
}
&.hide {
animation: fadeOut 0.5s forwards;
}
ul {
padding: 10px 0;
margin-bottom: 0;
list-style: none;
}
}
.openBtn {
float: right;
width: 38px;
height: 38px;
button {
width: 100%;
height: 100%;
border-radius: 50%;
color: white;
background: #0e7de7;
box-shadow: 0px 2px 4px 0px rgba(227, 233, 243, 1);
i:last-of-type {
display: none;
}
&.active {
i:first-of-type {
display: none;
}
i:last-of-type {
display: block;
}
}
}
}
}
@keyframes fadeIn {
0% {
width: auto;
height: auto;
opacity: 0;
}
5% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fadeOut {
0% {
opacity: 1;
}
60% {
opacity: 0;
}
100% {
opacity: 0;
width: 0;
height: 0;
}
}

View File

@ -0,0 +1,18 @@
import {
defaultAction,
} from '../actions';
import {
DEFAULT_ACTION,
} from '../constants';
describe('Onboarding actions', () => {
describe('Default Action', () => {
it('has a type of DEFAULT_ACTION', () => {
const expected = {
type: DEFAULT_ACTION,
};
expect(defaultAction()).toEqual(expected);
});
});
});

View File

@ -0,0 +1,10 @@
// import React from 'react';
// import { shallow } from 'enzyme';
// import { Onboarding } from '../index';
describe('<Onboarding />', () => {
it('Expect to have unit tests specified', () => {
expect(true).toEqual(true);
});
});

View File

@ -0,0 +1,9 @@
import { fromJS } from 'immutable';
import onboardingReducer from '../reducer';
describe('onboardingReducer', () => {
it('returns the initial state', () => {
expect(onboardingReducer(undefined, [])).toEqual(fromJS([]));
});
});

View File

@ -0,0 +1,15 @@
/**
* Test sagas
*/
/* eslint-disable redux-saga/yield-effects */
// import { take, call, put, select } from 'redux-saga/effects';
// import { defaultSaga } from '../saga';
// const generator = defaultSaga();
describe('defaultSaga Saga', () => {
it('Expect to have unit tests specified', () => {
expect(true).toEqual(true);
});
});

View File

@ -0,0 +1,10 @@
// import { fromJS } from 'immutable';
// import { makeSelectOnboardingDomain } from '../selectors';
// const selector = makeSelectOnboardingDomain();
describe('makeSelectOnboardingDomain', () => {
it('Expect to have unit tests specified', () => {
expect(true).toEqual(true);
});
});

View File

@ -13,7 +13,7 @@ import { createSelector } from 'reselect';
import BlockerComponent from 'components/BlockerComponent';
import ErrorBoundary from 'components/ErrorBoundary';
import { selectPlugins } from 'containers/App/selectors';
import { selectPlugins } from '../App/selectors';
export class PluginPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {

View File

@ -6,9 +6,9 @@ import { fromJS } from 'immutable';
import { combineReducers } from 'redux-immutable';
import { LOCATION_CHANGE } from 'react-router-redux';
import globalReducer from 'containers/App/reducer';
import languageProviderReducer from 'containers/LanguageProvider/reducer';
import notificationProviderReducer from 'containers/NotificationProvider/reducer';
import globalReducer from './containers/App/reducer';
import languageProviderReducer from './containers/LanguageProvider/reducer';
import notificationProviderReducer from './containers/NotificationProvider/reducer';
/*
* routeReducer

View File

@ -6,8 +6,8 @@ import { Provider } from 'react-redux';
import React from 'react';
import ReactDOM from 'react-dom';
import { ConnectedRouter } from 'react-router-redux';
import LanguageProvider from 'containers/LanguageProvider';
import App from 'containers/App';
import LanguageProvider from './containers/LanguageProvider';
import App from './containers/App';
import { history, store } from './createStore';
const render = (translatedMessages) => {
@ -23,4 +23,4 @@ const render = (translatedMessages) => {
);
};
export default render;
export default render;

View File

@ -8,8 +8,8 @@ import {
pluginLoaded,
unfreezeApp,
updatePlugin,
} from 'containers/App/actions';
import { showNotification } from 'containers/NotificationProvider/actions';
} from './containers/App/actions';
import { showNotification } from './containers/NotificationProvider/actions';
import injectReducer from './utils/injectReducer';
import injectSaga from './utils/injectSaga';
import { history, store } from './createStore';

View File

@ -84,6 +84,8 @@
"app.components.NotFoundPage.back": "Back to homepage",
"app.components.NotFoundPage.description": "Not Found",
"app.components.Official": "Official",
"app.components.Onboarding.label.completed": "% completed",
"app.components.Onboarding.title": "Get Started Videos",
"app.components.PluginCard.Button.label.download": "Download",
"app.components.PluginCard.Button.label.install": "Already installed",
"app.components.PluginCard.Button.label.support": "Support us",
@ -145,4 +147,4 @@
"notification.error.layout": "Couldn't retrieve the layout",
"request.error.model.unknown": "This model doesn't exist",
"app.utils.delete": "Delete"
}
}

View File

@ -85,6 +85,8 @@
"app.components.NotFoundPage.back": "Retourner à la page d'accueil",
"app.components.NotFoundPage.description": "Page introuvable",
"app.components.Official": "Officiel",
"app.components.Onboarding.label.completed": "% complétées",
"app.components.Onboarding.title": "Démarrons ensemble",
"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",
@ -146,4 +148,4 @@
"notification.error.layout": "Impossible de récupérer le layout de l'admin",
"request.error.model.unknown": "Le model n'existe pas",
"app.utils.delete": "Supprimer"
}
}

View File

@ -27,8 +27,10 @@
"dependencies": {
"intl": "^1.2.5",
"react-ga": "^2.4.1",
"redux": "^4.0.1",
"remove-markdown": "^0.2.2",
"shelljs": "^0.7.8"
"shelljs": "^0.7.8",
"video-react": "^0.13.2"
},
"devDependencies": {
"cross-env": "^5.0.5",

View File

@ -36,4 +36,4 @@ if (isDevelopmentMode) {
shell.exec('npm install', {silent});
}
shell.echo('Packaged installed successfully');
shell.echo('Packages installed successfully');

View File

@ -121,7 +121,7 @@ module.exports = {
const data = _.omit(values, <%= globalID %>.associations.map(ast => ast.alias));
// Create entry with no-relational data.
const entry = <%= globalID %>.forge(params).save(data);
const entry = await <%= globalID %>.forge(params).save(data);
// Create relational data and return the entry.
return <%= globalID %>.updateRelations(Object.assign(params, { values: relations }));

View File

@ -0,0 +1 @@
package-lock=false

View File

@ -16,9 +16,13 @@ if (!isSetup) {
strapi.log.level = 'silent';
(async () => {
await strapi.load({
environment: process.env.NODE_ENV,
});
try {
await strapi.load({
environment: process.env.NODE_ENV,
});
} catch (e) {
// console.log(e);
}
// Force exit process if an other process doen't exit during Strapi load.
process.exit();

View File

@ -47,7 +47,7 @@ const addDevMiddlewares = (app, webpackConfig) => {
/**
* Front-end middleware
*/
module.exports = (app) => {
module.exports = app => {
const webpackConfig = require('../../internals/webpack/webpack.dev.babel');
// const webpackConfig = require(path.resolve(process.cwd(), 'node_modules', 'strapi-helper-plugin', 'internals', 'webpack', 'webpack.dev.babel'));

View File

@ -11,7 +11,7 @@ import './public-path.js'; // eslint-disable-line import/extensions
import React from 'react';
import Loadable from 'react-loadable';
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
import LoadingIndicatorPage from './components/LoadingIndicatorPage';
import { translationMessages } from './i18n';
const LoadableApp = Loadable({

View File

@ -7,7 +7,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import Button from 'components/Button';
import Button from '../Button';
import styles from './styles.scss';
function EmptyAttributesBlock({ description, label, onClick, title, id }) {
@ -49,4 +49,4 @@ EmptyAttributesBlock.propTypes = {
title: PropTypes.string,
};
export default EmptyAttributesBlock;
export default EmptyAttributesBlock;

View File

@ -11,7 +11,7 @@ import { map } from 'lodash';
import PropTypes from 'prop-types';
// Utils
import { darken } from 'utils/colors';
import { darken } from '../../utils/colors';
// Styles
import styles from './styles.scss';

View File

@ -2,7 +2,7 @@ import React from 'react';
import { map } from 'lodash';
import PropTypes from 'prop-types';
import Ico from 'components/Ico';
import Ico from '../Ico';
import styles from './styles.scss';
function IcoContainer({ icons }) {

View File

@ -11,9 +11,9 @@ import PropTypes from 'prop-types';
import { get, has, isArray, isEmpty, startsWith, size } from 'lodash';
import cn from 'classnames';
import BkgImg from 'assets/icons/icon_upload.svg';
import ImgPreviewArrow from 'components/ImgPreviewArrow';
import ImgPreviewHint from 'components/ImgPreviewHint';
import BkgImg from '../../assets/icons/icon_upload.svg';
import ImgPreviewArrow from '../ImgPreviewArrow';
import ImgPreviewHint from '../ImgPreviewHint';
import styles from './styles.scss';

View File

@ -9,15 +9,15 @@ import PropTypes from 'prop-types';
import { isEmpty, isFunction } from 'lodash';
import cn from 'classnames';
// Design
import Label from 'components/Label';
import InputDescription from 'components/InputDescription';
import InputErrors from 'components/InputErrors';
import InputAddon from 'components/InputAddon';
import InputSpacer from 'components/InputSpacer';
// Utils
import validateInput from 'utils/inputsValidations';
import validateInput from '../../utils/inputsValidations';
// Design
import Label from '../Label';
import InputDescription from '../InputDescription';
import InputErrors from '../InputErrors';
import InputAddon from '../InputAddon';
import InputSpacer from '../InputSpacer';
import styles from './styles.scss';

View File

@ -1,6 +1,6 @@
import Loadable from 'react-loadable';
import LoadingIndicator from 'components/LoadingIndicator';
import LoadingIndicator from '../LoadingIndicator';
export default Loadable({
loader: () => import('./index'),

View File

@ -11,10 +11,10 @@ import { isEmpty, isObject, isFunction } from 'lodash';
import cn from 'classnames';
// Design
import InputDescription from 'components/InputDescription';
import InputErrors from 'components/InputErrors';
import InputCheckbox from 'components/InputCheckbox';
import InputSpacer from 'components/InputSpacer';
import InputDescription from '../InputDescription';
import InputErrors from '../InputErrors';
import InputCheckbox from '../InputCheckbox';
import InputSpacer from '../InputSpacer';
import styles from './styles.scss';

View File

@ -1,6 +1,6 @@
import Loadable from 'react-loadable';
import LoadingIndicator from 'components/LoadingIndicator';
import LoadingIndicator from '../LoadingIndicator';
export default Loadable({
loader: () => import('./index'),

View File

@ -9,15 +9,15 @@ import PropTypes from 'prop-types';
import { get, isEmpty, isFunction } from 'lodash';
import cn from 'classnames';
// Design
import Label from 'components/Label';
import InputDescription from 'components/InputDescription';
import InputErrors from 'components/InputErrors';
import InputDate from 'components/InputDate';
import InputSpacer from 'components/InputSpacer';
// Utils
import validateInput from 'utils/inputsValidations';
import validateInput from '../../utils/inputsValidations';
// Design
import Label from '../Label';
import InputDescription from '../InputDescription';
import InputErrors from '../InputErrors';
import InputDate from '../InputDate';
import InputSpacer from '../InputSpacer';
import styles from './styles.scss';

View File

@ -9,15 +9,15 @@ import PropTypes from 'prop-types';
import { isEmpty, isFunction } from 'lodash';
import cn from 'classnames';
// Design
import Label from 'components/Label';
import InputDescription from 'components/InputDescription';
import InputErrors from 'components/InputErrors';
import InputEmail from 'components/InputEmail';
import InputSpacer from 'components/InputSpacer';
// Utils
import validateInput from 'utils/inputsValidations';
import validateInput from '../../utils/inputsValidations';
// Design
import Label from '../Label';
import InputDescription from '../InputDescription';
import InputErrors from '../InputErrors';
import InputEmail from '../InputEmail';
import InputSpacer from '../InputSpacer';
import styles from './styles.scss';

View File

@ -11,8 +11,8 @@ import { FormattedMessage } from 'react-intl';
import { cloneDeep, isArray, isObject } from 'lodash';
import cn from 'classnames';
import ImgPreview from 'components/ImgPreview';
import InputFileDetails from 'components/InputFileDetails';
import ImgPreview from '../ImgPreview';
import InputFileDetails from '../InputFileDetails';
import styles from './styles.scss';

View File

@ -10,11 +10,11 @@ import cn from 'classnames';
import { differenceBy, isEmpty } from 'lodash';
// Design
import Label from 'components/Label';
import InputDescription from 'components/InputDescription';
import InputFile from 'components/InputFile';
import InputSpacer from 'components/InputSpacer';
import InputErrors from 'components/InputErrors';
import Label from '../Label';
import InputDescription from '../InputDescription';
import InputFile from '../InputFile';
import InputSpacer from '../InputSpacer';
import InputErrors from '../InputErrors';
// Styles
import styles from './styles.scss';

View File

@ -1,6 +1,6 @@
import Loadable from 'react-loadable';
import LoadingIndicator from 'components/LoadingIndicator';
import LoadingIndicator from '../LoadingIndicator';
export default Loadable({
loader: () => import('./index'),

View File

@ -3,15 +3,15 @@ import PropTypes from 'prop-types';
import { isEmpty, isFunction } from 'lodash';
import cn from 'classnames';
// Design
import Label from 'components/Label';
import InputDescription from 'components/InputDescription';
import InputErrors from 'components/InputErrors';
import InputNumber from 'components/InputNumber';
import InputSpacer from 'components/InputSpacer';
// Utils
import validateInput from 'utils/inputsValidations';
import validateInput from '../../utils/inputsValidations';
// Design
import Label from '../Label';
import InputDescription from '../InputDescription';
import InputErrors from '../InputErrors';
import InputNumber from '../InputNumber';
import InputSpacer from '../InputSpacer';
import styles from './styles.scss';

View File

@ -9,15 +9,15 @@ import PropTypes from 'prop-types';
import { isEmpty, isFunction } from 'lodash';
import cn from 'classnames';
// Design
import Label from 'components/Label';
import InputDescription from 'components/InputDescription';
import InputErrors from 'components/InputErrors';
import InputPassword from 'components/InputPassword';
import InputSpacer from 'components/InputSpacer';
// Utils
import validateInput from 'utils/inputsValidations';
import validateInput from '../../utils/inputsValidations';
// Design
import Label from '../Label';
import InputDescription from '../InputDescription';
import InputErrors from '../InputErrors';
import InputPassword from '../InputPassword';
import InputSpacer from '../InputSpacer';
import styles from './styles.scss';

View File

@ -9,15 +9,15 @@ import PropTypes from 'prop-types';
import { isEmpty, isFunction } from 'lodash';
import cn from 'classnames';
// Design
import Label from 'components/Label';
import InputDescription from 'components/InputDescription';
import InputErrors from 'components/InputErrors';
import InputSearch from 'components/InputSearch';
import InputSpacer from 'components/InputSpacer';
// Utils
import validateInput from 'utils/inputsValidations';
import validateInput from '../../utils/inputsValidations';
// Design
import Label from '../Label';
import InputDescription from '../InputDescription';
import InputErrors from '../InputErrors';
import InputSearch from '../InputSearch';
import InputSpacer from '../InputSpacer';
import styles from './styles.scss';

View File

@ -1,6 +1,6 @@
import Loadable from 'react-loadable';
import LoadingIndicator from 'components/LoadingIndicator';
import LoadingIndicator from '../LoadingIndicator';
export default Loadable({
loader: () => import('./index'),

View File

@ -10,7 +10,7 @@ import { isEmpty, isObject, map } from 'lodash';
import cn from 'classnames';
// Design
import SelectOption from 'components/SelectOption';
import SelectOption from '../SelectOption';
import styles from './styles.scss';

View File

@ -10,10 +10,10 @@ import { get, isEmpty, isFunction } from 'lodash';
import cn from 'classnames';
// Design
import Label from 'components/Label';
import InputDescription from 'components/InputDescription';
import InputErrors from 'components/InputErrors';
import InputSelect from 'components/InputSelect';
import Label from '../Label';
import InputDescription from '../InputDescription';
import InputErrors from '../InputErrors';
import InputSelect from '../InputSelect';
import styles from './styles.scss';

View File

@ -1,6 +1,6 @@
import Loadable from 'react-loadable';
import LoadingIndicator from 'components/LoadingIndicator';
import LoadingIndicator from '../LoadingIndicator';
export default Loadable({
loader: () => import('./index'),

View File

@ -3,15 +3,15 @@ import PropTypes from 'prop-types';
import { isEmpty, isFunction } from 'lodash';
import cn from 'classnames';
// Design
import Label from 'components/Label';
import InputDescription from 'components/InputDescription';
import InputErrors from 'components/InputErrors';
import InputTextArea from 'components/InputTextArea';
import InputSpacer from 'components/InputSpacer';
// Utils
import validateInput from 'utils/inputsValidations';
import validateInput from '../../utils/inputsValidations';
// Design
import Label from '../Label';
import InputDescription from '../InputDescription';
import InputErrors from '../InputErrors';
import InputTextArea from '../InputTextArea';
import InputSpacer from '../InputSpacer';
import styles from './styles.scss';

View File

@ -3,15 +3,15 @@ import PropTypes from 'prop-types';
import { isEmpty, isFunction } from 'lodash';
import cn from 'classnames';
// Design
import Label from 'components/Label';
import InputDescription from 'components/InputDescription';
import InputErrors from 'components/InputErrors';
import InputText from 'components/InputText';
import InputSpacer from 'components/InputSpacer';
// Utils
import validateInput from 'utils/inputsValidations';
import validateInput from '../../utils/inputsValidations';
// Design
import Label from '../Label';
import InputDescription from '../InputDescription';
import InputErrors from '../InputErrors';
import InputText from '../InputText';
import InputSpacer from '../InputSpacer';
import styles from './styles.scss';

View File

@ -9,11 +9,11 @@ import PropTypes from 'prop-types';
import cn from 'classnames';
import { isEmpty } from 'lodash';
// Design
import Label from 'components/Label';
import InputDescription from 'components/InputDescription';
import InputErrors from 'components/InputErrors';
import InputToggle from 'components/InputToggle';
import InputSpacer from 'components/InputSpacer';
import Label from '../Label';
import InputDescription from '../InputDescription';
import InputErrors from '../InputErrors';
import InputToggle from '../InputToggle';
import InputSpacer from '../InputSpacer';
import styles from './styles.scss';

View File

@ -9,18 +9,18 @@ import PropTypes from 'prop-types';
import { isEmpty, isObject, merge } from 'lodash';
// Design
import InputAddonWithErrors from 'components/InputAddonWithErrors';
import InputCheckboxWithErrors from 'components/InputCheckboxWithErrors';
import InputDateWithErrors from 'components/InputDateWithErrors';
import InputEmailWithErrors from 'components/InputEmailWithErrors';
import InputFileWithErrors from 'components/InputFileWithErrors';
import InputNumberWithErrors from 'components/InputNumberWithErrors';
import InputSearchWithErrors from 'components/InputSearchWithErrors';
import InputSelectWithErrors from 'components/InputSelectWithErrors';
import InputPasswordWithErrors from 'components/InputPasswordWithErrors';
import InputTextAreaWithErrors from 'components/InputTextAreaWithErrors';
import InputTextWithErrors from 'components/InputTextWithErrors';
import InputToggleWithErrors from 'components/InputToggleWithErrors';
import InputAddonWithErrors from '../InputAddonWithErrors';
import InputCheckboxWithErrors from '../InputCheckboxWithErrors';
import InputDateWithErrors from '../InputDateWithErrors';
import InputEmailWithErrors from '../InputEmailWithErrors';
import InputFileWithErrors from '../InputFileWithErrors';
import InputNumberWithErrors from '../InputNumberWithErrors';
import InputSearchWithErrors from '../InputSearchWithErrors';
import InputSelectWithErrors from '../InputSelectWithErrors';
import InputPasswordWithErrors from '../InputPasswordWithErrors';
import InputTextAreaWithErrors from '../InputTextAreaWithErrors';
import InputTextWithErrors from '../InputTextWithErrors';
import InputToggleWithErrors from '../InputToggleWithErrors';
const DefaultInputError = ({ type }) => <div>Your input type: <b>{type}</b> does not exist</div>;

View File

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import Button from 'components/Button';
import Button from '../Button';
import styles from './styles.scss';

View File

@ -10,7 +10,7 @@ import cn from 'classnames';
import { get } from 'lodash';
import { FormattedMessage } from 'react-intl';
import GlobalPagination from 'components/GlobalPagination';
import GlobalPagination from '../GlobalPagination';
import styles from './styles.scss';

View File

@ -8,8 +8,8 @@ import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import PluginHeaderTitle from 'components/PluginHeaderTitle';
import PluginHeaderActions from 'components/PluginHeaderActions';
import PluginHeaderTitle from '../PluginHeaderTitle';
import PluginHeaderActions from '../PluginHeaderActions';
import styles from './styles.scss';

View File

@ -8,7 +8,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { isArray, isFunction } from 'lodash';
import Button from 'components/Button';
import Button from '../Button';
import styles from './styles.scss';

View File

@ -9,7 +9,7 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { isEmpty, isFunction, isObject } from 'lodash';
import LoadingBar from 'components/LoadingBar';
import LoadingBar from '../LoadingBar';
import styles from './styles.scss';

View File

@ -1,6 +1,6 @@
import Loadable from 'react-loadable';
import LoadingIndicator from 'components/LoadingIndicator';
import LoadingIndicator from '../LoadingIndicator';
export default Loadable({
loader: () => import('./index'),

View File

@ -21,7 +21,12 @@ const auth = {
clearAppStorage() {
if (localStorage) {
const videos = auth.get('videos');
const onboarding = auth.get('onboarding');
localStorage.clear();
localStorage.setItem('videos', JSON.stringify(videos));
localStorage.setItem('onboarding', onboarding);
}
if (sessionStorage) {
@ -73,7 +78,6 @@ const auth = {
return null;
},
setToken(value = '', isLocalStorage = false, tokenKey = TOKEN_KEY) {
return auth.set(value, tokenKey, isLocalStorage);
},

View File

@ -1,5 +1,5 @@
import 'whatwg-fetch';
import auth from 'utils/auth';
import auth from './auth';
/**
* Parses the JSON returned by a network request
@ -20,7 +20,7 @@ function parseJSON(response) {
* @return {object|undefined} Returns either the response, or throws an error
*/
function checkStatus(response, checkToken = true) {
if (response.status >= 200 && response.status < 300) {
if ((response.status >= 200 && response.status < 300) || response.status === 0) {
return response;
}
@ -41,21 +41,22 @@ function checkTokenValidity(response) {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${auth.getToken()}`,
Authorization: `Bearer ${auth.getToken()}`,
},
};
if (auth.getToken()) {
return fetch(`${strapi.backendURL}/user/me`, options)
.then(() => {
if (response.status === 401) {
window.location = `${strapi.remoteURL}/plugins/users-permissions/auth/login`;
return fetch(`${strapi.backendURL}/user/me`, options).then(() => {
if (response.status === 401) {
window.location = `${
strapi.remoteURL
}/plugins/users-permissions/auth/login`;
auth.clearAppStorage();
}
auth.clearAppStorage();
}
return checkStatus(response, false);
});
return checkStatus(response, false);
});
}
}
@ -72,12 +73,12 @@ function formatQueryParams(params) {
}
/**
* Server restart watcher
* @param response
* @returns {object} the response data
*/
* Server restart watcher
* @param response
* @returns {object} the response data
*/
function serverRestartWatcher(response) {
return new Promise((resolve) => {
return new Promise(resolve => {
fetch(`${strapi.backendURL}/_health`, {
method: 'HEAD',
mode: 'no-cors',
@ -93,8 +94,7 @@ function serverRestartWatcher(response) {
})
.catch(() => {
setTimeout(() => {
return serverRestartWatcher(response)
.then(resolve);
return serverRestartWatcher(response).then(resolve);
}, 100);
});
});
@ -108,22 +108,38 @@ function serverRestartWatcher(response) {
*
* @return {object} The response data
*/
export default function request(url, options = {}, shouldWatchServerRestart = false, stringify = true ) {
export default function request(...args) {
let [url, options = {}, shouldWatchServerRestart, stringify = true, ...rest] = args;
let noAuth;
try {
[{ noAuth }] = rest;
} catch(err) {
noAuth = false;
}
// Set headers
if (!options.headers) {
options.headers = Object.assign({
'Content-Type': 'application/json',
}, options.headers, {
'X-Forwarded-Host': 'strapi',
});
options.headers = Object.assign(
{
'Content-Type': 'application/json',
},
options.headers,
{
'X-Forwarded-Host': 'strapi',
},
);
}
const token = auth.getToken();
if (token) {
options.headers = Object.assign({
'Authorization': `Bearer ${token}`,
}, options.headers);
if (token && !noAuth) {
options.headers = Object.assign(
{
Authorization: `Bearer ${token}`,
},
options.headers,
);
}
// Add parameters to url
@ -138,11 +154,11 @@ export default function request(url, options = {}, shouldWatchServerRestart = fa
if (options && options.body && stringify) {
options.body = JSON.stringify(options.body);
}
return fetch(url, options)
.then(checkStatus)
.then(parseJSON)
.then((response) => {
.then(response => {
if (shouldWatchServerRestart) {
// Display the global OverlayBlocker
strapi.lockApp(shouldWatchServerRestart);

View File

@ -155,6 +155,10 @@ module.exports = strapi => {
} catch (err) {
fs.mkdirSync(fileDirectory);
}
// Force base directory.
// Note: it removes the warning logs when starting the administration in development mode.
options.connection.filename = path.resolve(strapi.config.appPath, options.connection.filename);
// Disable warn log
// .returning() is not supported by sqlite3 and will not have any effect.

View File

@ -9,8 +9,8 @@ import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
// Design
import Button from 'components/CustomButton';
import Logo from '../../assets/images/icon_filter.png';
import Button from '../CustomButton';
import styles from './styles.scss';

View File

@ -7,9 +7,12 @@ import React from 'react';
import PropTypes from 'prop-types';
import { DragLayer } from 'react-dnd';
import { flow } from 'lodash';
import DragBox from 'components/DragBox';
import SelectManyDraggedItem from 'components/SelectManyDraggedItem';
import ItemTypes from 'utils/ItemTypes';
import ItemTypes from '../../utils/ItemTypes';
import DragBox from '../DragBox';
import SelectManyDraggedItem from '../SelectManyDraggedItem';
import styles from './styles.scss';
function getItemStyles(props) {
@ -79,4 +82,4 @@ CustomDragLayer.propTypes = {
itemType: PropTypes.string,
};
export default flow([withDragLayer])(CustomDragLayer);
export default flow([withDragLayer])(CustomDragLayer);

View File

@ -5,9 +5,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import DraggedRemovedIcon from 'components/DraggedRemovedIcon';
import GrabIcon from 'assets/images/icon_grab_blue.svg';
import GrabIcon from '../../assets/images/icon_grab_blue.svg';
import DraggedRemovedIcon from '../DraggedRemovedIcon';
import styles from './styles.scss';
@ -30,4 +31,4 @@ DragBox.propTypes = {
name: PropTypes.string,
};
export default DragBox;
export default DragBox;

View File

@ -14,13 +14,15 @@ import { getEmptyImage } from 'react-dnd-html5-backend';
import { flow } from 'lodash';
import PropTypes from 'prop-types';
import cn from 'classnames';
import ClickOverHint from 'components/ClickOverHint';
import DraggedRemovedIcon from 'components/DraggedRemovedIcon';
import VariableEditIcon from 'components/VariableEditIcon';
import ItemTypes from 'utils/ItemTypes';
import GrabIconBlue from 'assets/images/icon_grab_blue.svg';
import GrabIcon from 'assets/images/icon_grab.svg';
import ItemTypes from '../../utils/ItemTypes';
import GrabIconBlue from '../../assets/images/icon_grab_blue.svg';
import GrabIcon from '../../assets/images/icon_grab.svg';
import ClickOverHint from '../ClickOverHint';
import DraggedRemovedIcon from '../DraggedRemovedIcon';
import VariableEditIcon from '../VariableEditIcon';
import styles from './styles.scss';
@ -207,4 +209,4 @@ const withDragSource = DragSource(ItemTypes.NORMAL, draggableAttrSource, (connec
isDragging: monitor.isDragging(),
}));
export default flow([withDropTarget, withDragSource])(DraggableAttr);
export default flow([withDropTarget, withDragSource])(DraggableAttr);

View File

@ -18,8 +18,9 @@ import {
// ./node_modules/strapi-helper-plugin/lib/src
// or strapi/packages/strapi-helper-plugin/lib/src
import Input from 'components/InputsIndex';
import InputJSONWithErrors from 'components/InputJSONWithErrors';
import WysiwygWithErrors from 'components/WysiwygWithErrors';
import InputJSONWithErrors from '../InputJSONWithErrors';
import WysiwygWithErrors from '../WysiwygWithErrors';
import styles from './styles.scss';
const getInputType = (type = '') => {

View File

@ -9,8 +9,8 @@ import PropTypes from 'prop-types';
import { get } from 'lodash';
// Components.
import SelectOne from 'components/SelectOne';
import SelectMany from 'components/SelectMany';
import SelectOne from '../SelectOne';
import SelectMany from '../SelectMany';
import styles from './styles.scss';

View File

@ -7,8 +7,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import Button from 'components/Button';
import PluginHeader from 'components/PluginHeader';
import styles from './styles.scss';
function EmptyAttributesView({ currentModelName, history, modelEntries }) {

View File

@ -1,4 +1,4 @@
import FilterOptionsCTA from 'components/FilterOptionsCTA';
import FilterOptionsCTA from '../FilterOptionsCTA';
const Add = FilterOptionsCTA.extend`
&:after {

View File

@ -1,4 +1,4 @@
import FilterOptionsCTA from 'components/FilterOptionsCTA';
import FilterOptionsCTA from '../FilterOptionsCTA';
const Remove = FilterOptionsCTA.extend`
&:after {

View File

@ -8,13 +8,14 @@ import moment from 'moment';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { isObject, size } from 'lodash';
import FilterOptions from 'components/FilterOptions/Loadable';
// You can find these components in either
// ./node_modules/strapi-helper-plugin/lib/src
// or strapi/packages/strapi-helper-plugin/lib/src
import PluginHeader from 'components/PluginHeader';
import FilterOptions from '../FilterOptions/Loadable';
import Div from './Div';
import Flex from './Flex';
import SpanStyled from './SpanStyled';

Some files were not shown because too many files have changed in this diff Show More