Compare commits

...

18 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
3cd54653a8 Merge pull request #694 from bigcapitalhq/lerna-shared
feat: Add shared packages to Docker container
2024-10-06 17:26:39 +02:00
Ahmed Bouhuolia
6cad929738 feat: Lerna shared 2024-10-06 17:20:28 +02:00
Ahmed Bouhuolia
184648040c Merge pull request #693 from bigcapitalhq/fix-display-country-name
fix: Display country name
2024-10-06 13:19:17 +02:00
Ahmed Bouhuolia
df9d277e66 fix: Display country name 2024-10-06 13:18:31 +02:00
Ahmed Bouhuolia
75ec315de2 Merge pull request #689 from bigcapitalhq/download-payment-link-invoice-pdf
feat: Download invoice pdf of the payment link
2024-10-05 21:48:07 +02:00
Ahmed Bouhuolia
c89b2367e6 fix: Download invoice pdf of the payment link page 2024-10-05 21:46:48 +02:00
Ahmed Bouhuolia
bca5b3481c Merge pull request #691 from bigcapitalhq/pdf-templates-layout
fix: Pdf templates layout
2024-10-05 21:26:37 +02:00
Ahmed Bouhuolia
59996e7a40 feat: re-layout server-side pdf template 2024-10-05 21:24:07 +02:00
Ahmed Bouhuolia
af5726c48c fix: Pdf templates layout 2024-10-05 19:01:34 +02:00
Ahmed Bouhuolia
90f08c5d51 Merge pull request #690 from bigcapitalhq/fix-remove-empty-lines-from-address
fix: Remove empty lines from address formats
2024-10-05 16:09:03 +02:00
Ahmed Bouhuolia
a0a9f4a768 fix: Remove empty lines from address formats 2024-10-05 16:08:09 +02:00
Ahmed Bouhuolia
2649f1c326 feat: Download invoice pdf of the payment link 2024-10-05 13:56:25 +02:00
Ahmed Bouhuolia
c5ff1e4d4a Merge pull request #688 from bigcapitalhq/fix-pdf-template-addresses-controlling
fix: pdf template addresses controlling
2024-10-03 17:13:07 +02:00
Ahmed Bouhuolia
c74c8e896a fix: pdf template addresses controlling 2024-10-03 17:12:12 +02:00
Ahmed Bouhuolia
55fdc47ff0 Merge pull request #687 from bigcapitalhq/assign-default-pdf-template
feat: Assign default PDF template automatically
2024-10-03 17:02:17 +02:00
Ahmed Bouhuolia
126eb221d0 feat: invalidate invoice state once change default template 2024-10-03 17:01:35 +02:00
Ahmed Bouhuolia
3c7e22be43 feat: Assign default pdf template automatically 2024-10-03 16:36:44 +02:00
Ahmed Bouhuolia
b23112bc92 feat: Assign default PDF template automatically 2024-10-02 18:18:57 +02:00
85 changed files with 1951 additions and 457 deletions

View File

@@ -3,6 +3,7 @@
"version": "independent",
"npmClient": "pnpm",
"packages": [
"packages/*"
"packages/*",
"shared/*"
]
}

View File

@@ -4,11 +4,11 @@
"scripts": {
"dev": "lerna run dev",
"build": "lerna run build",
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\"",
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\"",
"dev:server": "lerna run dev --scope \"@bigcapital/server\"",
"build:server": "lerna run build --scope \"@bigcapital/server\"",
"serve:server": "lerna run serve --scope \"@bigcapital/server\"",
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\"",
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\"",
"dev:server": "lerna run dev --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
"build:server": "lerna run build --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
"serve:server": "lerna run serve --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
"test:e2e": "playwright test",
"prepare": "husky install"
},
@@ -29,5 +29,8 @@
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"dependencies": {
"tsup": "^8.3.0"
}
}

View File

@@ -90,11 +90,7 @@ RUN chown node:node /
RUN npm install -g pnpm
# Copy application dependency manifests to the container image.
COPY ./package*.json ./
COPY ./pnpm-lock.yaml ./pnpm-lock.yaml
COPY ./lerna.json ./lerna.json
COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml
COPY ./packages/server/package*.json ./packages/server/
COPY --chown=node:node ./ ./
# Install application dependencies
RUN apk update
@@ -109,6 +105,6 @@ RUN pnpm install
COPY --chown=node:node ./packages/server ./packages/server
# # Creates a "dist" folder with the production build
RUN npm run build:server --skip-nx-cache
RUN pnpm run build:server --skip-nx-cache
CMD [ "node", "./packages/server/build/index.js" ]

View File

@@ -22,6 +22,7 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.576.0",
"@aws-sdk/s3-request-presigner": "^3.583.0",
"@bigcapital/utils": "*",
"@casl/ability": "^5.4.3",
"@hapi/boom": "^7.4.3",
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",

View File

@@ -10,21 +10,37 @@ block head
position: relative;
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
}
.#{prefix}-header{
box-sizing: border-box;
display: flex;
flex-flow: wrap;
flex: 0 0 auto;
-webkit-box-align: start;
align-items: start;
-webkit-box-pack: start;
justify-content: flex-start;
gap: 10px;
}
.#{prefix}-header-details{
flex: 1;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 20px;
flex: 1 1 0%;
}
.#{prefix}-big-title {
font-size: 60px;
margin: 0;
line-height: 1;
margin-bottom: 25px;
font-weight: 500;
color: #333;
}
.#{prefix}-logo-wrap {
height: 120px;
width: 120px;
position: absolute;
right: 26px;
top: 26px;
overflow: hidden;
.#{prefix}-logo-wrap img {
height: auto;
width: auto;
max-width: 400px;
max-height: 160px;
}
.#{prefix}-terms-list {
display: flex;
@@ -132,22 +148,27 @@ block head
block content
div(class=`${prefix}-root`)
div(class=`${prefix}-big-title`) Credit Note
if showCompanyLogo && companyLogoUri
div(class=`${prefix}-logo-wrap`)
img(src=companyLogoUri alt=`Company Logo`)
div(class=`${prefix}-terms-list`)
if showCreditNoteNumber
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{creditNoteNumberLabel}:
div(class=`${prefix}-terms-item__value`) #{creditNoteNumebr}
//- Header (includes big title, details and logo)
div(class=`${prefix}-header`)
//- Header details (includes big title and details)
div(class=`${prefix}-header-details`)
div(class=`${prefix}-big-title`) Credit Note
if showCreditNoteDate
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{creditNoteDateLabel}:
div(class=`${prefix}-terms-item__value`) #{creditNoteDate}
div(class=`${prefix}-terms-list`)
if showCreditNoteNumber
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{creditNoteNumberLabel}:
div(class=`${prefix}-terms-item__value`) #{creditNoteNumebr}
if showCreditNoteDate
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{creditNoteDateLabel}:
div(class=`${prefix}-terms-item__value`) #{creditNoteDate}
if showCompanyLogo && companyLogoUri
div(class=`${prefix}-logo-wrap`)
img(src=companyLogoUri alt=`Company Logo`)
div(class=`${prefix}-address-section`)
if showCompanyAddress

View File

@@ -10,21 +10,37 @@ block head
position: relative;
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
}
.#{prefix}-header {
box-sizing: border-box;
display: flex;
flex-flow: wrap;
flex: 0 0 auto;
-webkit-box-align: start;
align-items: start;
-webkit-box-pack: start;
justify-content: flex-start;
gap: 10px;
}
.#{prefix}-header-details {
flex: 1;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 20px;
flex: 1 1 0%;
}
.#{prefix}-big-title {
font-size: 60px;
margin: 0;
line-height: 1;
margin-bottom: 25px;
font-weight: 500;
color: #333;
}
.#{prefix}-logo-wrap {
height: 120px;
width: 120px;
position: absolute;
right: 26px;
top: 26px;
overflow: hidden;
.#{prefix}-logo-wrap img {
height: auto;
width: auto;
max-width: 400px;
max-height: 160px;
}
.#{prefix}-terms {
display: flex;
@@ -131,26 +147,35 @@ block head
block content
div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
h1(class=`${prefix}-big-title`) Estimate
if showCompanyLogo && companyLogoUri
div(class=`${prefix}-logo-wrap`)
img(alt="Company logo", src=companyLogoUri)
//- Header (invluces big title, details and logo)
div(class=`${prefix}-header`)
//- Terms List
div(class=`${prefix}-terms`)
if showEstimateNumber
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{estimateNumberLabel}
div(class=`${prefix}-terms-item__value`) #{estimateNumebr}
if showEstimateDate
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{estimateDateLabel}
div(class=`${prefix}-terms-item__value`) #{estimateDate}
if showExpirationDate
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{expirationDateLabel}
div(class=`${prefix}-terms-item__value`) #{expirationDate}
//- Header details (includes big title and details )
div(class=`${prefix}-header-details`)
h1(class=`${prefix}-big-title`) Estimate
//- Terms List
div(class=`${prefix}-terms`)
if showEstimateNumber
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{estimateNumberLabel}
div(class=`${prefix}-terms-item__value`) #{estimateNumebr}
if showEstimateDate
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{estimateDateLabel}
div(class=`${prefix}-terms-item__value`) #{estimateDate}
if showExpirationDate
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{expirationDateLabel}
div(class=`${prefix}-terms-item__value`) #{expirationDate}
//- Company logo
if showCompanyLogo && companyLogoUri
div(class=`${prefix}-logo-wrap`)
img(alt="Company logo", src=companyLogoUri)
//- Addresses (Group section)
div(class=`${prefix}-addresses`)

View File

@@ -10,21 +10,37 @@ block head
position: relative;
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
}
.#{prefix}-header{
box-sizing: border-box;
display: flex;
flex-flow: wrap;
flex: 0 0 auto;
-webkit-box-align: start;
align-items: start;
-webkit-box-pack: start;
justify-content: flex-start;
gap: 10px;
}
.#{prefix}-header-details{
flex: 1;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 20px;
flex: 1 1 0%;
}
.#{prefix}-big-title {
font-size: 60px;
margin: 0;
line-height: 1;
margin-bottom: 25px;
font-weight: 500;
color: #333;
}
.#{prefix}-logo-wrap {
height: 120px;
width: 120px;
position: absolute;
right: 26px;
top: 26px;
overflow: hidden;
.#{prefix}-logo-wrap img {
height: auto;
width: auto;
max-width: 400px;
max-height: 160px;
}
.#{prefix}-details {
display: flex;
@@ -138,30 +154,35 @@ block head
block content
//- block head
div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
//- Title and company logo
h1(class=`${prefix}-big-title`) Invoice
if showCompanyLogo && companyLogoUri
div(class=`${prefix}-logo-wrap`)
img(alt="Company logo", src=companyLogoUri)
//- Header (includes big title, details and logo )
div(class=`${prefix}-header`)
//- Header details (includes big title and details )
div(class=`${prefix}-header-details`)
//- Title and company logo
h1(class=`${prefix}-big-title`) Invoice
//- Invoice details
div(class=`${prefix}-details`)
if showInvoiceNumber
div(class=`${prefix}-detail`)
div(class=`${prefix}-detail__label`) #{invoiceNumberLabel}
div(class=`${prefix}-detail__value`) #{invoiceNumber}
//- Invoice details
div(class=`${prefix}-details`)
if showInvoiceNumber
div(class=`${prefix}-detail`)
div(class=`${prefix}-detail__label`) #{invoiceNumberLabel}
div(class=`${prefix}-detail__value`) #{invoiceNumber}
if showDateIssue
div(class=`${prefix}-detail`)
div(class=`${prefix}-detail__label`) #{dateIssueLabel}
div(class=`${prefix}-detail__value`) #{dateIssue}
if showDateIssue
div(class=`${prefix}-detail`)
div(class=`${prefix}-detail__label`) #{dateIssueLabel}
div(class=`${prefix}-detail__value`) #{dateIssue}
if showDueDate
div(class=`${prefix}-detail`)
div(class=`${prefix}-detail__label`) #{dueDateLabel}
div(class=`${prefix}-detail__value`) #{dueDate}
if showDueDate
div(class=`${prefix}-detail`)
div(class=`${prefix}-detail__label`) #{dueDateLabel}
div(class=`${prefix}-detail__value`) #{dueDate}
//- Company logo
if showCompanyLogo && companyLogoUri
div(class=`${prefix}-logo-wrap`)
img(alt="Company logo", src=companyLogoUri)
//- Address section
div(class=`${prefix}-address-root`)

View File

@@ -10,22 +10,38 @@ block head
font-size: 12px;
position: relative;
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
}
.#{prefix}-header{
box-sizing: border-box;
display: flex;
flex-flow: wrap;
flex: 0 0 auto;
-webkit-box-align: start;
align-items: start;
-webkit-box-pack: start;
justify-content: flex-start;
gap: 10px;
}
.#{prefix}-header-details{
flex: 1;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 20px;
flex: 1 1 0%;
}
.#{prefix}-big-title{
font-size: 60px;
margin: 0;
line-height: 1;
margin-bottom: 25px;
font-weight: 500;
color: #333;
}
.#{prefix}-logo-wrap{
height: 120px;
width: 120px;
position: absolute;
right: 26px;
top: 26px;
overflow: hidden;
.#{prefix}-logo-wrap img {
height: auto;
width: auto;
max-width: 400px;
max-height: 160px;
}
.#{prefix}-terms-list{
display: flex;
@@ -120,23 +136,26 @@ block head
}
block content
div(class=`${prefix}-root`)
div(class=`${prefix}-big-title`) Payment
//- Header (includes big title, details and logo )
div(class=`${prefix}-header`)
//- Header details (includes big title and details )
div(class=`${prefix}-header-details`)
div(class=`${prefix}-big-title`) Payment
div(class=`${prefix}-terms-list`)
if showPaymentReceivedNumber
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{paymentReceivedNumberLabel}
div(class=`${prefix}-terms-item__value`) #{paymentReceivedNumebr}
if showCompanyLogo && companyLogoUri
div(class=`${prefix}-logo-wrap`)
img(src=companyLogoUri alt="Company Logo")
div(class=`${prefix}-terms-list`)
if showPaymentReceivedNumber
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{paymentReceivedNumberLabel}
div(class=`${prefix}-terms-item__value`) #{paymentReceivedNumebr}
if showPaymentReceivedDate
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{paymentReceivedDateLabel}
div(class=`${prefix}-terms-item__value`) #{paymentReceivedDate}
if showPaymentReceivedDate
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{paymentReceivedDateLabel}
div(class=`${prefix}-terms-item__value`) #{paymentReceivedDate}
if showCompanyLogo && companyLogoUri
div(class=`${prefix}-logo-wrap`)
img(src=companyLogoUri alt="Company Logo")
div(class=`${prefix}-addresses`)
if showCompanyAddress
div(class=`${prefix}-address-from`)

