Update homepage design

This commit is contained in:
soupette 2019-08-21 09:23:51 +02:00
parent a8e0dcb245
commit eef4939415
34 changed files with 585 additions and 1201 deletions

View File

@ -87,7 +87,6 @@
"type": "group",
"group": "ingredients",
"repeatable": true,
"min": 1,
"max": 10
},
"author": {

View File

@ -1,13 +1,14 @@
.wrapper {
import styled from 'styled-components';
const Wrapper = styled.div`
width: 372px;
height: 159px;
border-radius: 2px;
background-color: #fff;
}
`;
.content {
const Content = styled.div`
padding-top: 3rem;
// color: #F64D0A;
text-align: center;
font-family: Lato;
font-size: 1.3rem;
@ -26,9 +27,10 @@
}
> span {
color: #787E8F;
color: #787e8f;
font-size: 13px;
}
}
`;
}
export { Content, Wrapper };

View File

@ -1,26 +1,27 @@
/*
*
* DownloadInfo
*
*/
*
* DownloadInfo
*
*/
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Content, Wrapper } from './components';
import Icon from '../../assets/icons/icon_success.svg';
import styles from './styles.scss';
function DownloadInfo() {
return (
<div className={styles.wrapper}>
<div className={styles.content}>
<Wrapper>
<Content>
<img src={Icon} alt="info" />
<div>
<FormattedMessage id="app.components.DownloadInfo.download" />
<br />
<FormattedMessage id="app.components.DownloadInfo.text" />
</div>
</div>
</div>
</Content>
</Wrapper>
);
}

View File

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

View File

@ -1,36 +0,0 @@
/**
*
* HomePageBlock
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import styles from './styles.scss';
function HomePageBlock({ children, className }) {
return (
<div
className={cn(
className,
styles.homePageBlock,
)}
>
{children}
</div>
);
}
HomePageBlock.defaultProps = {
children: '',
className: '',
};
HomePageBlock.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
};
export default HomePageBlock;

View File

@ -1,8 +0,0 @@
.homePageBlock {
width: 100%;
margin-bottom: 34px;
background: #ffffff;
padding: 20px 30px 30px 30px;
box-shadow: 0 2px 4px 0 #E3E9F3;
border-radius: 3px;
}

View File

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

View File

@ -1,76 +0,0 @@
/**
*
* Sub
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { isFunction, isObject } from 'lodash';
import cn from 'classnames';
import { LoadingBar } from 'strapi-helper-plugin';
import styles from './styles.scss';
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}
{name}
</span>
)}
</FormattedMessage>
{content()}
</div>
);
}
return (
<a
className={cn(
styles.subWrapper,
bordered && styles.subBordered,
styles.link
)}
href={`https://blog.strapi.io/${link}`}
target="_blank"
rel="noopener noreferrer"
>
<span>{title}</span>
{title === '' && <LoadingBar />}
{content === '' && <LoadingBar style={{ width: '40%' }} />}
<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',
values: {},
},
underline: false,
};
Sub.propTypes = {
bordered: PropTypes.bool,
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,
};
export default Sub;

View File

@ -1,46 +0,0 @@
.subBordered {
margin-bottom: 18px;
border-bottom: 1px solid #F7F8F8;
}
.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: calc(100% - 150px);
margin-top: 18px;
color: #333740;
font-size: 14px;
transition: color .2s ease;
}
}
.underlinedTitle {
border-bottom: 3px solid #F0B41E;
}
.link{
&:hover, &:focus, &:active{
text-decoration: none;
}
&:hover{
> span, p {
color: lighten(#333740, 20%);
}
}
}

View File

@ -1,8 +0,0 @@
import Loadable from 'react-loadable';
import { LoadingIndicator } from 'strapi-helper-plugin';
export default Loadable({
loader: () => import('./index'),
loading: LoadingIndicator,
});

View File

@ -1,28 +0,0 @@
/**
*
* SupportUsCta
*/
import React from 'react';
import { FormattedMessage } from 'react-intl';
import styles from './styles.scss';
function SupportUsCta() {
return (
<FormattedMessage id="app.components.HomePage.support.link">
{message => (
<a
href="https://strapi.io/shop"
target="_blank"
className={styles.supportUsCta}
rel="noopener noreferrer"
>
{message}
</a>
)}
</FormattedMessage>
);
}
export default SupportUsCta;

View File

@ -1,18 +0,0 @@
.supportUsCta {
padding: 7px 12px 7px 20px;
border: 1px solid #FFFFFF;
border-radius: 3px;
color: #FFFFFF;
font-size: 13px;
font-weight: 800;
letter-spacing: 0.5px;
&:after {
content: '\f178';
font-family: 'FontAwesome';
margin-left: 10px;
}
&:hover {
color: #FFFFFF;
text-decoration: none;
}
}

View File

@ -1,8 +0,0 @@
import Loadable from 'react-loadable';
import { LoadingIndicator } from 'strapi-helper-plugin';
export default Loadable({
loader: () => import('./index'),
loading: LoadingIndicator,
});

View File

@ -1,19 +0,0 @@
/**
*
* SupportUsTitle
*/
import React from 'react';
import { FormattedMessage } from 'react-intl';
import styles from './styles.scss';
function SupportUsTitle() {
return (
<FormattedMessage id="app.components.HomePage.support">
{message => <span className={styles.supportUsTitle}>{message}</span>}
</FormattedMessage>
);
}
export default SupportUsTitle;

View File

@ -1,11 +0,0 @@
.supportUsTitle {
color: #FFFFFF;
font-size: 18px;
font-weight: 800;
letter-spacing: 0.5px;
&:after {
content: '❤️';
margin-left: 7px;
font-size: 18px;
}
}

View File

@ -221,6 +221,8 @@ export class Admin extends React.Component {
return <PluginDispatcher {...this.props} {...props} {...this.helpers} />;
};
renderRoute = (props, Component) => <Component {...this.props} {...props} />;
render() {
const {
admin: { isLoading, showLogoutComponent, showMenu, strapiVersion },
@ -256,7 +258,11 @@ export class Admin extends React.Component {
{showMenu ? <Header /> : ''}
<div className={this.getContentWrapperStyle().sub}>
<Switch>
<Route path="/" component={HomePage} exact />
<Route
path="/"
render={props => this.renderRoute(props, HomePage)}
exact
/>
<Route
path="/plugins/:pluginId"
render={this.renderPluginDispatcher}

View File

@ -1,39 +0,0 @@
/**
*
* BlockLink
*/
import React from 'react';
import { FormattedMessage } from 'react-intl';
import cn from 'classnames';
import PropTypes from 'prop-types';
import styles from './styles.scss';
function BlockLink({ content, isDocumentation, link, title }) {
return (
<a
className={cn(
styles.blockLink,
isDocumentation ? styles.blockLinkDocumentation : styles.blockLinkCode
)}
href={link}
target="_blank"
rel="noopener noreferrer"
>
<FormattedMessage {...title} />
<FormattedMessage {...content}>
{message => <p>{message}</p>}
</FormattedMessage>
</a>
);
}
BlockLink.propTypes = {
content: PropTypes.object.isRequired,
isDocumentation: PropTypes.bool.isRequired,
link: PropTypes.string.isRequired,
title: PropTypes.object.isRequired,
};
export default BlockLink;

View File

@ -0,0 +1,50 @@
import React, { memo } from 'react';
import PropTypes from 'prop-types';
import { LoadingBar } from 'strapi-helper-plugin';
const BlogPost = ({ error, isFirst, isLoading, title, content, link }) => {
if (isLoading) {
return (
<>
<LoadingBar style={{ marginBottom: 13 }} />
<LoadingBar style={{ width: '40%', marginBottom: 30 }} />
</>
);
}
if (error) {
return null;
}
return (
<a
rel="noopener noreferrer"
target="_blank"
href={`https://blog.strapi.io/${link}`}
style={{ color: '#333740' }}
>
<h2>{title}</h2>
<p style={{ marginTop: 17, marginBottom: isFirst ? 32 : 10 }}>
{content}
</p>
</a>
);
};
BlogPost.defaultProps = {
content: null,
isFirst: false,
link: null,
title: null,
};
BlogPost.propTypes = {
content: PropTypes.string,
error: PropTypes.bool.isRequired,
isFirst: PropTypes.bool,
isLoading: PropTypes.bool.isRequired,
link: PropTypes.string,
title: PropTypes.string,
};
export default memo(BlogPost);

View File

@ -1,27 +0,0 @@
/**
*
* CommunityContent
*
*/
import React from 'react';
import { FormattedMessage } from 'react-intl';
import styles from './styles.scss';
/* eslint-disable jsx-a11y/accessible-emoji */
function CommunityContent() {
return (
<React.Fragment>
<FormattedMessage id="app.components.HomePage.community.content">
{message => (
<p className={styles.communityContentP}>
{message}
</p>
)}
</FormattedMessage>
</React.Fragment>
);
}
export default CommunityContent;

View File

@ -1,30 +0,0 @@
/**
*
* CreateContent
*
*/
/* eslint-disable */
import React from 'react';
import { FormattedMessage } from 'react-intl';
function CreateContent() {
return (
<FormattedMessage id="app.components.HomePage.createBlock.content.first">
{message => (
<p>
{message}
<span style={{ fontStyle: 'italic', fontWeight: '500' }}>
Content Type Builder
</span>
<FormattedMessage id="app.components.HomePage.createBlock.content.second" />
<span style={{ fontStyle: 'italic', fontWeight: '500' }}>
"Quick Start"
</span>
<FormattedMessage id="app.components.HomePage.createBlock.content.tutorial" />
</p>
)}
</FormattedMessage>
);
}
export default CreateContent;

View File

@ -3,8 +3,7 @@
* SocialLink
*/
import React from 'react';
import cn from 'classnames';
import React, { memo } from 'react';
import PropTypes from 'prop-types';
import Gh from '../../assets/images/social_gh.png';
@ -14,7 +13,7 @@ import So from '../../assets/images/social_so.png';
import Twitter from '../../assets/images/social_twitter.png';
import Reddit from '../../assets/images/social_reddit.png';
import styles from './styles.scss';
import { SocialLinkWrapper } from './components';
/* eslint-disable jsx-a11y/alt-text */
function getSrc(name) {
@ -36,36 +35,22 @@ function getSrc(name) {
}
}
class SocialLink extends React.PureComponent {
state = { imgLoaded: false };
handleImgLoaded = () => this.setState({ imgLoaded: true });
render() {
const { link, name } = this.props;
const { imgLoaded } = this.state;
return (
<div className={cn(styles.socialLink, 'col-md-6 col-lg-6')}>
<a href={link} target="_blank" rel="noopener noreferrer">
<div>
{!imgLoaded && (
<div className={styles.spinner}>
<div />
</div>
)}
<img src={getSrc(name)} onLoad={this.handleImgLoaded} />
</div>
<span>{name}</span>
</a>
</div>
);
}
}
const SocialLink = ({ link, name }) => {
return (
<SocialLinkWrapper className="col-6">
<a href={link} target="_blank" rel="noopener noreferrer">
<div>
<img src={getSrc(name)} />
</div>
<span>{name}</span>
</a>
</SocialLinkWrapper>
);
};
SocialLink.propTypes = {
link: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
};
export default SocialLink;
export default memo(SocialLink);

View File

@ -1,65 +0,0 @@
/**
*
* WelcomeContent
*
*/
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({ hasContent }) {
return (
<React.Fragment>
<div className={styles.iconWave}>👋</div>
{!hasContent && (
<FormattedMessage id="app.components.HomePage.welcomeBlock.content">
{message => (
<p className={styles.welcomeContentP}>
{message}
<a
className={styles.welcomeContentA}
href="https://slack.strapi.io/"
target="_blank"
rel="noopener noreferrer"
>
Slack
</a>
<FormattedMessage id="app.components.HomePage.welcomeBlock.content.raise" />
<FormattedMessage id="app.components.HomePage.welcomeBlock.content.issues">
{message => (
<a
className={styles.welcomeContentA}
href="https://github.com/strapi/strapi/issues/new/choose"
target="_blank"
rel="noopener noreferrer"
>
{message}
</a>
)}
</FormattedMessage>
</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;

View File

@ -1,39 +0,0 @@
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,
value: target.value,
};
}
export function submit() {
return {
type: SUBMIT,
};
}
export function submitSucceeded() {
return {
type: SUBMIT_SUCCEEDED,
};
}

View File

@ -0,0 +1,261 @@
import styled, { css } from 'styled-components';
const Block = styled.div`
width: 100%;
position: relative;
margin-bottom: 34px;
background: #ffffff;
padding: 19px 30px 30px 30px;
box-shadow: 0 2px 4px 0 #e3e9f3;
border-radius: 3px;
line-heigth: 18px;
a {
position: relative;
text-decoration: none;
&:hover::after {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
border-radius: 0.3rem;
content: '';
opacity: 0.1;
background: #ffffff;
}
}
h2,
p {
line-height: 18px;
}
h2 {
display: inline-block;
}
#mainHeader {
&:after {
content: '';
width: 100%;
height: 3px;
margin-top: 4px;
display: block;
background: #f0b41e;
}
}
.social-wrapper {
span {
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
}
> div:nth-child(2n) {
padding-right: 0;
}
}
`;
const Container = styled.div`
padding: 47px 13px 0 13px;
> div {
margin: 0;
}
`;
const P = styled.p`
max-width: 550px;
padding-top: 10px;
padding-right: 30px;
color: #5c5f66;
font-size: 14px;
b {
font-weight: 600;
}
`;
const Wave = styled.div`
&:before {
content: '👋';
position: absolute;
top: 24px;
right: 30px;
font-size: 50px;
}
`;
const ALink = styled.a`
display: inline-block;
position: relative;
height: 34px;
margin-top: 16px;
padding-right: 20px;
border-radius: 3px;
text-overflow: ellipsis;
overflow: hidden;
line-height: 34px;
font-size: 13px;
font-weight: 600;
text-transform: uppercase;
&:before {
content: '\f105';
font-size: 18px;
font-weight: 600;
margin-right: 10px;
font-family: 'FontAwesome';
}
&:hover,
focus,
active {
text-decoration: none;
outline: 0;
}
&:hover::after {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
border-radius: 0.3rem;
content: '';
opacity: 0.1;
background: #ffffff;
}
${({ type }) => {
if (type === 'documentation') {
return css`
padding-left: 20px;
color: #ffffff;
background-color: #005fea;
&:hover {
color: #ffffff;
}
`;
} else if (type === 'blog') {
return css`
padding-left: 20px;
background-color: #333740;
color: #ffffff;
&:hover {
color: #ffffff;
}
`;
} else {
return css`
color: #005fea;
&:hover {
color: #005fea;
}
`;
}
}}
`;
const Separator = styled.div`
height: 2px;
background-color: #f7f8f8;
`;
const LinkWrapper = styled.a`
width: calc(50% - 6px);
position: relative;
padding: 21px 30px;
padding-left: 95px;
height: auto;
line-height: 18px;
background-color: #f7f8f8;
&:hover,
focus,
active {
text-decoration: none;
outline: 0;
}
&:before {
${({ type }) => {
if (type === 'doc') {
return css`
content: '\f02d';
color: #42b88e;
`;
}
return css`
content: '\f121';
color: #f0811e;
`;
}}
position: absolute;
left: 30px;
top: 38px;
font-family: 'FontAwesome';
font-size: 38px;
}
> p {
margin: 0;
font-size: 16px;
color: #919BAE;
text-overflow: ellipsis;
overflow: hidden;
}
.bold {
color: #333740
font-weight: 600;
}
`;
const SocialLinkWrapper = styled.div`
height: 54px;
font-size: 14px;
font-weight: 500;
position: relative;
a {
width: 100%;
color: #333740 !important;
text-decoration: none;
line-height: 18px;
div {
display: inline-block;
height: 25px;
width: 25px;
text-align: center;
vertical-align: top;
}
img {
max-height: 25px;
max-width: 25px;
}
span {
margin-left: 11px;
width: calc(100% - 40px);
}
&:hover {
text-decoration: none;
}
}
`;
export {
ALink,
Block,
Container,
LinkWrapper,
P,
Separator,
SocialLinkWrapper,
Wave,
};

View File

@ -1,5 +0,0 @@
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';

View File

@ -0,0 +1,55 @@
import { useEffect, useState } from 'react';
import { request } from 'strapi-helper-plugin';
const useFetch = () => {
const [state, setState] = useState({
error: false,
isLoading: true,
posts: [{ link: '1' }, { link: '2' }],
});
useEffect(() => {
const abortController = new AbortController();
const { signal } = abortController;
const fetchData = async () => {
try {
const response = await request(
'https://blog.strapi.io/ghost/api/v0.1/posts/?client_id=ghost-frontend&client_secret=1f260788b4ec&limit=2',
{
method: 'GET',
headers: {
'Access-Control-Allow-Origin': '*',
},
signal,
},
false,
false,
{ noAuth: true }
);
const posts = response.posts.reduce((acc, curr) => {
acc.push({
title: curr.title,
link: curr.slug,
content: curr.meta_description,
});
return acc;
}, []);
setState({ isLoading: false, posts, error: false });
} catch (err) {
setState({ isLoading: false, error: true, posts: [] });
}
};
fetchData();
return () => {
abortController.abort();
};
}, []);
return state;
};
export default useFetch;

View File

@ -4,90 +4,39 @@
*
*/
/* eslint-disable */
import React from 'react';
import { connect } from 'react-redux';
import React, { memo } from 'react';
import Helmet from 'react-helmet';
import { FormattedMessage } from 'react-intl';
import { bindActionCreators, compose } from 'redux';
import { createStructuredSelector } from 'reselect';
import PropTypes from 'prop-types';
import { get, isEmpty, upperFirst } from 'lodash';
import cn from 'classnames';
import { get, upperFirst } from 'lodash';
import { auth } from 'strapi-helper-plugin';
import useFetch from './hooks';
import {
Button,
InputText as Input,
auth,
validateInput,
} from 'strapi-helper-plugin';
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';
ALink,
Block,
Container,
LinkWrapper,
P,
Wave,
Separator,
} from './components';
import BlogPost from './BlogPost';
import SocialLink from './SocialLink';
import WelcomeContent from './WelcomeContent';
import { getArticles, onChange, submit } from './actions';
import makeSelectHomePage from './selectors';
import reducer from './reducer';
import saga from './saga';
import styles from './styles.scss';
const FIRST_BLOCK = [
{
title: {
id: 'app.components.HomePage.welcome',
},
content: () => <WelcomeContent />,
},
{
title: {
id: 'app.components.HomePage.create',
},
content: () => <CreateContent />,
},
];
const FIRST_BLOCK_LINKS = [
{
link: 'https://strapi.io/documentation/',
content: {
id: 'app.components.BlockLink.documentation.content',
},
isDocumentation: true,
title: {
id: 'app.components.BlockLink.documentation',
},
link:
'https://strapi.io/documentation/3.0.0-beta.x/getting-started/quick-start.html#_4-create-a-new-content-type',
contentId: 'app.components.BlockLink.documentation.content',
titleId: 'app.components.BlockLink.documentation',
},
{
link: 'https://github.com/strapi/strapi-examples',
content: {
id: 'app.components.BlockLink.code.content',
},
isDocumentation: false,
title: {
id: 'app.components.BlockLink.code',
},
link: 'https://github.com/strapi/foodadvisor',
contentId: 'app.components.BlockLink.code.content',
titleId: 'app.components.BlockLink.code',
},
];
const SECOND_BLOCK = {
title: {
id: 'app.components.HomePage.community',
},
content: () => <CommunityContent />,
};
const SOCIAL_LINKS = [
{
name: 'GitHub',
@ -115,196 +64,164 @@ const SOCIAL_LINKS = [
},
];
export class HomePage extends React.PureComponent {
// eslint-disable-line react/prefer-stateless-function
state = { errors: [] };
componentDidMount() {
this.props.getArticles();
}
handleSubmit = e => {
const HomePage = ({ global: { plugins }, history: { push } }) => {
const { error, isLoading, posts } = useFetch();
const handleClick = e => {
e.preventDefault();
const errors = validateInput(
this.props.homePage.body.email,
{ required: true },
'email'
);
this.setState({ errors });
if (isEmpty(errors)) {
return this.props.submit();
}
push('/plugins/content-type-builder/models');
};
const hasAlreadyCreatedContentTypes =
get(
plugins,
['content-manager', 'leftMenuSections', '0', 'links'],
[]
).filter(contentType => contentType.isDisplayed === true).length > 1;
showFirstBlock = () =>
get(this.context.plugins, 'content-manager.leftMenuSections.0.links', [])
.length === 0;
const headerId = hasAlreadyCreatedContentTypes
? 'HomePage.greetings'
: 'app.components.HomePage.welcome';
const username = get(auth.getUserInfo(), 'username', '');
const linkProps = hasAlreadyCreatedContentTypes
? {
id: 'app.components.HomePage.button.blog',
href: 'https://blog.strapi.io/',
onClick: () => {},
type: 'blog',
target: '_blank',
}
: {
id: 'app.components.HomePage.create',
href: '',
onClick: handleClick,
type: 'documentation',
};
renderButton = () => {
/* eslint-disable indent */
const data = this.showFirstBlock()
? {
className: styles.homePageTutorialButton,
href:
'https://strapi.io/documentation/getting-started/quick-start.html#_3-create-a-content-type',
id: 'app.components.HomePage.button.quickStart',
primary: true,
}
: {
className: styles.homePageBlogButton,
id: 'app.components.HomePage.button.blog',
href: 'https://blog.strapi.io/',
primary: false,
};
/* eslint-enable indent */
return (
<a href={data.href} target="_blank">
<Button className={data.className} primary={data.primary}>
<FormattedMessage id={data.id} />
</Button>
</a>
);
};
render() {
const {
homePage: { articles, body },
} = this.props;
const WELCOME_AGAIN_BLOCK = [
{
title: {
id: 'app.components.HomePage.welcome.again',
},
name: upperFirst(`${get(auth.getUserInfo(), 'username')}!`),
content: () => <WelcomeContent hasContent />,
},
];
return (
<div className={cn('container-fluid', styles.containerFluid)}>
<Helmet title="Home Page" />
return (
<>
<FormattedMessage id="HomePage.helmet.title">
{title => <Helmet title={title} />}
</FormattedMessage>
<Container className="container-fluid">
<div className="row">
<div className="col-md-8 col-lg-8">
<div className="col-lg-8 col-md-12">
<Block>
{this.showFirstBlock() &&
FIRST_BLOCK.map((value, key) => (
<Sub
key={key}
{...value}
underline={key === 0}
bordered={key === 0}
/>
))}
{!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}>
<div
className="row"
style={{ width: '100%', marginRight: '0' }}
>
{SOCIAL_LINKS.map((value, key) => (
<SocialLink key={key} {...value} />
<Wave />
<FormattedMessage
id={headerId}
values={{
name: upperFirst(username),
}}
>
{msg => <h2 id="mainHeader">{msg}</h2>}
</FormattedMessage>
{hasAlreadyCreatedContentTypes ? (
<FormattedMessage id="app.components.HomePage.welcomeBlock.content.again">
{msg => <P>{msg}</P>}
</FormattedMessage>
) : (
<FormattedMessage id="HomePage.welcome.congrats">
{congrats => {
return (
<FormattedMessage id="HomePage.welcome.congrats.content">
{content => {
return (
<FormattedMessage id="HomePage.welcome.congrats.content.bold">
{boldContent => {
return (
<P>
<b>{congrats}</b>&nbsp;
{content} &nbsp;
<b>{boldContent}</b>
</P>
);
}}
</FormattedMessage>
);
}}
</FormattedMessage>
);
}}
</FormattedMessage>
)}
{hasAlreadyCreatedContentTypes && (
<div style={{ marginTop: isLoading ? 60 : 50 }}>
{posts.map((post, index) => (
<BlogPost
{...post}
key={post.link}
isFirst={index === 0}
isLoading={isLoading}
error={error}
/>
))}
</div>
<div className={styles.newsLetterWrapper}>
<div>
<FormattedMessage id="app.components.HomePage.newsLetter" />
</div>
<form onSubmit={this.handleSubmit}>
<div className={cn(styles.homePageForm, 'row')}>
<div className="col-md-12">
<Input
value={body.email}
onChange={this.props.onChange}
name=""
placeholder="johndoe@gmail.com"
error={!isEmpty(this.state.errors)}
/>
<FormattedMessage id="app.components.HomePage.cta">
{message => <button type="submit">{message}</button>}
</FormattedMessage>
</div>
</div>
</form>
</div>
)}
<FormattedMessage id={linkProps.id}>
{msg => (
<ALink rel="noopener noreferrer" {...linkProps}>
{msg}
</ALink>
)}
</FormattedMessage>
<Separator style={{ marginTop: 38, marginBottom: 17 }} />
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
{FIRST_BLOCK_LINKS.map((data, index) => {
const type = index === 0 ? 'doc' : 'code';
return (
<LinkWrapper
href={data.link}
target="_blank"
key={data.link}
type={type}
>
<FormattedMessage id={data.titleId}>
{title => <p className="bold">{title}</p>}
</FormattedMessage>
<FormattedMessage id={data.contentId}>
{content => <p>{content}</p>}
</FormattedMessage>
</LinkWrapper>
);
})}
</div>
</Block>
</div>
<div className="col-lg-4 col-md-4">
<Block className={styles.blockShirt}>
<div>
<SupportUsTitle />
<FormattedMessage id="app.components.HomePage.support.content">
{message => <p>{message}</p>}
</FormattedMessage>
<SupportUsCta />
<div className="col-4">
<Block>
<FormattedMessage id="HomePage.community">
{msg => <h2>{msg}</h2>}
</FormattedMessage>
<FormattedMessage id="app.components.HomePage.community.content">
{content => <P style={{ marginTop: 7 }}>{content}</P>}
</FormattedMessage>
<FormattedMessage id="HomePage.roadmap">
{msg => (
<ALink
rel="noopener noreferrer"
href="https://portal.productboard.com/strapi/1-public-roadmap/tabs/2-under-consideration"
target="_blank"
>
{msg}
</ALink>
)}
</FormattedMessage>
<div
className="row social-wrapper"
style={{ display: 'flex', margin: 0, marginTop: 43 }}
>
{SOCIAL_LINKS.map((value, key) => (
<SocialLink key={key} {...value} />
))}
</div>
</Block>
</div>
</div>
</div>
);
}
}
HomePage.contextTypes = {
plugins: PropTypes.object,
};
HomePage.propTypes = {
getArticles: PropTypes.func.isRequired,
homePage: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
submit: PropTypes.func.isRequired,
};
const mapStateToProps = createStructuredSelector({
homePage: makeSelectHomePage(),
plugins: selectPlugins(),
});
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
getArticles,
onChange,
submit,
},
dispatch
</Container>
</>
);
}
};
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 memo(HomePage);

View File

@ -1,6 +0,0 @@
{
"welcome": {
"id": "app.components.HomePage.welcome",
"defaultMessage": "Welcome on board!"
}
}

View File

@ -1,33 +0,0 @@
/**
*
* HomePage reducer
*/
import { fromJS, List, Map } from 'immutable';
import { GET_ARTICLES_SUCCEEDED, ON_CHANGE, SUBMIT_SUCCEEDED } from './constants';
const initialState = fromJS({
articles: List([
{content: '', title: '', link: ''},
{content: '', title: '', link: ''},
]),
body: Map({
email: '',
}),
});
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:
return state.updateIn(['body', 'email'], () => '');
default:
return state;
}
}
export default homePageReducer;

View File

@ -1,67 +0,0 @@
/* eslint-disable */
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 { request } from 'strapi-helper-plugin';
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());
yield call(request, 'https://analytics.strapi.io/register', {
method: 'POST',
body,
});
} catch (err) {
// silent
} finally {
strapi.notification.success('HomePage.notification.newsLetter.success');
yield put(submitSucceeded());
}
}
function* defaultSaga() {
yield all([
fork(takeLatest, SUBMIT, submit),
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;

View File

@ -1,32 +0,0 @@
import { createSelector } from 'reselect';
/**
* Direct selector to the homePage state domain
*/
const selectHomePageDomain = () => (state) => state.get('homePage');
/**
* Other specific selectors
*/
/**
* Default selector used by HomePage
*/
const makeSelectHomePage = () => createSelector(
selectHomePageDomain(),
(substate) => substate.toJS(),
);
const makeSelectBody = () => createSelector(
selectHomePageDomain(),
(substate) => substate.get('body').toJS(),
);
export default makeSelectHomePage;
export {
makeSelectBody,
selectHomePageDomain,
};

View File

@ -1,289 +0,0 @@
.blockLink {
position: relative;
width: calc(50% - 6px);
height: auto;
margin-top: 41px;
padding: 22px 25px 19px 96px;
background: #F7F8F8;
border-radius: 3px;
line-height: 18px;
text-decoration: none;
> span {
font-family: Lato-Bold;
font-size: 16px;
color: #333740;
letter-spacing: 0;
}
> p {
font-family: Lato-Regular;
font-size: 13px;
color: #919BAE;
letter-spacing: 0;
line-height: 18px;
margin: 0;
}
&:hover {
text-decoration: none;
}
}
.blockLinkDocumentation {
&:before {
content: '\f02d';
position: absolute;
left: 3rem;
top: 4rem;
color: #42B88E;
font-family: 'FontAwesome';
font-size: 38px;
}
}
.blockLinkCode {
&:before {
content: '\f121';
position: absolute;
left: 3rem;
top: 4rem;
color: #F0811E;
font-family: 'FontAwesome';
font-size: 38px;
}
}
.blockShirt {
position: relative;
min-height: 34rem;
margin-bottom: 20px;
background-image: linear-gradient(45deg, #1A67DA 0%, #0097F6 100%) !important;
line-height: 18px;
> div {
position: absolute;
padding: 38px 0 62px 25px;
top: 0;
bottom: 0;
left: 0;
right: 0;
color: #FFFFFF;
> p {
max-width: 400px;
margin-top: 18px;
margin-bottom: 125px;
padding-right: 35px;
font-size: 13px;
font-weight: 400;
}
}
&:before {
opacity: 0.7;
content: '';
background-image: url('../../assets/images/bg_hp_tee_shirt.png') !important;
background-size: contain;
background-repeat: no-repeat;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
}
.communityContentP {
display: block;
max-width: 49rem !important;
margin-top: 0 !important;
margin-bottom: 51px;
color: #919BAE !important;
}
.containerFluid {
padding: 47px 13px 0 13px;
> div {
margin: 0;
}
}
.homePageFlex {
display: flex;
width: 100%;
justify-content: space-between;
}
.homePageForm {
padding-top: 19px;
padding-left: 15px;
div {
padding: 0;
}
input {
float: left;
width: calc(100% - 120px);
border-top-right-radius: 0;
border-bottom-right-radius: 0;
&:focus {
border-color: #E3E9F3;
}
&::-webkit-input-placeholder {
font-style: italic;
}
}
button {
float: left;
min-width: 100px;
height: 3.4rem;
margin-top: .9rem;
padding-left: 20px;
padding-right: 20px;
text-align: center;
background: #333740;
color: #FFFFFF;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
font-size: 12px;
font-weight: 800;
letter-spacing: 0.5px;
}
}
.homePageTutorialButton {
position: relative;
height: 34px;
margin-top: 17px;
margin-bottom: 1px;
padding-left: 40px;
padding-right: 20px;
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';
}
}
.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;
right: 0px;
font-size: 50px;
}
.newsLetterWrapper {
height: auto;
min-width: 50%;
padding: 20px 20px;
background: #F7F8F8;
border-radius: 3px;
line-height: 18px;
> div {
padding-right: 50px;
> span {
font-weight: 500;
font-size: 14px;
}
}
}
.socialLink {
height: 54px;
font-size: 14px;
font-weight: 500;
position: relative;
a {
color: #333740 !important;
text-decoration: none;
line-height: 18px;
div {
display: inline-block;
height: 25px;
width: 25px;
text-align: center;
vertical-align: top;
}
img {
max-height: 25px;
max-width: 25px;
}
span {
margin-left: 11px;
}
&:hover {
text-decoration: none;
}
}
}
.welcomeContentA {
color: #005FEA;
text-decoration: none;
&:hover {
text-decoration: none;
}
}
.welcomeContentP {
display: block;
max-width: 55rem !important;
margin-bottom: 31px;
}
.spinner {
display: flex;
justify-content: space-around;
width: 100%;
margin: auto;
> div {
border: 2px solid #f3f3f3; /* Light grey */
border-top: 2px solid #3498db; /* Blue */
border-radius: 50%;
width: 10px;
height: 10px;
animation: spin 2s linear infinite;
}
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

View File

@ -154,5 +154,12 @@
"notification.error": "An error occurred",
"notification.error.layout": "Couldn't retrieve the layout",
"request.error.model.unknown": "This model doesn't exist",
"app.utils.delete": "Delete"
"app.utils.delete": "Delete",
"HomePage.helmet.title": "Homepage",
"HomePage.welcome.congrats": "Congrats!",
"HomePage.welcome.congrats.content": "You are logged as the first administrator. To discover the powerful features provided by Strapi,",
"HomePage.welcome.congrats.content.bold": "we recommend you to create your first Content-Type.",
"HomePage.community": "Join the Community",
"HomePage.roadmap": "See our roadmap",
"HomePage.greetings": "Hi {name}!"
}

View File

@ -154,5 +154,12 @@
"notification.error": "Une erreur est survenue",
"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"
"app.utils.delete": "Supprimer",
"HomePage.helmet.title": "Accueil",
"HomePage.welcome.congrats": "Bravo!",
"HomePage.welcome.congrats.content": "Vous êtes connecté en tant que premier Administrateur. Afin de découvrir les fonctionnalités proposées par Strapi,",
"HomePage.welcome.congrats.content.bold": "nous vous conseillons de créer votre premier Type de Contenu.",
"HomePage.community": "Rejoignez la Communauté",
"HomePage.roadmap": "Regardez notre roadmap",
"HomePage.greetings": "Bonjour {name}!"
}