diff --git a/client/src/components/Dashboard/Dashboard.js b/client/src/components/Dashboard/Dashboard.js
index 996587acc..7be031c16 100644
--- a/client/src/components/Dashboard/Dashboard.js
+++ b/client/src/components/Dashboard/Dashboard.js
@@ -15,8 +15,10 @@ export default function Dashboard() {
-
-
+
+
+
+
@@ -32,4 +34,4 @@ export default function Dashboard() {
);
-}
\ No newline at end of file
+}
diff --git a/client/src/config/sidebarMenu.js b/client/src/config/sidebarMenu.js
index 8945fa9dd..f04efbf8d 100644
--- a/client/src/config/sidebarMenu.js
+++ b/client/src/config/sidebarMenu.js
@@ -194,6 +194,10 @@ export default [
text: ,
href: '/preferences',
},
+ {
+ text: ,
+ href: '/billing',
+ },
{
text: ,
href: '/auditing/list',
diff --git a/client/src/containers/Subscriptions/BillingForm.js b/client/src/containers/Subscriptions/BillingForm.js
new file mode 100644
index 000000000..33b1fafb8
--- /dev/null
+++ b/client/src/containers/Subscriptions/BillingForm.js
@@ -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 (
+
+ );
+}
+
+export default compose(withDashboardActions, withBillingActions)(BillingForm);
diff --git a/client/src/containers/Subscriptions/BillingTab.js b/client/src/containers/Subscriptions/BillingTab.js
new file mode 100644
index 000000000..df46a8977
--- /dev/null
+++ b/client/src/containers/Subscriptions/BillingTab.js
@@ -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 (
+
+ );
+}
+
+export default BillingTab;
diff --git a/client/src/containers/Subscriptions/LicenseTab.js b/client/src/containers/Subscriptions/LicenseTab.js
new file mode 100644
index 000000000..e485db707
--- /dev/null
+++ b/client/src/containers/Subscriptions/LicenseTab.js
@@ -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 (
+
+
+
+
+
+
+
+
+
}
+ intent={errors.license_code && touched.license_code && Intent.DANGER}
+ helperText={
+
+ }
+ className={'form-group-license_code'}
+ >
+
+
+
+ );
+}
+
+export default LicenseTab;
diff --git a/client/src/containers/Subscriptions/SubscriptionTabs.js b/client/src/containers/Subscriptions/SubscriptionTabs.js
new file mode 100644
index 000000000..7113fbc6b
--- /dev/null
+++ b/client/src/containers/Subscriptions/SubscriptionTabs.js
@@ -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 (
+
+
+ }
+ />
+
+
+
+ );
+};
+
+export const PaymentMethodTabs = ({ formik }) => {
+ const [animate, setAnimate] = useState(true);
+ const { formatMessage } = useIntl();
+
+ return (
+
+
+ }
+ />
+
+
+
+
+ );
+};
diff --git a/client/src/containers/Subscriptions/withBillingActions.js b/client/src/containers/Subscriptions/withBillingActions.js
new file mode 100644
index 000000000..a4f8e5586
--- /dev/null
+++ b/client/src/containers/Subscriptions/withBillingActions.js
@@ -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);
diff --git a/client/src/lang/en/index.js b/client/src/lang/en/index.js
index df8da8da8..58dd4d3ca 100644
--- a/client/src/lang/en/index.js
+++ b/client/src/lang/en/index.js
@@ -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',
};
diff --git a/client/src/routes/dashboard.js b/client/src/routes/dashboard.js
index 90eacfa50..303596f3f 100644
--- a/client/src/routes/dashboard.js
+++ b/client/src/routes/dashboard.js
@@ -322,4 +322,16 @@ export default [
}),
breadcrumb: 'Receipt List',
},
+
+//Subscriptions
+
+ {
+ path: `/billing`,
+ component: LazyLoader({
+ loader: () => import('containers/Subscriptions/BillingForm'),
+ }),
+ breadcrumb: 'New Billing',
+ },
+
+
];
diff --git a/client/src/store/billing/Billing.action.js b/client/src/store/billing/Billing.action.js
new file mode 100644
index 000000000..e1ca8945f
--- /dev/null
+++ b/client/src/store/billing/Billing.action.js
@@ -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);
+ });
+ });
+};
diff --git a/client/src/store/billing/Billing.type.js b/client/src/store/billing/Billing.type.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/client/src/store/types.js b/client/src/store/types.js
index 182dd8562..4a5b44cc8 100644
--- a/client/src/store/types.js
+++ b/client/src/store/types.js
@@ -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
};
diff --git a/client/src/style/App.scss b/client/src/style/App.scss
index b01485625..4fd5a4ea6 100644
--- a/client/src/style/App.scss
+++ b/client/src/style/App.scss
@@ -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';
diff --git a/client/src/style/pages/billing.scss b/client/src/style/pages/billing.scss
new file mode 100644
index 000000000..4ca989d44
--- /dev/null
+++ b/client/src/style/pages/billing.scss
@@ -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%;
+ }
+ }
+}
diff --git a/client/src/style/pages/preferences.scss b/client/src/style/pages/preferences.scss
index 5d9b25712..95aeefa1b 100644
--- a/client/src/style/pages/preferences.scss
+++ b/client/src/style/pages/preferences.scss
@@ -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%;