Compare commits

..

13 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
909a70e2c5 feat: correct the migration files 2024-06-04 17:42:29 +02:00
Ahmed Bouhuolia
84dd0fa86b Merge remote-tracking branch 'refs/remotes/origin/develop' into develop 2024-06-04 16:22:07 +02:00
Ahmed Bouhuolia
a4719fe15b fix: add Plaid env variables to docker-compose.prod file 2024-06-04 16:21:49 +02:00
Ahmed Bouhuolia
fd915b503f fix: Run migrations only for initialized tenants (#484) 2024-06-04 16:13:18 +02:00
Ahmed Bouhuolia
bbba54c08e fix: validate the s3 configures exist (#482) 2024-06-04 15:11:21 +02:00
Ahmed Bouhuolia
f241e2bede fix: Plaid syncs deposit imports as withdrawals (#481) 2024-06-03 21:56:29 +02:00
Ahmed Bouhuolia
175bc243f3 fix: Organize Plaid env variables for development and sandbox envs (#480) 2024-06-03 20:50:02 +02:00
Ahmed Bouhuolia
7c06c8bb8a fix: Lemon Squeezy redirect to base url (#479)
fix: Lemon Squeezy redirect to base url
2024-06-03 19:54:40 +02:00
Ahmed Bouhuolia
8fd930caac Merge pull request #478 from bigcapitalhq/virtual-docker-internal-network
feat: Internal docker virtual network
2024-06-02 21:25:55 +02:00
Ahmed Bouhuolia
e175307da4 feat: internal docker virtual network 2024-06-02 21:25:15 +02:00
Ahmed Bouhuolia
b1bf932e88 fix: add S3 env variables to docker-compose prod 2024-06-02 17:46:10 +02:00
Ahmed Bouhuolia
aa897212ab Merge remote-tracking branch 'refs/remotes/origin/develop' into develop 2024-06-02 17:35:49 +02:00
Ahmed Bouhuolia
890903e08b chore: change the variant id. 2024-06-02 17:34:52 +02:00
25 changed files with 163 additions and 302 deletions

View File

@@ -75,30 +75,9 @@ PLAID_ENV=sandbox
# Your Plaid keys, which can be found in the Plaid Dashboard. # Your Plaid keys, which can be found in the Plaid Dashboard.
# https://dashboard.plaid.com/account/keys # https://dashboard.plaid.com/account/keys
PLAID_CLIENT_ID= PLAID_CLIENT_ID=
PLAID_SECRET_DEVELOPMENT= PLAID_SECRET=
PLAID_SECRET_SANDBOX=
PLAID_LINK_WEBHOOK= PLAID_LINK_WEBHOOK=
# (Optional) Redirect URI settings section
# Only required for OAuth redirect URI testing (not common on desktop):
# Sandbox Mode:
# Set the PLAID_SANDBOX_REDIRECT_URI below to 'http://localhost:3001/oauth-link'.
# The OAuth redirect flow requires an endpoint on the developer's website
# that the bank website should redirect to. You will also need to configure
# this redirect URI for your client ID through the Plaid developer dashboard
# at https://dashboard.plaid.com/team/api.
# Development mode:
# When running in development mode, you must use an https:// url.
# You will need to configure this https:// redirect URI in the Plaid developer dashboard.
# Instructions to create a self-signed certificate for localhost can be found at
# https://github.com/plaid/pattern/blob/master/README.md#testing-oauth.
# If your system is not set up to run localhost with https://, you will be unable to test
# the OAuth in development and should leave the PLAID_DEVELOPMENT_REDIRECT_URI blank.
PLAID_SANDBOX_REDIRECT_URI=
PLAID_DEVELOPMENT_REDIRECT_URI=
# https://docs.lemonsqueezy.com/guides/developer-guide/getting-started#create-an-api-key # https://docs.lemonsqueezy.com/guides/developer-guide/getting-started#create-an-api-key
LEMONSQUEEZY_API_KEY= LEMONSQUEEZY_API_KEY=
LEMONSQUEEZY_STORE_ID= LEMONSQUEEZY_STORE_ID=

View File

@@ -22,11 +22,15 @@ services:
- server - server
- webapp - webapp
restart: on-failure restart: on-failure
networks:
- bigcapital_network
webapp: webapp:
container_name: bigcapital-webapp container_name: bigcapital-webapp
image: bigcapitalhq/webapp:latest image: bigcapitalhq/webapp:latest
restart: on-failure restart: on-failure
networks:
- bigcapital_network
server: server:
container_name: bigcapital-server container_name: bigcapital-server
@@ -89,14 +93,17 @@ services:
- GOTENBERG_URL=${GOTENBERG_URL} - GOTENBERG_URL=${GOTENBERG_URL}
- GOTENBERG_DOCS_URL=${GOTENBERG_DOCS_URL} - GOTENBERG_DOCS_URL=${GOTENBERG_DOCS_URL}
# Exchange Rate
- EXCHANGE_RATE_SERVICE=${EXCHANGE_RATE_SERVICE}
- OPEN_EXCHANGE_RATE_APP_ID-${OPEN_EXCHANGE_RATE_APP_ID}
# Bank Sync # Bank Sync
- BANKING_CONNECT=${BANKING_CONNECT} - BANKING_CONNECT=${BANKING_CONNECT}
# Plaid # Plaid
- PLAID_ENV=${PLAID_ENV} - PLAID_ENV=${PLAID_ENV}
- PLAID_CLIENT_ID=${PLAID_CLIENT_ID} - PLAID_CLIENT_ID=${PLAID_CLIENT_ID}
- PLAID_SECRET_DEVELOPMENT=${PLAID_SECRET_DEVELOPMENT} - PLAID_SECRET=${PLAID_SECRET}
- PLAID_SECRET_SANDBOX=${b8cf42b441e110451e2f69ad7e1e9f}
- PLAID_LINK_WEBHOOK=${PLAID_LINK_WEBHOOK} - PLAID_LINK_WEBHOOK=${PLAID_LINK_WEBHOOK}
# Lemon Squeez # Lemon Squeez
@@ -114,6 +121,15 @@ services:
- NEW_RELIC_LICENSE_KEY=${NEW_RELIC_LICENSE_KEY} - NEW_RELIC_LICENSE_KEY=${NEW_RELIC_LICENSE_KEY}
- NEW_RELIC_APP_NAME=${NEW_RELIC_APP_NAME} - NEW_RELIC_APP_NAME=${NEW_RELIC_APP_NAME}
# S3
- S3_REGION=${S3_REGION}
- S3_ACCESS_KEY_ID=${S3_ACCESS_KEY_ID}
- S3_SECRET_ACCESS_KEY=${S3_SECRET_ACCESS_KEY}
- S3_ENDPOINT=${S3_ENDPOINT}
- S3_BUCKET=${S3_BUCKET}
networks:
- bigcapital_network
database_migration: database_migration:
container_name: bigcapital-database-migration container_name: bigcapital-database-migration
build: build:
@@ -130,6 +146,8 @@ services:
- TENANT_DB_NAME_PERFIX=${TENANT_DB_NAME_PERFIX} - TENANT_DB_NAME_PERFIX=${TENANT_DB_NAME_PERFIX}
depends_on: depends_on:
- mysql - mysql
networks:
- bigcapital_network
mysql: mysql:
container_name: bigcapital-mysql container_name: bigcapital-mysql
@@ -145,6 +163,8 @@ services:
- mysql:/var/lib/mysql - mysql:/var/lib/mysql
expose: expose:
- '3306' - '3306'
networks:
- bigcapital_network
mongo: mongo:
container_name: bigcapital-mongo container_name: bigcapital-mongo
@@ -154,6 +174,8 @@ services:
- '27017' - '27017'
volumes: volumes:
- mongo:/var/lib/mongodb - mongo:/var/lib/mongodb
networks:
- bigcapital_network
redis: redis:
container_name: bigcapital-redis container_name: bigcapital-redis
@@ -164,11 +186,15 @@ services:
- '6379' - '6379'
volumes: volumes:
- redis:/data - redis:/data
networks:
- bigcapital_network
gotenberg: gotenberg:
image: gotenberg/gotenberg:7 image: gotenberg/gotenberg:7
expose: expose:
- '9000' - '9000'
networks:
- bigcapital_network
# Volumes # Volumes
volumes: volumes:
@@ -183,3 +209,8 @@ volumes:
redis: redis:
name: bigcapital_prod_redis name: bigcapital_prod_redis
driver: local driver: local
# Networks
networks:
bigcapital_network:
driver: bridge

View File

@@ -86,7 +86,7 @@
"multer-s3": "^3.0.1", "multer-s3": "^3.0.1",
"mustache": "^3.0.3", "mustache": "^3.0.3",
"mysql": "^2.17.1", "mysql": "^2.17.1",
"mysql2": "^3.9.8", "mysql2": "^1.6.5",
"newrelic": "^11.15.0", "newrelic": "^11.15.0",
"node-cache": "^4.2.1", "node-cache": "^4.2.1",
"nodemailer": "^6.3.0", "nodemailer": "^6.3.0",

View File

@@ -4,12 +4,16 @@ import { Router, Response, NextFunction, Request } from 'express';
import { body, param } from 'express-validator'; import { body, param } from 'express-validator';
import BaseController from '@/api/controllers/BaseController'; import BaseController from '@/api/controllers/BaseController';
import { AttachmentsApplication } from '@/services/Attachments/AttachmentsApplication'; import { AttachmentsApplication } from '@/services/Attachments/AttachmentsApplication';
import { AttachmentUploadPipeline } from '@/services/Attachments/S3UploadPipeline';
@Service() @Service()
export class AttachmentsController extends BaseController { export class AttachmentsController extends BaseController {
@Inject() @Inject()
private attachmentsApplication: AttachmentsApplication; private attachmentsApplication: AttachmentsApplication;
@Inject()
private uploadPipelineService: AttachmentUploadPipeline;
/** /**
* Router constructor. * Router constructor.
*/ */
@@ -18,7 +22,8 @@ export class AttachmentsController extends BaseController {
router.post( router.post(
'/', '/',
this.attachmentsApplication.uploadPipeline.single('file'), this.uploadPipelineService.validateS3Configured,
this.uploadPipelineService.uploadPipeline().single('file'),
this.validateUploadedFileExistance, this.validateUploadedFileExistance,
this.uploadAttachment.bind(this) this.uploadAttachment.bind(this)
); );

View File

@@ -71,6 +71,10 @@ function getAllSystemTenants(knex) {
return knex('tenants'); return knex('tenants');
} }
function getAllInitializedSystemTenants(knex) {
return knex('tenants').whereNotNull('initializedAt');
}
// module.exports = { // module.exports = {
// log, // log,
// success, // success,
@@ -183,7 +187,7 @@ commander
.action(async (cmd) => { .action(async (cmd) => {
try { try {
const sysKnex = await initSystemKnex(); const sysKnex = await initSystemKnex();
const tenants = await getAllSystemTenants(sysKnex); const tenants = await getAllInitializedSystemTenants(sysKnex);
const tenantsOrgsIds = tenants.map((tenant) => tenant.organizationId); const tenantsOrgsIds = tenants.map((tenant) => tenant.organizationId);
if (cmd.tenant_id && tenantsOrgsIds.indexOf(cmd.tenant_id) === -1) { if (cmd.tenant_id && tenantsOrgsIds.indexOf(cmd.tenant_id) === -1) {
@@ -220,7 +224,6 @@ commander
const oper = migrateTenant(cmd.tenant_id); const oper = migrateTenant(cmd.tenant_id);
migrateOpers.push(oper); migrateOpers.push(oper);
} }
Promise.all(migrateOpers).then(() => { Promise.all(migrateOpers).then(() => {
success('All tenants are migrated.'); success('All tenants are migrated.');
}); });
@@ -280,4 +283,3 @@ commander
exit(error); exit(error);
} }
}); });

View File

@@ -204,10 +204,7 @@ module.exports = {
plaid: { plaid: {
env: process.env.PLAID_ENV || 'sandbox', env: process.env.PLAID_ENV || 'sandbox',
clientId: process.env.PLAID_CLIENT_ID, clientId: process.env.PLAID_CLIENT_ID,
secretDevelopment: process.env.PLAID_SECRET_DEVELOPMENT, secret: process.env.PLAID_SECRET,
secretSandbox: process.env.PLAID_SECRET_SANDBOX,
redirectSandBox: process.env.PLAID_SANDBOX_REDIRECT_URI,
redirectDevelopment: process.env.PLAID_DEVELOPMENT_REDIRECT_URI,
linkWebhook: process.env.PLAID_LINK_WEBHOOK, linkWebhook: process.env.PLAID_LINK_WEBHOOK,
}, },
@@ -218,6 +215,7 @@ module.exports = {
key: process.env.LEMONSQUEEZY_API_KEY, key: process.env.LEMONSQUEEZY_API_KEY,
storeId: process.env.LEMONSQUEEZY_STORE_ID, storeId: process.env.LEMONSQUEEZY_STORE_ID,
webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET, webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET,
redirectTo: `${process.env.BASE_URL}/setup`,
}, },
/** /**
@@ -237,6 +235,6 @@ module.exports = {
accessKeyId: process.env.S3_ACCESS_KEY_ID, accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY, secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
endpoint: process.env.S3_ENDPOINT, endpoint: process.env.S3_ENDPOINT,
bucket: process.env.S3_BUCKET, bucket: process.env.S3_BUCKET || 'bigcapital-documents',
}, },
}; };

View File

@@ -0,0 +1,14 @@
exports.up = function (knex) {
return knex.schema.createTable('storage', (table) => {
table.increments('id').primary();
table.string('key').notNullable();
table.string('path').notNullable();
table.string('extension').notNullable();
table.integer('expire_in');
table.timestamps();
});
};
exports.down = function (knex) {
return knex.schema.dropTableIfExists('storage');
};

View File

@@ -0,0 +1,5 @@
exports.up = function (knex) {
return knex.schema.dropTableIfExists('storage');
};
exports.down = function (knex) {};

View File

@@ -70,10 +70,7 @@ export class PlaidClientWrapper {
baseOptions: { baseOptions: {
headers: { headers: {
'PLAID-CLIENT-ID': config.plaid.clientId, 'PLAID-CLIENT-ID': config.plaid.clientId,
'PLAID-SECRET': 'PLAID-SECRET': config.plaid.secret,
config.plaid.env === 'development'
? config.plaid.secretDevelopment
: config.plaid.secretSandbox,
'Plaid-Version': '2020-09-14', 'Plaid-Version': '2020-09-14',
}, },
}, },

View File

@@ -2,11 +2,9 @@ import { Inject, Service } from 'typedi';
import { UploadDocument } from './UploadDocument'; import { UploadDocument } from './UploadDocument';
import { DeleteAttachment } from './DeleteAttachment'; import { DeleteAttachment } from './DeleteAttachment';
import { GetAttachment } from './GetAttachment'; import { GetAttachment } from './GetAttachment';
import { AttachmentUploadPipeline } from './S3UploadPipeline';
import { LinkAttachment } from './LinkAttachment'; import { LinkAttachment } from './LinkAttachment';
import { UnlinkAttachment } from './UnlinkAttachment'; import { UnlinkAttachment } from './UnlinkAttachment';
import { getAttachmentPresignedUrl } from './GetAttachmentPresignedUrl'; import { getAttachmentPresignedUrl } from './GetAttachmentPresignedUrl';
import type { Multer } from 'multer';
@Service() @Service()
export class AttachmentsApplication { export class AttachmentsApplication {
@@ -19,9 +17,6 @@ export class AttachmentsApplication {
@Inject() @Inject()
private getDocumentService: GetAttachment; private getDocumentService: GetAttachment;
@Inject()
private uploadPipelineService: AttachmentUploadPipeline;
@Inject() @Inject()
private linkDocumentService: LinkAttachment; private linkDocumentService: LinkAttachment;
@@ -31,14 +26,6 @@ export class AttachmentsApplication {
@Inject() @Inject()
private getPresignedUrlService: getAttachmentPresignedUrl; private getPresignedUrlService: getAttachmentPresignedUrl;
/**
* Express middleware for uploading attachments to an S3 bucket.
* @returns {Multer}
*/
get uploadPipeline(): Multer {
return this.uploadPipelineService.uploadPipeline();
}
/** /**
* Saves the metadata of uploaded document to S3 on database. * Saves the metadata of uploaded document to S3 on database.
* @param {number} tenantId * @param {number} tenantId

View File

@@ -1,12 +1,38 @@
import multer from 'multer'; import multer from 'multer';
import type { Multer } from 'multer' import type { Multer } from 'multer';
import multerS3 from 'multer-s3'; import multerS3 from 'multer-s3';
import { s3 } from '@/lib/S3/S3'; import { s3 } from '@/lib/S3/S3';
import { Service } from 'typedi'; import { Service } from 'typedi';
import config from '@/config'; import config from '@/config';
import { NextFunction, Request, Response } from 'express';
@Service() @Service()
export class AttachmentUploadPipeline { export class AttachmentUploadPipeline {
/**
* Middleware to ensure that S3 configuration is properly set before proceeding.
* This function checks if the necessary S3 configuration keys are present and throws an error if any are missing.
*
* @param req The HTTP request object.
* @param res The HTTP response object.
* @param next The callback to pass control to the next middleware function.
*/
public validateS3Configured(req: Request, res: Response, next: NextFunction) {
if (
!config.s3.region ||
!config.s3.accessKeyId ||
!config.s3.secretAccessKey
) {
const missingKeys = [];
if (!config.s3.region) missingKeys.push('region');
if (!config.s3.accessKeyId) missingKeys.push('accessKeyId');
if (!config.s3.secretAccessKey) missingKeys.push('secretAccessKey');
const missing = missingKeys.join(', ');
throw new Error(`S3 configuration error: Missing ${missing}`);
}
next();
}
/** /**
* Express middleware for uploading attachments to an S3 bucket. * Express middleware for uploading attachments to an S3 bucket.
* It utilizes the multer middleware for handling multipart/form-data, specifically for file uploads. * It utilizes the multer middleware for handling multipart/form-data, specifically for file uploads.

View File

@@ -42,7 +42,12 @@ export const transformPlaidTrxsToCashflowCreate = R.curry(
): CreateUncategorizedTransactionDTO => { ): CreateUncategorizedTransactionDTO => {
return { return {
date: plaidTranasction.date, date: plaidTranasction.date,
amount: plaidTranasction.amount,
// Plaid: Positive values when money moves out of the account; negative values
// when money moves in. For example, debit card purchases are positive;
// credit card payments, direct deposits, and refunds are negative.
amount: -1 * plaidTranasction.amount,
description: plaidTranasction.name, description: plaidTranasction.name,
payee: plaidTranasction.payment_meta?.payee, payee: plaidTranasction.payment_meta?.payee,
currencyCode: plaidTranasction.iso_currency_code, currencyCode: plaidTranasction.iso_currency_code,

View File

@@ -2,6 +2,7 @@ import { Service } from 'typedi';
import { createCheckout } from '@lemonsqueezy/lemonsqueezy.js'; import { createCheckout } from '@lemonsqueezy/lemonsqueezy.js';
import { SystemUser } from '@/system/models'; import { SystemUser } from '@/system/models';
import { configureLemonSqueezy } from './utils'; import { configureLemonSqueezy } from './utils';
import config from '@/config';
@Service() @Service()
export class LemonSqueezyService { export class LemonSqueezyService {
@@ -28,7 +29,7 @@ export class LemonSqueezyService {
}, },
productOptions: { productOptions: {
enabledVariants: [variantId], enabledVariants: [variantId],
redirectUrl: `http://localhost:4000/dashboard/billing/`, redirectUrl: config.lemonSqueezy.redirectTo,
receiptButtonText: 'Go to Dashboard', receiptButtonText: 'Go to Dashboard',
receiptThankYouNote: 'Thank you for signing up to Lemon Stand!', receiptThankYouNote: 'Thank you for signing up to Lemon Stand!',
}, },

View File

@@ -50,7 +50,6 @@ import InvoiceMailDialog from '@/containers/Sales/Invoices/InvoiceMailDialog/Inv
import EstimateMailDialog from '@/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialog'; import EstimateMailDialog from '@/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialog';
import ReceiptMailDialog from '@/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialog'; import ReceiptMailDialog from '@/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialog';
import PaymentMailDialog from '@/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialog'; import PaymentMailDialog from '@/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialog';
import { ConnectBankDialog } from '@/containers/CashFlow/ConnectBankDialog';
import { ExportDialog } from '@/containers/Dialogs/ExportDialog'; import { ExportDialog } from '@/containers/Dialogs/ExportDialog';
/** /**
@@ -97,7 +96,6 @@ export default function DialogsContainer() {
<NotifyPaymentReceiveViaSMSDialog <NotifyPaymentReceiveViaSMSDialog
dialogName={DialogsName.NotifyPaymentViaForm} dialogName={DialogsName.NotifyPaymentViaForm}
/> />
<BadDebtDialog dialogName={DialogsName.BadDebtForm} /> <BadDebtDialog dialogName={DialogsName.BadDebtForm} />
<SMSMessageDialog dialogName={DialogsName.SMSMessageForm} /> <SMSMessageDialog dialogName={DialogsName.SMSMessageForm} />
<RefundCreditNoteDialog dialogName={DialogsName.RefundCreditNote} /> <RefundCreditNoteDialog dialogName={DialogsName.RefundCreditNote} />
@@ -148,8 +146,6 @@ export default function DialogsContainer() {
<EstimateMailDialog dialogName={DialogsName.EstimateMail} /> <EstimateMailDialog dialogName={DialogsName.EstimateMail} />
<ReceiptMailDialog dialogName={DialogsName.ReceiptMail} /> <ReceiptMailDialog dialogName={DialogsName.ReceiptMail} />
<PaymentMailDialog dialogName={DialogsName.PaymentMail} /> <PaymentMailDialog dialogName={DialogsName.PaymentMail} />
<ConnectBankDialog dialogName={DialogsName.ConnectBankCreditCard} />
<ExportDialog dialogName={DialogsName.Export} /> <ExportDialog dialogName={DialogsName.Export} />
</div> </div>
); );

View File

@@ -15,6 +15,7 @@ import {
FeatureCan, FeatureCan,
} from '@/components'; } from '@/components';
import { useRefreshCashflowAccounts } from '@/hooks/query'; import { useRefreshCashflowAccounts } from '@/hooks/query';
import { useOpenPlaidConnect } from '@/hooks/utils/useOpenPlaidConnect';
import { CashflowAction, AbilitySubject } from '@/constants/abilityOption'; import { CashflowAction, AbilitySubject } from '@/constants/abilityOption';
import withDialogActions from '@/containers/Dialog/withDialogActions'; import withDialogActions from '@/containers/Dialog/withDialogActions';
@@ -39,6 +40,9 @@ function CashFlowAccountsActionsBar({
}) { }) {
const { refresh } = useRefreshCashflowAccounts(); const { refresh } = useRefreshCashflowAccounts();
// Opens the Plaid popup.
const { openPlaidAsync, isPlaidLoading } = useOpenPlaidConnect();
// Handle refresh button click. // Handle refresh button click.
const handleRefreshBtnClick = () => { const handleRefreshBtnClick = () => {
refresh(); refresh();
@@ -64,7 +68,7 @@ function CashFlowAccountsActionsBar({
}; };
// Handle connect button click. // Handle connect button click.
const handleConnectToBank = () => { const handleConnectToBank = () => {
openDialog(DialogsName.ConnectBankCreditCard); openPlaidAsync();
}; };
return ( return (
@@ -116,6 +120,7 @@ function CashFlowAccountsActionsBar({
className={Classes.MINIMAL} className={Classes.MINIMAL}
text={'Connect to Bank / Credit Card'} text={'Connect to Bank / Credit Card'}
onClick={handleConnectToBank} onClick={handleConnectToBank}
disabled={isPlaidLoading}
/> />
<NavbarDivider /> <NavbarDivider />
</FeatureCan> </FeatureCan>

View File

@@ -1,32 +0,0 @@
// @ts-nocheck
import React from 'react';
import { Dialog, DialogSuspense } from '@/components';
import withDialogRedux from '@/components/DialogReduxConnect';
import { compose } from '@/utils';
const ConnectBankDialogBody = React.lazy(
() => import('./ConnectBankDialogBody'),
);
/**
* Connect bank dialog.
*/
function ConnectBankDialogRoot({ dialogName, payload = {}, isOpen }) {
return (
<Dialog
name={dialogName}
title={'Securly connect your bank or credit card.'}
isOpen={isOpen}
canEscapeJeyClose={true}
autoFocus={true}
>
<DialogSuspense>
<ConnectBankDialogBody dialogName={dialogName} />
</DialogSuspense>
</Dialog>
);
}
export const ConnectBankDialog = compose(withDialogRedux())(
ConnectBankDialogRoot,
);

View File

@@ -1,61 +0,0 @@
// @ts-nocheck
import * as R from 'ramda';
import { Form, Formik, FormikHelpers } from 'formik';
import classNames from 'classnames';
import { ConnectBankDialogContent } from './ConnectBankDialogContent';
import { useGetPlaidLinkToken } from '@/hooks/query';
import { useSetBankingPlaidToken } from '@/hooks/state/banking';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import { CLASSES } from '@/constants';
import { AppToaster } from '@/components';
import { Intent } from '@blueprintjs/core';
import { DialogsName } from '@/constants/dialogs';
const initialValues: ConnectBankDialogForm = {
serviceProvider: 'plaid',
};
interface ConnectBankDialogForm {
serviceProvider: 'plaid';
}
function ConnectBankDialogBodyRoot({
// #withDialogActions
closeDialog,
}) {
const { mutateAsync: getPlaidLinkToken } = useGetPlaidLinkToken();
const setPlaidId = useSetBankingPlaidToken();
// Handles the form submitting.
const handleSubmit = (
values: ConnectBankDialogForm,
{ setSubmitting }: FormikHelpers<ConnectBankDialogForm>,
) => {
setSubmitting(true);
getPlaidLinkToken()
.then((res) => {
setSubmitting(false);
closeDialog(DialogsName.ConnectBankCreditCard);
setPlaidId(res.data.link_token);
})
.catch(() => {
setSubmitting(false);
AppToaster.show({
message: 'Something went wrong.',
intent: Intent.DANGER,
});
});
};
return (
<div className={classNames(CLASSES.DIALOG_BODY)}>
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
<Form>
<ConnectBankDialogContent />
</Form>
</Formik>
</div>
);
}
export default R.compose(withDialogActions)(ConnectBankDialogBodyRoot);

View File

@@ -1,48 +0,0 @@
// @ts-nocheck
import styled from 'styled-components';
import { Stack } from '@/components';
import { TellerIcon } from '../Icons/TellerIcon';
import { YodleeIcon } from '../Icons/YodleeIcon';
import { PlaidIcon } from '../Icons/PlaidIcon';
import { BankServiceCard } from './ConnectBankServiceCard';
const TopDesc = styled('p')`
margin-bottom: 20px;
color: #5f6b7c;
`;
export function ConnectBankDialogContent() {
return (
<div>
<TopDesc>
Connect your bank accounts and fetch the bank transactions using
one of our supported third-party service providers.
</TopDesc>
<Stack>
<BankServiceCard
title={'Plaid (US, UK & Canada)'}
icon={<PlaidIcon />}
>
Plaid gives the connection to 12,000 financial institutions across US, UK and Canada.
</BankServiceCard>
<BankServiceCard
title={'Teller (US) — Soon'}
icon={<TellerIcon />}
disabled
>
Connect instantly with more than 5,000 financial institutions across US.
</BankServiceCard>
<BankServiceCard
title={'Yodlee (Global) — Soon'}
icon={<YodleeIcon />}
disabled
>
Connect instantly with a global network of financial institutions.
</BankServiceCard>
</Stack>
</div>
);
}

View File

@@ -1,72 +0,0 @@
import styled from 'styled-components';
import { Group } from '@/components';
const BankServiceIcon = styled('div')`
height: 40px;
width: 40px;
border: 1px solid #c8cad0;
border-radius: 3px;
display: flex;
svg {
margin: auto;
}
`;
const BankServiceContent = styled(`div`)`
flex: 1 0;
`;
const BankServiceCardRoot = styled('button')`
border-radius: 3px;
border: 1px solid #c8cad0;
transition: all 0.1s ease-in-out;
background: transparent;
text-align: inherit;
padding: 14px;
&:not(:disabled) {
cursor: pointer;
}
&:hover:not(:disabled) {
border-color: #0153cc;
}
&:disabled {
background: #f9fdff;
}
`;
const BankServiceTitle = styled(`h3`)`
font-weight: 600;
font-size: 14px;
color: #2d333d;
`;
const BankServiceDesc = styled('p')`
margin-top: 4px;
margin-bottom: 6px;
font-size: 13px;
color: #738091;
`;
interface BankServiceCardProps {
title: string;
children: React.ReactNode;
disabled?: boolean;
icon: React.ReactNode;
}
export function BankServiceCard({
title,
children,
icon,
disabled,
}: BankServiceCardProps) {
return (
<BankServiceCardRoot disabled={disabled}>
<Group>
<BankServiceIcon>{icon}</BankServiceIcon>
<BankServiceContent>
<BankServiceTitle>{title}</BankServiceTitle>
<BankServiceDesc>{children}</BankServiceDesc>
</BankServiceContent>
</Group>
</BankServiceCardRoot>
);
}

View File

@@ -1 +0,0 @@
export * from './ConnectBankDialog';

View File

@@ -26,7 +26,7 @@ function SubscriptionPricing({
useGetLemonSqueezyCheckout(); useGetLemonSqueezyCheckout();
const handleClick = () => { const handleClick = () => {
getLemonCheckout({ variantId: '338516' }) getLemonCheckout({ variantId: '337977' })
.then((res) => { .then((res) => {
const checkoutUrl = res.data.data.attributes.url; const checkoutUrl = res.data.data.attributes.url;
window.LemonSqueezy.Url.Open(checkoutUrl); window.LemonSqueezy.Url.Open(checkoutUrl);

View File

@@ -0,0 +1,25 @@
import { useCallback } from 'react';
import { useSetBankingPlaidToken } from '../state/banking';
import { AppToaster } from '@/components';
import { useGetPlaidLinkToken } from '../query';
import { Intent } from '@blueprintjs/core';
export const useOpenPlaidConnect = () => {
const { mutateAsync: getPlaidLinkToken, isLoading } = useGetPlaidLinkToken();
const setPlaidId = useSetBankingPlaidToken();
const openPlaidAsync = useCallback(() => {
return getPlaidLinkToken()
.then((res) => {
setPlaidId(res.data.link_token);
})
.catch(() => {
AppToaster.show({
message: 'Something went wrong.',
intent: Intent.DANGER,
});
});
}, [getPlaidLinkToken, setPlaidId]);
return { openPlaidAsync, isPlaidLoading: isLoading };
};

55
pnpm-lock.yaml generated
View File

@@ -58,7 +58,7 @@ importers:
version: 0.8.8 version: 0.8.8
'@types/knex': '@types/knex':
specifier: ^0.16.1 specifier: ^0.16.1
version: 0.16.1(mysql2@3.9.8)(mysql@2.18.1) version: 0.16.1(mysql2@1.7.0)(mysql@2.18.1)
'@types/mathjs': '@types/mathjs':
specifier: ^6.0.12 specifier: ^6.0.12
version: 6.0.12 version: 6.0.12
@@ -181,7 +181,7 @@ importers:
version: 8.5.1 version: 8.5.1
knex: knex:
specifier: ^0.95.15 specifier: ^0.95.15
version: 0.95.15(mysql2@3.9.8)(mysql@2.18.1) version: 0.95.15(mysql2@1.7.0)(mysql@2.18.1)
knex-cleaner: knex-cleaner:
specifier: ^1.3.0 specifier: ^1.3.0
version: 1.3.1 version: 1.3.1
@@ -234,8 +234,8 @@ importers:
specifier: ^2.17.1 specifier: ^2.17.1
version: 2.18.1 version: 2.18.1
mysql2: mysql2:
specifier: ^3.9.8 specifier: ^1.6.5
version: 3.9.8 version: 1.7.0
newrelic: newrelic:
specifier: ^11.15.0 specifier: ^11.15.0
version: 11.17.0 version: 11.17.0
@@ -6480,11 +6480,11 @@ packages:
'@types/node': 14.18.63 '@types/node': 14.18.63
dev: false dev: false
/@types/knex@0.16.1(mysql2@3.9.8)(mysql@2.18.1): /@types/knex@0.16.1(mysql2@1.7.0)(mysql@2.18.1):
resolution: {integrity: sha512-54gWD1HWwdVx5iLHaJ1qxH3I6KyBsj5fFqzRpXFn7REWiEB2jwspeVCombNsocSrqPd7IRPqKrsIME7/cD+TFQ==} resolution: {integrity: sha512-54gWD1HWwdVx5iLHaJ1qxH3I6KyBsj5fFqzRpXFn7REWiEB2jwspeVCombNsocSrqPd7IRPqKrsIME7/cD+TFQ==}
deprecated: This is a stub types definition. knex provides its own type definitions, so you do not need this installed. deprecated: This is a stub types definition. knex provides its own type definitions, so you do not need this installed.
dependencies: dependencies:
knex: 0.95.15(mysql2@3.9.8)(mysql@2.18.1) knex: 0.95.15(mysql2@1.7.0)(mysql@2.18.1)
transitivePeerDependencies: transitivePeerDependencies:
- mysql - mysql
- mysql2 - mysql2
@@ -10788,11 +10788,6 @@ packages:
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
dev: false dev: false
/denque@2.1.0:
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
engines: {node: '>=0.10'}
dev: false
/depd@1.1.2: /depd@1.1.2:
resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@@ -14219,6 +14214,13 @@ packages:
dependencies: dependencies:
safer-buffer: 2.1.2 safer-buffer: 2.1.2
/iconv-lite@0.5.2:
resolution: {integrity: sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==}
engines: {node: '>=0.10.0'}
dependencies:
safer-buffer: 2.1.2
dev: false
/iconv-lite@0.6.3: /iconv-lite@0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -16529,7 +16531,7 @@ packages:
dependencies: dependencies:
bluebird: 3.7.2 bluebird: 3.7.2
glob: 7.2.3 glob: 7.2.3
knex: 0.95.15(mysql2@3.9.8)(mysql@2.18.1) knex: 0.95.15(mysql2@1.7.0)(mysql@2.18.1)
lodash: 4.17.21 lodash: 4.17.21
dev: false dev: false
@@ -16539,7 +16541,7 @@ packages:
lodash: 4.17.21 lodash: 4.17.21
dev: true dev: true
/knex@0.95.15(mysql2@3.9.8)(mysql@2.18.1): /knex@0.95.15(mysql2@1.7.0)(mysql@2.18.1):
resolution: {integrity: sha512-Loq6WgHaWlmL2bfZGWPsy4l8xw4pOE+tmLGkPG0auBppxpI0UcK+GYCycJcqz9W54f2LiGewkCVLBm3Wq4ur/w==} resolution: {integrity: sha512-Loq6WgHaWlmL2bfZGWPsy4l8xw4pOE+tmLGkPG0auBppxpI0UcK+GYCycJcqz9W54f2LiGewkCVLBm3Wq4ur/w==}
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
@@ -16573,7 +16575,7 @@ packages:
interpret: 2.2.0 interpret: 2.2.0
lodash: 4.17.21 lodash: 4.17.21
mysql: 2.18.1 mysql: 2.18.1
mysql2: 3.9.8 mysql2: 1.7.0
pg-connection-string: 2.5.0 pg-connection-string: 2.5.0
rechoir: 0.7.0 rechoir: 0.7.0
resolve-from: 5.0.0 resolve-from: 5.0.0
@@ -17087,6 +17089,10 @@ packages:
'@sinonjs/commons': 1.8.6 '@sinonjs/commons': 1.8.6
dev: true dev: true
/long@4.0.0:
resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==}
dev: false
/long@5.2.3: /long@5.2.3:
resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==}
dev: false dev: false
@@ -17141,11 +17147,6 @@ packages:
resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
engines: {node: '>=12'} engines: {node: '>=12'}
/lru-cache@8.0.5:
resolution: {integrity: sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==}
engines: {node: '>=16.14'}
dev: false
/luxon@1.28.1: /luxon@1.28.1:
resolution: {integrity: sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==} resolution: {integrity: sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==}
dev: false dev: false
@@ -17381,7 +17382,6 @@ packages:
/memory-pager@1.5.0: /memory-pager@1.5.0:
resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==}
requiresBuild: true
dev: false dev: false
/memorystream@0.3.1: /memorystream@0.3.1:
@@ -17990,15 +17990,15 @@ packages:
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
dev: true dev: true
/mysql2@3.9.8: /mysql2@1.7.0:
resolution: {integrity: sha512-+5JKNjPuks1FNMoy9TYpl77f+5frbTklz7eb3XDwbpsERRLEeXiW2PDEkakYF50UuKU2qwfGnyXpKYvukv8mGA==} resolution: {integrity: sha512-xTWWQPjP5rcrceZQ7CSTKR/4XIDeH/cRkNH/uzvVGQ7W5c7EJ0dXeJUusk7OKhIoHj7uFKUxDVSCfLIl+jluog==}
engines: {node: '>= 8.0'} engines: {node: '>= 8.0'}
dependencies: dependencies:
denque: 2.1.0 denque: 1.5.1
generate-function: 2.3.1 generate-function: 2.3.1
iconv-lite: 0.6.3 iconv-lite: 0.5.2
long: 5.2.3 long: 4.0.0
lru-cache: 8.0.5 lru-cache: 5.1.1
named-placeholders: 1.1.3 named-placeholders: 1.1.3
seq-queue: 0.0.5 seq-queue: 0.0.5
sqlstring: 2.3.3 sqlstring: 2.3.3
@@ -18866,7 +18866,7 @@ packages:
ajv: 8.13.0 ajv: 8.13.0
ajv-formats: 2.1.1(ajv@8.13.0) ajv-formats: 2.1.1(ajv@8.13.0)
db-errors: 0.2.3 db-errors: 0.2.3
knex: 0.95.15(mysql2@3.9.8)(mysql@2.18.1) knex: 0.95.15(mysql2@1.7.0)(mysql@2.18.1)
dev: false dev: false
/oblivious-set@1.0.0: /oblivious-set@1.0.0:
@@ -23472,7 +23472,6 @@ packages:
/sparse-bitfield@3.0.3: /sparse-bitfield@3.0.3:
resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==}
requiresBuild: true
dependencies: dependencies:
memory-pager: 1.5.0 memory-pager: 1.5.0
dev: false dev: false