Merge branch 'develop' into change-email
This commit is contained in:
commit
69fefdb2ea
|
@ -8,32 +8,34 @@ import {
|
|||
withRouter,
|
||||
matchPath,
|
||||
} from 'react-router';
|
||||
import loadable from 'loadable-components';
|
||||
import loadable from '@loadable/component';
|
||||
import AuthRoute from 'components/AuthRoute';
|
||||
import Template, { TemplateProps } from 'components/Template';
|
||||
|
||||
// wrap components in loadable...import & they will be split
|
||||
const Home = loadable(() => import('pages/index'));
|
||||
const Create = loadable(() => import('pages/create'));
|
||||
const ProposalEdit = loadable(() => import('pages/proposal-edit'));
|
||||
const Proposals = loadable(() => import('pages/proposals'));
|
||||
const Proposal = loadable(() => import('pages/proposal'));
|
||||
const opts = { fallback: <Loader size="large" /> };
|
||||
const Home = loadable(() => import('pages/index'), opts);
|
||||
const Create = loadable(() => import('pages/create'), opts);
|
||||
const ProposalEdit = loadable(() => import('pages/proposal-edit'), opts);
|
||||
const Proposals = loadable(() => import('pages/proposals'), opts);
|
||||
const Proposal = loadable(() => import('pages/proposal'), opts);
|
||||
const Auth = loadable(() => import('pages/auth'));
|
||||
const SignOut = loadable(() => import('pages/sign-out'));
|
||||
const Profile = loadable(() => import('pages/profile'));
|
||||
const Settings = loadable(() => import('pages/settings'));
|
||||
const Exception = loadable(() => import('pages/exception'));
|
||||
const SignOut = loadable(() => import('pages/sign-out'), opts);
|
||||
const Profile = loadable(() => import('pages/profile'), opts);
|
||||
const Settings = loadable(() => import('pages/settings'), opts);
|
||||
const Exception = loadable(() => import('pages/exception'), opts);
|
||||
const Tos = loadable(() => import('pages/tos'));
|
||||
const About = loadable(() => import('pages/about'));
|
||||
const Privacy = loadable(() => import('pages/privacy'));
|
||||
const Contact = loadable(() => import('pages/contact'));
|
||||
const CodeOfConduct = loadable(() => import('pages/code-of-conduct'));
|
||||
const VerifyEmail = loadable(() => import('pages/email-verify'));
|
||||
const Callback = loadable(() => import('pages/callback'));
|
||||
const RecoverEmail = loadable(() => import('pages/email-recover'));
|
||||
const UnsubscribeEmail = loadable(() => import('pages/email-unsubscribe'));
|
||||
const About = loadable(() => import('pages/about'), opts);
|
||||
const Privacy = loadable(() => import('pages/privacy'), opts);
|
||||
const Contact = loadable(() => import('pages/contact'), opts);
|
||||
const CodeOfConduct = loadable(() => import('pages/code-of-conduct'), opts);
|
||||
const VerifyEmail = loadable(() => import('pages/email-verify'), opts);
|
||||
const Callback = loadable(() => import('pages/callback'), opts);
|
||||
const RecoverEmail = loadable(() => import('pages/email-recover'), opts);
|
||||
const UnsubscribeEmail = loadable(() => import('pages/email-unsubscribe'), opts);
|
||||
|
||||
import 'styles/style.less';
|
||||
import Loader from 'components/Loader';
|
||||
|
||||
interface RouteConfig extends RouteProps {
|
||||
route: RouteProps;
|
||||
|
@ -284,9 +286,15 @@ class Routes extends React.PureComponent<Props> {
|
|||
const routeComponents = routeConfigs.map(config => {
|
||||
const { route, onlyLoggedIn, onlyLoggedOut } = config;
|
||||
if (onlyLoggedIn || onlyLoggedOut) {
|
||||
return <AuthRoute key={route.path} onlyLoggedOut={onlyLoggedOut} {...route} />;
|
||||
return (
|
||||
<AuthRoute
|
||||
key={route.path as string}
|
||||
onlyLoggedOut={onlyLoggedOut}
|
||||
{...route}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <Route key={route.path} {...route} />;
|
||||
return <Route key={route.path as string} {...route} />;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ import { connect } from 'react-redux';
|
|||
import { compose } from 'recompose';
|
||||
import { withRouter, RouteComponentProps, Redirect } from 'react-router';
|
||||
import { Switch, Route, Link } from 'react-router-dom';
|
||||
import { Spin } from 'antd';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { authActions } from 'modules/auth';
|
||||
import Loader from 'components/Loader';
|
||||
import Exception from 'pages/exception';
|
||||
import SignIn from './SignIn';
|
||||
import SignUp from './SignUp';
|
||||
|
@ -65,7 +65,7 @@ class AuthFlow extends React.Component<Props> {
|
|||
const { isCheckingUser, match } = this.props;
|
||||
|
||||
if (isCheckingUser) {
|
||||
return <Spin size="large" />;
|
||||
return <Loader size="large" />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Route, Redirect, RouteProps } from 'react-router-dom';
|
||||
import { Spin, message } from 'antd';
|
||||
import { Route, Redirect, RouteProps } from 'react-router';
|
||||
import { message } from 'antd';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { authActions } from 'modules/auth';
|
||||
import Loader from 'components/Loader';
|
||||
|
||||
interface OwnProps {
|
||||
onlyLoggedOut?: boolean;
|
||||
|
@ -43,7 +44,7 @@ class AuthRoute extends React.Component<Props> {
|
|||
...routeProps
|
||||
} = this.props;
|
||||
if (isCheckingUser) {
|
||||
return <Spin tip="Checking authentication status" />;
|
||||
return <Loader size="large" tip="Checking authentication status" />;
|
||||
}
|
||||
if ((user && !onlyLoggedOut) || (!user && onlyLoggedOut)) {
|
||||
return <Route {...routeProps} />;
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { Form, Input, Spin, Button, Icon, Radio, message } from 'antd';
|
||||
import { Form, Input, Button, Icon, Radio, message } from 'antd';
|
||||
import { RadioChangeEvent } from 'antd/lib/radio';
|
||||
import QRCode from 'qrcode.react';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import { formatZcashURI, formatZcashCLI } from 'utils/formatters';
|
||||
import { ContributionWithAddresses } from 'types';
|
||||
import Loader from 'components/Loader';
|
||||
import './PaymentInfo.less';
|
||||
|
||||
interface Props {
|
||||
|
@ -73,7 +74,7 @@ export default class PaymentInfo extends React.Component<Props, State> {
|
|||
<span style={{ opacity: uri ? 1 : 0 }}>
|
||||
<QRCode value={uri || ''} />
|
||||
</span>
|
||||
{!uri && <Spin size="large" />}
|
||||
{!uri && <Loader />}
|
||||
</div>
|
||||
<div className="PaymentInfo-uri-info">
|
||||
<CopyInput
|
||||
|
|
|
@ -7,14 +7,6 @@
|
|||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
|
||||
&-loader {
|
||||
&-text {
|
||||
opacity: 0.8;
|
||||
font-size: 1.2rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&-message {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Spin, Icon } from 'antd';
|
||||
import { Icon } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Loader from 'components/Loader';
|
||||
import { createActions } from 'modules/create';
|
||||
import { AppState } from 'store/reducers';
|
||||
import './Final.less';
|
||||
|
@ -56,12 +57,7 @@ class CreateFinal extends React.Component<Props> {
|
|||
</div>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<div className="CreateFinal-loader">
|
||||
<Spin size="large" />
|
||||
<div className="CreateFinal-loader-text">Submitting your proposal...</div>
|
||||
</div>
|
||||
);
|
||||
content = <Loader size="large" tip="Submitting your proposal..." />;
|
||||
}
|
||||
|
||||
return <div className="CreateFinal">{content}</div>;
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { List, Button, Divider, Spin, Popconfirm, message } from 'antd';
|
||||
import { Spin, List, Button, Divider, Popconfirm, message } from 'antd';
|
||||
import Placeholder from 'components/Placeholder';
|
||||
import Loader from 'components/Loader';
|
||||
import { ProposalDraft, STATUS } from 'types';
|
||||
import { fetchDrafts, createDraft, deleteDraft } from 'modules/create/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
|
@ -70,7 +71,7 @@ class DraftList extends React.Component<Props, State> {
|
|||
const { deletingId } = this.state;
|
||||
|
||||
if (!drafts || isCreatingDraft) {
|
||||
return <Spin />;
|
||||
return <Loader size="large" />;
|
||||
}
|
||||
|
||||
let draftsEl;
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
@import '~styles/variables.less';
|
||||
|
||||
.Loader {
|
||||
position: relative;
|
||||
color: @primary-color;
|
||||
font-size: 2rem;
|
||||
|
||||
&:not(.is-inline) {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
&.is-large {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
&.is-small {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
&-tip {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
width: 200px;
|
||||
margin-top: 0.5rem;
|
||||
text-align: center;
|
||||
color: @text-color-secondary;
|
||||
font-size: 0.8rem;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import React from 'react';
|
||||
import { Icon } from 'antd';
|
||||
import classnames from 'classnames';
|
||||
import './index.less';
|
||||
|
||||
interface Props {
|
||||
size?: 'large' | 'small';
|
||||
inline?: boolean;
|
||||
tip?: string;
|
||||
}
|
||||
|
||||
const Loader: React.SFC<Props> = ({ inline, size, tip }) => (
|
||||
<div className={classnames('Loader', size && `is-${size}`, inline && 'is-inline')}>
|
||||
<Icon type="loading" theme="outlined" />
|
||||
{tip && <div className="Loader-tip">{tip}</div>}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Loader;
|
|
@ -8,7 +8,7 @@ import {
|
|||
} from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import { compose } from 'recompose';
|
||||
import { Spin, Tabs, Badge } from 'antd';
|
||||
import { Tabs, Badge } from 'antd';
|
||||
import { usersActions } from 'modules/users';
|
||||
import { AppState } from 'store/reducers';
|
||||
import HeaderDetails from 'components/HeaderDetails';
|
||||
|
@ -20,6 +20,7 @@ import ProfileContribution from './ProfileContribution';
|
|||
import ProfileComment from './ProfileComment';
|
||||
import ProfileInvite from './ProfileInvite';
|
||||
import Placeholder from 'components/Placeholder';
|
||||
import Loader from 'components/Loader';
|
||||
import Exception from 'pages/exception';
|
||||
import ContributionModal from 'components/ContributionModal';
|
||||
import LinkableTabs from 'components/LinkableTabs';
|
||||
|
@ -50,6 +51,7 @@ class Profile extends React.Component<Props, State> {
|
|||
componentDidMount() {
|
||||
this.fetchData();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const userLookupId = this.props.match.params.id;
|
||||
const prevUserLookupId = prevProps.match.params.id;
|
||||
|
@ -58,6 +60,7 @@ class Profile extends React.Component<Props, State> {
|
|||
this.fetchData();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { authUser, match, location } = this.props;
|
||||
const { activeContribution } = this.state;
|
||||
|
@ -76,7 +79,7 @@ class Profile extends React.Component<Props, State> {
|
|||
const isAuthedUser = user && authUser && user.userid === authUser.userid;
|
||||
|
||||
if (waiting) {
|
||||
return <Spin />;
|
||||
return <Loader size="large" />;
|
||||
}
|
||||
|
||||
if (user.fetchError) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { Spin, Form, Input, Button, Icon } from 'antd';
|
||||
import { Form, Input, Button, Icon } from 'antd';
|
||||
import { Proposal, STATUS } from 'types';
|
||||
import classnames from 'classnames';
|
||||
import { fromZat } from 'utils/units';
|
||||
|
@ -10,6 +10,7 @@ import { AppState } from 'store/reducers';
|
|||
import { withRouter } from 'react-router';
|
||||
import UnitDisplay from 'components/UnitDisplay';
|
||||
import ContributionModal from 'components/ContributionModal';
|
||||
import Loader from 'components/Loader';
|
||||
import { getAmountError } from 'utils/validators';
|
||||
import { CATEGORY_UI } from 'api/constants';
|
||||
import './style.less';
|
||||
|
@ -167,7 +168,7 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
|||
</React.Fragment>
|
||||
);
|
||||
} else {
|
||||
content = <Spin />;
|
||||
content = <Loader />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Spin, Button, message } from 'antd';
|
||||
import { Button, message } from 'antd';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Proposal } from 'types';
|
||||
import { fetchProposalComments, postProposalComment } from 'modules/proposals/actions';
|
||||
|
@ -12,6 +12,7 @@ import {
|
|||
import { getIsSignedIn } from 'modules/auth/selectors';
|
||||
import Comments from 'components/Comments';
|
||||
import Placeholder from 'components/Placeholder';
|
||||
import Loader from 'components/Loader';
|
||||
import MarkdownEditor, { MARKDOWN_TYPE } from 'components/MarkdownEditor';
|
||||
import './style.less';
|
||||
|
||||
|
@ -83,7 +84,7 @@ class ProposalComments extends React.Component<Props, State> {
|
|||
let content = null;
|
||||
|
||||
if (isFetchingComments) {
|
||||
content = <Spin />;
|
||||
content = <Loader />;
|
||||
} else if (commentsError) {
|
||||
content = (
|
||||
<>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Spin } from 'antd';
|
||||
import UserRow from 'components/UserRow';
|
||||
import Placeholder from 'components/Placeholder';
|
||||
import UnitDisplay from 'components/UnitDisplay';
|
||||
import Loader from 'components/Loader';
|
||||
import { toZat } from 'utils/units';
|
||||
import { fetchProposalContributions } from 'modules/proposals/actions';
|
||||
import {
|
||||
|
@ -83,7 +83,7 @@ class ProposalContributors extends React.Component<Props> {
|
|||
} else if (fetchContributionsError) {
|
||||
content = <Placeholder title="Something went wrong" subtitle={fetchContributionsError} />;
|
||||
} else {
|
||||
content = <Spin />;
|
||||
content = <Loader />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import lodash from 'lodash';
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { Alert, Steps, Spin } from 'antd';
|
||||
import { Alert, Steps } from 'antd';
|
||||
import { Proposal, MILESTONE_STATE } from 'types';
|
||||
import UnitDisplay from 'components/UnitDisplay';
|
||||
import Loader from 'components/Loader';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { connect } from 'react-redux';
|
||||
import classnames from 'classnames';
|
||||
|
@ -82,7 +83,7 @@ class ProposalMilestones extends React.Component<Props, State> {
|
|||
render() {
|
||||
const { proposal } = this.props;
|
||||
if (!proposal) {
|
||||
return <Spin />;
|
||||
return <Loader />;
|
||||
}
|
||||
const { milestones } = proposal;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Spin } from 'antd';
|
||||
import { Proposal } from 'types';
|
||||
import Loader from 'components/Loader';
|
||||
import UserRow from 'components/UserRow';
|
||||
|
||||
interface Props {
|
||||
|
@ -12,7 +12,7 @@ const TeamBlock = ({ proposal }: Props) => {
|
|||
if (proposal) {
|
||||
content = proposal.team.map(user => <UserRow key={user.displayName} user={user} />);
|
||||
} else {
|
||||
content = <Spin />;
|
||||
content = <Loader />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Spin } from 'antd';
|
||||
import Loader from 'components/Loader';
|
||||
import Markdown from 'components/Markdown';
|
||||
import moment from 'moment';
|
||||
import Placeholder from 'components/Placeholder';
|
||||
|
@ -58,7 +58,7 @@ class ProposalUpdates extends React.Component<Props, State> {
|
|||
let content = null;
|
||||
|
||||
if (isFetchingUpdates) {
|
||||
content = <Spin />;
|
||||
content = <Loader />;
|
||||
} else if (updatesError) {
|
||||
content = <Placeholder title="Something went wrong" subtitle={updatesError} />;
|
||||
} else if (updates) {
|
||||
|
|
|
@ -4,12 +4,13 @@ import { connect } from 'react-redux';
|
|||
import { Link } from 'react-router-dom';
|
||||
import Markdown from 'components/Markdown';
|
||||
import LinkableTabs from 'components/LinkableTabs';
|
||||
import Loader from 'components/Loader';
|
||||
import { proposalActions } from 'modules/proposals';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Proposal, STATUS } from 'types';
|
||||
import { getProposal } from 'modules/proposals/selectors';
|
||||
import { Spin, Tabs, Icon, Dropdown, Menu, Button, Alert } from 'antd';
|
||||
import { Tabs, Icon, Dropdown, Menu, Button, Alert } from 'antd';
|
||||
import { AlertProps } from 'antd/lib/alert';
|
||||
import CampaignBlock from './CampaignBlock';
|
||||
import TeamBlock from './TeamBlock';
|
||||
|
@ -93,7 +94,7 @@ export class ProposalDetail extends React.Component<Props, State> {
|
|||
const showExpand = !isBodyExpanded && isBodyOverflowing;
|
||||
|
||||
if (!proposal) {
|
||||
return <Spin />;
|
||||
return <Loader size="large" />;
|
||||
}
|
||||
|
||||
const deadline = 0; // TODO: Use actual date for deadline
|
||||
|
@ -198,7 +199,7 @@ export class ProposalDetail extends React.Component<Props, State> {
|
|||
{proposal ? (
|
||||
<Markdown source={proposal.content} />
|
||||
) : (
|
||||
<Spin size="large" />
|
||||
<Loader />
|
||||
)}
|
||||
</div>
|
||||
{showExpand && (
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Spin, Row, Col, Pagination } from 'antd';
|
||||
import { Row, Col, Pagination } from 'antd';
|
||||
import Loader from 'components/Loader';
|
||||
import ProposalCard from '../ProposalCard';
|
||||
|
||||
interface Props {
|
||||
|
@ -31,7 +32,7 @@ export default class ProposalResults extends React.Component<Props, State> {
|
|||
const { page } = this.state;
|
||||
|
||||
if (isFetchingProposals) {
|
||||
return <Spin size="large" />;
|
||||
return <Loader size="large" />;
|
||||
}
|
||||
|
||||
if (proposalsError) {
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
}
|
||||
|
||||
&-results {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@ import { AppState } from 'store/reducers';
|
|||
import { updateUserSettings, getUserSettings } from 'api/api';
|
||||
import { EmailSubscriptions as IEmailSubscriptions } from 'types';
|
||||
import EmailSubscriptionsForm from 'components/EmailSubscriptionsForm';
|
||||
import { Spin, message } from 'antd';
|
||||
import { message } from 'antd';
|
||||
import Loader from 'components/Loader';
|
||||
|
||||
interface StateProps {
|
||||
authUser: AppState['auth']['user'];
|
||||
|
@ -35,7 +36,7 @@ class EmailSubscriptions extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
if (!emailSubscriptions) {
|
||||
return <Spin />;
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
min-height: 100vh;
|
||||
|
||||
&-content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
|
|
|
@ -2,12 +2,12 @@ import '@babel/polyfill';
|
|||
import React from 'react';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { hydrate } from 'react-dom';
|
||||
import { loadComponents } from 'loadable-components';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { loadableReady } from '@loadable/component';
|
||||
import { configureStore } from 'store/configure';
|
||||
import history from 'store/history';
|
||||
import { massageSerializedState } from 'utils/api';
|
||||
|
@ -38,6 +38,6 @@ const App = hot(module)(() => (
|
|||
</I18nextProvider>
|
||||
));
|
||||
|
||||
loadComponents().then(() => {
|
||||
loadableReady(() => {
|
||||
hydrate(<App />, document.getElementById('app'));
|
||||
});
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import React from 'react';
|
||||
import { Spin, Alert } from 'antd';
|
||||
import { Alert } from 'antd';
|
||||
import qs from 'query-string';
|
||||
import { withRouter, RouteComponentProps, Redirect } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import { compose } from 'recompose';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { SOCIAL_INFO } from 'utils/social';
|
||||
import Loader from 'components/Loader';
|
||||
|
||||
interface StateProps {
|
||||
authUser: AppState['auth']['user'];
|
||||
|
@ -35,7 +36,7 @@ class Callback extends React.Component<Props, State> {
|
|||
const { hasCheckedAuthUser, authUser } = this.props;
|
||||
|
||||
if (!hasCheckedAuthUser) {
|
||||
return <Spin />;
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
if (hasCheckedAuthUser && !authUser) {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import React from 'react';
|
||||
import { Spin, Button } from 'antd';
|
||||
import { Button } from 'antd';
|
||||
import qs from 'query-string';
|
||||
import { withRouter, RouteComponentProps, Link } from 'react-router-dom';
|
||||
import Result from 'ant-design-pro/lib/Result';
|
||||
import { unsubscribeEmail } from 'api/api';
|
||||
import Loader from 'components/Loader';
|
||||
|
||||
interface State {
|
||||
isUnsubscribing: boolean;
|
||||
|
@ -82,7 +83,7 @@ class UnsubscribeEmail extends React.Component<RouteComponentProps, State> {
|
|||
/>
|
||||
);
|
||||
} else {
|
||||
return <Spin size="large" />;
|
||||
return <Loader size="large" />;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import React from 'react';
|
||||
import { Spin, Button } from 'antd';
|
||||
import { Button } from 'antd';
|
||||
import qs from 'query-string';
|
||||
import { withRouter, RouteComponentProps, Link } from 'react-router-dom';
|
||||
import Result from 'ant-design-pro/lib/Result';
|
||||
import { verifyEmail } from 'api/api';
|
||||
import Loader from 'components/Loader';
|
||||
|
||||
interface State {
|
||||
isVerifying: boolean;
|
||||
|
@ -82,7 +83,7 @@ class VerifyEmail extends React.Component<RouteComponentProps, State> {
|
|||
/>
|
||||
);
|
||||
} else {
|
||||
return <Spin size="large" />;
|
||||
return <Loader size="large" />;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ interface Props {
|
|||
code: '403' | '404' | '500';
|
||||
}
|
||||
|
||||
const ExceptionComponent: React.SFC<Props> = ({ code }) => (
|
||||
const ExceptionComponent = ({ code }: Props) => (
|
||||
<Exception type={code} {...content[code]} />
|
||||
);
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter, RouteComponentProps } from 'react-router';
|
||||
import { Spin } from 'antd';
|
||||
import CreateFlow from 'components/CreateFlow';
|
||||
import { initializeForm } from 'modules/create/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import Loader from 'components/Loader';
|
||||
|
||||
interface StateProps {
|
||||
form: AppState['create']['form'];
|
||||
|
@ -31,7 +31,7 @@ class ProposalEdit extends React.Component<Props> {
|
|||
} else if (initializeFormError) {
|
||||
return <h1>{initializeFormError}</h1>;
|
||||
} else {
|
||||
return <Spin />;
|
||||
return <Loader />;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
declare module 'loadable-components/server' {
|
||||
import * as React from 'react';
|
||||
|
||||
export function getLoadableState(
|
||||
rootElement: React.ReactElement<{}>,
|
||||
rootContext?: any,
|
||||
fetchRoot?: boolean,
|
||||
tree?: any,
|
||||
): Promise<DeferredState>;
|
||||
|
||||
export interface DeferredStateTree {
|
||||
id: string;
|
||||
children: DeferredStateTree[];
|
||||
}
|
||||
|
||||
export interface DeferredState {
|
||||
tree: DeferredStateTree;
|
||||
getScriptContent(): string;
|
||||
getScriptTag(): string;
|
||||
getScriptElement(): React.ReactHTMLElement<HTMLScriptElement>;
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ const tsBabelLoaderClient = {
|
|||
options: {
|
||||
plugins: [
|
||||
'dynamic-import-webpack', // for client
|
||||
'loadable-components/babel',
|
||||
'@loadable/babel-plugin',
|
||||
'react-hot-loader/babel',
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-proposal-class-properties',
|
||||
|
@ -47,7 +47,7 @@ const tsBabelLoaderServer = {
|
|||
options: {
|
||||
plugins: [
|
||||
'dynamic-import-node', // for server
|
||||
'loadable-components/babel',
|
||||
'@loadable/babel-plugin',
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-proposal-class-properties',
|
||||
['import', { libraryName: 'antd', style: false }],
|
||||
|
|
|
@ -6,6 +6,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|||
const ModuleDependencyWarning = require('./module-dependency-warning');
|
||||
const WebappWebpackPlugin = require('webapp-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const LoadablePlugin = require('@loadable/webpack-plugin');
|
||||
|
||||
const env = require('../env')();
|
||||
const paths = require('../paths');
|
||||
|
@ -75,6 +76,7 @@ const client = [
|
|||
return JSON.stringify(trans, null, 2);
|
||||
},
|
||||
}),
|
||||
new LoadablePlugin(),
|
||||
];
|
||||
|
||||
const server = [
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/preset-typescript": "^7.0.0",
|
||||
"@babel/register": "^7.0.0",
|
||||
"@loadable/component": "5.5.0",
|
||||
"@sentry/browser": "^4.3.2",
|
||||
"@sentry/node": "^4.3.2",
|
||||
"@svgr/webpack": "^2.4.0",
|
||||
|
@ -59,7 +60,7 @@
|
|||
"@types/react-dom": "16.0.9",
|
||||
"@types/react-helmet": "^5.0.7",
|
||||
"@types/react-redux": "^6.0.2",
|
||||
"@types/react-router": "^4.0.31",
|
||||
"@types/react-router": "4.4.3",
|
||||
"@types/react-router-dom": "^4.3.1",
|
||||
"@types/recompose": "^0.26.1",
|
||||
"@types/redux-actions": "^2.3.0",
|
||||
|
@ -110,7 +111,6 @@
|
|||
"less": "^3.7.1",
|
||||
"less-loader": "^4.1.0",
|
||||
"lint-staged": "^7.2.2",
|
||||
"loadable-components": "^2.2.3",
|
||||
"lodash": "^4.17.10",
|
||||
"markdown-loader": "^4.0.0",
|
||||
"mini-css-extract-plugin": "^0.4.2",
|
||||
|
@ -131,8 +131,8 @@
|
|||
"react-i18next": "^8.3.5",
|
||||
"react-mde": "^5.8.0",
|
||||
"react-redux": "^5.0.7",
|
||||
"react-router": "^4.3.1",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-router": "4.4.0-beta.6",
|
||||
"react-router-dom": "4.4.0-beta.6",
|
||||
"recompose": "^0.27.1",
|
||||
"redux": "^4.0.0",
|
||||
"redux-devtools-extension": "^2.13.2",
|
||||
|
@ -167,8 +167,13 @@
|
|||
"xss": "1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@loadable/babel-plugin": "5.5.0",
|
||||
"@loadable/server": "5.5.0",
|
||||
"@loadable/webpack-plugin": "5.5.0",
|
||||
"@storybook/react": "4.0.0-alpha.22",
|
||||
"@types/bn.js": "4.11.1",
|
||||
"@types/loadable__component": "5.2.0",
|
||||
"@types/loadable__server": "5.2.0",
|
||||
"@types/qrcode.react": "^0.8.1",
|
||||
"@types/query-string": "6.1.0",
|
||||
"@types/react-copy-to-clipboard": "^4.2.6",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as React from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { ChunkExtractor } from '@loadable/server';
|
||||
|
||||
export interface Props {
|
||||
children: any;
|
||||
|
@ -9,7 +10,7 @@ export interface Props {
|
|||
metaTags: Array<React.MetaHTMLAttributes<HTMLMetaElement>>;
|
||||
state: string;
|
||||
i18n: string;
|
||||
loadableStateScript: string;
|
||||
extractor: ChunkExtractor;
|
||||
}
|
||||
|
||||
const HTML: React.SFC<Props> = ({
|
||||
|
@ -20,7 +21,7 @@ const HTML: React.SFC<Props> = ({
|
|||
i18n,
|
||||
linkTags,
|
||||
metaTags,
|
||||
loadableStateScript,
|
||||
extractor,
|
||||
}) => {
|
||||
const head = Helmet.renderStatic();
|
||||
return (
|
||||
|
@ -43,6 +44,7 @@ const HTML: React.SFC<Props> = ({
|
|||
crossOrigin="anonymous"
|
||||
/> */}
|
||||
{/* Custom link & meta tags from webpack */}
|
||||
{extractor.getLinkElements()}
|
||||
{linkTags.map((l, idx) => (
|
||||
<link key={idx} {...l as any} />
|
||||
))}
|
||||
|
@ -57,9 +59,12 @@ const HTML: React.SFC<Props> = ({
|
|||
{head.link.toComponent()}
|
||||
{head.script.toComponent()}
|
||||
|
||||
{extractor.getStyleElements()}
|
||||
{css.map(href => {
|
||||
return <link key={href} rel="stylesheet" href={href} />;
|
||||
})}
|
||||
|
||||
{extractor.getScriptElements()}
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `window.__PRELOADED_STATE__ = ${state}`,
|
||||
|
@ -73,7 +78,6 @@ const HTML: React.SFC<Props> = ({
|
|||
</head>
|
||||
<body>
|
||||
<div id="app" dangerouslySetInnerHTML={{ __html: children }} />
|
||||
<script dangerouslySetInnerHTML={{ __html: loadableStateScript }} />
|
||||
{scripts.map(src => {
|
||||
return <script key={src} src={src} />;
|
||||
})}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import React from 'react';
|
||||
import { Request, Response } from 'express';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
import { getLoadableState } from 'loadable-components/server';
|
||||
import { ChunkExtractor } from '@loadable/server';
|
||||
import { StaticRouter as Router } from 'react-router-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
|
@ -19,63 +18,6 @@ import i18n from './i18n';
|
|||
// @ts-ignore
|
||||
import * as paths from '../config/paths';
|
||||
import { storeActionsForPath } from './ssrAsync';
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
let cachedStats: any;
|
||||
const getStats = () =>
|
||||
new Promise((res, rej) => {
|
||||
if (!isDev && cachedStats) {
|
||||
res(cachedStats);
|
||||
return;
|
||||
}
|
||||
const statsPath = path.join(paths.clientBuild, paths.publicPath, 'stats.json');
|
||||
fs.readFile(statsPath, (e, d) => {
|
||||
if (e) {
|
||||
rej(e);
|
||||
return;
|
||||
}
|
||||
cachedStats = JSON.parse(d.toString());
|
||||
res(cachedStats);
|
||||
});
|
||||
});
|
||||
|
||||
const extractLoadableIds = (tree: any): string[] => {
|
||||
const ids = (tree.id && [tree.id]) || [];
|
||||
if (tree.children) {
|
||||
return tree.children
|
||||
.reduce((a: string[], c: any) => a.concat(extractLoadableIds(c)), [])
|
||||
.concat(ids);
|
||||
}
|
||||
return ids;
|
||||
};
|
||||
|
||||
// TODO: write tests for this
|
||||
const chunkExtractFromLoadables = (loadableState: any) =>
|
||||
getStats().then((stats: any) => {
|
||||
const loadableIds = extractLoadableIds(loadableState.tree);
|
||||
const mods = stats.modules.filter(
|
||||
(m: any) =>
|
||||
m.reasons.filter((r: any) => loadableIds.indexOf(r.userRequest) > -1).length > 0,
|
||||
);
|
||||
const chunks = mods.reduce((a: string[], m: any) => a.concat(m.chunks), []);
|
||||
const origins = stats.chunks
|
||||
.filter((c: any) => chunks.indexOf(c.id) > -1)
|
||||
.map((c: any) => ({ loc: c.origins[0].loc, moduleId: c.origins[0].moduleId }));
|
||||
const origin = origins[0];
|
||||
const files = stats.chunks
|
||||
.filter(
|
||||
(c: any) =>
|
||||
c.origins.filter(
|
||||
(o: any) => origin && o.loc === origin.loc && o.moduleId === origin.moduleId,
|
||||
).length > 0,
|
||||
)
|
||||
.reduce((a: string[], c: any) => a.concat(c.files), []);
|
||||
|
||||
return {
|
||||
css: files.filter((f: string) => /.css$/.test(f)),
|
||||
js: files.filter((f: string) => /.js$/.test(f)),
|
||||
};
|
||||
});
|
||||
|
||||
const serverRenderer = () => async (req: Request, res: Response) => {
|
||||
const { store } = configureStore();
|
||||
|
@ -98,18 +40,22 @@ const serverRenderer = () => async (req: Request, res: Response) => {
|
|||
</I18nextProvider>
|
||||
);
|
||||
|
||||
let loadableState;
|
||||
let loadableFiles;
|
||||
let extractor;
|
||||
// 1. loadable state will render dynamic imports
|
||||
try {
|
||||
loadableState = await getLoadableState(reactApp);
|
||||
loadableFiles = await chunkExtractFromLoadables(loadableState);
|
||||
const statsFile = path.join(
|
||||
paths.clientBuild,
|
||||
paths.publicPath,
|
||||
'loadable-stats.json',
|
||||
);
|
||||
extractor = new ChunkExtractor({ statsFile, entrypoints: ['bundle'] });
|
||||
} catch (e) {
|
||||
const disp = `Error getting loadable state for SSR`;
|
||||
e.message = disp + ': ' + e.message;
|
||||
log.error(e);
|
||||
return res.status(500).send(disp + ' (more info in server logs)');
|
||||
}
|
||||
|
||||
// 2. render and collect state
|
||||
const content = renderToString(reactApp);
|
||||
const state = JSON.stringify(store.getState());
|
||||
|
@ -124,10 +70,10 @@ const serverRenderer = () => async (req: Request, res: Response) => {
|
|||
return res.status(500).send(disp);
|
||||
}
|
||||
|
||||
const cssFiles = ['bundle.css', 'vendor.css', ...loadableFiles.css]
|
||||
const cssFiles = ['bundle.css', 'vendor.css']
|
||||
.map(f => res.locals.assetPath(f))
|
||||
.filter(Boolean);
|
||||
const jsFiles = [...loadableFiles.js, 'vendor.js', 'bundle.js']
|
||||
const jsFiles = ['vendor.js', 'bundle.js']
|
||||
.map(f => res.locals.assetPath(f))
|
||||
.filter(Boolean);
|
||||
const mappedLinkTags = linkTags
|
||||
|
@ -147,7 +93,7 @@ const serverRenderer = () => async (req: Request, res: Response) => {
|
|||
metaTags={mappedMetaTags}
|
||||
state={state}
|
||||
i18n={i18nClient}
|
||||
loadableStateScript={loadableState.getScriptContent()}
|
||||
extractor={extractor}
|
||||
>
|
||||
{content}
|
||||
</Html>,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue