Merge branch 'master' of github.com:strapi/strapi into graphql

This commit is contained in:
Aurelsicoko 2018-04-17 15:10:30 +02:00
commit ad46c6b40a
13 changed files with 252 additions and 55 deletions

View File

@ -7,24 +7,39 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { isFunction, isObject } from 'lodash';
import cn from 'classnames'; import cn from 'classnames';
import styles from './styles.scss'; import styles from './styles.scss';
function Sub({ bordered, content, title, underline }) { function Sub({ bordered, content, link, name, style, title, underline }) {
if (isObject(title)) {
return ( return (
<div className={cn(styles.subWrapper, bordered && styles.subBordered)}> <div className={cn(styles.subWrapper, bordered && styles.subBordered)}>
<FormattedMessage {...title}> <FormattedMessage {...title}>
{message => <span className={cn(underline && styles.underlinedTitle)}>{message}</span>} {message => <span className={cn(underline && styles.underlinedTitle)}>{message}{name}</span>}
</FormattedMessage> </FormattedMessage>
{content()} {content()}
</div> </div>
); );
} }
return (
<a className={cn(styles.subWrapper, bordered && styles.subBordered, styles.link)} href={`https://blog.strapi.io/${link}`} target="_blank">
<span>{title}</span>
<p style={style}>
{isFunction(content) ? content() : content}
</p>
</a>
);
}
Sub.defaultProps = { Sub.defaultProps = {
bordered: false, bordered: false,
content: () => '', content: () => '',
link: '',
name: '',
style: {},
title: { title: {
id: 'app.utils.defaultMessage', id: 'app.utils.defaultMessage',
defaultMessage: 'app.utils.defaultMessage', defaultMessage: 'app.utils.defaultMessage',
@ -35,8 +50,17 @@ Sub.defaultProps = {
Sub.propTypes = { Sub.propTypes = {
bordered: PropTypes.bool, bordered: PropTypes.bool,
content: PropTypes.func, content: PropTypes.oneOfType([
title: PropTypes.object, PropTypes.func,
PropTypes.string,
]),
link: PropTypes.string,
name: PropTypes.string,
style: PropTypes.object,
title: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string,
]),
underline: PropTypes.bool, underline: PropTypes.bool,
}; };

View File

@ -6,19 +6,25 @@
.subWrapper { .subWrapper {
position: relative; position: relative;
line-height: 18px; line-height: 18px;
text-decoration: none;
> span { > span {
text-decoration: none;
font-family: Lato-Bold; font-family: Lato-Bold;
font-size: 20px; font-size: 20px;
color: #333740; color: #333740;
letter-spacing: 0; letter-spacing: 0;
transition: color .2s ease;
} }
p { p {
text-decoration: none;
display: block; display: block;
max-width: 550px; max-width: calc(100% - 150px);
margin-top: 18px; margin-top: 18px;
color: #333740; color: #333740;
font-size: 14px; font-size: 14px;
transition: color .2s ease;
} }
} }
@ -26,3 +32,15 @@
.underlinedTitle { .underlinedTitle {
border-bottom: 3px solid #F0B41E; border-bottom: 3px solid #F0B41E;
} }
.link{
&:hover, &:focus, &:active{
text-decoration: none;
}
&:hover{
> span, p {
color: lighten(#333740, 20%);
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -6,14 +6,16 @@
import React from 'react'; import React from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import styles from './styles.scss'; import styles from './styles.scss';
/* eslint-disable jsx-a11y/accessible-emoji */ /* eslint-disable jsx-a11y/accessible-emoji */
function WelcomeContent() { function WelcomeContent({ hasContent }) {
return ( return (
<React.Fragment> <React.Fragment>
<div className={styles.iconWave}>👋</div> <div className={styles.iconWave}>👋</div>
{!hasContent && (
<FormattedMessage id="app.components.HomePage.welcomeBlock.content"> <FormattedMessage id="app.components.HomePage.welcomeBlock.content">
{message => ( {message => (
<p className={styles.welcomeContentP}> <p className={styles.welcomeContentP}>
@ -36,8 +38,24 @@ function WelcomeContent() {
</p> </p>
)} )}
</FormattedMessage> </FormattedMessage>
)}
{hasContent && (
<FormattedMessage id="app.components.HomePage.welcomeBlock.content.again">
{message => (
<p className={styles.welcomeContentP}>{message}</p>
)}
</FormattedMessage>
)}
</React.Fragment> </React.Fragment>
); );
} }
WelcomeContent.defaultProps = {
hasContent: false,
};
WelcomeContent.propTypes = {
hasContent: PropTypes.bool,
};
export default WelcomeContent; export default WelcomeContent;

View File

@ -1,9 +1,24 @@
import { import {
GET_ARTICLES,
GET_ARTICLES_SUCCEEDED,
ON_CHANGE, ON_CHANGE,
SUBMIT, SUBMIT,
SUBMIT_SUCCEEDED, SUBMIT_SUCCEEDED,
} from './constants'; } from './constants';
export function getArticles() {
return {
type: GET_ARTICLES,
};
}
export function getArticlesSucceeded(articles) {
return {
type: GET_ARTICLES_SUCCEEDED,
articles,
};
}
export function onChange({ target }) { export function onChange({ target }) {
return { return {
type: ON_CHANGE, type: ON_CHANGE,

View File

@ -1,3 +1,5 @@
export const GET_ARTICLES = 'app/HomePage/GET_ARTICLES';
export const GET_ARTICLES_SUCCEEDED = 'app/HomePage/GET_ARTICLES_SUCCEEDED';
export const ON_CHANGE = 'app/HomePage/ON_CHANGE'; export const ON_CHANGE = 'app/HomePage/ON_CHANGE';
export const SUBMIT = 'app/HomePage/SUBMIT'; export const SUBMIT = 'app/HomePage/SUBMIT';
export const SUBMIT_SUCCEEDED = 'app/HomePage/SUBMIT_SUCCEEDED'; export const SUBMIT_SUCCEEDED = 'app/HomePage/SUBMIT_SUCCEEDED';

View File

@ -11,7 +11,7 @@ import { FormattedMessage } from 'react-intl';
import { bindActionCreators, compose } from 'redux'; import { bindActionCreators, compose } from 'redux';
import { createStructuredSelector } from 'reselect'; import { createStructuredSelector } from 'reselect';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { get, isEmpty } from 'lodash'; import { get, isEmpty, upperFirst } from 'lodash';
import cn from 'classnames'; import cn from 'classnames';
import Block from 'components/HomePageBlock/Loadable'; import Block from 'components/HomePageBlock/Loadable';
@ -23,6 +23,7 @@ import SupportUsTitle from 'components/SupportUsTitle/Loadable';
import { selectPlugins } from 'containers/App/selectors'; import { selectPlugins } from 'containers/App/selectors';
import auth from 'utils/auth';
import injectReducer from 'utils/injectReducer'; import injectReducer from 'utils/injectReducer';
import injectSaga from 'utils/injectSaga'; import injectSaga from 'utils/injectSaga';
import validateInput from 'utils/inputsValidations'; import validateInput from 'utils/inputsValidations';
@ -33,7 +34,7 @@ import CreateContent from './CreateContent';
import SocialLink from './SocialLink'; import SocialLink from './SocialLink';
import WelcomeContent from './WelcomeContent'; import WelcomeContent from './WelcomeContent';
import { onChange, submit } from './actions'; import { getArticles, onChange, submit } from './actions';
import makeSelectHomePage from './selectors'; import makeSelectHomePage from './selectors';
import reducer from './reducer'; import reducer from './reducer';
import saga from './saga'; import saga from './saga';
@ -54,6 +55,16 @@ const FIRST_BLOCK = [
}, },
]; ];
const WELCOME_AGAIN_BLOCK = [
{
title: {
id: 'app.components.HomePage.welcome.again',
},
name: upperFirst(`${get(auth.getUserInfo(), 'username')}!`),
content: () => <WelcomeContent hasContent />,
},
];
const FIRST_BLOCK_LINKS = [ const FIRST_BLOCK_LINKS = [
{ {
link: 'https://strapi.io/documentation/', link: 'https://strapi.io/documentation/',
@ -115,6 +126,10 @@ export class HomePage extends React.PureComponent {
// eslint-disable-line react/prefer-stateless-function // eslint-disable-line react/prefer-stateless-function
state = { errors: [] }; state = { errors: [] };
componentDidMount() {
this.props.getArticles();
}
handleSubmit = e => { handleSubmit = e => {
e.preventDefault(); e.preventDefault();
const errors = validateInput(this.props.homePage.body.email, { required: true }, 'email'); const errors = validateInput(this.props.homePage.body.email, { required: true }, 'email');
@ -128,29 +143,58 @@ export class HomePage extends React.PureComponent {
showFirstBlock = () => 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/getting-started/quick-start.html',
id: 'app.components.HomePage.button.quickStart',
primary: true,
}
: {
className: styles.homePageBlogButton,
id: 'app.components.HomePage.button.blog',
href: 'https://blog.strapi.io/',
primary: false,
};
return (
<a href={data.href} target="_blank">
<Button className={data.className} primary={data.primary}>
<FormattedMessage id={data.id} />
</Button>
</a>
);
};
render() { render() {
const { homePage: { body } } = this.props; const { homePage: { articles, body } } = this.props;
return ( return (
<div className={cn('container-fluid', styles.containerFluid)}> <div className={cn('container-fluid', styles.containerFluid)}>
<Helmet title="Home Page" /> <Helmet title="Home Page" />
<div className="row"> <div className="row">
<div className="col-md-8 col-lg-8"> <div className="col-md-8 col-lg-8">
{this.showFirstBlock() && (
<Block> <Block>
{FIRST_BLOCK.map((value, key) => ( {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} />
))} ))}
<a href="https://strapi.io/documentation/getting-started/quick-start.html" target="_blank"> {!this.showFirstBlock() &&
<Button className={styles.homePageTutorialButton} primary> WELCOME_AGAIN_BLOCK.concat(articles).map((value, key) => (
<FormattedMessage id="app.components.HomePage.button.quickStart" /> <Sub
</Button> key={key}
</a> {...value}
bordered={key === 0}
style={key === 1 ? { marginBottom: '33px' } : {}}
underline={key === 0}
/>
))}
{this.renderButton()}
<div className={styles.homePageFlex}> <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> </div>
</Block> </Block>
)}
<Block> <Block>
<Sub {...SECOND_BLOCK} /> <Sub {...SECOND_BLOCK} />
<div className={styles.homePageFlex}> <div className={styles.homePageFlex}>
@ -199,6 +243,7 @@ export class HomePage extends React.PureComponent {
} }
HomePage.propTypes = { HomePage.propTypes = {
getArticles: PropTypes.func.isRequired,
homePage: PropTypes.object.isRequired, homePage: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
plugins: PropTypes.object.isRequired, plugins: PropTypes.object.isRequired,
@ -213,6 +258,7 @@ const mapStateToProps = createStructuredSelector({
function mapDispatchToProps(dispatch) { function mapDispatchToProps(dispatch) {
return bindActionCreators( return bindActionCreators(
{ {
getArticles,
onChange, onChange,
submit, submit,
}, },

View File

@ -3,11 +3,12 @@
* HomePage reducer * HomePage reducer
*/ */
import { fromJS, Map } from 'immutable'; import { fromJS, List, Map } from 'immutable';
import { ON_CHANGE, SUBMIT_SUCCEEDED } from './constants'; import { GET_ARTICLES_SUCCEEDED, ON_CHANGE, SUBMIT_SUCCEEDED } from './constants';
const initialState = fromJS({ const initialState = fromJS({
articles: List([]),
body: Map({ body: Map({
email: '', email: '',
}), }),
@ -15,6 +16,8 @@ const initialState = fromJS({
function homePageReducer(state = initialState, action) { function homePageReducer(state = initialState, action) {
switch (action.type) { switch (action.type) {
case GET_ARTICLES_SUCCEEDED:
return state.update('articles', () => List(action.articles));
case ON_CHANGE: case ON_CHANGE:
return state.updateIn(['body', 'email'], () => action.value); return state.updateIn(['body', 'email'], () => action.value);
case SUBMIT_SUCCEEDED: case SUBMIT_SUCCEEDED:

View File

@ -1,3 +1,6 @@
import 'whatwg-fetch';
import { dropRight, take } from 'lodash';
import removeMd from 'remove-markdown';
import { import {
call, call,
fork, fork,
@ -8,10 +11,33 @@ import {
import request from 'utils/request'; import request from 'utils/request';
import { submitSucceeded } from './actions'; import { getArticlesSucceeded, submitSucceeded } from './actions';
import { SUBMIT } from './constants'; import { GET_ARTICLES, SUBMIT } from './constants';
import { makeSelectBody } from './selectors'; import { makeSelectBody } from './selectors';
function* getArticles() {
try {
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(' ');
acc.push({
title: curr.title,
link: curr.slug,
content: `${content} [...]`,
});
return acc;
}, []);
yield put(getArticlesSucceeded(posts));
} catch(err) {
// Silent
}
}
function* submit() { function* submit() {
try { try {
const body = yield select(makeSelectBody()); const body = yield select(makeSelectBody());
@ -26,6 +52,14 @@ function* submit() {
function* defaultSaga() { function* defaultSaga() {
yield fork(takeLatest, SUBMIT, submit); yield fork(takeLatest, SUBMIT, submit);
yield fork(takeLatest, GET_ARTICLES, getArticles);
} }
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;
});
}
export default defaultSaga; export default defaultSaga;

View File

@ -173,6 +173,32 @@
} }
} }
.homePageBlogButton {
position: relative;
height: 34px;
margin-top: 17px;
margin-bottom: 1px;
padding-left: 40px;
padding-right: 20px;
background: #333740;
color: white;
font-size: 13px;
font-weight: 800;
line-height: 33px;
letter-spacing: 0.46px;
text-align: left;
&:before {
content: '\f105';
position: absolute;
top: 0;
bottom: 0;
left: 20px;
font-size: 22px;
margin-right: 10px;
font-family: 'FontAwesome';
}
}
.iconWave { .iconWave {
position: absolute; position: absolute;
top: 24px; top: 24px;
@ -237,6 +263,6 @@
.welcomeContentP { .welcomeContentP {
display: block; display: block;
max-width: 49rem !important; max-width: 55rem !important;
margin-bottom: 31px; margin-bottom: 31px;
} }

View File

@ -9,18 +9,21 @@
"app.components.DownloadInfo.text": "This could take a minute. Thanks for your patience.", "app.components.DownloadInfo.text": "This could take a minute. Thanks for your patience.",
"app.components.HomePage.welcome": "Welcome on board!", "app.components.HomePage.welcome": "Welcome on board!",
"app.components.HomePage.welcome.again": "Welcome ",
"app.components.HomePage.cta": "CONFIRM", "app.components.HomePage.cta": "CONFIRM",
"app.components.HomePage.community": "Find the community on the web", "app.components.HomePage.community": "Find the community on the web",
"app.components.HomePage.newsLetter": "Subscribe to the newsletter to get in touch about Strapi", "app.components.HomePage.newsLetter": "Subscribe to the newsletter to get in touch about Strapi",
"app.components.HomePage.community.content": "Discuss with team members, contributors and developers on different channels.", "app.components.HomePage.community.content": "Discuss with team members, contributors and developers on different channels.",
"app.components.HomePage.create": "Create your first Content Type", "app.components.HomePage.create": "Create your first Content Type",
"app.components.HomePage.welcomeBlock.content": "We are happy to have you as one of community member. We are constantly looking for feedback so feel free to send us DM on\u0020", "app.components.HomePage.welcomeBlock.content": "We are happy to have you as one of community member. We are constantly looking for feedback so feel free to send us DM on\u0020",
"app.components.HomePage.welcomeBlock.content.again": "We hope you are making progress on your project... Feel free to read the latest new about Strapi. We are giving our best to improve the product based on your feedback.",
"app.components.HomePage.welcomeBlock.content.issues": "issues.", "app.components.HomePage.welcomeBlock.content.issues": "issues.",
"app.components.HomePage.welcomeBlock.content.raise": "\u0020or raise\u0020", "app.components.HomePage.welcomeBlock.content.raise": "\u0020or raise\u0020",
"app.components.HomePage.createBlock.content.first": "The\u0020", "app.components.HomePage.createBlock.content.first": "The\u0020",
"app.components.HomePage.createBlock.content.second": "\u0020plugin will help you to define the data structure of your models. If youre new here, we highly recommend you to follow our\u0020", "app.components.HomePage.createBlock.content.second": "\u0020plugin will help you to define the data structure of your models. If youre new here, we highly recommend you to follow our\u0020",
"app.components.HomePage.createBlock.content.tutorial": "\u0020tutorial.", "app.components.HomePage.createBlock.content.tutorial": "\u0020tutorial.",
"app.components.HomePage.button.quickStart": "START THE QUICK START TUTORIAL", "app.components.HomePage.button.quickStart": "START THE QUICK START TUTORIAL",
"app.components.HomePage.button.blog": "SEE MORE ON THE BLOG",
"app.components.HomePage.support": "SUPPORT US", "app.components.HomePage.support": "SUPPORT US",
"app.components.HomePage.support.content": "By buying the T-shirt (25€), it will allow us to continue our work on the project to give you the best possible experience!", "app.components.HomePage.support.content": "By buying the T-shirt (25€), it will allow us to continue our work on the project to give you the best possible experience!",
"app.components.HomePage.support.link": "GET YOUR T-SHIRT NOW", "app.components.HomePage.support.link": "GET YOUR T-SHIRT NOW",

View File

@ -9,17 +9,20 @@
"app.components.DownloadInfo.text": "Cela peut prendre une minute. Merci de patienter.", "app.components.DownloadInfo.text": "Cela peut prendre une minute. Merci de patienter.",
"app.components.HomePage.welcome": "Bienvenue à bord!", "app.components.HomePage.welcome": "Bienvenue à bord!",
"app.components.HomePage.welcome.again": "Bienvenue ",
"app.components.HomePage.cta": "CONFIRMEZ", "app.components.HomePage.cta": "CONFIRMEZ",
"app.components.HomePage.community": "Rejoignez la communauté", "app.components.HomePage.community": "Rejoignez la communauté",
"app.components.HomePage.community.content": "Discutez avec les membres de l'équipe, contributeurs et développeurs sur différent supports.", "app.components.HomePage.community.content": "Discutez avec les membres de l'équipe, contributeurs et développeurs sur différent supports.",
"app.components.HomePage.create": "Créez votre premier Content Type", "app.components.HomePage.create": "Créez votre premier Content Type",
"app.components.HomePage.welcomeBlock.content": "Nous sommes heureux de vous compter parmi nos membres. Nous sommes à l'écoute de vos retours alors, n'hésitez pas à nous envoyer des DM sur\u0020", "app.components.HomePage.welcomeBlock.content": "Nous sommes heureux de vous compter parmi nos membres. Nous sommes à l'écoute de vos retours alors, n'hésitez pas à nous envoyer des DM sur\u0020",
"app.components.HomePage.welcomeBlock.content.again": "Nous espérons que votre projet avance bien... Découvrez les derniers articles à propos de Strapi. Nous faisons de notre mieux pour améliorer le produit selon vos retours.",
"app.components.HomePage.welcomeBlock.content.issues": "issues", "app.components.HomePage.welcomeBlock.content.issues": "issues",
"app.components.HomePage.welcomeBlock.content.raise": "\u0020ou soumetez des\u0020", "app.components.HomePage.welcomeBlock.content.raise": "\u0020ou soumetez des\u0020",
"app.components.HomePage.createBlock.content.first": "Le\u0020", "app.components.HomePage.createBlock.content.first": "Le\u0020",
"app.components.HomePage.createBlock.content.second": "\u0020plugin vous permet de définir la structure de vos modèles. Nous vous conseillons fortement de suivre notre\u0020", "app.components.HomePage.createBlock.content.second": "\u0020plugin vous permet de définir la structure de vos modèles. Nous vous conseillons fortement de suivre notre\u0020",
"app.components.HomePage.createBlock.content.tutorial": "\u0020tutoriel.", "app.components.HomePage.createBlock.content.tutorial": "\u0020tutoriel.",
"app.components.HomePage.button.quickStart": "VOIR LE QUICK START TUTORIEL", "app.components.HomePage.button.quickStart": "VOIR LE QUICK START TUTORIEL",
"app.components.HomePage.button.blog": "VOIR PLUS D'ARTICLES SUR LE BLOG",
"app.components.HomePage.support": "SUPPORT US", "app.components.HomePage.support": "SUPPORT US",
"app.components.HomePage.support.content": "En achetant notre T-shirt (25€), vous nous aidez à poursuivre à maintenir le projet pour que nous puissions vous donner la meilleure expérience possible!", "app.components.HomePage.support.content": "En achetant notre T-shirt (25€), vous nous aidez à poursuivre à maintenir le projet pour que nous puissions vous donner la meilleure expérience possible!",
"app.components.HomePage.support.link": "OBTENEZ VOTRE T-SHIRT!", "app.components.HomePage.support.link": "OBTENEZ VOTRE T-SHIRT!",

View File

@ -24,6 +24,7 @@
}, },
"dependencies": { "dependencies": {
"react-ga": "^2.4.1", "react-ga": "^2.4.1",
"remove-markdown": "^0.2.2",
"shelljs": "^0.7.8" "shelljs": "^0.7.8"
}, },
"devDependencies": { "devDependencies": {