This commit is contained in:
elforjani3
2020-09-30 19:01:28 +02:00
15 changed files with 597 additions and 11 deletions

View File

@@ -15,8 +15,10 @@ export default function Dashboard() {
<div className={classNames('dashboard')}>
<Switch>
<Route path="/preferences">
<Sidebar />
<PreferencesSidebar />
<DashboardSplitPane>
<Sidebar />
<PreferencesSidebar />
</DashboardSplitPane>
<PreferencesContent />
</Route>
@@ -32,4 +34,4 @@ export default function Dashboard() {
<DialogsContainer />
</div>
);
}
}

View File

@@ -194,6 +194,10 @@ export default [
text: <T id={'preferences'} />,
href: '/preferences',
},
{
text: <T id={'billing'} />,
href: '/billing',
},
{
text: <T id={'auditing_system'} />,
href: '/auditing/list',

View File

@@ -0,0 +1,94 @@
import React, { useEffect, useMemo, useState } from 'react';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import { Button, Intent } from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
import { pick } from 'lodash';
import AppToaster from 'components/AppToaster';
import ErrorMessage from 'components/ErrorMessage';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import { MeteredBillingTabs, PaymentMethodTabs } from './SubscriptionTabs';
import withBillingActions from './withBillingActions';
import { compose } from 'utils';
function BillingForm({
// #withDashboardActions
changePageTitle,
//#withBillingActions
requestSubmitBilling,
}) {
// const defaultPlan = useMemo(() => ({
// plan_slug: [
// { id: 0, name: 'Basic', value: 'basic' },
// { id: 0, name: 'Pro', value: 'pro' },
// ],
// }));
const { formatMessage } = useIntl();
useEffect(() => {
changePageTitle(formatMessage({ id: 'billing' }));
}, [changePageTitle, formatMessage]);
const validationSchema = Yup.object().shape({
plan_slug: Yup.string()
.required()
.label(formatMessage({ id: 'plan_slug' })),
license_code: Yup.string().trim(),
});
const initialValues = useMemo(
() => ({
plan_slug: 'basic',
license_code: '',
}),
[],
);
const formik = useFormik({
enableReinitialize: true,
validationSchema: validationSchema,
initialValues: {
...initialValues,
},
onSubmit: (values, { setSubmitting, resetForm, setErrors }) => {
requestSubmitBilling(values)
.then((response) => {
AppToaster.show({
message: formatMessage({
id: 'the_biling_has_been_successfully_created',
}),
intent: Intent.SUCCESS,
});
setSubmitting(false);
})
.catch((errors) => {
setSubmitting(false);
});
},
});
console.log(formik.values, 'formik');
return (
<div className={'billing-form'}>
<form onSubmit={formik.handleSubmit}>
<MeteredBillingTabs formik={formik} planId={formik.values.plan_slug} />
<div className={'subscribe-button'}>
<Button
intent={Intent.PRIMARY}
type="submit"
loading={formik.isSubmitting}
>
<T id={'subscribe'} />
</Button>
</div>
</form>
</div>
);
}
export default compose(withDashboardActions, withBillingActions)(BillingForm);

View File

@@ -0,0 +1,164 @@
import React, {
useState,
useMemo,
useCallback,
useEffect,
useRef,
} from 'react';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { PaymentMethodTabs } from './SubscriptionTabs';
function BillingTab({ formik }) {
const [plan, setPlan] = useState();
const planRef = useRef(null);
const billingRef = useRef(null);
const handlePlan = () => {
const plans = planRef.current.querySelectorAll('a');
const planSelected = planRef.current.querySelector('.plan-selected');
plans.forEach((el) => {
el.addEventListener('click', () => {
planSelected.classList.remove('plan-selected');
el.classList.add('plan-selected');
});
});
};
const handleBilling = () => {
const billingPriod = billingRef.current.querySelectorAll('a');
const billingSelected = billingRef.current.querySelector(
'.billing-selected',
);
billingPriod.forEach((el) => {
el.addEventListener('click', () => {
billingSelected.classList.remove('billing-selected');
el.classList.add('billing-selected');
});
});
};
useEffect(() => {
handlePlan();
handleBilling();
});
return (
<div>
<section>
<h1 className={'bg-title'}>
<T id={'a_select_a_plan'} />
</h1>
<p className={'bg-message '}>
<T id={'please_enter_your_preferred_payment_method'} />
</p>
<div className={'billing-form__plan-container'} ref={planRef}>
<a
id={'basic-plan'}
className={`plan-wrapper plan-selected`}
onClick={() =>
setPlan({ ...formik.setFieldValue('plan_slug', 'basic') })
}
>
<div className={'plan-header'}>
<div className={'plan-name'}>
<T id={'Basic'} />
</div>
</div>
<div className={'plan-description'}>
<ul>
<li>Sales/purchases module.</li>
<li>Expense module.</li>
<li>Inventory module.</li>
<li>Unlimited status pages.</li>
<li>Unlimited status pages.</li>
</ul>
</div>
<div className={'plan-price'}>
<span className={'amount'}>1200 LYD</span>
<span className={'period'}>
<T id={'year_per'} />
</span>
</div>
</a>
<a
id={'pro-plan'}
className={`plan-wrapper`}
onClick={() =>
setPlan({ ...formik.setFieldValue('plan_slug', 'pro') })
}
>
<div className={'plan-header'}>
<div className={'plan-name'}>
<T id={'pro'} />
</div>
</div>
<div className={'plan-description'}>
<ul>
<li>Sales/purchases module.</li>
<li>Expense module.</li>
<li>Inventory module.</li>
<li>Unlimited status pages.</li>
<li>Unlimited status pages.</li>
</ul>
</div>
<div className={'plan-price'}>
<span className={'amount'}>1200 LYD</span>
<span className={'period'}>
<T id={'year_per'} />
</span>
</div>
</a>
</div>
</section>
<section>
<h1 className={'bg-title'}>
<T id={'b_choose_your_billing'} />
</h1>
<p className={'bg-message'}>
<T id={'please_enter_your_preferred_payment_method'} />
</p>
<div className={'payment-method-continer'} ref={billingRef}>
<a
href={'#'}
id={'monthly'}
className={'period-container billing-selected'}
>
<span className={'bg-period'}>
<T id={'monthly'} />
</span>
<div className={'plan-price'}>
<span className={'amount'}>1200 LYD</span>
<span className={'period'}>
<T id={'year'} />
</span>
</div>
</a>
<a href={'#'} id={'yearly'} className={'period-container'}>
<span className={'bg-period'}>
<T id={'yearly'} />
</span>
<div className={'plan-price'}>
<span className={'amount'}>1200 LYD</span>
<span className={'period'}>
<T id={'year'} />
</span>
</div>
</a>
</div>
</section>
<section>
<h1 className={'bg-title'}>
<T id={'c_payment_methods'} />
</h1>
<p className={'bg-message'}>
<T id={'please_enter_your_preferred_payment_method'} />
</p>
<PaymentMethodTabs formik={formik} />
</section>
</div>
);
}
export default BillingTab;

View File

@@ -0,0 +1,35 @@
import React from 'react';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { InputGroup, FormGroup, Intent } from '@blueprintjs/core';
import ErrorMessage from 'components/ErrorMessage';
function LicenseTab({
formik: { errors, touched, setFieldValue, getFieldProps },
}) {
return (
<div className={'license-container'}>
<h4>
<T id={'license_code'} />
</h4>
<p className={'bg-message'}>
<T id={'cards_will_be_charged'} />
</p>
<FormGroup
label={<T id={'license_number'} />}
intent={errors.license_code && touched.license_code && Intent.DANGER}
helperText={
<ErrorMessage name="license_code" {...{ errors, touched }} />
}
className={'form-group-license_code'}
>
<InputGroup
intent={errors.license_code && touched.license_code && Intent.DANGER}
{...getFieldProps('license_code')}
/>
</FormGroup>
</div>
);
}
export default LicenseTab;

View File

@@ -0,0 +1,55 @@
import React, { useState } from 'react';
import { Tabs, Tab } from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import BillingTab from './BillingTab';
import LicenseTab from './LicenseTab';
export const MeteredBillingTabs = ({ formik }) => {
const [animate, setAnimate] = useState(true);
const { formatMessage } = useIntl();
return (
<div>
<Tabs animate={animate} large={true}>
<Tab
title={formatMessage({ id: 'billing' })}
id={'billing'}
panel={<BillingTab formik={formik} />}
/>
<Tab
title={formatMessage({ id: 'usage' })}
id={'usage'}
disabled={true}
// panel={'Usage'}
/>
</Tabs>
</div>
);
};
export const PaymentMethodTabs = ({ formik }) => {
const [animate, setAnimate] = useState(true);
const { formatMessage } = useIntl();
return (
<div>
<Tabs animate={animate} large={true}>
<Tab
title={formatMessage({ id: 'license' })}
id={'license'}
panel={<LicenseTab formik={formik} />}
/>
<Tab
title={formatMessage({ id: 'credit_card' })}
id={'credit_card'}
disabled={true}
/>
<Tab
title={formatMessage({ id: 'paypal' })}
id={'paypal'}
disabled={true}
/>
</Tabs>
</div>
);
};

View File

@@ -0,0 +1,8 @@
import { connect } from 'react-redux';
import { submitBilling } from 'store/billing/Billing.action';
export const mapDispatchToProps = (dispatch) => ({
requestSubmitBilling: (form) => dispatch(submitBilling({ form })),
});
export default connect(null, mapDispatchToProps);

View File

@@ -670,4 +670,30 @@ export default {
'The payment receive has been successfully created.',
select_invoice: 'Select Invoice',
payment_mades: 'Payment Mades',
subscription: 'Subscription',
plan_slug: 'Plan slug',
billing: 'Billing',
the_billing_has_been_successfully_created:
'The billing has been successfully created.',
a_select_a_plan: 'A. Select a plan',
b_choose_your_billing: 'B. Choose your billing',
c_payment_methods: 'C. Payment methods',
usage: 'Usage',
basic: 'Basic',
license: 'License',
credit_card: 'Credit Card',
paypal: 'Paypal',
pro: 'PRO',
monthly: 'Monthly',
yearly: 'Yearly',
license_code: 'License code',
year: 'Year',
please_enter_your_preferred_payment_method:
'Please enter your preferred payment method below. You can use a credit / debit card or prepay through PayPal. ',
cards_will_be_charged:
'Cards will be charged either at the end of the month or whenever your balance exceeds the usage threshold.All major credit / debit cards accepted.',
license_number: 'License number',
subscribe: 'Subscribe',
year_per: 'year',
};

View File

@@ -322,4 +322,16 @@ export default [
}),
breadcrumb: 'Receipt List',
},
//Subscriptions
{
path: `/billing`,
component: LazyLoader({
loader: () => import('containers/Subscriptions/BillingForm'),
}),
breadcrumb: 'New Billing',
},
];