View File

@@ -10,19 +10,33 @@ block head
position: relative;
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
}
.#{prefix}-logo-wrap {
height: 120px;
width: 120px;
position: absolute;
right: 26px;
top: 26px;
overflow: hidden;
.#{prefix}-header{
box-sizing: border-box;
display: flex;
flex-flow: wrap;
flex: 0 0 auto;
align-items: start;
justify-content: flex-start;
gap: 10px;
}
.#{prefix}-header-details{
flex: 1;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 20px;
flex: 1 1 0%;
}
.#{prefix}-logo-wrap img {
height: auto;
width: auto;
max-width: 400px;
max-height: 160px;
}
.#{prefix}-big-title {
font-size: 60px;
margin: 0;
line-height: 1;
margin-bottom: 25px;
font-weight: 500;
color: #333;
}
@@ -124,25 +138,30 @@ block head
block content
//- block head
div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
//- Title and company logo
h1(class=`${prefix}-big-title`) Receipt
//- Company Logo
if showCompanyLogo && companyLogoUri
div(class=`${prefix}-logo-wrap`)
img(src=companyLogoUri alt=`Company Logo`)
//- Header (includes big title, details and logo )
div(class=`${prefix}-header`)
//- Header details (includes big title and details )
div(class=`${prefix}-header-details`)
//- Title and company logo
h1(class=`${prefix}-big-title`) Receipt
//- Terms List
div(class=`${prefix}-terms-list`)
if showReceiptNumber
div(class=`${prefix}-terms-item`)
span(class=`${prefix}-terms-item__label`)= receiptNumberLabel
span(class=`${prefix}-terms-item__value`)= receiptNumber
if showReceiptDate
div(class=`${prefix}-terms-item`)
span(class=`${prefix}-terms-item__label`)= receiptDateLabel
span(class=`${prefix}-terms-item__value`)= receiptDate
//- Terms List
div(class=`${prefix}-terms-list`)
if showReceiptNumber
div(class=`${prefix}-terms-item`)
span(class=`${prefix}-terms-item__label`)= receiptNumberLabel
span(class=`${prefix}-terms-item__value`)= receiptNumber
if showReceiptDate
div(class=`${prefix}-terms-item`)
span(class=`${prefix}-terms-item__label`)= receiptDateLabel
span(class=`${prefix}-terms-item__value`)= receiptDate
//- Company logo
if showCompanyLogo && companyLogoUri
div(class=`${prefix}-logo-wrap`)
img(src=companyLogoUri alt=`Company Logo`)
//- Address Section
div(class=`${prefix}-address-section`)

View File

