mirror of
https://github.com/strapi/strapi.git
synced 2025-12-23 05:03:41 +00:00
Merge branch 'master' into patch-1
This commit is contained in:
commit
193d2bdc93
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -26,3 +26,4 @@
|
||||
- [ ] MongoDB
|
||||
- [ ] MySQL
|
||||
- [ ] Postgres
|
||||
- [ ] SQLite
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
@ -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';
|
||||
@ -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,
|
||||
|
||||
@ -16,6 +16,8 @@ const initialState = fromJS({
|
||||
isLoading: true,
|
||||
layout: Map({}),
|
||||
strapiVersion: '3',
|
||||
eventName: '',
|
||||
shouldEmit: true,
|
||||
});
|
||||
|
||||
function adminPageReducer(state = initialState, action) {
|
||||
|
||||
@ -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),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
@ -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';
|
||||
182
packages/strapi-admin/admin/src/containers/Onboarding/index.js
Normal file
182
packages/strapi-admin/admin/src/containers/Onboarding/index.js
Normal 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);
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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,
|
||||
};
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
|
||||
import { fromJS } from 'immutable';
|
||||
import onboardingReducer from '../reducer';
|
||||
|
||||
describe('onboardingReducer', () => {
|
||||
it('returns the initial state', () => {
|
||||
expect(onboardingReducer(undefined, [])).toEqual(fromJS([]));
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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() {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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",
|
||||
|
||||
@ -36,4 +36,4 @@ if (isDevelopmentMode) {
|
||||
shell.exec('npm install', {silent});
|
||||
}
|
||||
|
||||
shell.echo('Packaged installed successfully');
|
||||
shell.echo('Packages installed successfully');
|
||||
|
||||
@ -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 }));
|
||||
|
||||
1
packages/strapi-generate-new/files/.npmrc
Normal file
1
packages/strapi-generate-new/files/.npmrc
Normal file
@ -0,0 +1 @@
|
||||
package-lock=false
|
||||
@ -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();
|
||||
|
||||
@ -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'));
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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 }) {
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Loadable from 'react-loadable';
|
||||
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import LoadingIndicator from '../LoadingIndicator';
|
||||
|
||||
export default Loadable({
|
||||
loader: () => import('./index'),
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Loadable from 'react-loadable';
|
||||
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import LoadingIndicator from '../LoadingIndicator';
|
||||
|
||||
export default Loadable({
|
||||
loader: () => import('./index'),
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Loadable from 'react-loadable';
|
||||
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import LoadingIndicator from '../LoadingIndicator';
|
||||
|
||||
export default Loadable({
|
||||
loader: () => import('./index'),
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Loadable from 'react-loadable';
|
||||
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import LoadingIndicator from '../LoadingIndicator';
|
||||
|
||||
export default Loadable({
|
||||
loader: () => import('./index'),
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Loadable from 'react-loadable';
|
||||
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import LoadingIndicator from '../LoadingIndicator';
|
||||
|
||||
export default Loadable({
|
||||
loader: () => import('./index'),
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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>;
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Loadable from 'react-loadable';
|
||||
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import LoadingIndicator from '../LoadingIndicator';
|
||||
|
||||
export default Loadable({
|
||||
loader: () => import('./index'),
|
||||
|
||||
@ -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);
|
||||
},
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 = '') => {
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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 }) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import FilterOptionsCTA from 'components/FilterOptionsCTA';
|
||||
import FilterOptionsCTA from '../FilterOptionsCTA';
|
||||
|
||||
const Add = FilterOptionsCTA.extend`
|
||||
&:after {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import FilterOptionsCTA from 'components/FilterOptionsCTA';
|
||||
import FilterOptionsCTA from '../FilterOptionsCTA';
|
||||
|
||||
const Remove = FilterOptionsCTA.extend`
|
||||
&:after {
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user