View File

@@ -0,0 +1,18 @@
import ApiService from 'services/ApiService';
import t from 'store/types';
export const submitBilling = ({ form }) => {
return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.post('payment', form)
.then((response) => {
resolve(response);
})
.catch((error) => {
const { response } = error;
const { data } = response;
reject(data?.errors);
});
});
};

View File

View File

@@ -23,6 +23,7 @@ import receipts from './receipt/receipt.type';
import bills from './Bills/bills.type';
import paymentReceives from './PaymentReceive/paymentReceive.type';
import vendors from './vendors/vendors.types';
import billing from './billing/Billing.type';
export default {
...authentication,
@@ -50,4 +51,5 @@ export default {
...bills,
...paymentReceives,
...vendors,
...billing
};

View File

@@ -24,6 +24,9 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
@import '@blueprintjs/core/src/blueprint.scss';
@import '@blueprintjs/datetime/src/blueprint-datetime.scss';
// Bootstrap
// @import '~bootstrap/scss/bootstrap';
@import 'basscss';
@import 'functions';
@@ -62,7 +65,7 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
@import 'pages/estimates';
@import 'pages/receipts';
@import 'pages/invoices';
@import 'pages/billing.scss';
// Views
@import 'views/filter-dropdown';

View File

@@ -0,0 +1,161 @@
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.billing-form {
padding: 25px 45px;
width: 800px;
margin: 0 auto;
&__plan-container {
display: flex;
flex-flow: row wrap;
margin-bottom: 30px;
.plan-wrapper {
display: flex;
flex-direction: column;
width: 215px;
height: 267px;
border-radius: 5px;
padding: 15px;
border: 1px solid #dcdcdc;
background: #fcfdff;
text-decoration: none;
color: #000;
cursor: pointer;
&.plan-wrapper:not(:first-child) {
margin-left: 20px;
}
.plan-header {
display: flex;
justify-content: flex-start;
margin-bottom: 10px;
}
.plan-name {
background: #3657ff;
border-radius: 3px;
padding: 1px 8px 1px 8px;
font-size: 13px;
color: #fff;
margin-bottom: 15px;
}
.plan-description {
font-size: 14px;
font-weight: 400;
line-height: 2em;
&.plan-description ul {
list-style: none;
}
}
.plan-price {
margin-top: auto;
.amount {
font-weight: 500;
}
.period {
font-weight: 400;
color: #666;
&.period::before {
content: '/';
display: inline-block;
margin: 0 2px;
}
}
}
}
a.plan-selected {
border: 1px solid #0069ff;
background-color: #fcfdff;
}
}
.payment-method-continer {
margin-bottom: 30px;
.period-container {
display: inline-flex;
background-color: #fcfdff;
justify-content: space-between;
align-items: center;
width: 215px;
height: 36px;
border-radius: 5px;
padding: 8px 10px;
color: #000;
border: 1px solid #dcdcdc;
cursor: pointer;
text-decoration: none;
&.period-container:not(:first-child) {
margin-left: 20px;
}
.period::before {
content: '/';
display: inline-block;
margin: 0 2px;
}
.bg-period {
font-size: 14px;
font-weight: 500;
}
}
a.billing-selected {
border: 1px solid #0069ff;
background-color: #fcfdff;
}
}
.bg-title {
font-size: 22px;
font-weight: 400;
line-height: normal;
}
.bg-message {
margin-bottom: 15px;
font-size: 14px;
}
.license-container {
.bp3-form-group {
margin-bottom: 20px;
.bp3-label {
margin-bottom: 15px;
}
}
.bp3-form-content {
.bp3-input-group {
display: block;
position: relative;
}
.bp3-input {
position: relative;
width: 59%;
height: 41px;
}
}
h4 {
font-size: 18px;
color: #444444;
}
p {
font-size: 14px;
}
}
.bp3-tab-list {
border-bottom: 2px solid #f1f1f1;
width: 95%;
}
.subscribe-button {
.bp3-button {
background-color: #0063ff;
min-height: 41px;
width: 240px;
// width: 25%;
}
}
}

View File

@@ -1,6 +1,8 @@
.dashboard-content--preferences {
margin-left: 430px;
height: 700px;
// margin-left: 430px;
margin-left: 410px;
width: 100%;
// height: max-content;
position: relative;
}
@@ -58,7 +60,9 @@
.preferences__sidebar {
background: #fdfdfd;
position: fixed;
left: 220px;
// left: 220px;
left: 200px;
top: 1px;
min-width: 210px;
max-width: 210px;
height: 100%;
@@ -104,10 +108,9 @@
// Preference
//---------------------------------
.preferences__inside-content--general {
.bp3-form-group {
margin: 25px 20px 20px;
.bp3-label {
min-width: 180px;
}
@@ -117,8 +120,7 @@
}
.form-group--org-name,
.form-group--org-industry{
.form-group--org-industry {
.bp3-form-content {
position: relative;
width: 70%;