Display version in settings (#293)

* Display version in settings

* Fix placeholder

* Fix button size

* Fix time format

* Add support link

* Add license
This commit is contained in:
Sung Won Cho 2019-10-30 00:58:08 -07:00 committed by GitHub
commit 478b412169
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 522 additions and 269 deletions

View file

@ -19,6 +19,10 @@ The following log documentes the history of the server project.
- `WebURL`: the URL to your Dnote server, without the trailing slash. (e.g. `https://my-server.com`) (Please see #290)
- `SmtpPort`: the SMTP port. (e.g. `465`) optional - required *if you want to configure email*
#### Added
- Display version number in the settings (#293)
#### Fixed
- Allow to customize the app URL in the emails (#290)

View file

@ -87,13 +87,16 @@ endif
# development
dev-server:
@echo "==> running dev environment"
@(cd ${GOPATH}/src/github.com/dnote/dnote/web && ./scripts/dev.sh)
@(cd ${GOPATH}/src/github.com/dnote/dnote/web && VERSION=master ./scripts/dev.sh)
.PHONY: dev-server
## build
build-web:
ifndef version
$(error version is required. Usage: make version=0.1.0 build-web)
endif
@echo "==> building web"
@(cd ${GOPATH}/src/github.com/dnote/dnote/web && ./scripts/build-prod.sh)
@(cd ${GOPATH}/src/github.com/dnote/dnote/web && VERSION=$(version) ./scripts/build-prod.sh)
.PHONY: build-web
build-server: build-web

View file

@ -23,3 +23,4 @@ declare module '*.scss';
declare const __STRIPE_PUBLIC_KEY__: string;
declare const __ROOT_URL__: string;
declare const __CDN_URL__: string;
declare const __VERSION__: string;

View file

@ -15,4 +15,5 @@ ASSET_BASE_URL="$assetBaseUrl" \
ROOT_URL="$rootUrl" \
PUBLIC_PATH="$publicPath" \
COMPILED_PATH="$compiledPath" \
VERSION="$VERSION" \
"$basePath"/web/scripts/build.sh

View file

@ -17,6 +17,7 @@ pushd "$basePath/web"
OUTPUT_PATH="$COMPILED_PATH" \
ROOT_URL="$ROOT_URL" \
VERSION="$VERSION" \
"$basePath"/web/node_modules/.bin/webpack\
--colors\
--display-error-details\

View file

@ -31,6 +31,7 @@ set +a
PUBLIC_PATH="$appPath"/public \
COMPILED_PATH="$basePath/web/compiled" \
IS_TEST=true \
VERSION="$VERSION" \
"$appPath"/scripts/webpack-dev.sh
) &
devServerPID=$!

View file

@ -19,6 +19,7 @@ appPath="$basePath"/web
node "$appPath"/scripts/placeholder.js &&
ROOT_URL=$ROOT_URL \
VERSION="$VERSION" \
"$appPath"/node_modules/.bin/webpack-dev-server\
--env.isTest="$IS_TEST"\
--config "$appPath"/webpack/dev.config.js

View file

@ -0,0 +1,62 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote.
*
* Dnote is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
*/
import React, { useState } from 'react';
import Helmet from 'react-helmet';
import classnames from 'classnames';
import SettingRow from '../SettingRow';
import styles from '../Settings.scss';
import { useSelector } from '../../../store';
import config from '../../../libs/config';
interface Props {}
const About: React.FunctionComponent<Props> = () => {
const { user } = useSelector(state => {
return {
user: state.auth.user.data
};
});
return (
<div>
<Helmet>
<title>About</title>
</Helmet>
<h1 className="sr-only">About</h1>
<div className={styles.wrapper}>
<section className={styles.section}>
<h2 className={styles['section-heading']}>Software</h2>
<SettingRow name="Version" value={config.version} />
{user.pro && (
<SettingRow
name="Support"
value={<a href="mailto:sung@getdnote.com">sung@getdnote.com</a>}
/>
)}
</section>
</div>
</div>
);
};
export default About;

View file

@ -83,7 +83,7 @@ const CancelPlanModal: React.FunctionComponent<Props> = ({
<Button
type="button"
kind="first"
size="small"
size="normal"
isBusy={inProgress}
onClick={onDismiss}
>
@ -93,7 +93,7 @@ const CancelPlanModal: React.FunctionComponent<Props> = ({
<Button
type="submit"
kind="second"
size="small"
size="normal"
isBusy={inProgress}
>
Cancel my plan.

View file

@ -0,0 +1,18 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote.
*
* Dnote is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
*/

View file

@ -0,0 +1,66 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote.
*
* Dnote is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
*/
import React, { useState, useEffect } from 'react';
import classnames from 'classnames';
import SettingRow from '../../SettingRow';
import { SourceData } from '../../../../store/auth/type';
import styles from '../../Settings.scss';
interface Props {
stripeLoaded: boolean;
source: SourceData;
setIsPaymentMethodModalOpen: (bool) => void;
}
const PaymentMethodRow: React.FunctionComponent<Props> = ({
stripeLoaded,
source,
setIsPaymentMethodModalOpen
}) => {
let value;
if (source.brand) {
value = `${source.brand} ending in ${source.last4}. expiry ${source.exp_month}/${source.exp_year}`;
} else {
value = 'No payment method';
}
return (
<SettingRow
id="T-payment-method-row"
name="Payment method"
value={value}
actionContent={
<button
id="T-update-payment-method-button"
className={classnames('button-no-ui', styles.edit)}
type="button"
onClick={() => {
setIsPaymentMethodModalOpen(true);
}}
disabled={!stripeLoaded}
>
Update
</button>
}
/>
);
};
export default PaymentMethodRow;

View file

@ -16,82 +16,34 @@
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
*/
@import '../../App/rem';
@import '../../App/font';
@import '../../App/theme';
@import '../../../App/rem';
@import '../../../App/font';
@import '../../../App/theme';
.content1 {
position: relative;
padding: rem(48px) rem(12px);
}
.content1-line1 {
width: rem(140px);
height: rem(16px);
}
.content1-line2 {
width: rem(240px);
height: rem(16px);
margin-top: rem(8px);
}
.content1-line3 {
width: rem(80px);
height: rem(16px);
margin-top: rem(8px);
}
.content2 {
.content {
display: flex;
justify-content: space-between;
flex-direction: column;
position: relative;
padding: rem(8px) rem(12px);
padding: rem(16px) rem(12px);
@include breakpoint(md) {
flex-direction: row;
}
}
.content2-line1 {
.content-line1 {
width: rem(120px);
height: rem(16px);
margin-top: rem(8px);
}
.content2-line2 {
.content-line2 {
width: rem(180px);
height: rem(16px);
margin-top: rem(8px);
}
.content2-right {
display: flex;
align-items: center;
}
.content3 {
display: flex;
justify-content: space-between;
flex-direction: column;
position: relative;
padding: rem(8px) rem(12px);
@include breakpoint(md) {
flex-direction: row;
}
}
.content3-line1 {
width: rem(120px);
height: rem(16px);
margin-top: rem(8px);
}
.content3-line2 {
width: rem(180px);
height: rem(16px);
margin-top: rem(8px);
}
.content3-right {
.content-right {
display: flex;
align-items: center;
}

View file

@ -0,0 +1,51 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote.
*
* Dnote is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
*/
import React from 'react';
import classnames from 'classnames';
import settingsStyles from '../../Settings.scss';
import styles from './Placeholder.scss';
const Placeholder: React.FunctionComponent = () => {
return (
<div className="container-wide">
<div className="row">
<div className="col-12 col-md-12 col-lg-10">
<section className={settingsStyles.section}>
<div className={styles.content}>
<div className={styles['content-left']}>
<div
className={classnames('holder', styles['content-line1'])}
/>
</div>
<div className={styles['content-right']}>
<div
className={classnames('holder', styles['content-line2'])}
/>
</div>
</div>
</section>
</div>
</div>
</div>
);
};
export default Placeholder;

View file

@ -0,0 +1,56 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote.
*
* Dnote is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
*/
import React, { Fragment } from 'react';
import classnames from 'classnames';
import PaymentMethodRow from './PaymentMethodRow';
import settingsStyles from '../../Settings.scss';
import { SourceData } from '../../../../store/auth/type';
import Placeholder from './Placeholder';
import styles from './Placeholder.scss';
interface Props {
source: SourceData;
setIsPaymentMethodModalOpen: (boolean) => void;
stripeLoaded: boolean;
isFetched: boolean;
}
const PaymentSection: React.FunctionComponent<Props> = ({
source,
setIsPaymentMethodModalOpen,
stripeLoaded,
isFetched
}) => {
if (!isFetched) {
return <Placeholder />;
}
return (
<Fragment>
<PaymentMethodRow
source={source}
setIsPaymentMethodModalOpen={setIsPaymentMethodModalOpen}
stripeLoaded={stripeLoaded}
/>
</Fragment>
);
};
export default PaymentSection;

View file

@ -0,0 +1,49 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote.
*
* Dnote is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
*/
import React, { useState, useEffect } from 'react';
import classnames from 'classnames';
import SettingRow from '../../SettingRow';
import styles from '../../Settings.scss';
interface Props {
setIsPlanModalOpen: (bool) => void;
}
const CancelRow: React.FunctionComponent<Props> = ({ setIsPlanModalOpen }) => {
return (
<SettingRow
name="Cancel current plan"
desc="If you cancel, the plan will expire at the end of current billing period."
actionContent={
<button
className={classnames('button-no-ui', styles.edit)}
type="button"
onClick={() => {
setIsPlanModalOpen(true);
}}
>
Cancel plan
</button>
}
/>
);
};
export default CancelRow;

View file

@ -0,0 +1,41 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote.
*
* Dnote is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
*/
@import '../../../App/rem';
@import '../../../App/font';
@import '../../../App/theme';
.content1 {
position: relative;
padding: rem(48px) rem(12px);
}
.content1-line1 {
width: rem(140px);
height: rem(16px);
}
.content1-line2 {
width: rem(240px);
height: rem(16px);
margin-top: rem(8px);
}
.content1-line3 {
width: rem(80px);
height: rem(16px);
margin-top: rem(8px);
}

View file

@ -19,7 +19,7 @@
import React from 'react';
import classnames from 'classnames';
import settingsStyles from '../Settings.scss';
import settingsStyles from '../../Settings.scss';
import styles from './Placeholder.scss';
const Placeholder: React.FunctionComponent = () => {
@ -28,10 +28,6 @@ const Placeholder: React.FunctionComponent = () => {
<div className="row">
<div className="col-12 col-md-12 col-lg-10">
<section className={settingsStyles.section}>
<div className={settingsStyles['section-heading']}>
<span>&nbsp;</span>
</div>
<div className={styles.content1}>
<div className={classnames('holder', styles['content1-line1'])} />
<div className={classnames('holder', styles['content1-line2'])} />
@ -55,26 +51,6 @@ const Placeholder: React.FunctionComponent = () => {
</div>
</div>
</section>
<section className={settingsStyles.section}>
<div className={settingsStyles['section-heading']}>
<span>&nbsp;</span>
</div>
<div className={styles.content3}>
<div className={styles['content3-left']}>
<div
className={classnames('holder', styles['content3-line1'])}
/>
</div>
<div className={styles['content3-right']}>
<div
className={classnames('holder', styles['content3-line2'])}
/>
</div>
</div>
</section>
</div>
</div>
</div>

View file

@ -16,9 +16,9 @@
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
*/
@import '../../App/rem';
@import '../../App/font';
@import '../../App/theme';
@import '../../../App/rem';
@import '../../../App/font';
@import '../../../App/theme';
.wrapper {
padding-top: rem(48px);

View file

@ -21,11 +21,11 @@ import { Link } from 'react-router-dom';
import classnames from 'classnames';
import { getPlanLabel } from 'web/libs/subscription';
import LogoIcon from '../../Icons/Logo';
import { nanosecToMillisec } from 'web/helpers/time';
import LogoIcon from '../../../Icons/Logo';
import { SECOND } from 'web/helpers/time';
import formatDate from 'web/helpers/time/format';
import styles from './PlanRow.scss';
import settingRowStyles from '../SettingRow.scss';
import settingRowStyles from '../../SettingRow.scss';
function getPlanPeriodMessage(subscription: any): string {
if (!subscription.id) {
@ -34,12 +34,12 @@ function getPlanPeriodMessage(subscription: any): string {
const label = getPlanLabel(subscription);
const endDate = new Date(nanosecToMillisec(subscription.current_period_end));
const endDate = new Date(subscription.current_period_end * SECOND);
if (subscription.cancel_at_period_end) {
return `Your ${label} plan will end on ${formatDate(
endDate,
'YYYY MMM Do'
'%YYYY %MMM %Do'
)} and will not renew.`;
}
@ -47,7 +47,7 @@ function getPlanPeriodMessage(subscription: any): string {
renewDate.setDate(endDate.getDate() + 1);
return `Your ${label} plan will renew on ${formatDate(
renewDate,
'YYYY MMM Do'
'%YYYY %MMM %Do'
)}.`;
}

View file

@ -20,10 +20,10 @@ import React, { useState } from 'react';
import classnames from 'classnames';
import services from 'web/libs/services';
import { useDispatch } from '../../../store';
import { getSubscription } from '../../../store/auth';
import SettingRow from '../SettingRow';
import styles from '../Settings.scss';
import { useDispatch } from '../../../../store';
import { getSubscription } from '../../../../store/auth';
import SettingRow from '../../SettingRow';
import styles from '../../Settings.scss';
interface Props {
subscriptionId: string;

View file

@ -0,0 +1,67 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote.
*
* Dnote is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
*/
import React, { Fragment } from 'react';
import classnames from 'classnames';
import PlanRow from './PlanRow';
import CancelRow from './CancelRow';
import ReactivateRow from './ReactivateRow';
import settingsStyles from '../../Settings.scss';
import { SubscriptionData } from '../../../../store/auth/type';
import Placeholder from './Placeholder';
import styles from './Placeholder.scss';
interface Props {
subscription: SubscriptionData;
setIsPlanModalOpen: (boolean) => void;
setSuccessMsg: (string) => void;
setFailureMsg: (string) => void;
isFetched: boolean;
}
const PlanSection: React.FunctionComponent<Props> = ({
subscription,
isFetched,
setIsPlanModalOpen,
setSuccessMsg,
setFailureMsg
}) => {
if (!isFetched) {
return <Placeholder />;
}
return (
<Fragment>
<PlanRow subscription={subscription} />
{subscription.id && !subscription.cancel_at_period_end && (
<CancelRow setIsPlanModalOpen={setIsPlanModalOpen} />
)}
{subscription.id && subscription.cancel_at_period_end && (
<ReactivateRow
subscriptionId={subscription.id}
setSuccessMsg={setSuccessMsg}
setFailureMsg={setFailureMsg}
/>
)}
</Fragment>
);
};
export default PlanSection;

View file

@ -32,143 +32,10 @@ import {
clearSource
} from '../../../store/auth';
import SettingRow from '../SettingRow';
import ReactivateRow from './ReactivateRow';
import PlanRow from './PlanRow';
import Placeholder from './Placeholder';
import PlanSection from './PlanSection';
import PaymentSection from './PaymentSection';
import styles from '../Settings.scss';
function CancelRow({ setIsPlanModalOpen }) {
return (
<SettingRow
name="Cancel current plan"
desc="If you cancel, the plan will expire at the end of current billing period."
actionContent={
<button
className={classnames('button-no-ui', styles.edit)}
type="button"
onClick={() => {
setIsPlanModalOpen(true);
}}
>
Cancel plan
</button>
}
/>
);
}
function PaymentMethodRow({
stripeLoaded,
source,
setIsPaymentMethodModalOpen
}) {
let value;
if (source.brand) {
value = `${source.brand} ending in ${source.last4}. expiry ${source.exp_month}/${source.exp_year}`;
} else {
value = 'No payment method';
}
return (
<SettingRow
id="T-payment-method-row"
name="Payment method"
value={value}
actionContent={
<button
id="T-update-payment-method-button"
className={classnames('button-no-ui', styles.edit)}
type="button"
onClick={() => {
setIsPaymentMethodModalOpen(true);
}}
disabled={!stripeLoaded}
>
Update
</button>
}
/>
);
}
interface ContentProps {
subscription: any;
source: any;
setIsPlanModalOpen: (boolean) => void;
setIsPaymentMethodModalOpen: (boolean) => void;
successMsg: string;
failureMsg: string;
setSuccessMsg: (string) => void;
setFailureMsg: (string) => void;
stripeLoaded: boolean;
}
const Content: React.FunctionComponent<ContentProps> = ({
subscription,
source,
setIsPlanModalOpen,
setIsPaymentMethodModalOpen,
successMsg,
failureMsg,
setSuccessMsg,
setFailureMsg,
stripeLoaded
}) => {
return (
<div>
<Flash
when={successMsg !== ''}
kind="success"
wrapperClassName={styles.flash}
onDismiss={() => {
setSuccessMsg('');
}}
>
{successMsg}
</Flash>
<Flash
when={failureMsg !== ''}
kind="danger"
wrapperClassName={styles.flash}
onDismiss={() => {
setFailureMsg('');
}}
>
{failureMsg}
</Flash>
<div className={styles.wrapper}>
<section className={styles.section}>
<h2 className={styles['section-heading']}>Plan</h2>
<PlanRow subscription={subscription} />
{subscription.id && !subscription.cancel_at_period_end && (
<CancelRow setIsPlanModalOpen={setIsPlanModalOpen} />
)}
{subscription.id && subscription.cancel_at_period_end && (
<ReactivateRow
subscriptionId={subscription.id}
setSuccessMsg={setSuccessMsg}
setFailureMsg={setFailureMsg}
/>
)}
</section>
<section className={styles.section}>
<h2 className={styles['section-heading']}>Payment</h2>
<PaymentMethodRow
source={source}
setIsPaymentMethodModalOpen={setIsPaymentMethodModalOpen}
stripeLoaded={stripeLoaded}
/>
</section>
</div>
</div>
);
};
const Billing: React.FunctionComponent = () => {
const [isPlanModalOpen, setIsPlanModalOpen] = useState(false);
const [isPaymentMethodModalOpen, setIsPaymentMethodModalOpen] = useState(
@ -239,21 +106,53 @@ const Billing: React.FunctionComponent = () => {
{stripeLoadError}
</Flash>
{!subscriptionData.isFetched || !sourceData.isFetched ? (
<Placeholder />
) : (
<Content
subscription={subscription}
source={source}
setIsPlanModalOpen={setIsPlanModalOpen}
successMsg={successMsg}
failureMsg={failureMsg}
setSuccessMsg={setSuccessMsg}
setFailureMsg={setFailureMsg}
setIsPaymentMethodModalOpen={setIsPaymentMethodModalOpen}
stripeLoaded={stripeLoaded}
/>
)}
<div>
<Flash
when={successMsg !== ''}
kind="success"
wrapperClassName={styles.flash}
onDismiss={() => {
setSuccessMsg('');
}}
>
{successMsg}
</Flash>
<Flash
when={failureMsg !== ''}
kind="danger"
wrapperClassName={styles.flash}
onDismiss={() => {
setFailureMsg('');
}}
>
{failureMsg}
</Flash>
<div className={styles.wrapper}>
<section className={styles.section}>
<h2 className={styles['section-heading']}>Plan</h2>
<PlanSection
subscription={subscriptionData.data}
setIsPlanModalOpen={setIsPlanModalOpen}
setSuccessMsg={setSuccessMsg}
setFailureMsg={setFailureMsg}
isFetched={subscriptionData.isFetched}
/>
</section>
<section className={styles.section}>
<h2 className={styles['section-heading']}>Payment</h2>
<PaymentSection
source={sourceData.data}
setIsPaymentMethodModalOpen={setIsPaymentMethodModalOpen}
stripeLoaded={stripeLoaded}
isFetched={sourceData.isFetched}
/>
</section>
</div>
</div>
<CancelPlanModal
isOpen={isPlanModalOpen}
@ -277,18 +176,4 @@ const Billing: React.FunctionComponent = () => {
);
};
// function mapStateToProps(state) {
// return {
// subscriptionData: state.auth.subscription,
// sourceData: state.auth.source
// };
// }
// const mapDispatchToProps = {
// doGetSubscription: getSubscription,
// doClearSubscription: clearSubscription,
// doGetSource: getSource,
// doClearSource: clearSource
// };
export default Billing;

View file

@ -62,6 +62,7 @@
.right {
display: flex;
flex-direction: column;
word-break: break-all;
@include breakpoint(md) {
flex-direction: row;

View file

@ -23,10 +23,10 @@ import styles from './SettingRow.scss';
interface Props {
name: string;
actionContent: React.ReactNode;
actionContent?: React.ReactNode;
id?: string;
desc?: string;
value?: string;
value?: React.ReactNode;
}
const SettingRow: React.FunctionComponent<Props> = ({
@ -45,7 +45,8 @@ const SettingRow: React.FunctionComponent<Props> = ({
<div className={styles.right}>
{value}
<div className={styles.action}>{actionContent}</div>
{actionContent && <div className={styles.action}>{actionContent}</div>}
</div>
</div>
);

View file

@ -47,6 +47,15 @@ const Sidebar: React.FunctionComponent<Props> = () => {
Billing
</NavLink>
</li>
<li>
<NavLink
className={styles.item}
activeClassName={styles.active}
to={getSettingsPath(SettingSections.about)}
>
About
</NavLink>
</li>
</ul>
</nav>
);

View file

@ -24,6 +24,7 @@ import classnames from 'classnames';
import { SettingSections } from 'web/libs/paths';
import Account from './Account';
import Sidebar from './Sidebar';
import About from './About';
import Billing from './Billing';
import styles from './Settings.scss';
@ -34,6 +35,9 @@ function renderContent(section: string): React.ReactNode {
if (section === SettingSections.billing) {
return <Billing />;
}
if (section === SettingSections.about) {
return <About />;
}
return <div>Not found</div>;
}

View file

@ -17,5 +17,6 @@
*/
export default {
cdnUrl: __CDN_URL__
cdnUrl: __CDN_URL__,
version: __VERSION__
};

View file

@ -210,8 +210,8 @@ export function getPasswordResetConfirmPath(searchObj = {}): Location {
export enum SettingSections {
account = 'account',
spacedRepeition = 'spaced-repetition',
billing = 'billing'
billing = 'billing',
about = 'about'
}
export function getSettingsPath(section: SettingSections) {

View file

@ -31,11 +31,13 @@ module.exports = ({ production = false, test = false } = {}) => {
}
const cdnUrl = 'https://cdn.getdnote.com';
const version = process.env.VERSION;
const compileTimeConstantForMinification = {
__ROOT_URL__: JSON.stringify(rootUrl),
__STRIPE_PUBLIC_KEY__: JSON.stringify(stripePublicKey),
__CDN_URL__: JSON.stringify(cdnUrl)
__CDN_URL__: JSON.stringify(cdnUrl),
__VERSION__: JSON.stringify(version)
};
if (!production) {