mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d6f901d33 | ||
|
|
908bbb9fa6 | ||
|
|
6c1870be8f | ||
|
|
f5834c72c6 | ||
|
|
7ee3392d3e | ||
|
|
c58822fd6c | ||
|
|
ba8091d697 |
@@ -121,7 +121,7 @@ export default class BillsController extends BaseController {
|
|||||||
check('entries.*.index').exists().isNumeric().toInt(),
|
check('entries.*.index').exists().isNumeric().toInt(),
|
||||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.discount')
|
check('entries.*.discount')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ export default class VendorCreditController extends BaseController {
|
|||||||
check('entries.*.index').exists().isNumeric().toInt(),
|
check('entries.*.index').exists().isNumeric().toInt(),
|
||||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.discount')
|
check('entries.*.discount')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
@@ -209,7 +209,7 @@ export default class VendorCreditController extends BaseController {
|
|||||||
check('entries.*.index').exists().isNumeric().toInt(),
|
check('entries.*.index').exists().isNumeric().toInt(),
|
||||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.discount')
|
check('entries.*.discount')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
check('entries.*.index').exists().isNumeric().toInt(),
|
check('entries.*.index').exists().isNumeric().toInt(),
|
||||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.discount')
|
check('entries.*.discount')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
@@ -755,9 +755,8 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await this.getCreditNoteStateService.getCreditNoteState(
|
const data =
|
||||||
tenantId
|
await this.getCreditNoteStateService.getCreditNoteState(tenantId);
|
||||||
);
|
|
||||||
return res.status(200).send({ data });
|
return res.status(200).send({ data });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
check('entries').exists().isArray({ min: 1 }),
|
check('entries').exists().isArray({ min: 1 }),
|
||||||
check('entries.*.index').exists().isNumeric().toInt(),
|
check('entries.*.index').exists().isNumeric().toInt(),
|
||||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.description').optional({ nullable: true }).trim(),
|
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||||
check('entries.*.discount')
|
check('entries.*.discount')
|
||||||
@@ -562,9 +562,8 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await this.saleEstimatesApplication.getSaleEstimateState(
|
const data =
|
||||||
tenantId
|
await this.saleEstimatesApplication.getSaleEstimateState(tenantId);
|
||||||
);
|
|
||||||
return res.status(200).send({ data });
|
return res.status(200).send({ data });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(),
|
check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
check('entries.*.index').exists().isNumeric().toInt(),
|
check('entries.*.index').exists().isNumeric().toInt(),
|
||||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.discount')
|
check('entries.*.discount')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
@@ -392,9 +392,8 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
|
|
||||||
// Retrieves receipt in pdf format.
|
// Retrieves receipt in pdf format.
|
||||||
try {
|
try {
|
||||||
const data = await this.saleReceiptsApplication.getSaleReceiptState(
|
const data =
|
||||||
tenantId
|
await this.saleReceiptsApplication.getSaleReceiptState(tenantId);
|
||||||
);
|
|
||||||
return res.status(200).send({ data });
|
return res.status(200).send({ data });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
@@ -102,12 +102,13 @@ export class PublicSharableLinkController extends BaseController {
|
|||||||
const { paymentLinkId } = req.params;
|
const { paymentLinkId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const pdfContent = await this.paymentLinkApp.getPaymentLinkInvoicePdf(
|
const [pdfContent, filename] =
|
||||||
paymentLinkId
|
await this.paymentLinkApp.getPaymentLinkInvoicePdf(paymentLinkId);
|
||||||
);
|
|
||||||
res.set({
|
res.set({
|
||||||
'Content-Type': 'application/pdf',
|
'Content-Type': 'application/pdf',
|
||||||
'Content-Length': pdfContent.length,
|
'Content-Length': pdfContent.length,
|
||||||
|
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||||
});
|
});
|
||||||
res.send(pdfContent);
|
res.send(pdfContent);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema
|
||||||
|
.table('items_entries', (table) => {
|
||||||
|
table.decimal('quantity', 13, 3).alter();
|
||||||
|
})
|
||||||
|
.table('inventory_transactions', (table) => {
|
||||||
|
table.decimal('quantity', 13, 3).alter();
|
||||||
|
})
|
||||||
|
.table('inventory_cost_lot_tracker', (table) => {
|
||||||
|
table.decimal('quantity', 13, 3).alter();
|
||||||
|
})
|
||||||
|
.table('items', (table) => {
|
||||||
|
table.decimal('quantityOnHand', 13, 3).alter();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema
|
||||||
|
.table('items_entries', (table) => {
|
||||||
|
table.integer('quantity').alter();
|
||||||
|
})
|
||||||
|
.table('inventory_transactions', (table) => {
|
||||||
|
table.integer('quantity').alter();
|
||||||
|
})
|
||||||
|
.table('inventory_cost_lot_tracker', (table) => {
|
||||||
|
table.integer('quantity').alter();
|
||||||
|
})
|
||||||
|
.table('items', (table) => {
|
||||||
|
table.integer('quantityOnHand').alter();
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -46,6 +46,7 @@ export class GetInvoicePaymentLinkMetadata {
|
|||||||
.withGraphFetched('customer')
|
.withGraphFetched('customer')
|
||||||
.withGraphFetched('taxes.taxRate')
|
.withGraphFetched('taxes.taxRate')
|
||||||
.withGraphFetched('paymentMethods.paymentIntegration')
|
.withGraphFetched('paymentMethods.paymentIntegration')
|
||||||
|
.withGraphFetched('pdfTemplate')
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
return this.transformer.transform(
|
return this.transformer.transform(
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ export class GetPaymentLinkInvoicePdf {
|
|||||||
* Retrieves the sale invoice PDF of the given payment link id.
|
* Retrieves the sale invoice PDF of the given payment link id.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} paymentLinkId
|
* @param {number} paymentLinkId
|
||||||
* @returns {Promise<Buffer>}
|
* @returns {Promise<Buffer, string>}
|
||||||
*/
|
*/
|
||||||
async getPaymentLinkInvoicePdf(paymentLinkId: string): Promise<Buffer> {
|
async getPaymentLinkInvoicePdf(
|
||||||
|
paymentLinkId: string
|
||||||
|
): Promise<[Buffer, string]> {
|
||||||
const paymentLink = await PaymentLink.query()
|
const paymentLink = await PaymentLink.query()
|
||||||
.findOne('linkId', paymentLinkId)
|
.findOne('linkId', paymentLinkId)
|
||||||
.where('resourceType', 'SaleInvoice')
|
.where('resourceType', 'SaleInvoice')
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ export class PaymentLinksApplication {
|
|||||||
* @param {number} paymentLinkId
|
* @param {number} paymentLinkId
|
||||||
* @returns {Promise<Buffer> }
|
* @returns {Promise<Buffer> }
|
||||||
*/
|
*/
|
||||||
public getPaymentLinkInvoicePdf(paymentLinkId: string): Promise<Buffer> {
|
public getPaymentLinkInvoicePdf(
|
||||||
|
paymentLinkId: string
|
||||||
|
): Promise<[Buffer, string]> {
|
||||||
return this.getPaymentLinkInvoicePdfService.getPaymentLinkInvoicePdf(
|
return this.getPaymentLinkInvoicePdfService.getPaymentLinkInvoicePdf(
|
||||||
paymentLinkId
|
paymentLinkId
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { SaleInvoiceTaxEntryTransformer } from './SaleInvoiceTaxEntryTransformer
|
|||||||
import { SaleInvoiceTransformer } from './SaleInvoiceTransformer';
|
import { SaleInvoiceTransformer } from './SaleInvoiceTransformer';
|
||||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||||
import { contactAddressTextFormat } from '@/utils/address-text-format';
|
import { contactAddressTextFormat } from '@/utils/address-text-format';
|
||||||
|
import { GetPdfTemplateTransformer } from '@/services/PdfTemplate/GetPdfTemplateTransformer';
|
||||||
|
|
||||||
export class GetInvoicePaymentLinkMetaTransformer extends SaleInvoiceTransformer {
|
export class GetInvoicePaymentLinkMetaTransformer extends SaleInvoiceTransformer {
|
||||||
/**
|
/**
|
||||||
@@ -45,6 +46,7 @@ export class GetInvoicePaymentLinkMetaTransformer extends SaleInvoiceTransformer
|
|||||||
'isReceivable',
|
'isReceivable',
|
||||||
'hasStripePaymentMethod',
|
'hasStripePaymentMethod',
|
||||||
'formattedCustomerAddress',
|
'formattedCustomerAddress',
|
||||||
|
'brandingTemplate',
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -63,6 +65,18 @@ export class GetInvoicePaymentLinkMetaTransformer extends SaleInvoiceTransformer
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the branding template for the payment link.
|
||||||
|
* @param {} invoice
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public brandingTemplate(invoice) {
|
||||||
|
return this.item(
|
||||||
|
invoice.pdfTemplate,
|
||||||
|
new GetInvoicePaymentLinkBrandingTemplate()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the entries of the sale invoice.
|
* Retrieves the entries of the sale invoice.
|
||||||
* @param {ISaleInvoice} invoice
|
* @param {ISaleInvoice} invoice
|
||||||
@@ -193,3 +207,17 @@ class GetInvoicePaymentLinkTaxEntryTransformer extends SaleInvoiceTaxEntryTransf
|
|||||||
return ['name', 'taxRateCode', 'taxRateAmount', 'taxRateAmountFormatted'];
|
return ['name', 'taxRateCode', 'taxRateAmount', 'taxRateAmountFormatted'];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GetInvoicePaymentLinkBrandingTemplate extends GetPdfTemplateTransformer {
|
||||||
|
public includeAttributes = (): string[] => {
|
||||||
|
return ['companyLogoUri', 'primaryColor'];
|
||||||
|
};
|
||||||
|
|
||||||
|
public excludeAttributes = (): string[] => {
|
||||||
|
return ['*'];
|
||||||
|
};
|
||||||
|
|
||||||
|
primaryColor = (template) => {
|
||||||
|
return template.attributes?.primaryColor;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { FormGroup, NumericInput, Intent } from '@blueprintjs/core';
|
import { FormGroup, NumericInput, Intent } from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { CellType } from '@/constants';
|
import { CellType } from '@/constants';
|
||||||
import { CLASSES } from '@/constants/classes';
|
import { CLASSES } from '@/constants/classes';
|
||||||
|
|
||||||
@@ -12,34 +10,41 @@ import { CLASSES } from '@/constants/classes';
|
|||||||
export default function NumericInputCell({
|
export default function NumericInputCell({
|
||||||
row: { index },
|
row: { index },
|
||||||
column: { id },
|
column: { id },
|
||||||
cell: { value: initialValue },
|
cell: { value: controlledInputValue },
|
||||||
payload,
|
payload,
|
||||||
}) {
|
}: any) {
|
||||||
const [value, setValue] = useState(initialValue);
|
const [valueAsNumber, setValueAsNumber] = useState<number | null>(
|
||||||
|
controlledInputValue || null,
|
||||||
|
);
|
||||||
|
const handleInputValueChange = (
|
||||||
|
valueAsNumber: number,
|
||||||
|
valueAsString: string,
|
||||||
|
) => {
|
||||||
|
setValueAsNumber(valueAsNumber);
|
||||||
|
};
|
||||||
|
const handleInputBlur = () => {
|
||||||
|
payload.updateData(index, id, valueAsNumber);
|
||||||
|
};
|
||||||
|
|
||||||
const handleValueChange = (newValue) => {
|
|
||||||
setValue(newValue);
|
|
||||||
};
|
|
||||||
const onBlur = () => {
|
|
||||||
payload.updateData(index, id, value);
|
|
||||||
};
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue(initialValue);
|
setValueAsNumber(controlledInputValue);
|
||||||
}, [initialValue]);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [controlledInputValue]);
|
||||||
|
|
||||||
const error = payload.errors?.[index]?.[id];
|
const error = payload.errors?.[index]?.[id];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
intent={error ? Intent.DANGER : null}
|
intent={error ? Intent.DANGER : undefined}
|
||||||
className={classNames(CLASSES.FILL)}
|
className={classNames(CLASSES.FILL)}
|
||||||
>
|
>
|
||||||
<NumericInput
|
<NumericInput
|
||||||
value={value}
|
asyncControl
|
||||||
onValueChange={handleValueChange}
|
value={controlledInputValue}
|
||||||
onBlur={onBlur}
|
onValueChange={handleInputValueChange}
|
||||||
fill={true}
|
onBlur={handleInputBlur}
|
||||||
buttonPosition={'none'}
|
buttonPosition={'none'}
|
||||||
|
fill
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as R from 'ramda';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import { sumBy, setWith, toSafeInteger, get, first } from 'lodash';
|
import { sumBy, setWith, get, first, toNumber } from 'lodash';
|
||||||
import {
|
import {
|
||||||
updateTableCell,
|
updateTableCell,
|
||||||
repeatValue,
|
repeatValue,
|
||||||
@@ -91,8 +91,8 @@ export function transformToEditForm(manualJournal) {
|
|||||||
* Entries adjustment.
|
* Entries adjustment.
|
||||||
*/
|
*/
|
||||||
function adjustmentEntries(entries) {
|
function adjustmentEntries(entries) {
|
||||||
const credit = sumBy(entries, (e) => toSafeInteger(e.credit));
|
const credit = sumBy(entries, (e) => toNumber(e.credit));
|
||||||
const debit = sumBy(entries, (e) => toSafeInteger(e.debit));
|
const debit = sumBy(entries, (e) => toNumber(e.debit));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
debit: Math.max(credit - debit, 0),
|
debit: Math.max(credit - debit, 0),
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
Tag,
|
Tag,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import {
|
import {
|
||||||
FormatNumberCell,
|
|
||||||
TextOverviewTooltipCell,
|
TextOverviewTooltipCell,
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
Choose,
|
Choose,
|
||||||
@@ -51,9 +50,8 @@ export const useBillReadonlyEntriesTableColumns = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: intl.get('quantity'),
|
Header: intl.get('quantity'),
|
||||||
accessor: 'quantity',
|
accessor: 'quantity_formatted',
|
||||||
Cell: FormatNumberCell,
|
width: getColumnWidth(entries, 'quantity_formatted', {
|
||||||
width: getColumnWidth(entries, 'quantity', {
|
|
||||||
minWidth: 60,
|
minWidth: 60,
|
||||||
magicSpacing: 5,
|
magicSpacing: 5,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -48,9 +48,8 @@ export const useCreditNoteReadOnlyEntriesColumns = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: intl.get('quantity'),
|
Header: intl.get('quantity'),
|
||||||
accessor: 'quantity',
|
accessor: 'quantity_formatted',
|
||||||
Cell: FormatNumberCell,
|
width: getColumnWidth(entries, 'quantity_formatted', {
|
||||||
width: getColumnWidth(entries, 'quantity', {
|
|
||||||
minWidth: 60,
|
minWidth: 60,
|
||||||
magicSpacing: 5,
|
magicSpacing: 5,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -54,11 +54,10 @@ export const useInvoiceReadonlyEntriesColumns = () => {
|
|||||||
{
|
{
|
||||||
Header: intl.get('quantity'),
|
Header: intl.get('quantity'),
|
||||||
accessor: 'quantity',
|
accessor: 'quantity',
|
||||||
Cell: FormatNumberCell,
|
|
||||||
align: 'right',
|
align: 'right',
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
textOverview: true,
|
textOverview: true,
|
||||||
width: getColumnWidth(entries, 'quantity', {
|
width: getColumnWidth(entries, 'quantity_formatted', {
|
||||||
minWidth: 60,
|
minWidth: 60,
|
||||||
magicSpacing: 5,
|
magicSpacing: 5,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export const useEstimateTransactionsColumns = () => {
|
|||||||
{
|
{
|
||||||
id: 'qunatity',
|
id: 'qunatity',
|
||||||
Header: intl.get('item.drawer_quantity_sold'),
|
Header: intl.get('item.drawer_quantity_sold'),
|
||||||
accessor: 'quantity',
|
accessor: 'quantity_formatted',
|
||||||
align: 'right',
|
align: 'right',
|
||||||
width: 100,
|
width: 100,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,9 +31,8 @@ export const useReceiptReadonlyEntriesTableColumns = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: intl.get('quantity'),
|
Header: intl.get('quantity'),
|
||||||
accessor: 'quantity',
|
accessor: 'quantity_formatted',
|
||||||
Cell: FormatNumberCell,
|
width: getColumnWidth(entries, 'quantity_formatted', {
|
||||||
width: getColumnWidth(entries, 'quantity', {
|
|
||||||
minWidth: 60,
|
minWidth: 60,
|
||||||
magicSpacing: 5,
|
magicSpacing: 5,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
|
|
||||||
|
:root {
|
||||||
|
--payment-page-background-color: #fff;
|
||||||
|
--payment-page-primary-button: #0052cc;
|
||||||
|
}
|
||||||
|
|
||||||
.rootBodyPage {
|
.rootBodyPage {
|
||||||
background: #1c1d29;
|
background: var(--payment-page-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Text, Classes, Button, Intent } from '@blueprintjs/core';
|
import { Text, Classes, Button, Intent } from '@blueprintjs/core';
|
||||||
import clsx from 'classnames';
|
import clsx from 'classnames';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
import { AppToaster, Box, Group, Stack } from '@/components';
|
import { AppToaster, Box, Group, Stack } from '@/components';
|
||||||
import { usePaymentPortalBoot } from './PaymentPortalBoot';
|
import { usePaymentPortalBoot } from './PaymentPortalBoot';
|
||||||
import { useDrawerActions } from '@/hooks/state';
|
import { useDrawerActions } from '@/hooks/state';
|
||||||
@@ -66,11 +67,11 @@ export function PaymentPortal() {
|
|||||||
<Stack spacing={0} className={styles.body}>
|
<Stack spacing={0} className={styles.body}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Group spacing={10}>
|
<Group spacing={10}>
|
||||||
{sharableLinkMeta?.organization?.logoUri && (
|
{sharableLinkMeta?.brandingTemplate?.companyLogoUri && (
|
||||||
<Box
|
<Box
|
||||||
className={styles.companyLogoWrap}
|
className={styles.companyLogoWrap}
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${sharableLinkMeta?.organization?.logoUri})`,
|
backgroundImage: `url(${sharableLinkMeta?.brandingTemplate?.companyLogoUri})`,
|
||||||
}}
|
}}
|
||||||
></Box>
|
></Box>
|
||||||
)}
|
)}
|
||||||
@@ -170,7 +171,22 @@ export function PaymentPortal() {
|
|||||||
sharableLinkMeta?.hasStripePaymentMethod && (
|
sharableLinkMeta?.hasStripePaymentMethod && (
|
||||||
<Button
|
<Button
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
className={clsx(styles.footerButton, styles.buyButton)}
|
className={clsx(
|
||||||
|
styles.footerButton,
|
||||||
|
styles.buyButton,
|
||||||
|
css`
|
||||||
|
&.bp4-button.bp4-intent-primary {
|
||||||
|
background-color: var(--payment-page-primary-button);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: var(
|
||||||
|
--payment-page-primary-button-hover
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
)}
|
||||||
loading={isStripeCheckoutLoading}
|
loading={isStripeCheckoutLoading}
|
||||||
onClick={handlePayButtonClick}
|
onClick={handlePayButtonClick}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { PaymentPortalBoot, usePaymentPortalBoot } from './PaymentPortalBoot';
|
|||||||
import { PaymentInvoicePreviewDrawer } from './drawers/PaymentInvoicePreviewDrawer/PaymentInvoicePreviewDrawer';
|
import { PaymentInvoicePreviewDrawer } from './drawers/PaymentInvoicePreviewDrawer/PaymentInvoicePreviewDrawer';
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
import styles from './PaymentPortal.module.scss';
|
import styles from './PaymentPortal.module.scss';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { hsl, lighten, parseToHsl } from 'polished';
|
||||||
|
|
||||||
export default function PaymentPortalPage() {
|
export default function PaymentPortalPage() {
|
||||||
const { linkId } = useParams<{ linkId: string }>();
|
const { linkId } = useParams<{ linkId: string }>();
|
||||||
@@ -14,6 +16,7 @@ export default function PaymentPortalPage() {
|
|||||||
<BodyClassName className={styles.rootBodyPage}>
|
<BodyClassName className={styles.rootBodyPage}>
|
||||||
<PaymentPortalBoot linkId={linkId}>
|
<PaymentPortalBoot linkId={linkId}>
|
||||||
<PaymentPortalHelmet />
|
<PaymentPortalHelmet />
|
||||||
|
<PaymentPortalCssVariables />
|
||||||
<PaymentPortal />
|
<PaymentPortal />
|
||||||
<PaymentInvoicePreviewDrawer name={DRAWERS.PAYMENT_INVOICE_PREVIEW} />
|
<PaymentInvoicePreviewDrawer name={DRAWERS.PAYMENT_INVOICE_PREVIEW} />
|
||||||
</PaymentPortalBoot>
|
</PaymentPortalBoot>
|
||||||
@@ -36,3 +39,33 @@ function PaymentPortalHelmet() {
|
|||||||
</Helmet>
|
</Helmet>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the dynamic CSS variables for the current payment page.
|
||||||
|
* @returns {React.ReactNode}
|
||||||
|
*/
|
||||||
|
function PaymentPortalCssVariables() {
|
||||||
|
const { sharableLinkMeta } = usePaymentPortalBoot();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (sharableLinkMeta?.brandingTemplate?.primaryColor) {
|
||||||
|
const primaryColorHsl = parseToHsl(
|
||||||
|
sharableLinkMeta?.brandingTemplate?.primaryColor,
|
||||||
|
);
|
||||||
|
document.body.style.setProperty(
|
||||||
|
'--payment-page-background-color',
|
||||||
|
hsl(primaryColorHsl.hue, 0.19, 0.14),
|
||||||
|
);
|
||||||
|
document.body.style.setProperty(
|
||||||
|
'--payment-page-primary-button',
|
||||||
|
sharableLinkMeta?.brandingTemplate?.primaryColor,
|
||||||
|
);
|
||||||
|
document.body.style.setProperty(
|
||||||
|
'--payment-page-primary-button-hover',
|
||||||
|
lighten(0.05, sharableLinkMeta?.brandingTemplate?.primaryColor),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [sharableLinkMeta?.brandingTemplate?.primaryColor]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|||||||
@@ -106,6 +106,10 @@ export interface GetInvoicePaymentLinkResponse {
|
|||||||
taxRateAmountFormatted: string;
|
taxRateAmountFormatted: string;
|
||||||
taxRateCode: string;
|
taxRateCode: string;
|
||||||
}>;
|
}>;
|
||||||
|
brandingTemplate: {
|
||||||
|
companyLogoUri: string;
|
||||||
|
primaryColor: string;
|
||||||
|
};
|
||||||
organization: GetInvoicePaymentLinkOrganizationRes;
|
organization: GetInvoicePaymentLinkOrganizationRes;
|
||||||
hasStripePaymentMethod: boolean;
|
hasStripePaymentMethod: boolean;
|
||||||
isReceivable: boolean;
|
isReceivable: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user