at the end of the day, it was inevitable
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Col, FormGroup, Label, Row } from 'reactstrap';
|
||||
import { getData } from 'country-list';
|
||||
import { CardElement } from '@stripe/react-stripe-js';
|
||||
|
||||
import { Input } from '../../../common/FormControls';
|
||||
import { Trans, translate } from 'react-i18next';
|
||||
|
||||
const countries = getData().map((v) => ({ label: v.name, value: v.code }));
|
||||
|
||||
const cardElementOptions = {
|
||||
hidePostalCode: true,
|
||||
style: {
|
||||
base: {
|
||||
fontSize: '16px',
|
||||
color: '#424770'
|
||||
},
|
||||
invalid: {
|
||||
color: '#d92550'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function BillingDetailsForm(props) {
|
||||
const { form, errors, handleChange, handleValidation, t } = props;
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col md={12}>
|
||||
<Input
|
||||
name="name"
|
||||
title={t('plans.billingForm.fullName')}
|
||||
type="text"
|
||||
required
|
||||
value={form.name}
|
||||
error={errors.name}
|
||||
handleChange={handleChange}
|
||||
handleValidation={handleValidation}
|
||||
/>
|
||||
</Col>
|
||||
<Col md={6}>
|
||||
<Input
|
||||
name="line1"
|
||||
title={t('plans.billingForm.addr1')}
|
||||
type="text"
|
||||
required
|
||||
description={t('plans.billingForm.addr1Desc')}
|
||||
value={form.line1}
|
||||
error={errors.line1}
|
||||
handleChange={handleChange}
|
||||
handleValidation={handleValidation}
|
||||
/>
|
||||
</Col>
|
||||
<Col md={6}>
|
||||
<Input
|
||||
name="line2"
|
||||
title={t('plans.billingForm.addr2')}
|
||||
type="text"
|
||||
description={t('plans.billingForm.addr2Desc')}
|
||||
value={form.line2}
|
||||
error={errors.line2}
|
||||
handleChange={handleChange}
|
||||
handleValidation={handleValidation}
|
||||
/>
|
||||
</Col>
|
||||
<Col md={6}>
|
||||
<Input
|
||||
name="city"
|
||||
title={t('plans.billingForm.city')}
|
||||
type="text"
|
||||
required
|
||||
description={t('plans.billingForm.cityDesc')}
|
||||
value={form.city}
|
||||
error={errors.city}
|
||||
handleChange={handleChange}
|
||||
handleValidation={handleValidation}
|
||||
/>
|
||||
</Col>
|
||||
<Col md={6}>
|
||||
<Input
|
||||
name="state"
|
||||
title={t('plans.billingForm.state')}
|
||||
type="text"
|
||||
required
|
||||
description={t('plans.billingForm.stateDesc')}
|
||||
value={form.state}
|
||||
error={errors.state}
|
||||
handleChange={handleChange}
|
||||
handleValidation={handleValidation}
|
||||
/>
|
||||
</Col>
|
||||
<Col md={6}>
|
||||
<Input
|
||||
name="postal_code"
|
||||
title={t('plans.billingForm.zip')}
|
||||
type="text"
|
||||
required
|
||||
description={t('plans.billingForm.zipDesc')}
|
||||
value={form.postal_code}
|
||||
error={errors.postal_code}
|
||||
handleChange={handleChange}
|
||||
handleValidation={handleValidation}
|
||||
/>
|
||||
</Col>
|
||||
<Col md={6}>
|
||||
<Input
|
||||
name="country"
|
||||
title={t('plans.billingForm.country')}
|
||||
type="select"
|
||||
required
|
||||
options={countries}
|
||||
value={form.country}
|
||||
error={errors.country}
|
||||
handleChange={handleChange}
|
||||
handleValidation={handleValidation}
|
||||
/>
|
||||
</Col>
|
||||
<Col md={6}>
|
||||
<Input
|
||||
name="email"
|
||||
title={t('plans.billingForm.email')}
|
||||
type="email"
|
||||
required
|
||||
value={form.email}
|
||||
error={errors.email}
|
||||
handleChange={handleChange}
|
||||
handleValidation={handleValidation}
|
||||
/>
|
||||
</Col>
|
||||
<Col md={6}>
|
||||
<Input
|
||||
name="phone"
|
||||
title={t('plans.billingForm.phone')}
|
||||
type="tel"
|
||||
required
|
||||
description={t('plans.billingForm.phoneDesc')}
|
||||
value={form.phone}
|
||||
error={errors.phone}
|
||||
handleChange={handleChange}
|
||||
handleValidation={handleValidation}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12} className="mb-2">
|
||||
<FormGroup>
|
||||
<Label>{t('plans.billingForm.cardHeading')}</Label>
|
||||
<CardElement
|
||||
options={cardElementOptions}
|
||||
className="border b-radius-5 p-3"
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col md={12}>
|
||||
<p className="text-muted">
|
||||
<Trans i18nKey="plans.billingForm.agreement">
|
||||
By submitting, you agree to our
|
||||
<a
|
||||
title="Privacy Policy"
|
||||
target="_blank"
|
||||
href="https://www.socialhose.io/en/legal/privacy"
|
||||
className="footer__link"
|
||||
>
|
||||
Privacy Policy
|
||||
</a>
|
||||
<a
|
||||
title="Terms and Conditions"
|
||||
target="_blank"
|
||||
href="https://www.socialhose.io/en/legal/terms"
|
||||
className="footer__link"
|
||||
>
|
||||
Terms & Conditions
|
||||
</a>
|
||||
<a
|
||||
title="Acceptable Use Policy"
|
||||
target="_blank"
|
||||
href="https://www.socialhose.io/en/legal/acceptable-use"
|
||||
className="footer__link"
|
||||
>
|
||||
Acceptable Use Policy
|
||||
</a>
|
||||
.
|
||||
</Trans>
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
BillingDetailsForm.propTypes = {
|
||||
t: PropTypes.func,
|
||||
form: PropTypes.object,
|
||||
errors: PropTypes.object,
|
||||
handleChange: PropTypes.func,
|
||||
handleValidation: PropTypes.func
|
||||
};
|
||||
|
||||
export default React.memo(
|
||||
translate(['tabsContent'], { wait: true })(BillingDetailsForm)
|
||||
);
|
||||
@@ -0,0 +1,244 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import useForm from '../../../common/hooks/useForm';
|
||||
import {
|
||||
ListGroupItem,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
Row,
|
||||
Form,
|
||||
Button,
|
||||
Col,
|
||||
ListGroup,
|
||||
Label
|
||||
} from 'reactstrap';
|
||||
import { Checkbox, Input } from '../../../common/FormControls';
|
||||
import { cancelPlan, cancelPlanHubspot } from '../../../../api/plans/userPlans';
|
||||
import { planRoutes } from './UserPlans';
|
||||
|
||||
const formParams = {
|
||||
rs1: 1,
|
||||
rs2: 2,
|
||||
rs3: 3,
|
||||
rs4: 4,
|
||||
rs5: 5,
|
||||
rs6: 'Other'
|
||||
};
|
||||
|
||||
const initForm = {
|
||||
[formParams.rs1]: false,
|
||||
[formParams.rs2]: false,
|
||||
[formParams.rs3]: false,
|
||||
[formParams.rs4]: false,
|
||||
[formParams.rs5]: false,
|
||||
[formParams.rs6]: false,
|
||||
Other: false,
|
||||
content: '',
|
||||
email: '',
|
||||
subject: 'Cancellation',
|
||||
errors: {
|
||||
email: null
|
||||
}
|
||||
};
|
||||
|
||||
function CancellationFeedback({ t, actions, isOpen = false, toggle, user }) {
|
||||
const {
|
||||
form,
|
||||
handleChange,
|
||||
handleValidation,
|
||||
validateSubmit,
|
||||
errors
|
||||
} = useForm(initForm);
|
||||
const [cancelLoading, setCancelLoading] = useState(false);
|
||||
const [reasonError, setReasonError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
handleChange('email', user.email);
|
||||
}, [user.email]);
|
||||
|
||||
useEffect(() => {
|
||||
if (Object.values(formParams).some((v) => form[v])) {
|
||||
setReasonError('');
|
||||
}
|
||||
}, [...Object.values(form)]);
|
||||
|
||||
function cancelSubscription() {
|
||||
const obj = validateSubmit();
|
||||
if (!obj) {
|
||||
return actions.addAlert({ type: 'error', transKey: 'requiredInfo' });
|
||||
} else if (!Object.values(formParams).some((v) => obj[v])) {
|
||||
setReasonError(t('plans.currentPlan.cancelModal.reasonSelect'));
|
||||
return;
|
||||
}
|
||||
|
||||
setCancelLoading(true);
|
||||
cancelPlan().then((res) => {
|
||||
if (res.error) {
|
||||
res.data
|
||||
? actions.addAlert(res.data)
|
||||
: actions.addAlert({ type: 'error', transKey: 'somethingWrong' });
|
||||
setCancelLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
cancelPlanHubspot({ ...obj });
|
||||
|
||||
actions.addAlert({
|
||||
type: 'notice',
|
||||
transKey: 'cancelledSubscription'
|
||||
});
|
||||
|
||||
// refresh page on success and move to active plan details
|
||||
setTimeout(() => {
|
||||
window.location.pathname = `/app/plans/${planRoutes.current}`;
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal size="lg" backdrop="static" isOpen={isOpen} toggle={toggle}>
|
||||
<ModalHeader toggle={toggle}>
|
||||
{t('plans.currentPlan.cancelModal.header')}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<Row>
|
||||
<Col md={6} className="mb-3">
|
||||
<p className="mb-3">
|
||||
{t('plans.currentPlan.cancelModal.line1', {
|
||||
firstName: user.firstName
|
||||
})}
|
||||
</p>
|
||||
<p className="mb-2">{t('plans.currentPlan.cancelModal.line2')}</p>
|
||||
<ListGroup className="text-muted">
|
||||
<ListGroupItem>
|
||||
{t('plans.currentPlan.cancelModal.warn1')}
|
||||
</ListGroupItem>
|
||||
<ListGroupItem>
|
||||
{t('plans.currentPlan.cancelModal.warn2')}
|
||||
</ListGroupItem>
|
||||
<ListGroupItem>
|
||||
{t('plans.currentPlan.cancelModal.warn3')}
|
||||
</ListGroupItem>
|
||||
<ListGroupItem>
|
||||
{t('plans.currentPlan.cancelModal.warn4')}
|
||||
</ListGroupItem>
|
||||
</ListGroup>
|
||||
</Col>
|
||||
<Col md={6} className="mb-3">
|
||||
<Form>
|
||||
<p className="mb-4">
|
||||
{t('plans.currentPlan.cancelModal.feedbackPara')}
|
||||
</p>
|
||||
<div>
|
||||
<Label className="d-inline-block mb-2">
|
||||
{t('plans.currentPlan.cancelModal.reasonCancellation')}
|
||||
<span className="text-danger">*</span>
|
||||
</Label>
|
||||
<div className="pl-3 mb-3">
|
||||
<Checkbox
|
||||
hideTitle
|
||||
name={formParams.rs1}
|
||||
formGroupClass="mb-0"
|
||||
title={t('plans.currentPlan.cancelModal.noNeeds')}
|
||||
description={t('plans.currentPlan.cancelModal.noNeeds')}
|
||||
value={form[formParams.rs1]}
|
||||
error={errors[formParams.rs1]}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
<Checkbox
|
||||
hideTitle
|
||||
name={formParams.rs2}
|
||||
formGroupClass="mb-0"
|
||||
title={t('plans.currentPlan.cancelModal.tooNoisy')}
|
||||
description={t('plans.currentPlan.cancelModal.tooNoisy')}
|
||||
value={form[formParams.rs2]}
|
||||
error={errors[formParams.rs2]}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
<Checkbox
|
||||
hideTitle
|
||||
name={formParams.rs3}
|
||||
formGroupClass="mb-0"
|
||||
title={t('plans.currentPlan.cancelModal.confusing')}
|
||||
description={t('plans.currentPlan.cancelModal.confusing')}
|
||||
value={form[formParams.rs3]}
|
||||
error={errors[formParams.rs3]}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
<Checkbox
|
||||
hideTitle
|
||||
name={formParams.rs4}
|
||||
formGroupClass="mb-0"
|
||||
title={t('plans.currentPlan.cancelModal.expensive')}
|
||||
description={t('plans.currentPlan.cancelModal.expensive')}
|
||||
value={form[formParams.rs4]}
|
||||
error={errors[formParams.rs4]}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
<Checkbox
|
||||
hideTitle
|
||||
name={formParams.rs5}
|
||||
formGroupClass="mb-0"
|
||||
title={t('plans.currentPlan.cancelModal.covid')}
|
||||
description={t('plans.currentPlan.cancelModal.covid')}
|
||||
value={form[formParams.rs5]}
|
||||
error={errors[formParams.rs5]}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
<Checkbox
|
||||
hideTitle
|
||||
name={formParams.rs6}
|
||||
formGroupClass="mb-0"
|
||||
title={t('plans.currentPlan.cancelModal.other')}
|
||||
description={t('plans.currentPlan.cancelModal.other')}
|
||||
value={form[formParams.rs6]}
|
||||
error={errors[formParams.rs6]}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
<span className="text-danger">{reasonError}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Input
|
||||
name="content"
|
||||
title={t('plans.currentPlan.cancelModal.tellMore')}
|
||||
type="textarea"
|
||||
value={form.content}
|
||||
error={errors.content}
|
||||
handleChange={handleChange}
|
||||
handleValidation={handleValidation}
|
||||
/>
|
||||
</Form>
|
||||
</Col>
|
||||
</Row>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="link" onClick={toggle}>
|
||||
{t('plans.currentPlan.cancelModal.undoBtn')}
|
||||
</Button>
|
||||
<Button
|
||||
color="danger"
|
||||
disabled={cancelLoading}
|
||||
onClick={cancelSubscription}
|
||||
>
|
||||
{cancelLoading
|
||||
? t('plans.currentPlan.cancelModal.loadingBtn')
|
||||
: t('plans.currentPlan.cancelModal.cancelSubscriptionBtn')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
CancellationFeedback.propTypes = {
|
||||
t: PropTypes.func,
|
||||
actions: PropTypes.object,
|
||||
isOpen: PropTypes.bool,
|
||||
toggle: PropTypes.func,
|
||||
user: PropTypes.object
|
||||
};
|
||||
|
||||
export default CancellationFeedback;
|
||||
@@ -0,0 +1,185 @@
|
||||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CardElement, useElements, useStripe } from '@stripe/react-stripe-js';
|
||||
import { Alert, Button, Card, CardBody, CardTitle, Col, Row } from 'reactstrap';
|
||||
|
||||
import { reduxActions } from '../../../../redux/utils/connect';
|
||||
import useForm from '../../../common/hooks/useForm';
|
||||
import useIsMounted from '../../../common/hooks/useIsMounted';
|
||||
import BillingDetailsForm from './BillingDetailsForm';
|
||||
import { changeCardDetails } from '../../../../api/plans/userPlans';
|
||||
import { planRoutes } from './UserPlans';
|
||||
import { setDocumentData } from '../../../../common/helper';
|
||||
import { translate } from 'react-i18next';
|
||||
|
||||
const initialForm = {
|
||||
name: '',
|
||||
line1: '',
|
||||
line2: '',
|
||||
city: '',
|
||||
state: '',
|
||||
postal_code: '',
|
||||
country: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
errors: {
|
||||
name: null,
|
||||
line1: null,
|
||||
city: null,
|
||||
state: null,
|
||||
postal_code: null,
|
||||
country: null,
|
||||
email: null,
|
||||
phone: null
|
||||
}
|
||||
};
|
||||
|
||||
function ChangeCard({ actions, t }) {
|
||||
const isMounted = useIsMounted();
|
||||
const stripe = useStripe();
|
||||
const elements = useElements();
|
||||
|
||||
const {
|
||||
form,
|
||||
errors,
|
||||
handleChange,
|
||||
handleValidation,
|
||||
validateSubmit
|
||||
} = useForm(initialForm);
|
||||
const [paymentError, setPaymentError] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setDocumentData('title', 'Change Card');
|
||||
|
||||
return () => setDocumentData('title'); // default
|
||||
}, []);
|
||||
|
||||
const submitPayment = async () => {
|
||||
if (!stripe || !elements) {
|
||||
// Stripe.js has not loaded yet.
|
||||
return;
|
||||
}
|
||||
|
||||
setPaymentError(false);
|
||||
setLoading(true);
|
||||
|
||||
const obj = validateSubmit();
|
||||
if (!obj) {
|
||||
setLoading(false);
|
||||
return actions.addAlert({
|
||||
type: 'error',
|
||||
transKey: 'requiredInfo'
|
||||
});
|
||||
}
|
||||
|
||||
const cardElement = elements.getElement(CardElement);
|
||||
const {
|
||||
name,
|
||||
line1,
|
||||
line2,
|
||||
city,
|
||||
state,
|
||||
postal_code,
|
||||
country,
|
||||
email,
|
||||
phone
|
||||
} = obj;
|
||||
const { error, paymentMethod } = await stripe.createPaymentMethod({
|
||||
type: 'card',
|
||||
card: cardElement,
|
||||
billing_details: {
|
||||
name,
|
||||
email,
|
||||
phone,
|
||||
address: {
|
||||
line1: line1,
|
||||
line2: line2,
|
||||
city: city,
|
||||
state: state,
|
||||
postal_code: postal_code,
|
||||
country: country
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (error) {
|
||||
setPaymentError(error);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const newObj = {};
|
||||
newObj.paymentID = paymentMethod.id; //stripe card element ID
|
||||
const res = await changeCardDetails(newObj);
|
||||
|
||||
if (!isMounted.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.error) {
|
||||
res.data
|
||||
? actions.addAlert(res.data)
|
||||
: actions.addAlert({ type: 'error', transKey: 'somethingWrong' });
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
actions.addAlert({ type: 'notice', transKey: 'cardUpdated' });
|
||||
// refresh page on success and move to active plan details
|
||||
setTimeout(() => {
|
||||
window.location.pathname = `/app/plans/${planRoutes.current}`;
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
return (
|
||||
<Col xs="12" lg="8" xl="9">
|
||||
<Card className="mb-3">
|
||||
<CardBody>
|
||||
<CardTitle>{t('plans.changeCard.heading')}</CardTitle>
|
||||
<p className="text-muted mb-3">{t('plans.changeCard.subText')}</p>
|
||||
<BillingDetailsForm
|
||||
form={form}
|
||||
errors={errors}
|
||||
handleChange={handleChange}
|
||||
handleValidation={handleValidation}
|
||||
/>
|
||||
<Row className="divider" />
|
||||
{paymentError && (
|
||||
<Alert color="danger">
|
||||
<Fragment>
|
||||
<p className="font-size-xs font-weight-bold text-uppercase">
|
||||
{t('plans.changeCard.error')}
|
||||
</p>
|
||||
{paymentError.message}
|
||||
</Fragment>
|
||||
</Alert>
|
||||
)}
|
||||
<div className="text-right">
|
||||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
onClick={submitPayment}
|
||||
disabled={!stripe || !elements || loading}
|
||||
className="btn-wide btn-hover-shine mb-2 mb-sm-0"
|
||||
size="lg"
|
||||
>
|
||||
{loading
|
||||
? t('plans.changeCard.loadingBtn')
|
||||
: t('plans.changeCard.changeCardBtn')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
ChangeCard.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
actions: PropTypes.object
|
||||
};
|
||||
|
||||
export default reduxActions()(
|
||||
translate(['tabsContent'], { wait: true })(ChangeCard)
|
||||
);
|
||||
@@ -0,0 +1,286 @@
|
||||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Card, CardBody, CardTitle, Col, Row } from 'reactstrap';
|
||||
import reduxConnect from '../../../../redux/utils/connect';
|
||||
import { planRoutes } from './UserPlans';
|
||||
import { allMediaTypes } from '../../../../redux/modules/appState/searchByFilters';
|
||||
import { capitalize } from 'lodash';
|
||||
import { convertUTCtoLocal, setDocumentData } from '../../../../common/helper';
|
||||
import { translate } from 'react-i18next';
|
||||
import CancellationFeedback from './CancellationFeedback';
|
||||
|
||||
function CurrentPlan({ actions, user, t }) {
|
||||
const [cancelModal, setCancelModal] = useState(false);
|
||||
|
||||
const { restrictions } = user;
|
||||
const { push } = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
setDocumentData('title', 'Active Plan Details');
|
||||
|
||||
return () => setDocumentData('title'); // default
|
||||
}, []);
|
||||
|
||||
function changePlan() {
|
||||
push(`/app/plans/${planRoutes.update}`);
|
||||
}
|
||||
|
||||
function toggleCancelModal() {
|
||||
setCancelModal((prev) => !prev);
|
||||
}
|
||||
|
||||
const {
|
||||
plans,
|
||||
limits,
|
||||
isPlanCancelled,
|
||||
subStartDate,
|
||||
subEndDate
|
||||
} = restrictions;
|
||||
|
||||
const selectedMedias = [];
|
||||
const notSelectedMedias = [];
|
||||
|
||||
allMediaTypes.map((v) => {
|
||||
if (plans[v]) {
|
||||
selectedMedias.push(t(`searchTab.sourceTypes.${v}`, capitalize(v)));
|
||||
} else {
|
||||
notSelectedMedias.push(t(`searchTab.sourceTypes.${v}`, capitalize(v)));
|
||||
}
|
||||
});
|
||||
|
||||
const isRTL = document.documentElement.dir === 'rtl';
|
||||
return (
|
||||
<Col xs="12" lg="8" xl="9">
|
||||
<Row>
|
||||
<Col sm="6" md="4">
|
||||
<div className="card mb-3 widget-chart text-left">
|
||||
<div className="widget-chart-content">
|
||||
<div className="widget-subheading">
|
||||
{t('plans.currentPlan.subHeading')}
|
||||
</div>
|
||||
<div className="widget-numbers">
|
||||
{plans.price === 0
|
||||
? t('plans.currentPlan.freePlan')
|
||||
: `$${plans.price}`}
|
||||
</div>
|
||||
<div className="widget-description">
|
||||
<span>
|
||||
{plans.price === 0 ? (
|
||||
<Fragment> </Fragment>
|
||||
) : subStartDate && subEndDate ? (
|
||||
`${convertUTCtoLocal(
|
||||
subStartDate,
|
||||
'MMM D, YYYY'
|
||||
)} - ${convertUTCtoLocal(subEndDate, 'MMM D, YYYY')}`
|
||||
) : (
|
||||
t('plans.currentPlan.perMonth')
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col sm="6">
|
||||
<button
|
||||
className="card mb-3 widget-chart bg-success text-white text-left"
|
||||
onClick={changePlan}
|
||||
>
|
||||
<div className="widget-chart-content">
|
||||
<div className="widget-subheading">
|
||||
{t('plans.currentPlan.changePlan')}
|
||||
</div>
|
||||
<div className="widget-numbers font-size-xlg">
|
||||
{t('plans.currentPlan.upgradeYourPlan')}
|
||||
</div>
|
||||
<div className="widget-description">
|
||||
<span>{t('plans.currentPlan.upgradeText')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</Col>
|
||||
<Col xs="12">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>{t('plans.currentPlan.currentPlanDetails')}</CardTitle>
|
||||
<div className="mb-3">
|
||||
<p className="text-muted">
|
||||
{t('plans.currentPlan.selectedMediaTypes')}
|
||||
</p>
|
||||
<p className="font-size-xlg">
|
||||
{selectedMedias.length > 0
|
||||
? selectedMedias.join(', ')
|
||||
: t('plans.currentPlan.none')}
|
||||
{notSelectedMedias.length > 0 ? (
|
||||
<span className="font-size-md opacity-6 ml-2">
|
||||
({t('plans.currentPlan.upgradeToGet')}:{' '}
|
||||
{notSelectedMedias.join(', ')})
|
||||
</span>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="divider" />
|
||||
<div className="mb-3">
|
||||
<p className="text-muted mb-2">
|
||||
{t('plans.currentPlan.selectedLicenses')}
|
||||
</p>
|
||||
<Row>
|
||||
<Col xs="12" sm="6" md="3">
|
||||
<div className="mb-3 card widget-chart">
|
||||
{!isRTL ? (
|
||||
<div className="widget-numbers">
|
||||
{limits.savedFeeds.current}/{limits.savedFeeds.limit}
|
||||
</div>
|
||||
) : (
|
||||
<div className="widget-numbers">
|
||||
{limits.savedFeeds.limit}/{limits.savedFeeds.current}
|
||||
</div>
|
||||
)}
|
||||
<div className="widget-subheading mb-3">
|
||||
{t('plans.currentPlan.feedsLicenses')}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs="12" sm="6" md="3">
|
||||
<div className="mb-3 card widget-chart">
|
||||
{!isRTL ? (
|
||||
<div className="widget-numbers">
|
||||
{limits.searchesPerDay.current}/
|
||||
{limits.searchesPerDay.limit}
|
||||
</div>
|
||||
) : (
|
||||
<div className="widget-numbers">
|
||||
{limits.searchesPerDay.limit}/
|
||||
{limits.searchesPerDay.current}
|
||||
</div>
|
||||
)}
|
||||
<div className="widget-subheading mb-3">
|
||||
{t('plans.currentPlan.searchLicenses')}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs="12" sm="6" md="3">
|
||||
<div className="mb-3 card widget-chart">
|
||||
{!isRTL ? (
|
||||
<div className="widget-numbers">
|
||||
{limits.webFeeds.current}/{limits.webFeeds.limit}
|
||||
</div>
|
||||
) : (
|
||||
<div className="widget-numbers">
|
||||
{limits.webFeeds.limit}/{limits.webFeeds.current}
|
||||
</div>
|
||||
)}
|
||||
<div className="widget-subheading mb-3">
|
||||
{t('plans.currentPlan.webfeedLicenses')}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs="12" sm="6" md="3">
|
||||
<div className="mb-3 card widget-chart">
|
||||
{!isRTL ? (
|
||||
<div className="widget-numbers">
|
||||
{limits.alerts.current}/{limits.alerts.limit}
|
||||
</div>
|
||||
) : (
|
||||
<div className="widget-numbers">
|
||||
{limits.alerts.limit}/{limits.alerts.current}
|
||||
</div>
|
||||
)}
|
||||
<div className="widget-subheading mb-3">
|
||||
{t('plans.currentPlan.alertLicenses')}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs="12" sm="6" md="3">
|
||||
<div className="mb-3 card widget-chart">
|
||||
{!isRTL ? (
|
||||
<div className="widget-numbers">
|
||||
{limits.subscriberAccounts.current}/
|
||||
{limits.subscriberAccounts.limit}
|
||||
</div>
|
||||
) : (
|
||||
<div className="widget-numbers">
|
||||
{limits.subscriberAccounts.limit}/
|
||||
{limits.subscriberAccounts.current}
|
||||
</div>
|
||||
)}
|
||||
<div className="widget-subheading mb-3">
|
||||
{t('plans.currentPlan.userAccounts')}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
<div className="divider" />
|
||||
<div className="mb-3">
|
||||
<p className="text-muted">{t('plans.currentPlan.features')}</p>
|
||||
<p className="font-size-xlg">
|
||||
{plans.analytics ? (
|
||||
t('plans.currentPlan.analytics')
|
||||
) : (
|
||||
<Fragment>
|
||||
{t('plans.currentPlan.none')}
|
||||
<span className="font-size-md opacity-6 ml-2">
|
||||
({t('plans.currentPlan.upgradeToGet')}:{' '}
|
||||
{t('plans.currentPlan.analytics')})
|
||||
</span>
|
||||
</Fragment>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
{plans.price > 0 && (
|
||||
<Fragment>
|
||||
<div className="divider" />
|
||||
<div className="mb-3">
|
||||
{!isPlanCancelled ? (
|
||||
<div className="text-muted">
|
||||
<Button
|
||||
color="danger"
|
||||
outline
|
||||
onClick={toggleCancelModal}
|
||||
>
|
||||
{t('plans.currentPlan.cancelSubscriptionBtn')}
|
||||
</Button>
|
||||
<p className="text-muted mt-2">
|
||||
{t('plans.currentPlan.cancelWarning')}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-muted">
|
||||
<Button color="secondary" outline disabled>
|
||||
{t('plans.currentPlan.cancelSubscriptionBtn')}
|
||||
</Button>
|
||||
<p className="d-block d-md-inline-block ml-md-3 mt-md-0 mt-2 ml-0 text-muted">
|
||||
{t('plans.currentPlan.alreadyCancelled')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
<CancellationFeedback
|
||||
isOpen={cancelModal}
|
||||
toggle={toggleCancelModal}
|
||||
actions={actions}
|
||||
user={user}
|
||||
t={t}
|
||||
/>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
CurrentPlan.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
actions: PropTypes.object,
|
||||
user: PropTypes.object
|
||||
};
|
||||
|
||||
export default reduxConnect('user', ['common', 'auth', 'user'])(
|
||||
translate(['tabsContent'], { wait: true })(CurrentPlan)
|
||||
);
|
||||
@@ -0,0 +1,168 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
Row,
|
||||
Table
|
||||
} from 'reactstrap';
|
||||
import { convertUTCtoLocal } from '../../../../common/helper';
|
||||
import moment from 'moment';
|
||||
import { capitalize } from 'lodash';
|
||||
import { translate } from 'react-i18next';
|
||||
|
||||
function ShowTransactionDetails(props) {
|
||||
const { data, closeModal, t } = props;
|
||||
|
||||
const plan = data && data.lines && data.lines.data && data.lines.data[0];
|
||||
|
||||
useEffect(() => {
|
||||
return () => closeModal();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Modal isOpen={!!data && !!plan} toggle={closeModal} size="lg">
|
||||
<ModalHeader toggle={closeModal}>
|
||||
{t('plans.transactions.modal.heading')}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
{data && (
|
||||
<Row>
|
||||
<Col xs="12" lg="6" className="mb-3">
|
||||
<h6 className="mb-3">
|
||||
{t('plans.transactions.modal.transactionDetails')}
|
||||
</h6>
|
||||
<Table striped>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{t('plans.transactions.modal.transactionDate')}</th>
|
||||
<td>
|
||||
{convertUTCtoLocal(
|
||||
moment.unix(
|
||||
data.status_transitions &&
|
||||
data.status_transitions.paid_at
|
||||
),
|
||||
'MM/DD/YYYY hh:mm:ss a'
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('plans.transactions.modal.activationDate')}</th>
|
||||
<td>
|
||||
{convertUTCtoLocal(
|
||||
moment.unix(plan && plan.period.start),
|
||||
'MM/DD/YYYY'
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('plans.transactions.modal.expirationDate')}</th>
|
||||
<td>
|
||||
{convertUTCtoLocal(
|
||||
moment.unix(plan && plan.period.end),
|
||||
'MM/DD/YYYY'
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('plans.transactions.modal.amount')}</th>
|
||||
<td>${data.amount_paid / 100}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('plans.transactions.modal.status')}</th>
|
||||
<td>{capitalize(data.status)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
</Col>
|
||||
<Col xs="12" lg="6" className="mb-3">
|
||||
<h6 className="mb-3">
|
||||
{t('plans.transactions.modal.billingDetails')}
|
||||
</h6>
|
||||
<Table striped>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{t('plans.transactions.modal.name')}</th>
|
||||
<td>{data.customer_name || '-'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('plans.transactions.modal.email')}</th>
|
||||
<td>{data.customer_email || '-'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('plans.transactions.modal.phone')}</th>
|
||||
<td>{data.customer_phone || '-'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('plans.transactions.modal.address')}</th>
|
||||
<td>{data.customer_address || '-'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('plans.transactions.modal.invoiceNo')}</th>
|
||||
<td>
|
||||
{data.number} (
|
||||
<a
|
||||
href={data.hosted_invoice_url}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{t('plans.transactions.modal.showInvoiceLink')}
|
||||
</a>
|
||||
)
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
</Col>
|
||||
{/* <Col xs="12" lg="6" className="mb-3">
|
||||
<h6 className="mb-3">Plan Details</h6>
|
||||
<Table striped>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Feeds Licenses</th>
|
||||
<td>0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Webfeed Licenses</th>
|
||||
<td>0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Newsletter Licenses</th>
|
||||
<td>0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>User Accounts</th>
|
||||
<td>0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Analytics</th>
|
||||
<td>No</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
</Col> */}
|
||||
</Row>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="link" onClick={closeModal}>
|
||||
{t('plans.transactions.modal.cancelBtn')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
ShowTransactionDetails.propTypes = {
|
||||
t: PropTypes.func,
|
||||
closeModal: PropTypes.func,
|
||||
data: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired
|
||||
};
|
||||
|
||||
export default React.memo(
|
||||
translate(['tabsContent'], { wait: true })(ShowTransactionDetails)
|
||||
);
|
||||
@@ -0,0 +1,749 @@
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
import React, { Fragment, useCallback, useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Slider from 'rc-slider';
|
||||
import Tooltip from 'rc-tooltip';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
CardBody,
|
||||
CardTitle,
|
||||
Col,
|
||||
Form,
|
||||
FormGroup,
|
||||
Label,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
Row
|
||||
} from 'reactstrap';
|
||||
|
||||
import {
|
||||
licenses,
|
||||
mediaTypes,
|
||||
features,
|
||||
addonFeatures
|
||||
} from '../../../LoginRegister/Registration/PlanConstants';
|
||||
import useForm from '../../../common/hooks/useForm';
|
||||
import { debounce } from 'lodash';
|
||||
import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
|
||||
|
||||
import useIsMounted from '../../../common/hooks/useIsMounted';
|
||||
import reduxConnect from '../../../../redux/utils/connect';
|
||||
import {
|
||||
getPlans,
|
||||
updatePrice
|
||||
} from '../../../../api/registration/registration';
|
||||
import {
|
||||
updatePlanHubspot,
|
||||
updatePlanPayment
|
||||
} from '../../../../api/plans/userPlans';
|
||||
import { planRoutes } from './UserPlans';
|
||||
import BillingDetailsForm from './BillingDetailsForm';
|
||||
|
||||
import simpleNumberLocalizer from 'react-widgets-simple-number';
|
||||
import NumberPicker from 'react-widgets/lib/NumberPicker';
|
||||
import LoadersAdvanced from '../../../common/Loader/Loader';
|
||||
import { IoIosWarning } from 'react-icons/io';
|
||||
import { convertUTCtoLocal, setDocumentData } from '../../../../common/helper';
|
||||
import { translate } from 'react-i18next';
|
||||
|
||||
simpleNumberLocalizer();
|
||||
|
||||
const Handle = Slider.Handle;
|
||||
|
||||
const handle = (props) => {
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const { value, dragging, index, ...restProps } = props;
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
key={index}
|
||||
prefixCls="rc-slider-tooltip"
|
||||
overlay={value}
|
||||
visible={dragging}
|
||||
placement="top"
|
||||
>
|
||||
<Handle value={value} {...restProps} />
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const initialForm = {
|
||||
savedFeeds: 0,
|
||||
searchesPerDay: 0,
|
||||
webFeeds: 0,
|
||||
alerts: 0,
|
||||
news: 0,
|
||||
blog: 0,
|
||||
reddit: 0,
|
||||
instagram: 0,
|
||||
twitter: 0,
|
||||
analytics: 0,
|
||||
subscriberAccounts: 0,
|
||||
masterAccounts: 0
|
||||
};
|
||||
|
||||
const initialPaymentForm = {
|
||||
name: '',
|
||||
line1: '',
|
||||
line2: '',
|
||||
city: '',
|
||||
state: '',
|
||||
postal_code: '',
|
||||
country: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
errors: {
|
||||
name: null,
|
||||
line1: null,
|
||||
city: null,
|
||||
state: null,
|
||||
postal_code: null,
|
||||
country: null,
|
||||
email: null,
|
||||
phone: null
|
||||
}
|
||||
};
|
||||
|
||||
function UpdatePlan({ actions, restrictions, t }) {
|
||||
const stripe = useStripe();
|
||||
const elements = useElements();
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
// first step
|
||||
const { form, handleChange, resetForm } = useForm(initialForm);
|
||||
const [updatingPrice, setUpdatingPrice] = useState(true);
|
||||
const [totalCost, setTotalCost] = useState(' - ');
|
||||
const [modal, setModal] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [planLoading, setPlanLoading] = useState(true);
|
||||
const [planError, setPlanError] = useState(false);
|
||||
const [planList, setPlanList] = useState([]);
|
||||
const [disableUpdate, setDisableUpdate] = useState(true);
|
||||
|
||||
// second step
|
||||
const [nextStep, setNextStep] = useState(false);
|
||||
const {
|
||||
form: paymentForm,
|
||||
handleChange: handlePaymentForm,
|
||||
errors: paymentFormErrors,
|
||||
handleValidation: handlePaymentValidation,
|
||||
validateSubmit
|
||||
} = useForm(initialPaymentForm);
|
||||
const [paymentError, setPaymentError] = useState(false);
|
||||
const [paymentLoading, setPaymentLoading] = useState(false);
|
||||
|
||||
// to update price when input changes
|
||||
useEffect(() => {
|
||||
if (planList.length > 0) {
|
||||
debouncePrice(form);
|
||||
}
|
||||
}, [...Object.values(form)]);
|
||||
|
||||
const debouncePrice = useCallback(
|
||||
debounce((form) => {
|
||||
setUpdatingPrice(true);
|
||||
updatePrice(form).then((res) => {
|
||||
if (!isMounted.current) {
|
||||
return false;
|
||||
}
|
||||
if (res.error || isNaN(res.data.totalPrice)) {
|
||||
actions.addAlert(res.data);
|
||||
setUpdatingPrice(false);
|
||||
setTotalCost('Error');
|
||||
return;
|
||||
}
|
||||
setTotalCost(res.data.totalPrice);
|
||||
setUpdatingPrice(false);
|
||||
});
|
||||
}, 1000),
|
||||
[isMounted.current]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!restrictions.isPlanCancelled && !restrictions.isPlanDowngrade) {
|
||||
setDisableUpdate(false);
|
||||
} else {
|
||||
setDisableUpdate(true);
|
||||
}
|
||||
}, [restrictions.isPlanCancelled, restrictions.isPlanDowngrade]);
|
||||
|
||||
useEffect(() => {
|
||||
getBillingPlans();
|
||||
|
||||
setDocumentData('title', 'Update Plan');
|
||||
return () => setDocumentData('title'); // default
|
||||
}, []);
|
||||
|
||||
function getBillingPlans() {
|
||||
setPlanLoading(true);
|
||||
setPlanError(false);
|
||||
getPlans().then((res) => {
|
||||
if (!isMounted.current) {
|
||||
return false;
|
||||
}
|
||||
if (res.error || !res.data || !res.data.length) {
|
||||
setPlanError(true);
|
||||
setPlanLoading(false);
|
||||
res.data && res.data.length > 0 && actions.addAlert(res.data);
|
||||
return;
|
||||
}
|
||||
setPlanLoading(false);
|
||||
setPlanList(res.data);
|
||||
|
||||
const modified = { ...initialForm };
|
||||
let selectedPlan = {};
|
||||
if (restrictions.plans.price > 0) {
|
||||
selectedPlan = { ...restrictions.plans };
|
||||
Object.entries(restrictions.limits).map(([key, value]) => {
|
||||
selectedPlan[key] = value.limit;
|
||||
});
|
||||
selectedPlan.blog = selectedPlan.blogs;
|
||||
delete selectedPlan.blogs;
|
||||
} else {
|
||||
selectedPlan = res.data[0];
|
||||
}
|
||||
|
||||
Object.keys(initialForm).map((key) => {
|
||||
modified[key] =
|
||||
selectedPlan[key] === undefined
|
||||
? modified[key]
|
||||
: selectedPlan[key] === true
|
||||
? 1
|
||||
: selectedPlan[key] === false
|
||||
? 0
|
||||
: selectedPlan[key];
|
||||
});
|
||||
resetForm(modified);
|
||||
});
|
||||
}
|
||||
|
||||
function changePlan(id) {
|
||||
const selectedPlan = planList.find((plan) => plan.id === id);
|
||||
const modified = { ...initialForm };
|
||||
Object.keys(initialForm).map((key) => {
|
||||
modified[key] =
|
||||
selectedPlan[key] === undefined
|
||||
? modified[key]
|
||||
: selectedPlan[key] === true
|
||||
? 1
|
||||
: selectedPlan[key] === false
|
||||
? 0
|
||||
: selectedPlan[key];
|
||||
});
|
||||
resetForm(modified);
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
if (restrictions.isPlanCancelled || restrictions.isPlanDowngrade) {
|
||||
return;
|
||||
}
|
||||
// move to payment page if new basic user
|
||||
// instruct according to upgrade and downgrade
|
||||
// if card already stored then only update the plan by showing modal or providing option to change card
|
||||
setLoading(true);
|
||||
if (restrictions.isPaymentId) {
|
||||
setModal(true); // show details of card
|
||||
} else {
|
||||
setNextStep(true);
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
setModal((prev) => !prev);
|
||||
}
|
||||
|
||||
function proceedToDetails() {
|
||||
toggle();
|
||||
setNextStep(true);
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
const submitPayment = async () => {
|
||||
if (!stripe || !elements) {
|
||||
// Stripe.js has not loaded yet.
|
||||
return;
|
||||
}
|
||||
|
||||
if (restrictions.isPlanCancelled || restrictions.isPlanDowngrade) {
|
||||
return;
|
||||
}
|
||||
|
||||
setPaymentError(false);
|
||||
setPaymentLoading(true);
|
||||
|
||||
const obj = validateSubmit();
|
||||
if (!obj) {
|
||||
setPaymentLoading(false);
|
||||
return actions.addAlert({
|
||||
type: 'error',
|
||||
transKey: 'requiredInfo'
|
||||
});
|
||||
}
|
||||
|
||||
const cardElement = elements.getElement(CardElement);
|
||||
const {
|
||||
name,
|
||||
line1,
|
||||
line2,
|
||||
city,
|
||||
state,
|
||||
postal_code,
|
||||
country,
|
||||
email,
|
||||
phone
|
||||
} = obj;
|
||||
const { error, paymentMethod } = await stripe.createPaymentMethod({
|
||||
type: 'card',
|
||||
card: cardElement,
|
||||
billing_details: {
|
||||
name,
|
||||
email,
|
||||
phone,
|
||||
address: {
|
||||
line1: line1,
|
||||
line2: line2,
|
||||
city: city,
|
||||
state: state,
|
||||
postal_code: postal_code,
|
||||
country: country
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (error) {
|
||||
setPaymentError(error);
|
||||
setPaymentLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const newObj = { ...form };
|
||||
newObj.masterAccounts = '1';
|
||||
newObj.paymentID = paymentMethod.id; //stripe card element ID
|
||||
const res = await updatePlanPayment(newObj);
|
||||
|
||||
if (res.error) {
|
||||
res.data
|
||||
? actions.addAlert(res.data)
|
||||
: actions.addAlert({ type: 'error', transKey: 'somethingWrong' });
|
||||
setPaymentLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
window.gtag &&
|
||||
window.gtag('event', 'purchase', {
|
||||
currency: 'USD',
|
||||
value: totalCost
|
||||
});
|
||||
|
||||
await updatePlanHubspot({ ...obj, ...form, totalCost });
|
||||
|
||||
actions.addAlert({ type: 'notice', transKey: 'planUpdated' });
|
||||
|
||||
// refresh page on success and move to active plan details
|
||||
setTimeout(() => {
|
||||
window.location.pathname = `/app/plans/${planRoutes.current}`;
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const proceedPayment = async () => {
|
||||
// payment with old card
|
||||
setLoading(true);
|
||||
|
||||
const newObj = { ...form };
|
||||
newObj.masterAccounts = '1';
|
||||
const res = await updatePlanPayment(newObj);
|
||||
|
||||
if (res.error) {
|
||||
res.data
|
||||
? actions.addAlert(res.data)
|
||||
: actions.addAlert({ type: 'error', transKey: 'somethingWrong' });
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
window.gtag &&
|
||||
window.gtag('event', 'purchase', {
|
||||
currency: 'USD',
|
||||
value: totalCost
|
||||
});
|
||||
|
||||
await updatePlanHubspot({ ...form, totalCost });
|
||||
|
||||
actions.addAlert({ type: 'notice', transKey: 'planUpdated' });
|
||||
|
||||
// refresh page on success and move to active plan details
|
||||
setTimeout(() => {
|
||||
window.location.pathname = `/app/plans/${planRoutes.current}`;
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
function moveBack() {
|
||||
window.scrollTo(0, 0);
|
||||
setNextStep(false);
|
||||
}
|
||||
|
||||
if (planError || planLoading) {
|
||||
return (
|
||||
<Col xs="12" lg="8" xl="9">
|
||||
<Card className="h-75 mb-3">
|
||||
<CardBody>
|
||||
<CardTitle>{t('plans.updatePlan.heading')}</CardTitle>
|
||||
{planError && (
|
||||
<div className="text-danger text-center p-4">
|
||||
<IoIosWarning
|
||||
className="d-block mx-auto mb-2"
|
||||
fontSize="32px"
|
||||
/>
|
||||
{t('plans.updatePlan.planLoadingFailed')}{' '}
|
||||
<Button color="link" onClick={getBillingPlans} className="p-0">
|
||||
{t('plans.updatePlan.tryAgainBtn')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</CardBody>
|
||||
{planLoading && <LoadersAdvanced />}
|
||||
</Card>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
const isRTL = document.documentElement.dir === 'rtl';
|
||||
|
||||
return (
|
||||
<Col xs="12" lg="8" xl="9">
|
||||
<Card className="mb-3">
|
||||
{!nextStep ? (
|
||||
<CardBody>
|
||||
<CardTitle>{t('plans.updatePlan.heading')}</CardTitle>
|
||||
<p className="text-muted">
|
||||
{t('plans.updatePlan.subText')}{' '}
|
||||
<a
|
||||
href="https://www.socialhose.io/en/pricing"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{t('plans.updatePlan.learnMoreBtn')}
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<hr />
|
||||
<Form>
|
||||
<Row>
|
||||
<Col md={12}>
|
||||
<div className="mb-3">
|
||||
<h6 className="font-weight-bold mb-3">
|
||||
{t('plans.updatePlan.prePlans')}
|
||||
</h6>
|
||||
<div className="d-flex flex-wrap justify-content-center justify-content-md-start">
|
||||
{planList.map((plan) => (
|
||||
<Button
|
||||
outline
|
||||
key={plan.id}
|
||||
color="primary"
|
||||
type="button"
|
||||
className="btn-wide btn-lg p-sm-3 mb-2 mr-2"
|
||||
onClick={() => changePlan(plan.id)}
|
||||
>
|
||||
{plan.name}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="mb-3">
|
||||
<h6 className="font-weight-bold mb-3">
|
||||
{t('plans.updatePlan.mediaTypes')}
|
||||
</h6>
|
||||
<div>
|
||||
{mediaTypes.map((type) => (
|
||||
<Button
|
||||
key={type.name}
|
||||
size="lg"
|
||||
type="button"
|
||||
title={
|
||||
form[type.name]
|
||||
? 'Click to deselect'
|
||||
: 'Click to select'
|
||||
}
|
||||
outline={!form[type.name]}
|
||||
className="btn-pill mb-2 mr-2"
|
||||
color={form[type.name] ? 'success' : 'light'}
|
||||
onClick={() =>
|
||||
handleChange(type.name, !form[type.name])
|
||||
}
|
||||
>
|
||||
{t(`searchTab.sourceTypes.${type.transKey}`)} (
|
||||
{type.price})
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="mb-3">
|
||||
<h6 className="font-weight-bold mb-3">
|
||||
{t('plans.updatePlan.licenses')}
|
||||
</h6>
|
||||
<Row noGutters className="justify-content-center">
|
||||
{licenses.map((license) => (
|
||||
<Col sm={6} key={license.name}>
|
||||
<div className="p-4 m-2 border b-radius-5 shadow-sm">
|
||||
<FormGroup>
|
||||
<div className="d-flex justify-content-between">
|
||||
<Label title={license.title}>
|
||||
{t(`plans.currentPlan.${license.transKey}`)}
|
||||
</Label>
|
||||
<span className="font-size-lg font-weight-bold text-primary">
|
||||
{form[license.name]}
|
||||
</span>
|
||||
</div>
|
||||
<Slider
|
||||
{...license.props}
|
||||
reverse={isRTL}
|
||||
handle={handle}
|
||||
value={form[license.name]}
|
||||
onChange={(val) =>
|
||||
handleChange(license.name, val)
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
<hr />
|
||||
<Row>
|
||||
<Col md="6">
|
||||
<div className="mb-3">
|
||||
<h6 className="font-weight-bold mb-3">
|
||||
{t('plans.updatePlan.features')}
|
||||
</h6>
|
||||
<div>
|
||||
{features.map((type) => (
|
||||
<Button
|
||||
key={type.name}
|
||||
size="lg"
|
||||
type="button"
|
||||
title={
|
||||
form[type.name]
|
||||
? t('plans.updatePlan.deselectTooltip')
|
||||
: t('plans.updatePlan.selectTooltip')
|
||||
}
|
||||
outline={!form[type.name]}
|
||||
className="btn-pill mb-2 mr-2"
|
||||
color={form[type.name] ? 'success' : 'light'}
|
||||
onClick={() =>
|
||||
handleChange(type.name, !form[type.name])
|
||||
}
|
||||
>
|
||||
{t(`plans.currentPlan.${type.transKey}`)} (
|
||||
{type.price})
|
||||
</Button>
|
||||
))}
|
||||
<div className="pl-2">
|
||||
{features.map((type) =>
|
||||
form[type.name] ? (
|
||||
<p
|
||||
key={type.name}
|
||||
className="font-size-sm text-muted mb-1"
|
||||
>
|
||||
{type.desc}
|
||||
</p>
|
||||
) : null
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col md="6">
|
||||
<div className="mb-3">
|
||||
<h6 className="font-weight-bold mb-3">
|
||||
{t('plans.updatePlan.addOns')}
|
||||
</h6>
|
||||
<Row className="px-3">
|
||||
{addonFeatures.map((type) => (
|
||||
<Col xs="12" key={type.name}>
|
||||
<FormGroup>
|
||||
<Label>
|
||||
{t(`plans.currentPlan.${type.transKey}`)}
|
||||
</Label>
|
||||
<NumberPicker
|
||||
{...type.props}
|
||||
value={form[type.name]}
|
||||
onChange={(val) =>
|
||||
handleChange(type.name, val)
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<div className="widget-content total-price">
|
||||
<div className="widget-content-wrapper justify-content-start justify-content-md-end mr-5">
|
||||
<div className="widget-content-left">
|
||||
<div className="widget-heading">
|
||||
{t('plans.updatePlan.totalCost')}
|
||||
</div>
|
||||
<div className="widget-subheading">
|
||||
{t('plans.updatePlan.monthly')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="widget-content-right position-relative ml-0 ml-5">
|
||||
{/* {updatingPrice && (
|
||||
<div className="widget-numbers position-absolute text-secondary px-3">
|
||||
<FontAwesomeIcon icon={faSpinner} pulse />
|
||||
</div>
|
||||
)} */}
|
||||
<div
|
||||
className={`widget-numbers text-warning ${
|
||||
updatingPrice ? 'opacity-3' : ''
|
||||
}`}
|
||||
>
|
||||
${totalCost}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<hr />
|
||||
{restrictions.isPlanCancelled || restrictions.isPlanDowngrade ? (
|
||||
<p className="text-danger mb-3">
|
||||
{t('plans.updatePlan.cancelledWarning', {
|
||||
text: restrictions.isPlanCancelled
|
||||
? 'cancelled'
|
||||
: 'downgraded'
|
||||
})}{' '}
|
||||
{restrictions.subStartDate && restrictions.subEndDate
|
||||
? `(${convertUTCtoLocal(
|
||||
restrictions.subStartDate,
|
||||
'MMM D, YYYY'
|
||||
)} - ${convertUTCtoLocal(
|
||||
restrictions.subEndDate,
|
||||
'MMM D, YYYY'
|
||||
)})`
|
||||
: ''}
|
||||
</p>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
<div className="text-right">
|
||||
<Button
|
||||
type="button"
|
||||
disabled={updatingPrice || loading || disableUpdate}
|
||||
onClick={handleSubmit}
|
||||
className="btn-wide"
|
||||
color="primary"
|
||||
size="lg"
|
||||
>
|
||||
{loading
|
||||
? t('plans.updatePlan.continueBtnLoading')
|
||||
: t('plans.updatePlan.continueBtn')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</CardBody>
|
||||
) : (
|
||||
<CardBody>
|
||||
<CardTitle>{t('plans.updatePlan.billingHeading')}</CardTitle>
|
||||
<BillingDetailsForm
|
||||
form={paymentForm}
|
||||
errors={paymentFormErrors}
|
||||
handleChange={handlePaymentForm}
|
||||
handleValidation={handlePaymentValidation}
|
||||
/>
|
||||
<Row className="divider" />
|
||||
{paymentError && (
|
||||
<Alert color="danger">
|
||||
<Fragment>
|
||||
<p className="font-size-xs font-weight-bold text-uppercase">
|
||||
{t('plans.updatePlan.error')}
|
||||
</p>
|
||||
{paymentError.message}
|
||||
</Fragment>
|
||||
</Alert>
|
||||
)}
|
||||
<div className="d-flex justify-content-between flex-column-reverse flex-sm-row">
|
||||
<Button
|
||||
type="button"
|
||||
color="secondary"
|
||||
size="lg"
|
||||
disabled={paymentLoading}
|
||||
onClick={moveBack}
|
||||
>
|
||||
{t('plans.updatePlan.back')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
onClick={submitPayment}
|
||||
disabled={!stripe || !elements || paymentLoading}
|
||||
className="btn-wide btn-hover-shine mb-2 mb-sm-0"
|
||||
size="lg"
|
||||
>
|
||||
{paymentLoading
|
||||
? t('plans.updatePlan.payLoading')
|
||||
: t('plans.updatePlan.payBtn', { totalCost })}
|
||||
</Button>
|
||||
</div>
|
||||
</CardBody>
|
||||
)}
|
||||
</Card>
|
||||
<Modal isOpen={modal} toggle={toggle} backdrop="static">
|
||||
<ModalHeader toggle={toggle}>
|
||||
{t('plans.updatePlan.confirmationHeading')}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div>
|
||||
{restrictions.plans && restrictions.plans.price > 0 ? (
|
||||
restrictions.plans.price === totalCost ? null : restrictions.plans
|
||||
.price < totalCost ? (
|
||||
<p className="text-muted mb-3">
|
||||
{t('plans.updatePlan.upgradeNotice')}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-muted mb-3">
|
||||
{t('plans.updatePlan.downgradeNotice')}
|
||||
</p>
|
||||
)
|
||||
) : null}
|
||||
<p>{t('plans.updatePlan.alreadyStoredCard')}</p>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="link" onClick={proceedToDetails} disabled={loading}>
|
||||
{t('plans.updatePlan.payWithOtherCardBtn')}
|
||||
</Button>
|
||||
<Button color="primary" disabled={loading} onClick={proceedPayment}>
|
||||
{loading
|
||||
? t('plans.updatePlan.payLoading')
|
||||
: t('plans.updatePlan.payWithStoredCardBtn')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
UpdatePlan.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
actions: PropTypes.object,
|
||||
restrictions: PropTypes.object
|
||||
};
|
||||
|
||||
export default reduxConnect('restrictions', [
|
||||
'common',
|
||||
'auth',
|
||||
'user',
|
||||
'restrictions'
|
||||
])(translate(['tabsContent'], { wait: true })(UpdatePlan));
|
||||
@@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button, Modal, ModalBody, ModalHeader } from 'reactstrap';
|
||||
import { planRoutes } from './UserPlans';
|
||||
import { Trans, translate } from 'react-i18next';
|
||||
|
||||
function UpgradePlanModal({ isModalOpen = false, toggle, t }) {
|
||||
function toggleModal() {
|
||||
return toggle();
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
toggle={toggleModal}
|
||||
modalClassName="zoom-modal"
|
||||
backdrop="static"
|
||||
>
|
||||
<ModalHeader toggle={toggleModal} />
|
||||
<ModalBody className="px-4 px-sm-5 pb-5">
|
||||
<div className="text-center">
|
||||
<div className="display-4 mb-2">
|
||||
<i className="lnr-rocket text-primary"></i>
|
||||
</div>
|
||||
<h3 className="mb-3">{t('plans.upgradeModal.heading')}</h3>
|
||||
<div className="mb-4">
|
||||
<p className="text-muted">
|
||||
<Trans i18nKey="plans.upgradeModal.text">
|
||||
You have to upgrade your plan to get access of these features.
|
||||
Take a look at our bite-sized
|
||||
<strong>à la carte menu options</strong> with monthly billing.
|
||||
</Trans>{' '}
|
||||
<a
|
||||
href="https://www.socialhose.io/en/pricing"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{t('plans.upgradeModal.learnMore')}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
tag={Link}
|
||||
to={`/app/plans/${planRoutes.update}`}
|
||||
onClick={toggleModal}
|
||||
className="btn-pill btn-wide d-block mx-auto"
|
||||
color="success"
|
||||
size="lg"
|
||||
>
|
||||
{t('plans.upgradeModal.upgradeNowBtn')}
|
||||
</Button>
|
||||
<Button color="link" size="sm" onClick={toggleModal}>
|
||||
{t('plans.upgradeModal.maybeLaterBtn')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
UpgradePlanModal.propTypes = {
|
||||
isModalOpen: PropTypes.bool,
|
||||
t: PropTypes.func.isRequired,
|
||||
toggle: PropTypes.func
|
||||
};
|
||||
|
||||
export default React.memo(
|
||||
translate(['tabsContent'], { wait: true })(UpgradePlanModal)
|
||||
);
|
||||
@@ -0,0 +1,128 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
NavLink,
|
||||
Redirect,
|
||||
Route,
|
||||
Switch,
|
||||
useRouteMatch
|
||||
} from 'react-router-dom';
|
||||
import reduxConnect from '../../../../redux/utils/connect';
|
||||
import ChangeCard from './ChangeCard';
|
||||
import CurrentPlan from './CurrentPlan';
|
||||
import UpdatePlan from './UpdatePlan';
|
||||
import UserTransactions from './UserTransactions';
|
||||
import { Card, CardBody, Col, Row } from 'reactstrap';
|
||||
import { translate } from 'react-i18next';
|
||||
|
||||
export const planRoutes = {
|
||||
current: 'current',
|
||||
changeCard: 'change-card',
|
||||
txn: 'transactions',
|
||||
update: 'update'
|
||||
};
|
||||
|
||||
function UserPlans({ actions, restrictions, t }) {
|
||||
const match = useRouteMatch();
|
||||
|
||||
useEffect(() => {
|
||||
const { setEnableClosedSidebar } = actions;
|
||||
actions.getRestrictions();
|
||||
setEnableClosedSidebar(true);
|
||||
|
||||
return () => setEnableClosedSidebar(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col xs={12} lg={4} xl={3}>
|
||||
<Card className="mb-3">
|
||||
<CardBody className="navigation-vertical">
|
||||
<ul className="navigation-ul">
|
||||
<li className="navigation-item">
|
||||
<NavLink
|
||||
className="navigation-link"
|
||||
activeClassName="active"
|
||||
to={`${match.url}/${planRoutes.current}`}
|
||||
>
|
||||
<em>
|
||||
<i className="font-size-lg lnr-file-empty"> </i>
|
||||
</em>
|
||||
<span>{t('plans.sidebar.activePlanDetails')}</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
{restrictions.isPaymentId && (
|
||||
<li>
|
||||
<NavLink
|
||||
className="navigation-link"
|
||||
activeClassName="active"
|
||||
to={`${match.url}/${planRoutes.changeCard}`}
|
||||
>
|
||||
<em>
|
||||
<i className="font-size-lg lnr-license"> </i>
|
||||
</em>
|
||||
<span>{t('plans.sidebar.changeCard')}</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<NavLink
|
||||
className="navigation-link"
|
||||
activeClassName="active"
|
||||
to={`${match.url}/${planRoutes.update}`}
|
||||
>
|
||||
<em>
|
||||
<i className="font-size-lg lnr-arrow-up-circle"> </i>
|
||||
</em>
|
||||
<span>{t('plans.sidebar.updatePlan')}</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink
|
||||
className="navigation-link"
|
||||
activeClassName="active"
|
||||
to={`${match.url}/${planRoutes.txn}`}
|
||||
>
|
||||
<em>
|
||||
<i className="font-size-lg lnr-list"> </i>
|
||||
</em>
|
||||
<span>{t('plans.sidebar.yourTransactions')}</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Col>
|
||||
<Switch>
|
||||
<Route path={`${match.url}/${planRoutes.current}`}>
|
||||
<CurrentPlan />
|
||||
</Route>
|
||||
{restrictions.isPaymentId && (
|
||||
<Route path={`${match.url}/${planRoutes.changeCard}`}>
|
||||
<ChangeCard />
|
||||
</Route>
|
||||
)}
|
||||
<Route path={`${match.url}/${planRoutes.txn}`}>
|
||||
<UserTransactions />
|
||||
</Route>
|
||||
<Route path={`${match.url}/${planRoutes.update}`}>
|
||||
<UpdatePlan />
|
||||
</Route>
|
||||
<Redirect to={`${match.url}/current`} />
|
||||
</Switch>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
UserPlans.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
actions: PropTypes.object.isRequired,
|
||||
restrictions: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default reduxConnect('restrictions', [
|
||||
'common',
|
||||
'auth',
|
||||
'user',
|
||||
'restrictions'
|
||||
])(translate(['tabsContent'], { wait: true })(UserPlans));
|
||||
@@ -0,0 +1,132 @@
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
/* eslint-disable react/prop-types */
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { reduxActions } from '../../../../redux/utils/connect';
|
||||
import {
|
||||
convertUTCtoLocal,
|
||||
getQueryParams,
|
||||
setDocumentData
|
||||
} from '../../../../common/helper';
|
||||
import { getTransactions } from '../../../../api/plans/userPlans';
|
||||
import Table from '../../../common/Table/Table';
|
||||
import { Button, Col } from 'reactstrap';
|
||||
import ShowTransactionDetails from './ShowTransactionDetails';
|
||||
import moment from 'moment';
|
||||
import { capitalize } from 'lodash';
|
||||
import { translate } from 'react-i18next';
|
||||
|
||||
function UserTransactions(props) {
|
||||
const [dataSource, setDataSource] = useState({ data: [] });
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedData, setSelectedData] = useState(false);
|
||||
const { actions, t } = props;
|
||||
|
||||
useEffect(() => {
|
||||
setDocumentData('title', 'User Transactions');
|
||||
|
||||
return () => setDocumentData('title'); // default
|
||||
}, []);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
id: 'activeDate',
|
||||
Header: t('plans.transactions.activationDate'),
|
||||
accessor: (d) => d.lines.data[0] && d.lines.data[0].period.start,
|
||||
Cell: (props) => convertUTCtoLocal(moment.unix(props.value), 'MM/DD/YYYY')
|
||||
},
|
||||
{
|
||||
id: 'expireDate',
|
||||
Header: t('plans.transactions.expirationDate'),
|
||||
accessor: (d) => d.lines.data[0] && d.lines.data[0].period.end,
|
||||
Cell: (props) => convertUTCtoLocal(moment.unix(props.value), 'MM/DD/YYYY')
|
||||
},
|
||||
{
|
||||
id: 'paid_at',
|
||||
Header: t('plans.transactions.transactionDate'),
|
||||
accessor: (d) => d.status_transitions.paid_at,
|
||||
Cell: (props) =>
|
||||
convertUTCtoLocal(moment.unix(props.value), 'MM/DD/YYYY HH:mm:ss')
|
||||
},
|
||||
{
|
||||
Header: t('plans.transactions.amount'),
|
||||
accessor: 'amount_paid',
|
||||
Cell: (props) => (props.value ? `$${props.value / 100}` : '-')
|
||||
},
|
||||
{
|
||||
Header: t('plans.transactions.status'),
|
||||
accessor: 'status',
|
||||
Cell: (props) => capitalize(props.value)
|
||||
},
|
||||
{
|
||||
Header: t('plans.transactions.actions'),
|
||||
accessor: 'id',
|
||||
Cell: (props) => (
|
||||
<Button
|
||||
outline
|
||||
className="border-0 btn-transition"
|
||||
color="primary"
|
||||
size="sm"
|
||||
onClick={() => setSelectedData(props.original)}
|
||||
>
|
||||
{t('plans.transactions.more')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
function closeModal() {
|
||||
setSelectedData(false);
|
||||
}
|
||||
|
||||
const getTransactionList = useCallback((page, pageSize) => {
|
||||
setLoading(true);
|
||||
const params = getQueryParams({ page, pageSize });
|
||||
getTransactions(params).then((res) => {
|
||||
if (res.error || !res.data || !res.data.success || !res.data.data) {
|
||||
setDataSource({ data: [] }); // comment this line when API is ready
|
||||
setLoading(false);
|
||||
return actions.addAlert({
|
||||
type: 'error',
|
||||
transKey: 'somethingWrong'
|
||||
});
|
||||
}
|
||||
|
||||
// setDataSource(sampleData); // comment this line when API is ready
|
||||
setDataSource({
|
||||
data:
|
||||
res.data.data.data && res.data.data.data.length > 0
|
||||
? res.data.data.data
|
||||
: []
|
||||
});
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { data = [], totalCount = 0, limit = 100, page = 1 } = dataSource;
|
||||
return (
|
||||
<Col xs="12" lg="8" xl="9">
|
||||
<Table
|
||||
cardTitle={t('plans.transactions.heading')}
|
||||
columns={columns}
|
||||
data={data}
|
||||
totalCount={totalCount}
|
||||
showTotalCount
|
||||
limit={limit}
|
||||
page={page}
|
||||
isLoading={loading}
|
||||
onFetchData={getTransactionList}
|
||||
/>
|
||||
<ShowTransactionDetails data={selectedData} closeModal={closeModal} />
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
UserTransactions.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
actions: PropTypes.object
|
||||
};
|
||||
|
||||
export default reduxActions()(
|
||||
translate(['tabsContent'], { wait: true })(UserTransactions)
|
||||
);
|
||||
Reference in New Issue
Block a user