@@ -27,6 +27,7 @@ import GetCreditNoteAssociatedAppliedInvoices from '@/services/CreditNotes/GetCr
import GetRefundCreditTransaction from '@/services/CreditNotes/GetRefundCreditNoteTransaction';
import GetCreditNotePdf from '../../../services/CreditNotes/GetCreditNotePdf';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { GetCreditNoteState } from '@/services/CreditNotes/GetCreditNoteState';
/**
* Credit notes controller.
* @service
@@ -81,6 +82,9 @@ export default class PaymentReceivesController extends BaseController {
@Inject()
creditNotePdf: GetCreditNotePdf;
@Inject()
getCreditNoteStateService: GetCreditNoteState;
/**
* Router constructor.
*/
@@ -105,6 +109,12 @@ export default class PaymentReceivesController extends BaseController {
this.asyncMiddleware(this.newCreditNote),
this.handleServiceErrors
);
router.get(
'/state',
CheckPolicies(CreditNoteAction.View, AbilitySubject.CreditNote),
this.asyncMiddleware(this.getCreditNoteState.bind(this)),
this.handleServiceErrors
);
// Get specific credit note.
router.get(
'/:id',
@@ -736,6 +746,23 @@ export default class PaymentReceivesController extends BaseController {
}
};
private getCreditNoteState = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
try {
const data = await this.getCreditNoteStateService.getCreditNoteState(
tenantId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
};
/**
* Handles service errors.
* @param {Error} error

View File

@@ -95,6 +95,12 @@ export default class PaymentReceivesController extends BaseController {
asyncMiddleware(this.getPaymentReceiveInvoices.bind(this)),
this.handleServiceErrors
);
router.get(
'/state',
CheckPolicies(PaymentReceiveAction.View, AbilitySubject.PaymentReceive),
this.getPaymentReceivedState.bind(this),
this.handleServiceErrors
);
router.get(
'/:id',
CheckPolicies(PaymentReceiveAction.View, AbilitySubject.PaymentReceive),
@@ -391,6 +397,29 @@ export default class PaymentReceivesController extends BaseController {
}
}
/**
*
* @async
* @param {Request} req -
* @param {Response} res -
*/
private async getPaymentReceivedState(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
try {
const data = await this.paymentReceiveApplication.getPaymentReceivedState(
tenantId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
}
/**
* Retrieve the given payment receive details.
* @async

View File

@@ -51,7 +51,7 @@ export default class SalesEstimatesController extends BaseController {
router.post(
'/:id/approve',
CheckPolicies(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate),
[this.validateSpecificEstimateSchema],
[...this.validateSpecificEstimateSchema],
this.validationResult,
asyncMiddleware(this.approveSaleEstimate.bind(this)),
this.handleServiceErrors
@@ -59,7 +59,7 @@ export default class SalesEstimatesController extends BaseController {
router.post(
'/:id/reject',
CheckPolicies(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate),
[this.validateSpecificEstimateSchema],
[...this.validateSpecificEstimateSchema],
this.validationResult,
asyncMiddleware(this.rejectSaleEstimate.bind(this)),
this.handleServiceErrors
@@ -105,6 +105,12 @@ export default class SalesEstimatesController extends BaseController {
asyncMiddleware(this.deleteEstimate.bind(this)),
this.handleServiceErrors
);
router.get(
'/state',
CheckPolicies(SaleEstimateAction.View, AbilitySubject.SaleEstimate),
this.getSaleEstimateState.bind(this),
this.handleServiceErrors
);
router.get(
'/:id',
CheckPolicies(SaleEstimateAction.View, AbilitySubject.SaleEstimate),
@@ -546,6 +552,23 @@ export default class SalesEstimatesController extends BaseController {
}
};
private getSaleEstimateState = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
try {
const data = await this.saleEstimatesApplication.getSaleEstimateState(
tenantId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
};
/**
* Handles service errors.
* @param {Error} error

View File

@@ -130,6 +130,12 @@ export default class SaleInvoicesController extends BaseController {
this.asyncMiddleware(this.getInvoicePaymentTransactions),
this.handleServiceErrors
);
router.get(
'/state',
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
asyncMiddleware(this.getSaleInvoiceState.bind(this)),
this.handleServiceErrors
);
router.get(
'/:id',
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
@@ -138,6 +144,7 @@ export default class SaleInvoicesController extends BaseController {
asyncMiddleware(this.getSaleInvoice.bind(this)),
this.handleServiceErrors
);
router.get(
'/',
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
@@ -453,6 +460,24 @@ export default class SaleInvoicesController extends BaseController {
return res.status(200).send({ saleInvoice });
}
}
private async getSaleInvoiceState(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
try {
const data = await this.saleInvoiceApplication.getSaleInvoiceState(
tenantId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
}
/**
* Retrieve paginated sales invoices with custom view metadata.
* @param {Request} req

View File

@@ -108,6 +108,12 @@ export default class SalesReceiptsController extends BaseController {
this.handleServiceErrors,
this.dynamicListService.handlerErrorsToResponse
);
router.get(
'/state',
CheckPolicies(SaleReceiptAction.View, AbilitySubject.SaleReceipt),
asyncMiddleware(this.getSaleReceiptState.bind(this)),
this.handleServiceErrors
);
router.get(
'/:id',
CheckPolicies(SaleReceiptAction.View, AbilitySubject.SaleReceipt),
@@ -369,6 +375,30 @@ export default class SalesReceiptsController extends BaseController {
}
}
/**
*
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public async getSaleReceiptState(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
// Retrieves receipt in pdf format.
try {
const data = await this.saleReceiptsApplication.getSaleReceiptState(
tenantId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
}
/**
* Sale receipt notification via SMS.
* @param {Request} req

View File

@@ -22,6 +22,13 @@ export class PublicSharableLinkController extends BaseController {
this.getPaymentLinkPublicMeta.bind(this),
this.validationResult
);
router.get(
'/:paymentLinkId/invoice/pdf',
[param('paymentLinkId').exists()],
this.validationResult,
this.getPaymentLinkInvoicePdf.bind(this),
this.validationResult
);
router.post(
'/:paymentLinkId/stripe_checkout_session',
[param('paymentLinkId').exists()],
@@ -80,4 +87,31 @@ export class PublicSharableLinkController extends BaseController {
next(error);
}
}
/**
* Retrieves the sale invoice pdf of the given payment link.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public async getPaymentLinkInvoicePdf(
req: Request<{ paymentLinkId: string }>,
res: Response,
next: NextFunction
) {
const { paymentLinkId } = req.params;
try {
const pdfContent = await this.paymentLinkApp.getPaymentLinkInvoicePdf(
paymentLinkId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
} catch (error) {
next(error);
}
}
}

View File

@@ -314,3 +314,7 @@ export interface CreditNotePdfTemplateAttributes {
showCreditNoteDate: boolean;
creditNoteDateLabel: string;
}
export interface ICreditNoteState {
defaultTemplateId: number;
}

View File

@@ -238,3 +238,8 @@ export interface PaymentReceivedPdfTemplateAttributes {
showPaymentReceivedDate: boolean;
paymentReceivedDateLabel: string;
}
export interface IPaymentReceivedState {
defaultTemplateId: number;
}

View File

@@ -144,3 +144,6 @@ export interface ISaleEstimateMailPresendEvent {
messageOptions: SaleEstimateMailOptionsDTO;
}
export interface ISaleEstimateState {
defaultTemplateId: number;
}

View File

@@ -20,7 +20,7 @@ export interface PaymentIntegrationTransactionLinkEventPayload {
referenceType: string;
referenceId: number;
saleInvoiceId: number;
trx?: Knex.Transaction
trx?: Knex.Transaction;
}
export interface PaymentIntegrationTransactionLinkDeleteEventPayload {
@@ -30,7 +30,7 @@ export interface PaymentIntegrationTransactionLinkDeleteEventPayload {
referenceType: string;
referenceId: number;
oldSaleInvoiceId: number;
trx?: Knex.Transaction
trx?: Knex.Transaction;
}
export interface ISaleInvoice {
@@ -174,7 +174,7 @@ export interface ISaleInvoiceDeletingPayload {
tenantId: number;
oldSaleInvoice: ISaleInvoice;
saleInvoiceId: number;
trx: Knex.Transaction;
trx: Knex.Transaction;
}
export interface ISaleInvoiceDeletedPayload {
@@ -339,3 +339,7 @@ export interface InvoicePdfTemplateAttributes {
showStatement: boolean;
statement: string;
}
export interface ISaleInvocieState {
defaultTemplateId: number;
}

View File

@@ -211,3 +211,8 @@ export interface ISaleReceiptBrandingTemplateAttributes {
showReceiptDate: boolean;
receiptDateLabel: string;
}
export interface ISaleReceiptState {
defaultTemplateId: number;
}

View File

@@ -29,6 +29,20 @@ export class PdfTemplate extends TenantModel {
};
}
/**
* Model modifiers.
*/
static get modifiers() {
return {
/**
* Filters the due invoices.
*/
default(query) {
query.where('default', true);
},
};
}
/**
* Virtual attributes.
*/

View File

@@ -0,0 +1,26 @@
import { Inject, Service } from 'typedi';
import { ICreditNoteState } from '@/interfaces';
import HasTenancyService from '../Tenancy/TenancyService';
@Service()
export class GetCreditNoteState {
@Inject()
private tenancy: HasTenancyService;
/**
* Retrieves the create/edit initial state of the payment received.
* @param {Number} saleInvoiceId -
* @return {Promise<ISaleInvoice>}
*/
public async getCreditNoteState(tenantId: number): Promise<ICreditNoteState> {
const { PdfTemplate } = this.tenancy.models(tenantId);
const defaultPdfTemplate = await PdfTemplate.query()
.findOne({ resource: 'CreditNote' })
.modify('default');
return {
defaultTemplateId: defaultPdfTemplate?.id,
};
}
}

View File

@@ -0,0 +1,33 @@
import { Inject, Service } from 'typedi';
import { initalizeTenantServices } from '@/api/middleware/TenantDependencyInjection';
import { SaleInvoicePdf } from '../Sales/Invoices/SaleInvoicePdf';
import { PaymentLink } from '@/system/models';
@Service()
export class GetPaymentLinkInvoicePdf {
@Inject()
private getSaleInvoicePdfService: SaleInvoicePdf;
/**
* Retrieves the sale invoice PDF of the given payment link id.
* @param {number} tenantId
* @param {number} paymentLinkId
* @returns {Promise<Buffer>}
*/
async getPaymentLinkInvoicePdf(paymentLinkId: string): Promise<Buffer> {
const paymentLink = await PaymentLink.query()
.findOne('linkId', paymentLinkId)
.where('resourceType', 'SaleInvoice')
.throwIfNotFound();
const tenantId = paymentLink.tenantId;
await initalizeTenantServices(tenantId);
const saleInvoiceId = paymentLink.resourceId;
return this.getSaleInvoicePdfService.saleInvoicePdf(
tenantId,
saleInvoiceId
);
}
}

View File

@@ -2,6 +2,7 @@ import { Inject, Service } from 'typedi';
import { GetInvoicePaymentLinkMetadata } from './GetInvoicePaymentLinkMetadata';
import { CreateInvoiceCheckoutSession } from './CreateInvoiceCheckoutSession';
import { StripeInvoiceCheckoutSessionPOJO } from '@/interfaces/StripePayment';
import { GetPaymentLinkInvoicePdf } from './GetPaymentLinkInvoicePdf';
@Service()
export class PaymentLinksApplication {
@@ -10,6 +11,9 @@ export class PaymentLinksApplication {
@Inject()
private createInvoiceCheckoutSessionService: CreateInvoiceCheckoutSession;
@Inject()
private getPaymentLinkInvoicePdfService: GetPaymentLinkInvoicePdf;
/**
* Retrieves the invoice payment link.
@@ -34,4 +38,16 @@ export class PaymentLinksApplication {
paymentLinkId
);
}
/**
* Retrieves the sale invoice pdf of the given payment link id.
* @param {number} tenantId
* @param {number} paymentLinkId
* @returns {Promise<Buffer> }
*/
public getPaymentLinkInvoicePdf(paymentLinkId: string): Promise<Buffer> {
return this.getPaymentLinkInvoicePdfService.getPaymentLinkInvoicePdf(
paymentLinkId
);
}
}

View File

@@ -0,0 +1,28 @@
import { Inject, Service } from 'typedi';
import { ISaleEstimateState } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class GetSaleEstimateState {
@Inject()
private tenancy: HasTenancyService;
/**
* Retrieves the create/edit sale estimate state.
* @param {Number} saleEstimateId -
* @return {Promise<ISaleEstimateState>}
*/
public async getSaleEstimateState(
tenantId: number
): Promise<ISaleEstimateState> {
const { PdfTemplate } = this.tenancy.models(tenantId);
const defaultPdfTemplate = await PdfTemplate.query()
.findOne({ resource: 'SaleEstimate' })
.modify('default');
return {
defaultTemplateId: defaultPdfTemplate?.id,
};
}
}

View File

@@ -20,6 +20,7 @@ import { RejectSaleEstimate } from './RejectSaleEstimate';
import { SaleEstimateNotifyBySms } from './SaleEstimateSmsNotify';
import { SaleEstimatesPdf } from './SaleEstimatesPdf';
import { SendSaleEstimateMail } from './SendSaleEstimateMail';
import { GetSaleEstimateState } from './GetSaleEstimateState';
@Service()
export class SaleEstimatesApplication {
@@ -56,6 +57,9 @@ export class SaleEstimatesApplication {
@Inject()
private sendEstimateMailService: SendSaleEstimateMail;
@Inject()
private getSaleEstimateStateService: GetSaleEstimateState;
/**
* Create a sale estimate.
* @param {number} tenantId - The tenant id.
@@ -249,4 +253,13 @@ export class SaleEstimatesApplication {
saleEstimateId
);
}
/**
* Retrieves the current state of the sale estimate.
* @param {number} tenantId - The ID of the tenant.
* @returns {Promise<ISaleEstimateState>} - A promise resolving to the sale estimate state.
*/
public getSaleEstimateState(tenantId: number) {
return this.getSaleEstimateStateService.getSaleEstimateState(tenantId);
}
}

View File

@@ -104,12 +104,24 @@ export class GetInvoicePaymentLinkMetaTransformer extends SaleInvoiceTransformer
);
}
protected formattedCustomerAddress(invoice) {
return contactAddressTextFormat(invoice.customer, `{ADDRESS_1}
get customerAddressFormat() {
return `{ADDRESS_1}
{ADDRESS_2}
{CITY}, {STATE} {POSTAL_CODE}
{CITY} {STATE} {POSTAL_CODE}
{COUNTRY}
{PHONE}`);
{PHONE}`;
}
/**
* Retrieves the formatted customer address.
* @param invoice
* @returns {string}
*/
protected formattedCustomerAddress(invoice) {
return contactAddressTextFormat(
invoice.customer,
this.customerAddressFormat
);
}
}

View File

@@ -0,0 +1,28 @@
import { Inject, Service } from 'typedi';
import { ISaleInvocieState } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class GetSaleInvoiceState {
@Inject()
private tenancy: HasTenancyService;
/**
* Retrieves the create/edit invoice state.
* @param {Number} saleInvoiceId -
* @return {Promise<ISaleInvoice>}
*/
public async getSaleInvoiceState(
tenantId: number
): Promise<ISaleInvocieState> {
const { PdfTemplate } = this.tenancy.models(tenantId);
const defaultPdfTemplate = await PdfTemplate.query()
.findOne({ resource: 'SaleInvoice' })
.modify('default');
return {
defaultTemplateId: defaultPdfTemplate?.id,
};
}
}

View File

@@ -28,6 +28,7 @@ import { SaleInvoiceNotifyBySms } from './SaleInvoiceNotifyBySms';
import { SendInvoiceMailReminder } from './SendSaleInvoiceMailReminder';
import { SendSaleInvoiceMail } from './SendSaleInvoiceMail';
import { GetSaleInvoiceMailReminder } from './GetSaleInvoiceMailReminder';
import { GetSaleInvoiceState } from './GetSaleInvoiceState';
@Service()
export class SaleInvoiceApplication {
@@ -73,6 +74,9 @@ export class SaleInvoiceApplication {
@Inject()
private getSaleInvoiceReminderService: GetSaleInvoiceMailReminder;
@Inject()
private getSaleInvoiceStateService: GetSaleInvoiceState;
/**
* Creates a new sale invoice with associated GL entries.
* @param {number} tenantId
@@ -169,6 +173,16 @@ export class SaleInvoiceApplication {
);
}
/**
* Retrieves the sale invoice state.
* @param {number} tenantId
* @param {number} saleInvoiceId
* @returns
*/
public getSaleInvoiceState(tenantId: number) {
return this.getSaleInvoiceStateService.getSaleInvoiceState(tenantId);
}
/**
* Mark the given sale invoice as delivered.
* @param {number} tenantId

View File

@@ -0,0 +1,28 @@
import { Inject, Service } from 'typedi';
import { IPaymentReceivedState } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class GetPaymentReceivedState {
@Inject()
private tenancy: HasTenancyService;
/**
* Retrieves the create/edit initial state of the payment received.
* @param {number} tenantId - The ID of the tenant.
* @returns {Promise<IPaymentReceivedState>} - A promise resolving to the payment received state.
*/
public async getPaymentReceivedState(
tenantId: number
): Promise<IPaymentReceivedState> {
const { PdfTemplate } = this.tenancy.models(tenantId);
const defaultPdfTemplate = await PdfTemplate.query()
.findOne({ resource: 'PaymentReceive' })
.modify('default');
return {
defaultTemplateId: defaultPdfTemplate?.id,
};
}
}

View File

@@ -19,6 +19,7 @@ import { GetPaymentReceivedInvoices } from './GetPaymentReceivedInvoices';
import { PaymentReceiveNotifyBySms } from './PaymentReceivedSmsNotify';
import GetPaymentReceivedPdf from './GetPaymentReceivedPdf';
import { SendPaymentReceiveMailNotification } from './PaymentReceivedMailNotification';
import { GetPaymentReceivedState } from './GetPaymentReceivedState';
@Service()
export class PaymentReceivesApplication {
@@ -49,6 +50,9 @@ export class PaymentReceivesApplication {
@Inject()
private getPaymentReceivePdfService: GetPaymentReceivedPdf;
@Inject()
private getPaymentReceivedStateService: GetPaymentReceivedState;
/**
* Creates a new payment receive.
* @param {number} tenantId
@@ -223,4 +227,15 @@ export class PaymentReceivesApplication {
paymentReceiveId
);
};
/**
* Retrieves the create/edit initial state of the payment received.
* @param {number} tenantId - The ID of the tenant.
* @returns {Promise<IPaymentReceivedState>}
*/
public getPaymentReceivedState = (tenantId: number) => {
return this.getPaymentReceivedStateService.getPaymentReceivedState(
tenantId
);
};
}

View File

@@ -0,0 +1,28 @@
import { Inject, Service } from 'typedi';
import { ISaleReceiptState } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class GetSaleReceiptState {
@Inject()
private tenancy: HasTenancyService;
/**
* Retireves the sale receipt state.
* @param {Number} tenantId -
* @return {Promise<ISaleReceiptState>}
*/
public async getSaleReceiptState(
tenantId: number
): Promise<ISaleReceiptState> {
const { PdfTemplate } = this.tenancy.models(tenantId);
const defaultPdfTemplate = await PdfTemplate.query()
.findOne({ resource: 'SaleReceipt' })
.modify('default');
return {
defaultTemplateId: defaultPdfTemplate?.id,
};
}
}

View File

@@ -4,6 +4,7 @@ import {
IFilterMeta,
IPaginationMeta,
ISaleReceipt,
ISaleReceiptState,
ISalesReceiptsFilter,
SaleReceiptMailOpts,
SaleReceiptMailOptsDTO,
@@ -16,6 +17,7 @@ import { CloseSaleReceipt } from './CloseSaleReceipt';
import { SaleReceiptsPdf } from './SaleReceiptsPdfService';
import { SaleReceiptNotifyBySms } from './SaleReceiptNotifyBySms';
import { SaleReceiptMailNotification } from './SaleReceiptMailNotification';
import { GetSaleReceiptState } from './GetSaleReceiptState';
@Service()
export class SaleReceiptApplication {
@@ -46,6 +48,9 @@ export class SaleReceiptApplication {
@Inject()
private saleReceiptNotifyByMailService: SaleReceiptMailNotification;
@Inject()
private getSaleReceiptStateService: GetSaleReceiptState;
/**
* Creates a new sale receipt with associated entries.
* @param {number} tenantId
@@ -207,4 +212,13 @@ export class SaleReceiptApplication {
saleReceiptId
);
}
/**
* Retrieves the current state of the sale receipt.
* @param {number} tenantId - The ID of the tenant.
* @returns {Promise<ISaleReceiptState>} - A promise resolving to the sale receipt state.
*/
public getSaleReceiptState(tenantId: number): Promise<ISaleReceiptState> {
return this.getSaleReceiptStateService.getSaleReceiptState(tenantId);
}
}

View File

@@ -1,5 +1,9 @@
import { organizationAddressTextFormat } from '@/utils/address-text-format';
import {
defaultOrganizationAddressFormat,
organizationAddressTextFormat,
} from '@/utils/address-text-format';
import BaseModel from 'models/Model';
import { findByIsoCountryCode } from '@bigcapital/utils';
import { getUploadedObjectUri } from '../../services/Attachments/utils';
export default class TenantMetadata extends BaseModel {
@@ -67,14 +71,9 @@ export default class TenantMetadata extends BaseModel {
* @returns {string}
*/
public get addressTextFormatted() {
const defaultMessage = `<strong>{ORGANIZATION_NAME}</strong>
{ADDRESS_1}
{ADDRESS_2}
{CITY}, {STATE} {POSTAL_CODE}
{COUNTRY}
{PHONE}
`;
return organizationAddressTextFormat(defaultMessage, {
const addressCountry = findByIsoCountryCode(this.location);
return organizationAddressTextFormat(defaultOrganizationAddressFormat, {
organizationName: this.name,
address1: this.address?.address1,
address2: this.address?.address2,
@@ -82,7 +81,7 @@ export default class TenantMetadata extends BaseModel {
city: this.address?.city,
postalCode: this.address?.postalCode,
phone: this.address?.phone,
country: 'United State',
country: addressCountry?.name ?? '',
});
}
}

View File

@@ -11,13 +11,13 @@ interface OrganizationAddressFormatArgs {
phone?: string;
}
const defaultMessage = `
<strong>{ORGANIZATION_NAME}</strong>
{ADDRESS_1}
{ADDRESS_2}
{CITY}, {STATE} {POSTAL_CODE}
{COUNTRY}
{PHONE}
export const defaultOrganizationAddressFormat = `
<strong>{ORGANIZATION_NAME}</strong>
{ADDRESS_1}
{ADDRESS_2}
{CITY} {STATE} {POSTAL_CODE}
{COUNTRY}
{PHONE}
`;
/**
* Formats the address text based on the provided message and arguments.
@@ -36,7 +36,9 @@ const formatText = (message: string, replacements: Record<string, string>) => {
},
message
);
formattedMessage = formattedMessage.replace(/\n{2,}/g, '\n').trim();
// Removes any empty lines.
formattedMessage = formattedMessage.replace(/^\s*[\r\n]/gm, '');
formattedMessage = formattedMessage.replace(/\n{2,}/g, '\n');
formattedMessage = formattedMessage.replace(/\n/g, '<br />');
formattedMessage = formattedMessage.trim();
@@ -72,17 +74,17 @@ interface ContactAddressTextFormatArgs {
phone?: string;
}
const contactFormatMessage = `{CONTACT_NAME}
export const defaultContactAddressFormat = `{CONTACT_NAME}
{ADDRESS_1}
{ADDRESS_2}
{CITY}, {STATE} {POSTAL_CODE}
{CITY} {STATE} {POSTAL_CODE}
{COUNTRY}
{PHONE}
`;
export const contactAddressTextFormat = (
contact: IContact,
message: string = contactFormatMessage
message: string = defaultContactAddressFormat
) => {
const args = {
displayName: contact.displayName,

View File

@@ -5,11 +5,7 @@ USER root
WORKDIR /app
# Copy application dependency manifests to the container image.
COPY ./package*.json ./
COPY ./pnpm-lock.yaml ./pnpm-lock.yaml
COPY ./lerna.json ./lerna.json
COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml
COPY ./packages/webapp/package*.json ./packages/webapp/
COPY . .
# Install application dependencies
RUN apk update
@@ -23,7 +19,6 @@ RUN npm install -g pnpm
RUN pnpm install
# Build webapp package
COPY ./packages/webapp /app/packages/webapp
RUN pnpm run build:webapp
FROM nginx

View File

@@ -3,6 +3,7 @@
"version": "0.10.2",
"private": true,
"dependencies": {
"@bigcapital/utils": "*",
"@blueprintjs-formik/core": "^0.3.6",
"@blueprintjs-formik/datetime": "^0.3.7",
"@blueprintjs-formik/select": "^0.3.5",

View File

@@ -27,11 +27,14 @@ export interface GroupProps extends React.ComponentPropsWithoutRef<'div'> {
/** Defines align-items css property */
align?: React.CSSProperties['alignItems'];
flex?: React.CSSProperties['flex'];
}
const defaultProps: Partial<GroupProps> = {
position: 'left',
spacing: 20,
flex: 'none'
};
export function Group({ children, ...props }: GroupProps) {
@@ -48,6 +51,7 @@ const GroupStyled = styled(Box)`
box-sizing: border-box;
display: flex;
flex-direction: row;
flex: ${(props: GroupProps) => (props.flex)};
align-items: ${(props: GroupProps) => (props.align || 'center')};
flex-wrap: ${(props: GroupProps) => (props.noWrap ? 'nowrap' : 'wrap')};
justify-content: ${(props: GroupProps) =>

View File

@@ -11,12 +11,15 @@ export interface StackProps extends React.ComponentPropsWithoutRef<'div'> {
/** justify-content CSS property */
justify?: React.CSSProperties['justifyContent'];
flex?: React.CSSProperties['flex'];
}
const defaultProps: Partial<StackProps> = {
spacing: 20,
align: 'stretch',
justify: 'top',
flex: 'none',
};
export function Stack(props: StackProps) {
@@ -33,4 +36,5 @@ const StackStyled = styled(Box)`
align-items: ${(props: StackProps) => props.align};
justify-content: justify;
gap: ${(props: StackProps) => props.spacing}px;
flex: ${(props: StackProps) => props.flex};
`;

View File

@@ -1,10 +1,14 @@
import { Text, Classes, Button, Intent, Tag } from '@blueprintjs/core';
import { Text, Classes, Button, Intent } from '@blueprintjs/core';
import clsx from 'classnames';
import { AppToaster, Box, Group, Stack } from '@/components';
import { usePaymentPortalBoot } from './PaymentPortalBoot';
import { useDrawerActions } from '@/hooks/state';
import { useCreateStripeCheckoutSession } from '@/hooks/query/payment-link';
import {
useCreateStripeCheckoutSession,
useGeneratePaymentLinkInvoicePdf,
} from '@/hooks/query/payment-link';
import { DRAWERS } from '@/constants/drawers';
import { downloadFile } from '@/hooks/useDownloadFile';
import styles from './PaymentPortal.module.scss';
export function PaymentPortal() {
@@ -15,10 +19,34 @@ export function PaymentPortal() {
isLoading: isStripeCheckoutLoading,
} = useCreateStripeCheckoutSession();
const {
mutateAsync: generatePaymentLinkInvoice,
isLoading: isInvoiceGenerating,
} = useGeneratePaymentLinkInvoicePdf();
// Handles invoice preview button click.
const handleInvoicePreviewBtnClick = () => {
openDrawer(DRAWERS.PAYMENT_INVOICE_PREVIEW);
};
// Handles invoice download button click.
const handleInvoiceDownloadBtnClick = () => {
generatePaymentLinkInvoice({ paymentLinkId: linkId })
.then((data) => {
downloadFile(
data,
`Invoice ${sharableLinkMeta?.invoiceNo}`,
'application/pdf',
);
})
.catch(() => {
AppToaster.show({
intent: Intent.DANGER,
message: 'Something went wrong.',
});
});
};
// handles the pay button click.
const handlePayButtonClick = () => {
createStripeCheckoutSession({ linkId })
@@ -125,6 +153,8 @@ export function PaymentPortal() {
<Button
minimal
className={clsx(styles.footerButton, styles.downloadInvoiceButton)}
onClick={handleInvoiceDownloadBtnClick}
loading={isInvoiceGenerating}
>
Download Invoice
</Button>

View File

@@ -7,6 +7,7 @@ import { Button, FormGroup, Intent } from '@blueprintjs/core';
import { TimezonePicker } from '@blueprintjs/timezone';
import { ErrorMessage, FastField } from 'formik';
import { useHistory } from 'react-router-dom';
import { getAllCountries } from '@bigcapital/utils';
import {
FieldRequiredHint,
@@ -23,7 +24,6 @@ import { getAllCurrenciesOptions } from '@/constants/currencies';
import { getFiscalYear } from '@/constants/fiscalYearOptions';
import { getLanguages } from '@/constants/languagesOptions';
import { useGeneralFormContext } from './GeneralFormProvider';
import { getAllCountries } from '@/utils/countries';
import { shouldBaseCurrencyUpdate } from './utils';

View File

@@ -1,4 +1,4 @@
import { Box, Stack } from '@/components';
import { Box, Group, Stack } from '@/components';
import {
PaperTemplate,
PaperTemplateProps,
@@ -13,6 +13,13 @@ import {
} from '@/constants/PdfTemplates';
export interface CreditNotePaperTemplateProps extends PaperTemplateProps {
// # Company logo
showCompanyLogo?: boolean;
companyLogoUri?: string;
// # Company name
companyName?: string;
// Address
showCustomerAddress?: boolean;
customerAddress?: string;
@@ -122,26 +129,30 @@ export function CreditNotePaperTemplate({
creditNoteDateLabel = 'Credit Note Date',
}: CreditNotePaperTemplateProps) {
return (
<PaperTemplate
primaryColor={primaryColor}
secondaryColor={secondaryColor}
showCompanyLogo={showCompanyLogo}
companyLogoUri={companyLogoUri}
bigtitle={'Credit Note'}
>
<PaperTemplate primaryColor={primaryColor} secondaryColor={secondaryColor}>
<Stack spacing={24}>
<PaperTemplate.TermsList>
{showCreditNoteNumber && (
<PaperTemplate.TermsItem label={creditNoteNumberLabel}>
{creditNoteNumebr}
</PaperTemplate.TermsItem>
<Group align={'start'} spacing={10}>
<Stack flex={1}>
<PaperTemplate.BigTitle title={'Credit Note'} />
<PaperTemplate.TermsList>
{showCreditNoteNumber && (
<PaperTemplate.TermsItem label={creditNoteNumberLabel}>
{creditNoteNumebr}
</PaperTemplate.TermsItem>
)}
{showCreditNoteDate && (
<PaperTemplate.TermsItem label={creditNoteDateLabel}>
{creditNoteDate}
</PaperTemplate.TermsItem>
)}
</PaperTemplate.TermsList>
</Stack>
{companyLogoUri && showCompanyLogo && (
<PaperTemplate.Logo logoUri={companyLogoUri} />
)}
{showCreditNoteDate && (
<PaperTemplate.TermsItem label={creditNoteDateLabel}>
{creditNoteDate}
</PaperTemplate.TermsItem>
)}
</PaperTemplate.TermsList>
</Group>
<PaperTemplate.AddressesGroup>
{showCompanyAddress && (

View File

@@ -62,12 +62,12 @@ export const fieldsGroups = [
label: 'Credit Note #',
},
{
enableKey: 'showBilledToAddress',
enableKey: 'showCustomerAddress',
labelKey: 'billedToLabel',
label: 'Bill To',
},
{
enableKey: 'showBilledFromAddress',
enableKey: 'showCompanyAddress',
label: 'Billed From',
},
],

View File

@@ -67,6 +67,7 @@ function CreditNoteForm({
newCreditNote,
createCreditNoteMutate,
editCreditNoteMutate,
creditNoteState,
} = useCreditNoteFormContext();
// Credit number.
@@ -85,6 +86,7 @@ function CreditNoteForm({
currency_code: base_currency,
terms_conditions: defaultTo(creditTermsConditions, ''),
note: defaultTo(creditCustomerNotes, ''),
pdf_template_id: creditNoteState?.defaultTemplateId,
...newCreditNote,
}),
};

View File

@@ -17,10 +17,19 @@ import {
useBranches,
useSettingsCreditNotes,
useInvoice,
useGetCreditNoteState,
CreditNoteStateResponse,
} from '@/hooks/query';
import { useGetPdfTemplates } from '@/hooks/query/pdf-templates';
const CreditNoteFormContext = React.createContext();
interface CreditNoteFormProviderValue {
creditNoteState: CreditNoteStateResponse;
isCreditNoteStateLoading: boolean;
}
const CreditNoteFormContext = React.createContext<CreditNoteFormProviderValue>(
{} as CreditNoteFormProviderValue,
);
/**
* Credit note data provider.
@@ -76,7 +85,11 @@ function CreditNoteFormProvider({ creditNoteId, ...props }) {
// Fetches branding templates of invoice.
const { data: brandingTemplates, isLoading: isBrandingTemplatesLoading } =
useGetPdfTemplates({ resource: 'PaymentReceive' });
useGetPdfTemplates({ resource: 'CreditNote' });
// Fetches the credit note state.
const { data: creditNoteState, isLoading: isCreditNoteStateLoading } =
useGetCreditNoteState();
// Handle fetching settings.
useSettingsCreditNotes();
@@ -124,6 +137,10 @@ function CreditNoteFormProvider({ creditNoteId, ...props }) {
// Branding templates.
brandingTemplates,
isBrandingTemplatesLoading,
// Credit note state
creditNoteState,
isCreditNoteStateLoading,
};
const isLoading =
@@ -140,6 +157,7 @@ function CreditNoteFormProvider({ creditNoteId, ...props }) {
);
}
const useCreditNoteFormContext = () => React.useContext(CreditNoteFormContext);
const useCreditNoteFormContext = () =>
React.useContext<CreditNoteFormProviderValue>(CreditNoteFormContext);
export { CreditNoteFormProvider, useCreditNoteFormContext };

View File

@@ -1,4 +1,4 @@
import { Box, Stack } from '@/components';
import { Box, Group, Stack } from '@/components';
import {
PaperTemplate,
PaperTemplateProps,
@@ -13,6 +13,10 @@ import {
} from '@/constants/PdfTemplates';
export interface EstimatePaperTemplateProps extends PaperTemplateProps {
// # Company
showCompanyLogo?: boolean;
companyLogoUri?: string;
// # Estimate number
estimateNumebr?: string;
estimateNumberLabel?: string;
@@ -132,31 +136,35 @@ export function EstimatePaperTemplate({
expirationDate = 'September 3, 2024',
}: EstimatePaperTemplateProps) {
return (
<PaperTemplate
primaryColor={primaryColor}
secondaryColor={secondaryColor}
showCompanyLogo={showCompanyLogo}
companyLogoUri={companyLogoUri}
bigtitle={'Estimate'}
>
<PaperTemplate primaryColor={primaryColor} secondaryColor={secondaryColor}>
<Stack spacing={24}>
<PaperTemplate.TermsList>
{showEstimateNumber && (
<PaperTemplate.TermsItem label={estimateNumberLabel}>
{estimateNumebr}
</PaperTemplate.TermsItem>
<Group align={'start'} spacing={10}>
<Stack flex={1}>
<PaperTemplate.BigTitle title={'Estimate'} />
<PaperTemplate.TermsList>
{showEstimateNumber && (
<PaperTemplate.TermsItem label={estimateNumberLabel}>
{estimateNumebr}
</PaperTemplate.TermsItem>
)}
{showEstimateDate && (
<PaperTemplate.TermsItem label={estimateDateLabel}>
{estimateDate}
</PaperTemplate.TermsItem>
)}
{showExpirationDate && (
<PaperTemplate.TermsItem label={expirationDateLabel}>
{expirationDate}
</PaperTemplate.TermsItem>
)}
</PaperTemplate.TermsList>
</Stack>
{companyLogoUri && showCompanyLogo && (
<PaperTemplate.Logo logoUri={companyLogoUri} />
)}
{showEstimateDate && (
<PaperTemplate.TermsItem label={estimateDateLabel}>
{estimateDate}
</PaperTemplate.TermsItem>
)}
{showExpirationDate && (
<PaperTemplate.TermsItem label={expirationDateLabel}>
{expirationDate}
</PaperTemplate.TermsItem>
)}
</PaperTemplate.TermsList>
</Group>
<PaperTemplate.AddressesGroup>
{showCompanyAddress && (

View File

@@ -25,7 +25,7 @@ export const initialValues = {
// Customer address
showCustomerAddress: true,
// Company address
showCompanyAddress: true,
companyAddress: '',
@@ -73,12 +73,12 @@ export const fieldsGroups = [
label: 'Expiration Date',
},
{
enableKey: 'showBilledToAddress',
enableKey: 'showCustomerAddress',
labelKey: 'billedToLabel',
label: 'Bill To',
},
{
enableKey: 'showBilledFromAddress',
enableKey: 'showCompanyAddress',
label: 'Billed From',
},
],

View File

@@ -58,6 +58,7 @@ function EstimateForm({
submitPayload,
createEstimateMutate,
editEstimateMutate,
saleEstimateState,
} = useEstimateFormContext();
const estimateNumber = transactionNumber(
@@ -79,6 +80,7 @@ function EstimateForm({
currency_code: base_currency,
terms_conditions: defaultTo(estimateTermsConditions, ''),
note: defaultTo(estimateCustomerNotes, ''),
pdf_template_id: saleEstimateState?.defaultTemplateId,
}),
};

View File

@@ -11,6 +11,8 @@ import {
useSettingsEstimates,
useCreateEstimate,
useEditEstimate,
useGetSaleEstimatesState,
ISaleEstimatesStateResponse,
} from '@/hooks/query';
import { useProjects } from '@/containers/Projects/hooks';
import { useGetPdfTemplates } from '@/hooks/query/pdf-templates';
@@ -18,7 +20,12 @@ import { Features } from '@/constants';
import { useFeatureCan } from '@/hooks/state';
import { ITEMS_FILTER_ROLES } from './utils';
const EstimateFormContext = createContext();
interface EstimateFormProviderValues {
saleEstimateState: ISaleEstimatesStateResponse;
isSaleEstimateStateLoading: boolean;
}
const EstimateFormContext = createContext({} as EstimateFormProviderValues);
/**
* Estimate form provider.
@@ -76,6 +83,10 @@ function EstimateFormProvider({ query, estimateId, ...props }) {
const { data: brandingTemplates, isLoading: isBrandingTemplatesLoading } =
useGetPdfTemplates({ resource: 'SaleEstimate' });
// Fetches the sale estimate state.
const { data: saleEstimateState, isLoading: isSaleEstimateStateLoading } =
useGetSaleEstimatesState();
// Handle fetch settings.
useSettingsEstimates();
@@ -118,15 +129,21 @@ function EstimateFormProvider({ query, estimateId, ...props }) {
createEstimateMutate,
editEstimateMutate,
// Branding templates
brandingTemplates,
isBrandingTemplatesLoading,
// Estimate state
saleEstimateState,
isSaleEstimateStateLoading,
};
const isLoading =
isCustomersLoading ||
isItemsLoading ||
isEstimateLoading ||
isBrandingTemplatesLoading;
isBrandingTemplatesLoading ||
isSaleEstimateStateLoading;
return (
<DashboardInsider loading={isLoading} name={'estimate-form'}>
@@ -135,6 +152,7 @@ function EstimateFormProvider({ query, estimateId, ...props }) {
);
}
const useEstimateFormContext = () => useContext(EstimateFormContext);
const useEstimateFormContext = () =>
useContext<EstimateFormProviderValues>(EstimateFormContext);
export { EstimateFormProvider, useEstimateFormContext };

View File

@@ -11,16 +11,13 @@
width: 794px;
height: 1123px;
}
.bigTitle{
font-size: 60px;
margin: 0;
line-height: 1;
margin-bottom: 25px;
font-weight: 500;
color: #333;
}
.details {
display: flex;
flex-direction: column;
@@ -35,13 +32,11 @@
min-width: 120px;
color: #333;
}
.addressRoot{
> div{
flex: 1;
}
}
.table {
width: 100%;
border-collapse: collapse;
@@ -66,10 +61,6 @@
}
tbody{
tr {
}
td{
border-bottom: 1px solid #F6F6F6;
padding: 12px 10px;
@@ -80,7 +71,6 @@
&:last-of-type{
padding-right: 0;
}
&.rate,
&.total{
text-align: right;
@@ -88,7 +78,6 @@
}
}
}
.totals{
display: flex;
flex-direction: column;
@@ -112,25 +101,22 @@
.totalBottomGrayBordered {
border-bottom: 1px solid #DADADA;
}
.logoWrap{
height: 120px;
width: 120px;
position: absolute;
right: 26px;
top: 26px;
border-radius: 5px;
overflow: hidden;
img{
max-width: 100%;
}
}
.logoImg {
height: auto;
width: auto;
max-width: 400px;
max-height: 160px;
}
.footer{
}
.paragraph{
margin-bottom: 20px;
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { PaperTemplate, PaperTemplateTotalBorder } from './PaperTemplate';
import { Box, Stack } from '@/components';
import { Box, Group, Stack } from '@/components';
import {
DefaultPdfTemplateTerms,
DefaultPdfTemplateItemDescription,
@@ -178,31 +178,35 @@ export function InvoicePaperTemplate({
statement = DefaultPdfTemplateStatement,
}: InvoicePaperTemplateProps) {
return (
<PaperTemplate
primaryColor={primaryColor}
secondaryColor={secondaryColor}
showCompanyLogo={showCompanyLogo}
companyLogoUri={companyLogoUri}
bigtitle={'Invoice'}
>
<PaperTemplate primaryColor={primaryColor} secondaryColor={secondaryColor}>
<Stack spacing={24}>
<PaperTemplate.TermsList>
{showInvoiceNumber && (
<PaperTemplate.TermsItem label={invoiceNumberLabel}>
{invoiceNumber}
</PaperTemplate.TermsItem>
<Group align="start" spacing={10}>
<Stack flex={1}>
<PaperTemplate.BigTitle title={'Invoice'} />
<PaperTemplate.TermsList>
{showInvoiceNumber && (
<PaperTemplate.TermsItem label={invoiceNumberLabel}>
{invoiceNumber}
</PaperTemplate.TermsItem>
)}
{showDateIssue && (
<PaperTemplate.TermsItem label={dateIssueLabel}>
{dateIssue}
</PaperTemplate.TermsItem>
)}
{showDueDate && (
<PaperTemplate.TermsItem label={dueDateLabel}>
{dueDate}
</PaperTemplate.TermsItem>
)}
</PaperTemplate.TermsList>
</Stack>
{companyLogoUri && showCompanyLogo && (
<PaperTemplate.Logo logoUri={companyLogoUri} />
)}
{showDateIssue && (
<PaperTemplate.TermsItem label={dateIssueLabel}>
{dateIssue}
</PaperTemplate.TermsItem>
)}
{showDueDate && (
<PaperTemplate.TermsItem label={dueDateLabel}>
{dueDate}
</PaperTemplate.TermsItem>
)}
</PaperTemplate.TermsList>
</Group>
<PaperTemplate.AddressesGroup>
{showCompanyAddress && (

View File

@@ -7,38 +7,18 @@ import styles from './InvoicePaperTemplate.module.scss';
export interface PaperTemplateProps {
primaryColor?: string;
secondaryColor?: string;
showCompanyLogo?: boolean;
companyLogoUri?: string;
companyName?: string;
bigtitle?: string;
children?: React.ReactNode;
}
export function PaperTemplate({
primaryColor,
secondaryColor,
showCompanyLogo,
companyLogoUri,
bigtitle = 'Invoice',
children,
}: PaperTemplateProps) {
return (
<div className={styles.root}>
<style>{`:root { --invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor}; }`}</style>
<div>
<h1 className={styles.bigTitle}>{bigtitle}</h1>
{showCompanyLogo && companyLogoUri && (
<div className={styles.logoWrap}>
<img alt="" src={companyLogoUri} />
</div>
)}
</div>
{children}
</div>
);
@@ -53,6 +33,26 @@ interface PaperTemplateTableProps {
data: Array<Record<string, any>>;
}
interface PaperTemplateBigTitleProps {
title: string;
}
PaperTemplate.BigTitle = ({ title }: PaperTemplateBigTitleProps) => {
return <h1 className={styles.bigTitle}>{title}</h1>;
};
interface PaperTemplateLogoProps {
logoUri: string;
}
PaperTemplate.Logo = ({ logoUri }: PaperTemplateLogoProps) => {
return (
<div className={styles.logoWrap}>
<img className={styles.logoImg} alt="" src={logoUri} />
</div>
);
};
PaperTemplate.Table = ({ columns, data }: PaperTemplateTableProps) => {
return (
<table className={styles.table}>

View File

@@ -83,12 +83,12 @@ export const fieldsGroups = [
label: 'Due Date',
},
{
enableKey: 'showBillingToAddress',
enableKey: 'showCustomerAddress',
labelKey: 'billedToLabel',
label: 'Bill To',
},
{
enableKey: 'showBilledFromAddress',
enableKey: 'showCompanyAddress',
label: 'Billed From',
},
],

View File

@@ -61,6 +61,7 @@ function InvoiceForm({
createInvoiceMutate,
editInvoiceMutate,
submitPayload,
saleInvoiceState
} = useInvoiceFormContext();
// Invoice number.
@@ -83,6 +84,7 @@ function InvoiceForm({
currency_code: base_currency,
invoice_message: defaultTo(invoiceCustomerNotes, ''),
terms_conditions: defaultTo(invoiceTermsConditions, ''),
pdf_template_id: saleInvoiceState?.defaultTemplateId,
...newInvoice,
}),
};

View File

@@ -16,13 +16,22 @@ import {
useEditInvoice,
useSettingsInvoices,
useEstimate,
useGetSaleInvoiceState,
GetSaleInvoiceStateResponse,
} from '@/hooks/query';
import { useProjects } from '@/containers/Projects/hooks';
import { useTaxRates } from '@/hooks/query/taxRates';
import { useGetPdfTemplates } from '@/hooks/query/pdf-templates';
import { useGetPaymentServices } from '@/hooks/query/payment-services';
const InvoiceFormContext = createContext();
interface InvoiceFormContextValue {
saleInvoiceState: GetSaleInvoiceStateResponse | null;
isInvoiceStateLoading: boolean;
}
const InvoiceFormContext = createContext<InvoiceFormContextValue>(
{} as InvoiceFormContextValue,
);
/**
* Accounts chart data provider.
@@ -100,6 +109,9 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) {
isSuccess: isBranchesSuccess,
} = useBranches({}, { enabled: isBranchFeatureCan });
const { data: saleInvoiceState, isLoading: isInvoiceStateLoading } =
useGetSaleInvoiceState();
// Handle fetching settings.
const { isLoading: isSettingsLoading } = useSettingsInvoices();
@@ -154,24 +166,28 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) {
// Payment Services
paymentServices,
isPaymentServicesLoading,
// Invoice state
saleInvoiceState,
isInvoiceStateLoading,
};
const isLoading =
isInvoiceLoading ||
isItemsLoading ||
isCustomersLoading ||
isEstimateLoading ||
isSettingsLoading ||
isInvoiceStateLoading;
return (
<DashboardInsider
loading={
isInvoiceLoading ||
isItemsLoading ||
isCustomersLoading ||
isEstimateLoading ||
isSettingsLoading
}
name={'invoice-form'}
>
<DashboardInsider loading={isLoading} name={'invoice-form'}>
<InvoiceFormContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const useInvoiceFormContext = () => React.useContext(InvoiceFormContext);
const useInvoiceFormContext = () =>
React.useContext<InvoiceFormContextValue>(InvoiceFormContext);
export { InvoiceFormProvider, useInvoiceFormContext };

View File

@@ -69,6 +69,7 @@ function PaymentReceiveForm({
editPaymentReceiveMutate,
createPaymentReceiveMutate,
isExcessConfirmed,
paymentReceivedState,
} = usePaymentReceiveFormContext();
// Payment receive number.
@@ -77,29 +78,21 @@ function PaymentReceiveForm({
paymentReceiveNextNumber,
);
// Form initial values.
const initialValues = useMemo(
() => ({
...(!isEmpty(paymentReceiveEditPage)
? transformToEditForm(paymentReceiveEditPage, paymentEntriesEditPage)
: {
...defaultPaymentReceive,
// If the auto-increment mode is enabled, take the next payment
// number from the settings.
...(paymentReceiveAutoIncrement && {
payment_receive_no: nextPaymentNumber,
}),
deposit_account_id: defaultTo(preferredDepositAccount, ''),
currency_code: base_currency,
const initialValues = {
...(!isEmpty(paymentReceiveEditPage)
? transformToEditForm(paymentReceiveEditPage, paymentEntriesEditPage)
: {
...defaultPaymentReceive,
// If the auto-increment mode is enabled, take the next payment
// number from the settings.
...(paymentReceiveAutoIncrement && {
payment_receive_no: nextPaymentNumber,
}),
}),
[
paymentReceiveEditPage,
nextPaymentNumber,
paymentEntriesEditPage,
paymentReceiveAutoIncrement,
preferredDepositAccount,
],
);
deposit_account_id: defaultTo(preferredDepositAccount, ''),
currency_code: base_currency,
pdf_template_id: paymentReceivedState?.defaultTemplateId,
}),
};
// Handle form submit.
const handleSubmitForm = (
values,

View File

@@ -12,11 +12,21 @@ import {
useBranches,
useCreatePaymentReceive,
useEditPaymentReceive,
usePaymentReceivedState,
PaymentReceivedStateResponse,
} from '@/hooks/query';
import { useGetPdfTemplates } from '@/hooks/query/pdf-templates';
interface PaymentReceivedFormContextValue {
isPaymentReceivedStateLoading: boolean;
paymentReceivedState: PaymentReceivedStateResponse;
}
// Payment receive form context.
const PaymentReceiveFormContext = createContext();
const PaymentReceiveFormContext =
createContext<PaymentReceivedFormContextValue>(
{} as PaymentReceivedFormContextValue,
);
/**
* Payment receive form provider.
@@ -70,6 +80,12 @@ function PaymentReceiveFormProvider({ query, paymentReceiveId, ...props }) {
const { data: brandingTemplates, isLoading: isBrandingTemplatesLoading } =
useGetPdfTemplates({ resource: 'PaymentReceive' });
// Fetches the payment received initial state.
const {
data: paymentReceivedState,
isLoading: isPaymentReceivedStateLoading,
} = usePaymentReceivedState();
// Detarmines whether the new mode.
const isNewMode = !paymentReceiveId;
@@ -111,13 +127,18 @@ function PaymentReceiveFormProvider({ query, paymentReceiveId, ...props }) {
// Branding templates
brandingTemplates,
isBrandingTemplatesLoading,
// Payment received state
isPaymentReceivedStateLoading,
paymentReceivedState,
};
const isLoading =
isPaymentLoading ||
isAccountsLoading ||
isCustomersLoading ||
isBrandingTemplatesLoading;
isBrandingTemplatesLoading ||
isPaymentReceivedStateLoading;
return (
<DashboardInsider loading={isLoading} name={'payment-receive-form'}>
@@ -127,6 +148,6 @@ function PaymentReceiveFormProvider({ query, paymentReceiveId, ...props }) {
}
const usePaymentReceiveFormContext = () =>
useContext(PaymentReceiveFormContext);
useContext<PaymentReceivedFormContextValue>(PaymentReceiveFormContext);
export { PaymentReceiveFormProvider, usePaymentReceiveFormContext };

View File

@@ -1,4 +1,4 @@
import { Box, Stack } from '@/components';
import { Box, Group, Stack } from '@/components';
import {
PaperTemplate,
PaperTemplateProps,
@@ -10,6 +10,13 @@ import {
} from '@/constants/PdfTemplates';
export interface PaymentReceivedPaperTemplateProps extends PaperTemplateProps {
// # Company logo
showCompanyLogo?: boolean;
companyLogoUri?: string;
// # Company name
companyName?: string;
// Customer address
showCustomerAddress?: boolean;
customerAddress?: string;
@@ -93,27 +100,31 @@ export function PaymentReceivedPaperTemplate({
paymentReceivedDateLabel = 'Payment Date',
}: PaymentReceivedPaperTemplateProps) {
return (
<PaperTemplate
primaryColor={primaryColor}
secondaryColor={secondaryColor}
showCompanyLogo={showCompanyLogo}
companyLogoUri={companyLogoUri}
bigtitle={'Payment'}
>
<PaperTemplate primaryColor={primaryColor} secondaryColor={secondaryColor}>
<Stack spacing={24}>
<PaperTemplate.TermsList>
{showPaymentReceivedNumber && (
<PaperTemplate.TermsItem label={paymentReceivedNumberLabel}>
{paymentReceivedNumebr}
</PaperTemplate.TermsItem>
)}
<Group align={'start'} spacing={10}>
<Stack flex={1}>
<PaperTemplate.BigTitle title={'Payment'} />
{showPaymentReceivedDate && (
<PaperTemplate.TermsItem label={paymentReceivedDateLabel}>
{paymentReceivedDate}
</PaperTemplate.TermsItem>
<PaperTemplate.TermsList>
{showPaymentReceivedNumber && (
<PaperTemplate.TermsItem label={paymentReceivedNumberLabel}>
{paymentReceivedNumebr}
</PaperTemplate.TermsItem>
)}
{showPaymentReceivedDate && (
<PaperTemplate.TermsItem label={paymentReceivedDateLabel}>
{paymentReceivedDate}
</PaperTemplate.TermsItem>
)}
</PaperTemplate.TermsList>
</Stack>
{companyLogoUri && showCompanyLogo && (
<PaperTemplate.Logo logoUri={companyLogoUri} />
)}
</PaperTemplate.TermsList>
</Group>
<PaperTemplate.AddressesGroup>
{showCompanyAddress && (

View File

@@ -59,12 +59,12 @@ export const fieldsGroups = [
label: 'Payment Date',
},
{
enableKey: 'showBillingToAddress',
enableKey: 'showCustomerAddress',
labelKey: 'billedToLabel',
label: 'Bill To',
},
{
enableKey: 'showBilledFromAddress',
enableKey: 'showCompanyAddress',
label: 'Billed From',
},
],

View File

@@ -1,4 +1,4 @@
import { Box, Stack } from '@/components';
import { Box, Group, Stack } from '@/components';
import {
PaperTemplate,
PaperTemplateProps,
@@ -13,6 +13,13 @@ import {
} from '@/constants/PdfTemplates';
export interface ReceiptPaperTemplateProps extends PaperTemplateProps {
// # Company logo
showCompanyLogo?: boolean;
companyLogoUri?: string;
// # Company name
companyName?: string;
// Addresses
showCustomerAddress?: boolean;
customerAddress?: string;
@@ -117,26 +124,30 @@ export function ReceiptPaperTemplate({
receiptDateLabel = 'Receipt Date',
}: ReceiptPaperTemplateProps) {
return (
<PaperTemplate
primaryColor={primaryColor}
secondaryColor={secondaryColor}
showCompanyLogo={showCompanyLogo}
companyLogoUri={companyLogoUri}
bigtitle={'Receipt'}
>
<PaperTemplate primaryColor={primaryColor} secondaryColor={secondaryColor}>
<Stack spacing={24}>
<PaperTemplate.TermsList>
{showReceiptNumber && (
<PaperTemplate.TermsItem label={receiptNumberLabel}>
{receiptNumebr}
</PaperTemplate.TermsItem>
<Group align={'start'} spacing={10}>
<Stack flex={1}>
<PaperTemplate.BigTitle title={'Receipt'} />
<PaperTemplate.TermsList>
{showReceiptNumber && (
<PaperTemplate.TermsItem label={receiptNumberLabel}>
{receiptNumebr}
</PaperTemplate.TermsItem>
)}
{showReceiptDate && (
<PaperTemplate.TermsItem label={receiptDateLabel}>
{receiptDate}
</PaperTemplate.TermsItem>
)}
</PaperTemplate.TermsList>
</Stack>
{companyLogoUri && showCompanyLogo && (
<PaperTemplate.Logo logoUri={companyLogoUri} />
)}
{showReceiptDate && (
<PaperTemplate.TermsItem label={receiptDateLabel}>
{receiptDate}
</PaperTemplate.TermsItem>
)}
</PaperTemplate.TermsList>
</Group>
<PaperTemplate.AddressesGroup>
{showCompanyAddress && (

View File

@@ -21,7 +21,7 @@ export const initialValues = {
// Company name
companyName: 'Bigcapital Technology, Inc.',
// Customer address
// Customer address
showCustomerAddress: true,
// Company address
@@ -67,12 +67,12 @@ export const fieldsGroups = [
label: 'Receipt Date',
},
{
enableKey: 'showBilledToAddress',
enableKey: 'showCustomerAddress',
labelKey: 'billedToLabel',
label: 'Bill To',
},
{
enableKey: 'showBilledFromAddress',
enableKey: 'showCompanyAddress',
label: 'Billed From',
},
],

View File

@@ -63,6 +63,7 @@ function ReceiptForm({
createReceiptMutate,
submitPayload,
isNewMode,
saleReceiptState,
} = useReceiptFormContext();
// The next receipt number.
@@ -84,6 +85,7 @@ function ReceiptForm({
currency_code: base_currency,
receipt_message: receiptMessage,
terms_conditions: receiptTermsConditions,
pdf_template_id: saleReceiptState?.defaultTemplateId,
}),
};
// Handle the form submit.
@@ -171,7 +173,7 @@ function ReceiptForm({
{/*---------- Dialogs ---------*/}
<ReceiptFormDialogs />
css
{/*---------- Effects ---------*/}
<ReceiptSyncIncrementSettingsToForm />
<ReceiptSyncAutoExRateToForm />

View File

@@ -13,11 +13,20 @@ import {
useItems,
useCreateReceipt,
useEditReceipt,
useGetReceiptState,
IGetReceiptStateResponse,
} from '@/hooks/query';
import { useProjects } from '@/containers/Projects/hooks';
import { useGetPdfTemplates } from '@/hooks/query/pdf-templates';
const ReceiptFormContext = createContext();
const ReceiptFormContext = createContext<ReceiptFormProviderValue>(
{} as ReceiptFormProviderValue,
);
interface ReceiptFormProviderValue {
isSaleReceiptStateLoading: boolean;
saleReceiptState: IGetReceiptStateResponse;
}
/**
* Receipt form provider.
@@ -96,6 +105,10 @@ function ReceiptFormProvider({ receiptId, ...props }) {
const { data: brandingTemplates, isLoading: isBrandingTemplatesLoading } =
useGetPdfTemplates({ resource: 'SaleReceipt' });
// Fetches the sale receipt state.
const { data: saleReceiptState, isLoading: isSaleReceiptStateLoading } =
useGetReceiptState();
// Fetch receipt settings.
const { isLoading: isSettingLoading } = useSettingsReceipts();
@@ -137,6 +150,10 @@ function ReceiptFormProvider({ receiptId, ...props }) {
// Branding templates
brandingTemplates,
isBrandingTemplatesLoading,
// State
isSaleReceiptStateLoading,
saleReceiptState,
};
const isLoading =
isReceiptLoading ||
@@ -144,7 +161,8 @@ function ReceiptFormProvider({ receiptId, ...props }) {
isCustomersLoading ||
isItemsLoading ||
isSettingLoading ||
isBrandingTemplatesLoading;
isBrandingTemplatesLoading ||
isSaleReceiptStateLoading;
return (
<DashboardInsider loading={isLoading} name={'receipt-form'}>
@@ -153,6 +171,7 @@ function ReceiptFormProvider({ receiptId, ...props }) {
);
}
const useReceiptFormContext = () => React.useContext(ReceiptFormContext);
const useReceiptFormContext = () =>
React.useContext<ReceiptFormProviderValue>(ReceiptFormContext);
export { ReceiptFormProvider, useReceiptFormContext };

View File

@@ -4,6 +4,7 @@ import { FastField, Form, ErrorMessage } from 'formik';
import { Button, Intent, FormGroup, Classes } from '@blueprintjs/core';
import classNames from 'classnames';
import { TimezonePicker } from '@blueprintjs/timezone';
import { getAllCountries } from '@bigcapital/utils';
import {
FFormGroup,
FInputGroup,
@@ -17,7 +18,6 @@ import { inputIntent } from '@/utils';
import { getFiscalYear } from '@/constants/fiscalYearOptions';
import { getLanguages } from '@/constants/languagesOptions';
import { getAllCurrenciesOptions } from '@/constants/currencies';
import { getAllCountries } from '@/utils/countries';
const countries = getAllCountries();

View File

@@ -1,7 +1,7 @@
// @ts-nocheck
import { useQueryClient, useMutation } from 'react-query';
import { useQueryClient, useMutation, useQuery } from 'react-query';
import { useRequestQuery } from '../useQueryRequest';
import { transformPagination } from '@/utils';
import { transformPagination, transformToCamelCase } from '@/utils';
import useApiRequest from '../useRequest';
import { useRequestPdf } from '../useRequestPdf';
import t from './types';
@@ -356,3 +356,21 @@ export function useRefundCreditTransaction(id, props, requestProps) {
export function usePdfCreditNote(creditNoteId) {
return useRequestPdf({ url: `sales/credit_notes/${creditNoteId}` });
}
export interface CreditNoteStateResponse {
defaultTemplateId: number;
}
export function useGetCreditNoteState(
options?: UseQueryOptions<CreditNoteStateResponse, Error>,
): UseQueryResult<CreditNoteStateResponse, Error> {
const apiRequest = useApiRequest();
return useQuery<CreditNoteStateResponse, Error>(
['CREDIT_NOTE_STATE'],
() =>
apiRequest
.get('/sales/credit_notes/state')
.then((res) => transformToCamelCase(res.data?.data)),
{ ...options },
);
}

View File

@@ -1,8 +1,8 @@
// @ts-nocheck
import { useQueryClient, useMutation } from 'react-query';
import { useQueryClient, useMutation, useQuery } from 'react-query';
import { useRequestQuery } from '../useQueryRequest';
import useApiRequest from '../useRequest';
import { transformPagination } from '@/utils';
import { transformPagination, transformToCamelCase } from '@/utils';
import t from './types';
import { useRequestPdf } from '../useRequestPdf';
@@ -270,3 +270,22 @@ export function useSaleEstimateDefaultOptions(estimateId, props) {
},
);
}
export interface ISaleEstimatesStateResponse {
defaultTemplateId: number;
}
export function useGetSaleEstimatesState(
options?: UseQueryOptions<ISaleEstimatesStateResponse, Error>,
): UseQueryResult<ISaleEstimatesStateResponse, Error> {
const apiRequest = useApiRequest();
return useQuery<ISaleEstimatesStateResponse, Error>(
['SALE_ESTIMATE_STATE'],
() =>
apiRequest
.get('/sales/estimates/state')
.then((res) => transformToCamelCase(res.data?.data)),
{ ...options },
);
}

View File

@@ -1,7 +1,7 @@
// @ts-nocheck
import { useQueryClient, useMutation } from 'react-query';
import { useQueryClient, useMutation, useQuery } from 'react-query';
import { useRequestQuery } from '../useQueryRequest';
import { transformPagination } from '@/utils';
import { transformPagination, transformToCamelCase } from '@/utils';
import useApiRequest from '../useRequest';
import { useRequestPdf } from '../useRequestPdf';
import t from './types';
@@ -341,3 +341,22 @@ export function useSaleInvoiceDefaultOptions(invoiceId, props) {
},
);
}
export interface GetSaleInvoiceStateResponse {
defaultTemplateId: number;
}
export function useGetSaleInvoiceState(
options?: UseQueryOptions<GetSaleInvoiceStateResponse, Error>,
): UseQueryResult<GetSaleInvoiceStateResponse, Error> {
const apiRequest = useApiRequest();
return useQuery<GetSaleInvoiceStateResponse, Error>(
['SALE_INVOICE_STATE'],
() =>
apiRequest
.get(`/sales/invoices/state`)
.then((res) => transformToCamelCase(res.data?.data)),
{ ...options },
);
}

View File

@@ -11,6 +11,7 @@ import useApiRequest from '../useRequest';
import { transformToCamelCase, transfromToSnakeCase } from '@/utils';
const GetPaymentLinkInvoice = 'GetPaymentLinkInvoice';
const GetPaymentLinkInvoicePdf = 'GetPaymentLinkInvoicePdf';
// Create Payment Link
// ------------------------------------
@@ -170,3 +171,62 @@ export const useCreateStripeCheckoutSession = (
{ ...options },
);
};
// Get Payment Link Invoice PDF
// ------------------------------------
interface GetPaymentLinkInvoicePdfResponse {}
interface GeneratePaymentLinkInvoicePdfValues {
paymentLinkId: string;
}
export const useGeneratePaymentLinkInvoicePdf = (
options?: UseMutationOptions<
GetPaymentLinkInvoicePdfResponse,
Error,
GeneratePaymentLinkInvoicePdfValues
>,
): UseMutationResult<
GetPaymentLinkInvoicePdfResponse,
Error,
GeneratePaymentLinkInvoicePdfValues
> => {
const apiRequest = useApiRequest();
return useMutation<
GetPaymentLinkInvoicePdfResponse,
Error,
GeneratePaymentLinkInvoicePdfValues
>(
(values: GeneratePaymentLinkInvoicePdfValues) => {
return apiRequest
.get(`/payment-links/${values.paymentLinkId}/invoice/pdf`, {
responseType: 'blob',
headers: { accept: 'application/pdf' },
})
.then((res) => res?.data);
},
{ ...options },
);
};
export const useGetPaymentLinkInvoicePdf = (
invoiceId: string,
options?: UseQueryOptions<GetPaymentLinkInvoicePdfResponse, Error>,
): UseQueryResult<GetPaymentLinkInvoicePdfResponse, Error> => {
const apiRequest = useApiRequest();
return useQuery<GetPaymentLinkInvoicePdfResponse, Error>(
[GetPaymentLinkInvoicePdf, invoiceId],
() =>
apiRequest
.get(`/payment-links/${invoiceId}/invoice/pdf`, {
responseType: 'blob',
headers: { accept: 'application/pdf' },
})
.then((res) => res.data),
{
...options,
},
);
};

View File

@@ -1,11 +1,16 @@
// @ts-nocheck
import { useMutation, useQueryClient } from 'react-query';
import { useRequestQuery } from '../useQueryRequest';
import {
useMutation,
useQueryClient,
UseQueryOptions,
UseQueryResult,
useQuery,
} from 'react-query';
import useApiRequest from '../useRequest';
import { transformPagination, saveInvoke } from '@/utils';
import t from './types';
import { useRequestQuery } from '../useQueryRequest';
import { transformPagination, saveInvoke, transformToCamelCase } from '@/utils';
import { useRequestPdf } from '../useRequestPdf';
import t from './types';
// Common invalidate queries.
const commonInvalidateQueries = (client) => {
@@ -269,3 +274,30 @@ export function usePaymentReceiveDefaultOptions(paymentReceiveId, props) {
},
);
}
export interface PaymentReceivedStateResponse {
defaultTemplateId: number;
}
/**
* Retrieves the payment receive state.
* @param {Record<string, any>} query - Query parameters for the request.
* @param {UseQueryOptions<PaymentReceivedStateResponse, Error>} options - Optional query options.
* @returns {UseQueryResult<PaymentReceivedStateResponse, Error>} The query result.
*/
export function usePaymentReceivedState(
options?: UseQueryOptions<PaymentReceivedStateResponse, Error>,
): UseQueryResult<PaymentReceivedStateResponse, Error> {
const apiRequest = useApiRequest();
return useQuery<PaymentReceivedStateResponse, Error>(
['PAYMENT_RECEIVED_STATE'],
() =>
apiRequest
.get('/sales/payment_receives/state')
.then((res) => transformToCamelCase(res.data?.data)),
{
...options,
},
);
}

View File

@@ -198,6 +198,11 @@ export const useAssignPdfTemplateAsDefault = (
{
onSuccess: () => {
queryClient.invalidateQueries([PdfTemplatesQueryKey]);
queryClient.invalidateQueries(['SALE_INVOICE_STATE']);
queryClient.invalidateQueries(['SALE_ESTIMATE_STATE']);
queryClient.invalidateQueries(['SALE_RECEIPT_STATE']);
queryClient.invalidateQueries(['CREDIT_NOTE_STATE']);
queryClient.invalidateQueries(['PAYMENT_RECEIVED_STATE']);
},
...options,
},

View File

@@ -1,8 +1,14 @@
// @ts-nocheck
import { useQueryClient, useMutation } from 'react-query';
import {
useQueryClient,
useMutation,
UseQueryResult,
UseQueryOptions,
useQuery,
} from 'react-query';
import { useRequestQuery } from '../useQueryRequest';
import useApiRequest from '../useRequest';
import { transformPagination } from '@/utils';
import { transformPagination, transformToCamelCase } from '@/utils';
import { useRequestPdf } from '../useRequestPdf';
import t from './types';
@@ -244,3 +250,22 @@ export function useSaleReceiptDefaultOptions(invoiceId, props) {
},
);
}
export interface IGetReceiptStateResponse {
defaultTemplateId: number;
}
export function useGetReceiptState(
options?: UseQueryOptions<IGetReceiptStateResponse, Error>,
): UseQueryResult<IGetReceiptStateResponse, Error> {
const apiRequest = useApiRequest();
return useQuery<IGetReceiptStateResponse, Error>(
['SALE_RECEIPT_STATE'],
() =>
apiRequest
.get(`/sales/receipts/state`)
.then((res) => transformToCamelCase(res.data?.data)),
{ ...options },
);
}

View File

@@ -33,9 +33,15 @@ export const useDownloadFile = (args: IArgs) => {
return { ...mutation };
};
export function downloadFile(data, filename, mime, bom) {
export function downloadFile(
data,
filename,
mime = 'application/octet-stream',
bom?: any,
) {
var blobData = typeof bom !== 'undefined' ? [bom, data] : [data];
var blob = new Blob(blobData, { type: mime || 'application/octet-stream' });
var blob = new Blob(blobData, { type: mime });
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE workaround for "HTML7007: One or more blob URLs were
// revoked by closing the blob for which they were created.

View File

@@ -1,10 +0,0 @@
import { Countries } from '@/constants/countries';
export const getAllCountries = () => {
return Object.keys(Countries).map((countryCode) => {
return {
...Countries[countryCode],
countryCode,
}
});
};

614
pnpm-lock.yaml generated
View File

@@ -7,6 +7,10 @@ settings:
importers:
.:
dependencies:
tsup:
specifier: ^8.3.0
version: 8.3.0(typescript@4.9.5)
devDependencies:
'@commitlint/cli':
specifier: ^17.4.2
@@ -41,6 +45,9 @@ importers:
'@aws-sdk/s3-request-presigner':
specifier: ^3.583.0
version: 3.583.0
'@bigcapital/utils':
specifier: '*'
version: link:../../shared/bigcapital-utils
'@casl/ability':
specifier: ^5.4.3
version: 5.4.4
@@ -455,7 +462,7 @@ importers:
version: 3.9.10
webpack:
specifier: ^5.75.0
version: 5.91.0(webpack-cli@4.10.0)
version: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
webpack-cli:
specifier: ^4.10.0
version: 4.10.0(webpack@5.91.0)
@@ -471,6 +478,9 @@ importers:
packages/webapp:
dependencies:
'@bigcapital/utils':
specifier: '*'
version: link:../../shared/bigcapital-utils
'@blueprintjs-formik/core':
specifier: ^0.3.6
version: 0.3.6(@babel/core@7.24.5)(@blueprintjs/core@4.20.2)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)
@@ -755,7 +765,7 @@ importers:
version: 5.3.4(react@18.3.1)
react-scripts:
specifier: 5.0.1
version: 5.0.1(@babel/plugin-syntax-flow@7.24.1)(@babel/plugin-transform-react-jsx@7.23.4)(eslint@8.57.0)(react@18.3.1)(sass@1.77.2)(ts-node@10.9.2)(typescript@4.9.5)
version: 5.0.1(@babel/plugin-syntax-flow@7.24.1)(@babel/plugin-transform-react-jsx@7.23.4)(esbuild@0.23.1)(eslint@8.57.0)(react@18.3.1)(sass@1.77.2)(ts-node@10.9.2)(typescript@4.9.5)
react-scroll-sync:
specifier: ^0.7.1
version: 0.7.1(prop-types@15.8.1)(react-dom@18.3.1)(react@18.3.1)
@@ -829,6 +839,8 @@ importers:
specifier: ^0.28.1
version: 0.28.5
shared/bigcapital-utils: {}
packages:
/@alloc/quick-lru@5.2.0:
@@ -3531,7 +3543,7 @@ packages:
dependencies:
cross-spawn: 7.0.3
lodash: 4.17.21
react-scripts: 5.0.1(@babel/plugin-syntax-flow@7.24.1)(@babel/plugin-transform-react-jsx@7.23.4)(eslint@8.57.0)(react@18.3.1)(sass@1.77.2)(ts-node@10.9.2)(typescript@4.9.5)
react-scripts: 5.0.1(@babel/plugin-syntax-flow@7.24.1)(@babel/plugin-transform-react-jsx@7.23.4)(esbuild@0.23.1)(eslint@8.57.0)(react@18.3.1)(sass@1.77.2)(ts-node@10.9.2)(typescript@4.9.5)
webpack-merge: 4.2.2
dev: false
@@ -3789,6 +3801,198 @@ packages:
resolution: {integrity: sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==}
dev: false
/@esbuild/aix-ppc64@0.23.1:
resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
requiresBuild: true
optional: true
/@esbuild/android-arm64@0.23.1:
resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
requiresBuild: true
optional: true
/@esbuild/android-arm@0.23.1:
resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
requiresBuild: true
optional: true
/@esbuild/android-x64@0.23.1:
resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
requiresBuild: true
optional: true
/@esbuild/darwin-arm64@0.23.1:
resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
optional: true
/@esbuild/darwin-x64@0.23.1:
resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
requiresBuild: true
optional: true
/@esbuild/freebsd-arm64@0.23.1:
resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
requiresBuild: true
optional: true
/@esbuild/freebsd-x64@0.23.1:
resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
requiresBuild: true
optional: true
/@esbuild/linux-arm64@0.23.1:
resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/linux-arm@0.23.1:
resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/linux-ia32@0.23.1:
resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/linux-loong64@0.23.1:
resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/linux-mips64el@0.23.1:
resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/linux-ppc64@0.23.1:
resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/linux-riscv64@0.23.1:
resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/linux-s390x@0.23.1:
resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/linux-x64@0.23.1:
resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/netbsd-x64@0.23.1:
resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
requiresBuild: true
optional: true
/@esbuild/openbsd-arm64@0.23.1:
resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
requiresBuild: true
optional: true
/@esbuild/openbsd-x64@0.23.1:
resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
requiresBuild: true
optional: true
/@esbuild/sunos-x64@0.23.1:
resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
requiresBuild: true
optional: true
/@esbuild/win32-arm64@0.23.1:
resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
requiresBuild: true
optional: true
/@esbuild/win32-ia32@0.23.1:
resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
requiresBuild: true
optional: true
/@esbuild/win32-x64@0.23.1:
resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
requiresBuild: true
optional: true
/@eslint-community/eslint-utils@4.4.0(eslint@8.57.0):
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -5003,7 +5207,7 @@ packages:
react-refresh: 0.11.0
schema-utils: 3.3.0
source-map: 0.7.4
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
webpack-dev-server: 4.15.2(webpack@5.91.0)
dev: false
@@ -5137,6 +5341,134 @@ packages:
rollup: 2.79.1
dev: false
/@rollup/rollup-android-arm-eabi@4.24.0:
resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==}
cpu: [arm]
os: [android]
requiresBuild: true
dev: false
optional: true
/@rollup/rollup-android-arm64@4.24.0:
resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: false
optional: true
/@rollup/rollup-darwin-arm64@4.24.0:
resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/@rollup/rollup-darwin-x64@4.24.0:
resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/@rollup/rollup-linux-arm-gnueabihf@4.24.0:
resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@rollup/rollup-linux-arm-musleabihf@4.24.0:
resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@rollup/rollup-linux-arm64-gnu@4.24.0:
resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@rollup/rollup-linux-arm64-musl@4.24.0:
resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@rollup/rollup-linux-powerpc64le-gnu@4.24.0:
resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==}
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@rollup/rollup-linux-riscv64-gnu@4.24.0:
resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==}
cpu: [riscv64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@rollup/rollup-linux-s390x-gnu@4.24.0:
resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==}
cpu: [s390x]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@rollup/rollup-linux-x64-gnu@4.24.0:
resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@rollup/rollup-linux-x64-musl@4.24.0:
resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@rollup/rollup-win32-arm64-msvc@4.24.0:
resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@rollup/rollup-win32-ia32-msvc@4.24.0:
resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@rollup/rollup-win32-x64-msvc@4.24.0:
resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@rushstack/eslint-patch@1.10.3:
resolution: {integrity: sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==}
dev: false
@@ -6362,6 +6694,10 @@ packages:
/@types/estree@1.0.5:
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
/@types/estree@1.0.6:
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
dev: false
/@types/express-serve-static-core@4.19.1:
resolution: {integrity: sha512-ej0phymbFLoCB26dbbq5PGScsf2JAJ4IJHjG10LalgUV36XKTmA4GdA+PVllKvRk0sEKt64X8975qFnkSi0hqA==}
dependencies:
@@ -7266,7 +7602,7 @@ packages:
webpack: 4.x.x || 5.x.x
webpack-cli: 4.x.x
dependencies:
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
webpack-cli: 4.10.0(webpack@5.91.0)
/@webpack-cli/info@1.5.0(webpack-cli@4.10.0):
@@ -8198,7 +8534,7 @@ packages:
loader-utils: 2.0.4
make-dir: 3.1.0
schema-utils: 2.7.1
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: false
/babel-loader@9.1.3(@babel/core@7.24.5)(webpack@5.91.0):
@@ -8211,7 +8547,7 @@ packages:
'@babel/core': 7.24.5
find-cache-dir: 4.0.0
schema-utils: 4.2.0
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: false
/babel-plugin-emotion@10.2.2:
@@ -8932,6 +9268,16 @@ packages:
semver: 7.6.2
dev: true
/bundle-require@5.0.0(esbuild@0.23.1):
resolution: {integrity: sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
peerDependencies:
esbuild: '>=0.18'
dependencies:
esbuild: 0.23.1
load-tsconfig: 0.2.5
dev: false
/busboy@1.6.0:
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
engines: {node: '>=10.16.0'}
@@ -8954,6 +9300,11 @@ packages:
engines: {node: '>= 0.8'}
dev: false
/cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
dev: false
/cacache@17.1.4:
resolution: {integrity: sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -9716,6 +10067,11 @@ packages:
engines: {node: '>=0.8'}
dev: false
/consola@3.2.3:
resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==}
engines: {node: ^14.18.0 || >=16.10.0}
dev: false
/console-browserify@1.2.0:
resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==}
dev: true
@@ -10261,10 +10617,10 @@ packages:
postcss-modules-values: 4.0.0(postcss@8.4.38)
postcss-value-parser: 4.2.0
semver: 7.6.2
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: false
/css-minimizer-webpack-plugin@3.4.1(webpack@5.91.0):
/css-minimizer-webpack-plugin@3.4.1(esbuild@0.23.1)(webpack@5.91.0):
resolution: {integrity: sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==}
engines: {node: '>= 12.13.0'}
peerDependencies:
@@ -10284,12 +10640,13 @@ packages:
optional: true
dependencies:
cssnano: 5.1.15(postcss@8.4.38)
esbuild: 0.23.1
jest-worker: 27.5.1
postcss: 8.4.38
schema-utils: 4.2.0
serialize-javascript: 6.0.2
source-map: 0.6.1
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: false
/css-prefers-color-scheme@6.0.3(postcss@8.4.38):
@@ -10646,6 +11003,18 @@ packages:
ms: 2.1.2
supports-color: 5.5.0
/debug@4.3.7:
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.3
dev: false
/decamelize-keys@1.1.1:
resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==}
engines: {node: '>=0.10.0'}
@@ -11125,7 +11494,7 @@ packages:
webpack: ^4 || ^5
dependencies:
dotenv-defaults: 2.0.2
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: false
/dotenv@10.0.0:
@@ -11515,6 +11884,37 @@ packages:
es6-symbol: 3.1.4
dev: false
/esbuild@0.23.1:
resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==}
engines: {node: '>=18'}
hasBin: true
requiresBuild: true
optionalDependencies:
'@esbuild/aix-ppc64': 0.23.1
'@esbuild/android-arm': 0.23.1
'@esbuild/android-arm64': 0.23.1
'@esbuild/android-x64': 0.23.1
'@esbuild/darwin-arm64': 0.23.1
'@esbuild/darwin-x64': 0.23.1
'@esbuild/freebsd-arm64': 0.23.1
'@esbuild/freebsd-x64': 0.23.1
'@esbuild/linux-arm': 0.23.1
'@esbuild/linux-arm64': 0.23.1
'@esbuild/linux-ia32': 0.23.1
'@esbuild/linux-loong64': 0.23.1
'@esbuild/linux-mips64el': 0.23.1
'@esbuild/linux-ppc64': 0.23.1
'@esbuild/linux-riscv64': 0.23.1
'@esbuild/linux-s390x': 0.23.1
'@esbuild/linux-x64': 0.23.1
'@esbuild/netbsd-x64': 0.23.1
'@esbuild/openbsd-arm64': 0.23.1
'@esbuild/openbsd-x64': 0.23.1
'@esbuild/sunos-x64': 0.23.1
'@esbuild/win32-arm64': 0.23.1
'@esbuild/win32-ia32': 0.23.1
'@esbuild/win32-x64': 0.23.1
/escalade@3.1.2:
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
engines: {node: '>=6'}
@@ -11691,7 +12091,7 @@ packages:
node-libs-browser: 2.2.1
resolve: 1.22.8
semver: 5.7.2
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
transitivePeerDependencies:
- supports-color
dev: true
@@ -11709,7 +12109,7 @@ packages:
object-assign: 4.1.1
object-hash: 1.3.1
rimraf: 2.7.1
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: true
/eslint-module-utils@2.8.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.11.1)(eslint@8.57.0):
@@ -12001,7 +12401,7 @@ packages:
micromatch: 4.0.7
normalize-path: 3.0.0
schema-utils: 4.2.0
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: false
/eslint@8.57.0:
@@ -12512,6 +12912,17 @@ packages:
pend: 1.2.0
dev: false
/fdir@6.4.0(picomatch@4.0.2):
resolution: {integrity: sha512-3oB133prH1o4j/L5lLW7uOCF1PlD+/It2L0eL/iAqWMB91RBbqTewABqxhj0ibBd90EEmWZq7ntIWzVaWcXTGQ==}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
dependencies:
picomatch: 4.0.2
dev: false
/feature-policy@0.3.0:
resolution: {integrity: sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ==}
engines: {node: '>=4.0.0'}
@@ -12542,7 +12953,7 @@ packages:
dependencies:
loader-utils: 2.0.4
schema-utils: 3.3.0
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: false
/file-selector@0.4.0:
@@ -12835,7 +13246,7 @@ packages:
semver: 5.7.2
tapable: 1.1.3
typescript: 4.9.5
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
worker-rpc: 0.1.1
transitivePeerDependencies:
- supports-color
@@ -12870,7 +13281,7 @@ packages:
semver: 7.6.2
tapable: 1.1.3
typescript: 4.9.5
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: false
/form-data@2.3.3:
@@ -13034,7 +13445,7 @@ packages:
resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==}
engines: {node: '>= 4.0'}
os: [darwin]
deprecated: The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2
deprecated: Upgrade to fsevents v2 to mitigate potential security issues
requiresBuild: true
dependencies:
bindings: 1.5.0
@@ -13995,7 +14406,7 @@ packages:
lodash: 4.17.21
pretty-error: 4.0.0
tapable: 2.2.1
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: false
/htmlparser2@6.1.0:
@@ -16167,6 +16578,11 @@ packages:
hasBin: true
dev: false
/joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
dev: false
/js-cookie@2.2.1:
resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==}
dev: false
@@ -16903,6 +17319,11 @@ packages:
type-fest: 0.6.0
dev: true
/load-tsconfig@0.2.5:
resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: false
/loader-fs-cache@1.0.3:
resolution: {integrity: sha512-ldcgZpjNJj71n+2Mf6yetz+c9bM4xpKtNds4LbqXzU/PTdeAX0g3ytnU1AJMEcTk2Lex4Smpe3Q/eCTsvUBxbA==}
dependencies:
@@ -17592,7 +18013,7 @@ packages:
dependencies:
schema-utils: 4.2.0
tapable: 2.2.1
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: false
/minimalistic-assert@1.0.1:
@@ -19546,6 +19967,11 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
/picomatch@4.0.2:
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
engines: {node: '>=12'}
dev: false
/pidtree@0.3.1:
resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==}
engines: {node: '>=0.10'}
@@ -20041,6 +20467,27 @@ packages:
yaml: 2.4.2
dev: true
/postcss-load-config@6.0.1:
resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
engines: {node: '>= 18'}
peerDependencies:
jiti: '>=1.21.0'
postcss: '>=8.0.9'
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
jiti:
optional: true
postcss:
optional: true
tsx:
optional: true
yaml:
optional: true
dependencies:
lilconfig: 3.1.1
dev: false
/postcss-loader@6.2.1(postcss@8.4.38)(webpack@5.91.0):
resolution: {integrity: sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==}
engines: {node: '>= 12.13.0'}
@@ -20052,7 +20499,7 @@ packages:
klona: 2.0.6
postcss: 8.4.38
semver: 7.6.2
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: false
/postcss-logical@5.0.4(postcss@8.4.38):
@@ -20665,7 +21112,7 @@ packages:
dependencies:
chalk: 3.0.0
progress: 2.0.3
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: true
/progress@2.0.1:
@@ -21371,7 +21818,7 @@ packages:
strip-ansi: 6.0.0
text-table: 0.2.0
typescript: 4.9.5
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
transitivePeerDependencies:
- eslint
- supports-color
@@ -21413,7 +21860,7 @@ packages:
strip-ansi: 6.0.1
text-table: 0.2.0
typescript: 4.9.5
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
transitivePeerDependencies:
- eslint
- supports-color
@@ -21720,7 +22167,7 @@ packages:
react-dom: 18.3.1(react@18.3.1)
dev: false
/react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.1)(@babel/plugin-transform-react-jsx@7.23.4)(eslint@8.57.0)(react@18.3.1)(sass@1.77.2)(ts-node@10.9.2)(typescript@4.9.5):
/react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.1)(@babel/plugin-transform-react-jsx@7.23.4)(esbuild@0.23.1)(eslint@8.57.0)(react@18.3.1)(sass@1.77.2)(ts-node@10.9.2)(typescript@4.9.5):
resolution: {integrity: sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==}
engines: {node: '>=14.0.0'}
hasBin: true
@@ -21744,7 +22191,7 @@ packages:
camelcase: 6.3.0
case-sensitive-paths-webpack-plugin: 2.4.0
css-loader: 6.11.0(webpack@5.91.0)
css-minimizer-webpack-plugin: 3.4.1(webpack@5.91.0)
css-minimizer-webpack-plugin: 3.4.1(esbuild@0.23.1)(webpack@5.91.0)
dotenv: 10.0.0
dotenv-expand: 5.1.0
eslint: 8.57.0
@@ -21775,9 +22222,9 @@ packages:
source-map-loader: 3.0.2(webpack@5.91.0)
style-loader: 3.3.4(webpack@5.91.0)
tailwindcss: 3.4.3(ts-node@10.9.2)
terser-webpack-plugin: 5.3.10(webpack@5.91.0)
terser-webpack-plugin: 5.3.10(esbuild@0.23.1)(webpack@5.91.0)
typescript: 4.9.5
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
webpack-dev-server: 4.15.2(webpack@5.91.0)
webpack-manifest-plugin: 4.1.1(webpack@5.91.0)
workbox-webpack-plugin: 6.6.0(webpack@5.91.0)
@@ -22740,6 +23187,32 @@ packages:
fsevents: 2.3.3
dev: false
/rollup@4.24.0:
resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
dependencies:
'@types/estree': 1.0.6
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.24.0
'@rollup/rollup-android-arm64': 4.24.0
'@rollup/rollup-darwin-arm64': 4.24.0
'@rollup/rollup-darwin-x64': 4.24.0
'@rollup/rollup-linux-arm-gnueabihf': 4.24.0
'@rollup/rollup-linux-arm-musleabihf': 4.24.0
'@rollup/rollup-linux-arm64-gnu': 4.24.0
'@rollup/rollup-linux-arm64-musl': 4.24.0
'@rollup/rollup-linux-powerpc64le-gnu': 4.24.0
'@rollup/rollup-linux-riscv64-gnu': 4.24.0
'@rollup/rollup-linux-s390x-gnu': 4.24.0
'@rollup/rollup-linux-x64-gnu': 4.24.0
'@rollup/rollup-linux-x64-musl': 4.24.0
'@rollup/rollup-win32-arm64-msvc': 4.24.0
'@rollup/rollup-win32-ia32-msvc': 4.24.0
'@rollup/rollup-win32-x64-msvc': 4.24.0
fsevents: 2.3.3
dev: false
/rope-sequence@1.3.4:
resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==}
dev: false
@@ -22896,7 +23369,7 @@ packages:
klona: 2.0.6
neo-async: 2.6.2
sass: 1.77.2
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: false
/sass@1.77.2:
@@ -23470,7 +23943,7 @@ packages:
abab: 2.0.6
iconv-lite: 0.6.3
source-map-js: 1.2.0
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: false
/source-map-loader@4.0.2(webpack@5.91.0):
@@ -23481,7 +23954,7 @@ packages:
dependencies:
iconv-lite: 0.6.3
source-map-js: 1.2.0
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: false
/source-map-resolve@0.5.3:
@@ -24087,7 +24560,7 @@ packages:
peerDependencies:
webpack: ^5.0.0
dependencies:
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: false
/styled-components@5.3.11(@babel/core@7.24.5)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1):
@@ -24411,7 +24884,7 @@ packages:
supports-hyperlinks: 2.3.0
dev: false
/terser-webpack-plugin@5.3.10(webpack@5.91.0):
/terser-webpack-plugin@5.3.10(esbuild@0.23.1)(webpack@5.91.0):
resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==}
engines: {node: '>= 10.13.0'}
peerDependencies:
@@ -24428,11 +24901,12 @@ packages:
optional: true
dependencies:
'@jridgewell/trace-mapping': 0.3.25
esbuild: 0.23.1
jest-worker: 27.5.1
schema-utils: 3.3.0
serialize-javascript: 6.0.2
terser: 5.31.0
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
/terser@5.31.0:
resolution: {integrity: sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==}
@@ -24577,6 +25051,14 @@ packages:
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
dev: false
/tinyglobby@0.2.9:
resolution: {integrity: sha512-8or1+BGEdk1Zkkw2ii16qSS7uVrQJPre5A9o/XkWPATkk23FZh/15BKFxPnlTy6vkljZxLqYCzzBMj30ZrSvjw==}
engines: {node: '>=12.0.0'}
dependencies:
fdir: 6.4.0(picomatch@4.0.2)
picomatch: 4.0.2
dev: false
/tippy.js@6.3.7:
resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==}
dependencies:
@@ -24734,6 +25216,11 @@ packages:
punycode: 2.3.1
dev: false
/tree-kill@1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
dev: false
/trim-newlines@3.0.1:
resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}
engines: {node: '>=8'}
@@ -24768,7 +25255,7 @@ packages:
semver: 7.6.2
source-map: 0.7.4
typescript: 3.9.10
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: true
/ts-node@10.9.2(@types/node@20.5.1)(typescript@4.9.5):
@@ -24869,6 +25356,49 @@ packages:
engines: {node: '>=0.6.x'}
dev: false
/tsup@8.3.0(typescript@4.9.5):
resolution: {integrity: sha512-ALscEeyS03IomcuNdFdc0YWGVIkwH1Ws7nfTbAPuoILvEV2hpGQAY72LIOjglGo4ShWpZfpBqP/jpQVCzqYQag==}
engines: {node: '>=18'}
hasBin: true
peerDependencies:
'@microsoft/api-extractor': ^7.36.0
'@swc/core': ^1
postcss: ^8.4.12
typescript: '>=4.5.0'
peerDependenciesMeta:
'@microsoft/api-extractor':
optional: true
'@swc/core':
optional: true
postcss:
optional: true
typescript:
optional: true
dependencies:
bundle-require: 5.0.0(esbuild@0.23.1)
cac: 6.7.14
chokidar: 3.6.0
consola: 3.2.3
debug: 4.3.7
esbuild: 0.23.1
execa: 5.1.1
joycon: 3.1.1
picocolors: 1.0.1
postcss-load-config: 6.0.1
resolve-from: 5.0.0
rollup: 4.24.0
source-map: 0.8.0-beta.0
sucrase: 3.35.0
tinyglobby: 0.2.9
tree-kill: 1.2.2
typescript: 4.9.5
transitivePeerDependencies:
- jiti
- supports-color
- tsx
- yaml
dev: false
/tsutils@3.21.0(typescript@3.9.10):
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'}
@@ -25669,7 +26199,7 @@ packages:
import-local: 3.1.0
interpret: 2.2.0
rechoir: 0.7.1
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
webpack-merge: 5.10.0
/webpack-dev-middleware@5.3.4(webpack@5.91.0):
@@ -25683,7 +26213,7 @@ packages:
mime-types: 2.1.35
range-parser: 1.2.1
schema-utils: 4.2.0
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: false
/webpack-dev-server@4.15.2(webpack@5.91.0):
@@ -25727,7 +26257,7 @@ packages:
serve-index: 1.9.1
sockjs: 0.3.24
spdy: 4.0.2
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
webpack-dev-middleware: 5.3.4(webpack@5.91.0)
ws: 8.17.0
transitivePeerDependencies:
@@ -25744,7 +26274,7 @@ packages:
webpack: ^4.44.2 || ^5.47.0
dependencies:
tapable: 2.2.1
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
webpack-sources: 2.3.1
dev: false
@@ -25792,7 +26322,7 @@ packages:
chalk: 2.4.2
dev: true
/webpack@5.91.0(webpack-cli@4.10.0):
/webpack@5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0):
resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==}
engines: {node: '>=10.13.0'}
hasBin: true
@@ -25823,7 +26353,7 @@ packages:
neo-async: 2.6.2
schema-utils: 3.3.0
tapable: 2.2.1
terser-webpack-plugin: 5.3.10(webpack@5.91.0)
terser-webpack-plugin: 5.3.10(esbuild@0.23.1)(webpack@5.91.0)
watchpack: 2.4.1
webpack-cli: 4.10.0(webpack@5.91.0)
webpack-sources: 3.2.3
@@ -26202,7 +26732,7 @@ packages:
fast-json-stable-stringify: 2.1.0
pretty-bytes: 5.6.0
upath: 1.2.0
webpack: 5.91.0(webpack-cli@4.10.0)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
webpack-sources: 1.4.3
workbox-build: 6.6.0
transitivePeerDependencies:

View File

@@ -1,3 +1,4 @@
packages:
# all packages in direct subdirs of packages/
- 'packages/*'
- 'shared/*'

1
shared/bigcapital-utils/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/dist

View File

@@ -0,0 +1,22 @@
{
"name": "@bigcapital/utils",
"version": "1.0.0",
"description": "",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.mjs"
}
},
"scripts": {
"build:cjs": "tsup src/index.ts --format cjs --dts --sourcemap",
"build:esm": "tsup src/index.ts --format esm --dts --sourcemap",
"build": "npm run build:cjs && npm run build:esm",
"dev": "npm run build -- --watch"
},
"author": "",
"license": "ISC"
}

View File

@@ -1,13 +1,4 @@
interface Country {
name: string;
native: string;
phone: number[];
continent: string;
continents?: string[];
capital: string;
currency: string[];
languages: string[];
}
import { Country } from './types';
export const Countries: Record<string, Country> = {
AD: {

View File

@@ -0,0 +1,20 @@
import { Countries } from './constant';
import { Country, Maybe } from './types';
export const getAllCountries = () => {
return Object.keys(Countries).map((countryCode) => {
return {
...Countries[countryCode],
countryCode,
};
});
};
export const findByIsoCountryCode = (
isoCode: string
): Maybe<Country & { countryCode: string }> => {
const _isoCode = isoCode?.toUpperCase();
const country = Countries[_isoCode];
return country ? { ...country, countryCode: isoCode } : null;
};

View File

@@ -0,0 +1,12 @@
export interface Country {
name: string;
native: string;
phone: number[];
continent: string;
continents?: string[];
capital: string;
currency: string[];
languages: string[];
}
export type Maybe<T> = T | null;

View File

@@ -0,0 +1,3 @@
export * from './countries';
export const test = () => {};

View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ES6", // Equivalent to ES6 output
"module": "ESNext", // CommonJS for Node.js compatibility
"outDir": "dist", // Output directory for compiled files
"declaration": true, // Generates .d.ts files (same as dts: true)
"declarationDir": "dist", // Specifies where to output declaration files
"sourceMap": true, // Generate sourcemaps
"esModuleInterop": true, // Enables interop between CommonJS and ESModules
"strict": true, // Enables strict type-checking options
"moduleResolution": "node", // Resolve modules using Node.js-style resolution
"skipLibCheck": true, // Skip type checking of declaration files
"forceConsistentCasingInFileNames": true // Enforces consistent casing in import paths
},
"include": ["src/**/*"], // Includes all TypeScript files in the src directory
"exclude": ["node_modules"] // Excludes node_modules from being compiled
}