Merge branch 'develop' into expired-notif
This commit is contained in:
commit
7235c15862
|
@ -173,7 +173,7 @@ class MFAuth extends React.Component<Props, State> {
|
||||||
<ol>
|
<ol>
|
||||||
<li>Save two-factor recovery codes</li>
|
<li>Save two-factor recovery codes</li>
|
||||||
<li>
|
<li>
|
||||||
Setup up TOTP authentication device, typically a smartphone with Google
|
Setup TOTP authentication device, typically a smartphone with Google
|
||||||
Authenticator, Authy, 1Password or other compatible authenticator app.
|
Authenticator, Authy, 1Password or other compatible authenticator app.
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
|
@ -7,6 +7,9 @@ REDISTOGO_URL="redis://localhost:6379"
|
||||||
SECRET_KEY="not-so-secret"
|
SECRET_KEY="not-so-secret"
|
||||||
SENDGRID_API_KEY="optional, but emails won't send without it"
|
SENDGRID_API_KEY="optional, but emails won't send without it"
|
||||||
|
|
||||||
|
# set this so third-party cookie blocking doesn't kill backend sessions (production)
|
||||||
|
# SESSION_COOKIE_DOMAIN="zfnd.org"
|
||||||
|
|
||||||
# SENTRY_DSN="https://PUBLICKEY@sentry.io/PROJECTID"
|
# SENTRY_DSN="https://PUBLICKEY@sentry.io/PROJECTID"
|
||||||
# SENTRY_RELEASE="optional, provides sentry logging with release info"
|
# SENTRY_RELEASE="optional, provides sentry logging with release info"
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ ENV = env.str("FLASK_ENV", default="production")
|
||||||
DEBUG = ENV == "development"
|
DEBUG = ENV == "development"
|
||||||
SITE_URL = env.str('SITE_URL', default='https://zfnd.org')
|
SITE_URL = env.str('SITE_URL', default='https://zfnd.org')
|
||||||
SQLALCHEMY_DATABASE_URI = env.str("DATABASE_URL")
|
SQLALCHEMY_DATABASE_URI = env.str("DATABASE_URL")
|
||||||
SQLALCHEMY_ECHO = False # True will print queries to log
|
SQLALCHEMY_ECHO = False # True will print queries to log
|
||||||
QUEUES = ["default"]
|
QUEUES = ["default"]
|
||||||
SECRET_KEY = env.str("SECRET_KEY")
|
SECRET_KEY = env.str("SECRET_KEY")
|
||||||
BCRYPT_LOG_ROUNDS = env.int("BCRYPT_LOG_ROUNDS", default=13)
|
BCRYPT_LOG_ROUNDS = env.int("BCRYPT_LOG_ROUNDS", default=13)
|
||||||
|
@ -25,6 +25,9 @@ DEBUG_TB_INTERCEPT_REDIRECTS = False
|
||||||
CACHE_TYPE = "simple" # Can be "memcached", "redis", etc.
|
CACHE_TYPE = "simple" # Can be "memcached", "redis", etc.
|
||||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
|
|
||||||
|
# so backend session cookies are first-party
|
||||||
|
SESSION_COOKIE_DOMAIN = env.str('SESSION_COOKIE_DOMAIN', default=None)
|
||||||
|
|
||||||
SENDGRID_API_KEY = env.str("SENDGRID_API_KEY", default="")
|
SENDGRID_API_KEY = env.str("SENDGRID_API_KEY", default="")
|
||||||
SENDGRID_DEFAULT_FROM = "noreply@zfnd.org"
|
SENDGRID_DEFAULT_FROM = "noreply@zfnd.org"
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,9 @@ import {
|
||||||
import loadable from '@loadable/component';
|
import loadable from '@loadable/component';
|
||||||
import AuthRoute from 'components/AuthRoute';
|
import AuthRoute from 'components/AuthRoute';
|
||||||
import Template, { TemplateProps } from 'components/Template';
|
import Template, { TemplateProps } from 'components/Template';
|
||||||
|
import ErrorWrap from 'components/ErrorWrap';
|
||||||
|
import Loader from 'components/Loader';
|
||||||
|
import 'styles/style.less';
|
||||||
|
|
||||||
// wrap components in loadable...import & they will be split
|
// wrap components in loadable...import & they will be split
|
||||||
// Make sure you specify chunkname! Must replace slashes with dashes.
|
// Make sure you specify chunkname! Must replace slashes with dashes.
|
||||||
|
@ -38,9 +41,6 @@ const ArbiterEmail = loadable(() => import('pages/email-arbiter'), opts);
|
||||||
const RFP = loadable(() => import('pages/rfp'), opts);
|
const RFP = loadable(() => import('pages/rfp'), opts);
|
||||||
const RFPs = loadable(() => import('pages/rfps'), opts);
|
const RFPs = loadable(() => import('pages/rfps'), opts);
|
||||||
|
|
||||||
import 'styles/style.less';
|
|
||||||
import Loader from 'components/Loader';
|
|
||||||
|
|
||||||
interface RouteConfig extends RouteProps {
|
interface RouteConfig extends RouteProps {
|
||||||
route: RouteProps;
|
route: RouteProps;
|
||||||
template: TemplateProps;
|
template: TemplateProps;
|
||||||
|
@ -336,7 +336,9 @@ class Routes extends React.PureComponent<Props> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template {...currentRoute.template}>
|
<Template {...currentRoute.template}>
|
||||||
<Switch>{routeComponents}</Switch>
|
<ErrorWrap key={currentRoute.route.path as string}>
|
||||||
|
<Switch>{routeComponents}</Switch>
|
||||||
|
</ErrorWrap>
|
||||||
</Template>
|
</Template>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
@import '~styles/variables.less';
|
||||||
|
|
||||||
|
.ErrorScreen {
|
||||||
|
padding: 2rem 0;
|
||||||
|
|
||||||
|
&-desc {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
|
||||||
|
&-error {
|
||||||
|
display: block;
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
background: rgba(#000, 0.03);
|
||||||
|
border: 1px solid rgba(#000, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-buttons {
|
||||||
|
.ant-btn {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Antd overrides
|
||||||
|
.antd-pro-exception-exception {
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
.antd-pro-exception-imgBlock {
|
||||||
|
flex: 0 0 45%;
|
||||||
|
width: 45%;
|
||||||
|
|
||||||
|
@media @tablet-query {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.antd-pro-exception-content {
|
||||||
|
padding-right: 2rem;
|
||||||
|
|
||||||
|
@media @tablet-query {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 4.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
import React from 'react';
|
||||||
|
import * as Sentry from '@sentry/browser';
|
||||||
|
import { Button } from 'antd';
|
||||||
|
import Exception from 'ant-design-pro/lib/Exception';
|
||||||
|
import './index.less';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
error: Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ErrorScreen extends React.PureComponent<Props> {
|
||||||
|
componentDidMount() {
|
||||||
|
const { error } = this.props;
|
||||||
|
Sentry.captureException(error);
|
||||||
|
console.error('Error screen showing due to the following error:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { error } = this.props;
|
||||||
|
return (
|
||||||
|
<div className="ErrorScreen">
|
||||||
|
<Exception
|
||||||
|
type="404"
|
||||||
|
title="Whoa nelly."
|
||||||
|
desc={
|
||||||
|
<div className="ErrorScreen-desc">
|
||||||
|
<p>Something went wrong, and we've logged the following error:</p>
|
||||||
|
<code className="ErrorScreen-desc-error">{error.message}</code>
|
||||||
|
<p>
|
||||||
|
Our developers will get right on fixing it. You can either return home and
|
||||||
|
try again, or open an issue on Github to provide us some more details.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
actions={
|
||||||
|
<div className="ErrorScreen-buttons">
|
||||||
|
<a href="/">
|
||||||
|
<Button icon="home" size="large" type="primary">
|
||||||
|
Return home
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/grant-project/zcash-grant-system/issues/new">
|
||||||
|
<Button icon="github" size="large">
|
||||||
|
Open an issue
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ErrorScreen from './ErrorScreen';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: React.ReactNode;
|
||||||
|
isFullscreen?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
error: Error | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ErrorWrap extends React.Component<Props, State> {
|
||||||
|
state: State = {
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidCatch(error: Error) {
|
||||||
|
this.setState({ error });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let style;
|
||||||
|
if (this.props.isFullscreen) {
|
||||||
|
style = {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '100vw',
|
||||||
|
minHeight: '100vh',
|
||||||
|
padding: '0 2rem',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.error) {
|
||||||
|
return (
|
||||||
|
<div style={style}>
|
||||||
|
<ErrorScreen error={this.state.error} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,8 +51,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&-logo {
|
&-logo {
|
||||||
height: 1.8rem;
|
height: 6.8rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 0.5rem;
|
||||||
|
margin-top: -1.25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
@import '~styles/variables.less';
|
@import '~styles/variables.less';
|
||||||
@header-height: 62px;
|
@header-height: 68px;
|
||||||
@header-transition: 200ms;
|
@header-transition: 200ms;
|
||||||
@link-padding: 0.7rem;
|
@link-padding: 0.7rem;
|
||||||
@small-query: ~'(max-width: 820px)';
|
@small-query: ~'(max-width: 820px)';
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&-logo {
|
&-logo {
|
||||||
height: 1.4rem;
|
height: 3.6rem;
|
||||||
width: auto;
|
width: auto;
|
||||||
transition: transform @header-transition ease;
|
transition: transform @header-transition ease;
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import history from 'store/history';
|
||||||
import { massageSerializedState } from 'utils/api';
|
import { massageSerializedState } from 'utils/api';
|
||||||
import Routes from './Routes';
|
import Routes from './Routes';
|
||||||
import i18n from './i18n';
|
import i18n from './i18n';
|
||||||
|
import ErrorWrap from 'components/ErrorWrap';
|
||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: process.env.SENTRY_DSN,
|
dsn: process.env.SENTRY_DSN,
|
||||||
|
@ -26,13 +27,15 @@ i18n.changeLanguage(i18nLanguage.locale);
|
||||||
i18n.addResourceBundle(i18nLanguage.locale, 'common', i18nLanguage.resources, true);
|
i18n.addResourceBundle(i18nLanguage.locale, 'common', i18nLanguage.resources, true);
|
||||||
|
|
||||||
const App = hot(module)(() => (
|
const App = hot(module)(() => (
|
||||||
<I18nextProvider i18n={i18n}>
|
<ErrorWrap isFullscreen>
|
||||||
<Provider store={store}>
|
<I18nextProvider i18n={i18n}>
|
||||||
<Router history={history}>
|
<Provider store={store}>
|
||||||
<Routes />
|
<Router history={history}>
|
||||||
</Router>
|
<Routes />
|
||||||
</Provider>
|
</Router>
|
||||||
</I18nextProvider>
|
</Provider>
|
||||||
|
</I18nextProvider>
|
||||||
|
</ErrorWrap>
|
||||||
));
|
));
|
||||||
|
|
||||||
loadableReady(() => {
|
loadableReady(() => {
|
||||||
|
|
|
@ -7,9 +7,6 @@ import { withRouter, RouteComponentProps } from 'react-router';
|
||||||
type RouteProps = RouteComponentProps<any>;
|
type RouteProps = RouteComponentProps<any>;
|
||||||
|
|
||||||
class ProposalPage extends Component<RouteProps> {
|
class ProposalPage extends Component<RouteProps> {
|
||||||
constructor(props: RouteProps) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
render() {
|
render() {
|
||||||
const proposalId = extractIdFromSlug(this.props.match.params.id);
|
const proposalId = extractIdFromSlug(this.props.match.params.id);
|
||||||
return <Proposal proposalId={proposalId} />;
|
return <Proposal proposalId={proposalId} />;
|
||||||
|
|
Loading…
Reference in New Issue