mirror of
https://github.com/strapi/strapi.git
synced 2025-08-15 04:08:04 +00:00
Merge branch 'master' of github.com:strapi/strapi into graphql
This commit is contained in:
commit
ad46c6b40a
@ -7,24 +7,39 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { isFunction, isObject } from 'lodash';
|
||||
import cn from 'classnames';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
function Sub({ bordered, content, title, underline }) {
|
||||
function Sub({ bordered, content, link, name, style, title, underline }) {
|
||||
if (isObject(title)) {
|
||||
return (
|
||||
<div className={cn(styles.subWrapper, bordered && styles.subBordered)}>
|
||||
<FormattedMessage {...title}>
|
||||
{message => <span className={cn(underline && styles.underlinedTitle)}>{message}</span>}
|
||||
{message => <span className={cn(underline && styles.underlinedTitle)}>{message}{name}</span>}
|
||||
</FormattedMessage>
|
||||
{content()}
|
||||
</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 = {
|
||||
bordered: false,
|
||||
content: () => '',
|
||||
link: '',
|
||||
name: '',
|
||||
style: {},
|
||||
title: {
|
||||
id: 'app.utils.defaultMessage',
|
||||
defaultMessage: 'app.utils.defaultMessage',
|
||||
@ -35,8 +50,17 @@ Sub.defaultProps = {
|
||||
|
||||
Sub.propTypes = {
|
||||
bordered: PropTypes.bool,
|
||||
content: PropTypes.func,
|
||||
title: PropTypes.object,
|
||||
content: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.string,
|
||||
]),
|
||||
link: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
title: PropTypes.oneOfType([
|
||||
PropTypes.object,
|
||||
PropTypes.string,
|
||||
]),
|
||||
underline: PropTypes.bool,
|
||||
};
|
||||
|
||||
|
@ -6,19 +6,25 @@
|
||||
.subWrapper {
|
||||
position: relative;
|
||||
line-height: 18px;
|
||||
text-decoration: none;
|
||||
|
||||
> span {
|
||||
text-decoration: none;
|
||||
font-family: Lato-Bold;
|
||||
font-size: 20px;
|
||||
color: #333740;
|
||||
letter-spacing: 0;
|
||||
transition: color .2s ease;
|
||||
}
|
||||
|
||||
p {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
max-width: 550px;
|
||||
max-width: calc(100% - 150px);
|
||||
margin-top: 18px;
|
||||
color: #333740;
|
||||
font-size: 14px;
|
||||
transition: color .2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,3 +32,15 @@
|
||||
.underlinedTitle {
|
||||
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
@ -6,14 +6,16 @@
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
/* eslint-disable jsx-a11y/accessible-emoji */
|
||||
function WelcomeContent() {
|
||||
function WelcomeContent({ hasContent }) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className={styles.iconWave}>👋</div>
|
||||
{!hasContent && (
|
||||
<FormattedMessage id="app.components.HomePage.welcomeBlock.content">
|
||||
{message => (
|
||||
<p className={styles.welcomeContentP}>
|
||||
@ -36,8 +38,24 @@ function WelcomeContent() {
|
||||
</p>
|
||||
)}
|
||||
</FormattedMessage>
|
||||
)}
|
||||
{hasContent && (
|
||||
<FormattedMessage id="app.components.HomePage.welcomeBlock.content.again">
|
||||
{message => (
|
||||
<p className={styles.welcomeContentP}>{message}</p>
|
||||
)}
|
||||
</FormattedMessage>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
WelcomeContent.defaultProps = {
|
||||
hasContent: false,
|
||||
};
|
||||
|
||||
WelcomeContent.propTypes = {
|
||||
hasContent: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default WelcomeContent;
|
||||
|
@ -1,9 +1,24 @@
|
||||
import {
|
||||
GET_ARTICLES,
|
||||
GET_ARTICLES_SUCCEEDED,
|
||||
ON_CHANGE,
|
||||
SUBMIT,
|
||||
SUBMIT_SUCCEEDED,
|
||||
} from './constants';
|
||||
|
||||
export function getArticles() {
|
||||
return {
|
||||
type: GET_ARTICLES,
|
||||
};
|
||||
}
|
||||
|
||||
export function getArticlesSucceeded(articles) {
|
||||
return {
|
||||
type: GET_ARTICLES_SUCCEEDED,
|
||||
articles,
|
||||
};
|
||||
}
|
||||
|
||||
export function onChange({ target }) {
|
||||
return {
|
||||
type: ON_CHANGE,
|
||||
|
@ -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 SUBMIT = 'app/HomePage/SUBMIT';
|
||||
export const SUBMIT_SUCCEEDED = 'app/HomePage/SUBMIT_SUCCEEDED';
|
||||
|
@ -11,7 +11,7 @@ import { FormattedMessage } from 'react-intl';
|
||||
import { bindActionCreators, compose } from 'redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import PropTypes from 'prop-types';
|
||||
import { get, isEmpty } from 'lodash';
|
||||
import { get, isEmpty, upperFirst } from 'lodash';
|
||||
import cn from 'classnames';
|
||||
|
||||
import Block from 'components/HomePageBlock/Loadable';
|
||||
@ -23,6 +23,7 @@ import SupportUsTitle from 'components/SupportUsTitle/Loadable';
|
||||
|
||||
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';
|
||||
@ -33,7 +34,7 @@ import CreateContent from './CreateContent';
|
||||
import SocialLink from './SocialLink';
|
||||
import WelcomeContent from './WelcomeContent';
|
||||
|
||||
import { onChange, submit } from './actions';
|
||||
import { getArticles, onChange, submit } from './actions';
|
||||
import makeSelectHomePage from './selectors';
|
||||
import reducer from './reducer';
|
||||
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 = [
|
||||
{
|
||||
link: 'https://strapi.io/documentation/',
|
||||
@ -115,6 +126,10 @@ export class HomePage extends React.PureComponent {
|
||||
// eslint-disable-line react/prefer-stateless-function
|
||||
state = { errors: [] };
|
||||
|
||||
componentDidMount() {
|
||||
this.props.getArticles();
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
const errors = validateInput(this.props.homePage.body.email, { required: true }, 'email');
|
||||
@ -128,29 +143,58 @@ export class HomePage extends React.PureComponent {
|
||||
showFirstBlock = () =>
|
||||
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() {
|
||||
const { homePage: { body } } = this.props;
|
||||
const { homePage: { articles, body } } = this.props;
|
||||
|
||||
return (
|
||||
<div className={cn('container-fluid', styles.containerFluid)}>
|
||||
<Helmet title="Home Page" />
|
||||
<div className="row">
|
||||
<div className="col-md-8 col-lg-8">
|
||||
{this.showFirstBlock() && (
|
||||
<Block>
|
||||
{FIRST_BLOCK.map((value, key) => (
|
||||
{this.showFirstBlock() &&
|
||||
FIRST_BLOCK.map((value, key) => (
|
||||
<Sub key={key} {...value} underline={key === 0} bordered={key === 0} />
|
||||
))}
|
||||
<a href="https://strapi.io/documentation/getting-started/quick-start.html" target="_blank">
|
||||
<Button className={styles.homePageTutorialButton} primary>
|
||||
<FormattedMessage id="app.components.HomePage.button.quickStart" />
|
||||
</Button>
|
||||
</a>
|
||||
{!this.showFirstBlock() &&
|
||||
WELCOME_AGAIN_BLOCK.concat(articles).map((value, key) => (
|
||||
<Sub
|
||||
key={key}
|
||||
{...value}
|
||||
bordered={key === 0}
|
||||
style={key === 1 ? { marginBottom: '33px' } : {}}
|
||||
underline={key === 0}
|
||||
/>
|
||||
))}
|
||||
{this.renderButton()}
|
||||
<div className={styles.homePageFlex}>
|
||||
{FIRST_BLOCK_LINKS.map((value, key) => <BlockLink {...value} key={key} />)}
|
||||
</div>
|
||||
</Block>
|
||||
)}
|
||||
<Block>
|
||||
<Sub {...SECOND_BLOCK} />
|
||||
<div className={styles.homePageFlex}>
|
||||
@ -199,6 +243,7 @@ export class HomePage extends React.PureComponent {
|
||||
}
|
||||
|
||||
HomePage.propTypes = {
|
||||
getArticles: PropTypes.func.isRequired,
|
||||
homePage: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
plugins: PropTypes.object.isRequired,
|
||||
@ -213,6 +258,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(
|
||||
{
|
||||
getArticles,
|
||||
onChange,
|
||||
submit,
|
||||
},
|
||||
|
@ -3,11 +3,12 @@
|
||||
* 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({
|
||||
articles: List([]),
|
||||
body: Map({
|
||||
email: '',
|
||||
}),
|
||||
@ -15,6 +16,8 @@ const initialState = fromJS({
|
||||
|
||||
function homePageReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case GET_ARTICLES_SUCCEEDED:
|
||||
return state.update('articles', () => List(action.articles));
|
||||
case ON_CHANGE:
|
||||
return state.updateIn(['body', 'email'], () => action.value);
|
||||
case SUBMIT_SUCCEEDED:
|
||||
|
@ -1,3 +1,6 @@
|
||||
import 'whatwg-fetch';
|
||||
import { dropRight, take } from 'lodash';
|
||||
import removeMd from 'remove-markdown';
|
||||
import {
|
||||
call,
|
||||
fork,
|
||||
@ -8,10 +11,33 @@ import {
|
||||
|
||||
import request from 'utils/request';
|
||||
|
||||
import { submitSucceeded } from './actions';
|
||||
import { SUBMIT } from './constants';
|
||||
import { getArticlesSucceeded, submitSucceeded } from './actions';
|
||||
import { GET_ARTICLES, SUBMIT } from './constants';
|
||||
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() {
|
||||
try {
|
||||
const body = yield select(makeSelectBody());
|
||||
@ -26,6 +52,14 @@ function* submit() {
|
||||
|
||||
function* defaultSaga() {
|
||||
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;
|
||||
|
@ -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 {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
@ -237,6 +263,6 @@
|
||||
|
||||
.welcomeContentP {
|
||||
display: block;
|
||||
max-width: 49rem !important;
|
||||
max-width: 55rem !important;
|
||||
margin-bottom: 31px;
|
||||
}
|
||||
|
@ -9,18 +9,21 @@
|
||||
"app.components.DownloadInfo.text": "This could take a minute. Thanks for your patience.",
|
||||
|
||||
"app.components.HomePage.welcome": "Welcome on board!",
|
||||
"app.components.HomePage.welcome.again": "Welcome ",
|
||||
"app.components.HomePage.cta": "CONFIRM",
|
||||
"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.community.content": "Discuss with team members, contributors and developers on different channels.",
|
||||
"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.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.raise": "\u0020or raise\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 you’re new here, we highly recommend you to follow our\u0020",
|
||||
"app.components.HomePage.createBlock.content.tutorial": "\u0020tutorial.",
|
||||
"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.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",
|
||||
|
@ -9,17 +9,20 @@
|
||||
"app.components.DownloadInfo.text": "Cela peut prendre une minute. Merci de patienter.",
|
||||
|
||||
"app.components.HomePage.welcome": "Bienvenue à bord!",
|
||||
"app.components.HomePage.welcome.again": "Bienvenue ",
|
||||
"app.components.HomePage.cta": "CONFIRMEZ",
|
||||
"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.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.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.raise": "\u0020ou soumetez des\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.tutorial": "\u0020tutoriel.",
|
||||
"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.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!",
|
||||
|
@ -24,6 +24,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"react-ga": "^2.4.1",
|
||||
"remove-markdown": "^0.2.2",
|
||||
"shelljs": "^0.7.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user