fix: attach branding template attrs to payment page

This commit is contained in:
Ahmed Bouhuolia
2024-11-11 20:25:42 +02:00
parent c58822fd6c
commit 7ee3392d3e
6 changed files with 93 additions and 6 deletions

View File

@@ -25,7 +25,7 @@ export class GetInvoicePaymentLinkMetadata {
.findOne('linkId', linkId) .findOne('linkId', linkId)
.where('resourceType', 'SaleInvoice') .where('resourceType', 'SaleInvoice')
.throwIfNotFound(); .throwIfNotFound();
// Validate the expiry at date. // Validate the expiry at date.
if (paymentLink.expiryAt) { if (paymentLink.expiryAt) {
const currentDate = moment(); const currentDate = moment();
@@ -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(

View File

@@ -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
@@ -114,7 +128,7 @@ export class GetInvoicePaymentLinkMetaTransformer extends SaleInvoiceTransformer
/** /**
* Retrieves the formatted customer address. * Retrieves the formatted customer address.
* @param invoice * @param invoice
* @returns {string} * @returns {string}
*/ */
protected formattedCustomerAddress(invoice) { protected formattedCustomerAddress(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;
};
}

View File

@@ -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 {

View File

@@ -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}
> >

View File

@@ -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;
}

View File

@@ -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;