From 37fd4a1fdb033a08aae6bf03e73879bcee4df515 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 24 Sep 2024 20:28:19 +0200 Subject: [PATCH] feat: Uploading company logo --- .../services/PdfTemplate/GetPdfTemplate.ts | 11 +- .../PdfTemplate/GetPdfTemplateTransformer.ts | 62 +++++++++++ .../BrandingTemplateForm.tsx | 38 +++++-- .../containers/BrandingTemplates/_utils.ts | 19 +++- .../src/containers/BrandingTemplates/types.ts | 3 + .../ElementCustomize/ElementCustomize.tsx | 2 +- .../BrandingCompanyLogoUploadField.tsx | 22 ++++ .../components/CompanyLogoUpload.module.scss | 43 +++++++ .../components/CompanyLogoUpload.tsx | 105 ++++++++++++++++++ .../CreditNoteCustomizeGeneralFields.tsx | 23 ++-- .../EstimateCustomizeFieldsGeneral.tsx | 27 +++-- .../InvoiceCustomize/CompanyLogoUpload.tsx | 88 --------------- .../InvoiceCustomizeGeneralFields.tsx | 46 +++----- .../InvoiceCustomize/InvoicePaperTemplate.tsx | 6 +- .../Invoices/InvoiceCustomize/constants.ts | 4 +- .../Sales/Invoices/InvoiceCustomize/types.ts | 3 +- .../PaymentReceivedCustomizeFieldsGeneral.tsx | 24 ++-- .../ReceiptCustomizeFieldsGeneral.tsx | 23 ++-- .../webapp/src/hooks/query/attachments.ts | 17 ++- packages/webapp/src/utils/index.tsx | 9 +- 20 files changed, 404 insertions(+), 171 deletions(-) create mode 100644 packages/server/src/services/PdfTemplate/GetPdfTemplateTransformer.ts create mode 100644 packages/webapp/src/containers/ElementCustomize/components/BrandingCompanyLogoUploadField.tsx create mode 100644 packages/webapp/src/containers/ElementCustomize/components/CompanyLogoUpload.module.scss create mode 100644 packages/webapp/src/containers/ElementCustomize/components/CompanyLogoUpload.tsx delete mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/CompanyLogoUpload.tsx diff --git a/packages/server/src/services/PdfTemplate/GetPdfTemplate.ts b/packages/server/src/services/PdfTemplate/GetPdfTemplate.ts index c41fe651d..feaa402a2 100644 --- a/packages/server/src/services/PdfTemplate/GetPdfTemplate.ts +++ b/packages/server/src/services/PdfTemplate/GetPdfTemplate.ts @@ -1,12 +1,17 @@ import { Knex } from 'knex'; import { Inject, Service } from 'typedi'; import HasTenancyService from '../Tenancy/TenancyService'; +import { GetPdfTemplateTransformer } from './GetPdfTemplateTransformer'; +import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable'; @Service() export class GetPdfTemplate { @Inject() private tenancy: HasTenancyService; + @Inject() + private transformer: TransformerInjectable + /** * Retrieves a pdf template by its ID. * @param {number} tenantId - The ID of the tenant. @@ -24,6 +29,10 @@ export class GetPdfTemplate { .findById(templateId) .throwIfNotFound(); - return template; + return this.transformer.transform( + tenantId, + template, + new GetPdfTemplateTransformer() + ); } } diff --git a/packages/server/src/services/PdfTemplate/GetPdfTemplateTransformer.ts b/packages/server/src/services/PdfTemplate/GetPdfTemplateTransformer.ts new file mode 100644 index 000000000..d7977dced --- /dev/null +++ b/packages/server/src/services/PdfTemplate/GetPdfTemplateTransformer.ts @@ -0,0 +1,62 @@ +import { Transformer } from '@/lib/Transformer/Transformer'; +import { getTransactionTypeLabel } from '@/utils/transactions-types'; + +export class GetPdfTemplateTransformer extends Transformer { + /** + * Includeded attributes. + * @returns {string[]} + */ + public includeAttributes = (): string[] => { + return ['createdAtFormatted', 'resourceFormatted', 'attributes']; + }; + + /** + * Formats the creation date of the PDF template. + * @param {Object} template + * @returns {string} A formatted string representing the creation date of the template. + */ + protected createdAtFormatted = (template) => { + return this.formatDate(template.createdAt); + }; + + /** + * Formats the creation date of the PDF template. + * @param {Object} template - + * @returns {string} A formatted string representing the creation date of the template. + */ + protected resourceFormatted = (template) => { + return getTransactionTypeLabel(template.resource); + }; + + /** + * Retrieves transformed brand attributes. + * @param {} template + * @returns + */ + protected attributes = (template) => { + return this.item( + template.attributes, + new GetPdfTemplateAttributesTransformer() + ); + }; +} + +class GetPdfTemplateAttributesTransformer extends Transformer { + /** + * Includeded attributes. + * @returns {string[]} + */ + public includeAttributes = (): string[] => { + return ['companyLogoUri']; + }; + + /** + * Retrieves the company logo uri. + * @returns {string} + */ + protected companyLogoUri(template) { + return template.companyLogoKey + ? `https://bigcapital.sfo3.digitaloceanspaces.com/${template.companyLogoKey}` + : ''; + } +} diff --git a/packages/webapp/src/containers/BrandingTemplates/BrandingTemplateForm.tsx b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplateForm.tsx index af94643a1..97eaa2da9 100644 --- a/packages/webapp/src/containers/BrandingTemplates/BrandingTemplateForm.tsx +++ b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplateForm.tsx @@ -19,6 +19,7 @@ import { import { FormikHelpers } from 'formik'; import { BrandingTemplateValues } from './types'; import { useUploadAttachments } from '@/hooks/query/attachments'; +import { excludePrivateProps } from '@/utils'; interface BrandingTemplateFormProps extends ElementCustomizeProps { resource: string; @@ -41,7 +42,7 @@ export function BrandingTemplateForm({ const initialValues = useBrandingTemplateFormInitialValues(defaultValues); const [isUploading, setIsLoading] = useState(false); - + // Uploads the attachments. const { mutateAsync: uploadAttachments } = useUploadAttachments({ onSuccess: () => { @@ -53,38 +54,53 @@ export function BrandingTemplateForm({ // - Push the updated data. const handleFormSubmit = async ( values: T, - { setSubmitting }: FormikHelpers, + { setSubmitting, setFieldValue }: FormikHelpers, ) => { + const _values = { ...values }; + + // Handle create/edit request success. const handleSuccess = (message: string) => { AppToaster.show({ intent: Intent.SUCCESS, message }); setSubmitting(false); onSuccess && onSuccess(); }; + // Handle create/edit request error. const handleError = (message: string) => { AppToaster.show({ intent: Intent.DANGER, message }); setSubmitting(false); onError && onError(); }; - - if (values.companyLogoFile) { - isUploading(true); + // Start upload the company logo file if it is presented. + if (values._companyLogoFile) { + setIsLoading(true); const formData = new FormData(); const key = Date.now().toString(); - formData.append('file', values.companyLogoFile); + formData.append('file', values._companyLogoFile); formData.append('internalKey', key); try { - await uploadAttachments(formData); + const uploadedAttachmentRes = await uploadAttachments(formData); setIsLoading(false); + + // Adds the attachment key to the values after finishing upload. + _values['companyLogoKey'] = uploadedAttachmentRes?.key; } catch { handleError('An error occurred while uploading company logo.'); setIsLoading(false); return; } } + // Exclude all the private props that starts with _. + const excludedPrivateValues = excludePrivateProps(_values); + + // Transform the the form values to request based on the mode (new or edit mode). + const reqValues = templateId + ? transformToEditRequest(excludedPrivateValues, initialValues) + : transformToNewRequest(excludedPrivateValues, initialValues, resource); + + // Template id is presented means edit mode. if (templateId) { - const reqValues = transformToEditRequest(values); setSubmitting(true); try { @@ -94,7 +110,6 @@ export function BrandingTemplateForm({ handleError('An error occurred while updating the PDF template.'); } } else { - const reqValues = transformToNewRequest(values, resource); setSubmitting(true); try { @@ -119,3 +134,8 @@ export function BrandingTemplateForm({ export const validationSchema = Yup.object().shape({ templateName: Yup.string().required('Template Name is required'), }); + + +// Initial values - companyLogoKey, companyLogoUri +// Form - _companyLogoFile, companyLogoKey, companyLogoUri +// Request - companyLogoKey diff --git a/packages/webapp/src/containers/BrandingTemplates/_utils.ts b/packages/webapp/src/containers/BrandingTemplates/_utils.ts index 50e5122a8..33a6dbdf2 100644 --- a/packages/webapp/src/containers/BrandingTemplates/_utils.ts +++ b/packages/webapp/src/containers/BrandingTemplates/_utils.ts @@ -7,26 +7,35 @@ import { import { useBrandingTemplateBoot } from './BrandingTemplateBoot'; import { transformToForm } from '@/utils'; import { BrandingTemplateValues } from './types'; -import { useFormikContext } from 'formik'; import { DRAWERS } from '@/constants/drawers'; +const commonExcludedAttrs = ['templateName', 'companyLogoUri']; + export const transformToEditRequest = ( values: T, + defaultValues: T, ): EditPdfTemplateValues => { return { templateName: values.templateName, - attributes: omit(values, ['templateName']), + attributes: transformToForm( + omit(values, commonExcludedAttrs), + defaultValues, + ), }; }; export const transformToNewRequest = ( values: T, + defaultValues: T, resource: string, ): CreatePdfTemplateValues => { return { resource, templateName: values.templateName, - attributes: omit(values, ['templateName']), + attributes: transformToForm( + omit(values, commonExcludedAttrs), + defaultValues, + ), }; }; @@ -66,5 +75,5 @@ export const getButtonLabelFromResource = (resource: string) => { CreditNote: 'Create Credit Note Branding', PaymentReceive: 'Create Payment Branding', }; - return R.prop(resource, pairs) || 'Create Branding Template'; -} \ No newline at end of file + return R.prop(resource, pairs) || 'Create Branding Template'; +}; diff --git a/packages/webapp/src/containers/BrandingTemplates/types.ts b/packages/webapp/src/containers/BrandingTemplates/types.ts index a74109c46..e60c142f5 100644 --- a/packages/webapp/src/containers/BrandingTemplates/types.ts +++ b/packages/webapp/src/containers/BrandingTemplates/types.ts @@ -2,4 +2,7 @@ export interface BrandingTemplateValues { templateName: string; + + companyLogoKey?: string; + companyLogoUri?: string; } \ No newline at end of file diff --git a/packages/webapp/src/containers/ElementCustomize/ElementCustomize.tsx b/packages/webapp/src/containers/ElementCustomize/ElementCustomize.tsx index 8c55d16bc..05dd960ee 100644 --- a/packages/webapp/src/containers/ElementCustomize/ElementCustomize.tsx +++ b/packages/webapp/src/containers/ElementCustomize/ElementCustomize.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Box, Group } from '@/components'; +import { Group } from '@/components'; import { ElementCustomizeProvider } from './ElementCustomizeProvider'; import { ElementCustomizeForm, diff --git a/packages/webapp/src/containers/ElementCustomize/components/BrandingCompanyLogoUploadField.tsx b/packages/webapp/src/containers/ElementCustomize/components/BrandingCompanyLogoUploadField.tsx new file mode 100644 index 000000000..fe711cdc9 --- /dev/null +++ b/packages/webapp/src/containers/ElementCustomize/components/BrandingCompanyLogoUploadField.tsx @@ -0,0 +1,22 @@ +// @ts-nocheck +import { useFormikContext } from 'formik'; +import { FFormGroup } from '@/components'; +import { CompanyLogoUpload } from './CompanyLogoUpload'; + +export function BrandingCompanyLogoUploadField() { + const { setFieldValue, values } = useFormikContext(); + + return ( + + { + const imageUrl = file ? URL.createObjectURL(file) : ''; + + setFieldValue('_companyLogoFile', file); + setFieldValue('companyLogoUri', imageUrl); + }} + /> + + ); +} diff --git a/packages/webapp/src/containers/ElementCustomize/components/CompanyLogoUpload.module.scss b/packages/webapp/src/containers/ElementCustomize/components/CompanyLogoUpload.module.scss new file mode 100644 index 000000000..dd74e7b72 --- /dev/null +++ b/packages/webapp/src/containers/ElementCustomize/components/CompanyLogoUpload.module.scss @@ -0,0 +1,43 @@ +.root { + min-height: 120px; + height: 120px; + padding: 10px; + border: 1px solid; + display: flex; + border: 1px solid #E1E1E1; + position: relative; + display: flex; + justify-content: center; + + &:hover .removeButton{ + visibility: visible; + opacity: 1; + } +} + +.removeButton{ + position: absolute; + right: 5px; + top: 5px; + border-radius: 24px; + visibility: hidden; + opacity: 0; +} + +.contentPrePreview { + color: #738091; + font-size: 13px; + height: 100%; + justify-content: center; +} + +.dropzoneContent{ + height: 100%; + width: 100%; + text-align: center; +} + +.previewImage{ + max-width: 100%; + max-height: 100%; +} \ No newline at end of file diff --git a/packages/webapp/src/containers/ElementCustomize/components/CompanyLogoUpload.tsx b/packages/webapp/src/containers/ElementCustomize/components/CompanyLogoUpload.tsx new file mode 100644 index 000000000..2901cdea6 --- /dev/null +++ b/packages/webapp/src/containers/ElementCustomize/components/CompanyLogoUpload.tsx @@ -0,0 +1,105 @@ +// @ts-nocheck +import { useRef, useState } from 'react'; +import { Button, Intent } from '@blueprintjs/core'; +import { Icon, Stack } from '@/components'; +import { Dropzone, DropzoneProps } from '@/components/Dropzone'; +import { MIME_TYPES } from '@/components/Dropzone/mine-types'; +import { useUncontrolled } from '@/hooks/useUncontrolled'; +import styles from './CompanyLogoUpload.module.scss'; + +export interface CompanyLogoUploadProps { + /** Initial preview uri. */ + initialPreview?: string; + + /** The initial file object for uploading */ + initialValue?: File; + + /** The current file object for uploading */ + value?: File; + + /** Function called when the file is changed */ + onChange?: (file: File) => void; + + /** Props for the Dropzone component */ + dropzoneProps?: DropzoneProps; + + /** Icon element for the upload button */ + uploadIcon?: JSX.Element; + + /** Title displayed in the component */ + title?: string; + + /** Custom CSS class names for styling */ + classNames?: Record; +} + +export function CompanyLogoUpload({ + initialPreview, + initialValue, + value, + onChange, + dropzoneProps, + uploadIcon = , + title = 'Drag images here or click to select files', + classNames, +}: CompanyLogoUploadProps) { + const [localValue, handleChange] = useUncontrolled({ + value, + initialValue, + finalValue: null, + onChange, + }); + const [initialLocalPreview, setInitialLocalPreview] = useState( + initialPreview || null, + ); + + const openRef = useRef<() => void>(null); + + const handleRemove = () => { + handleChange(null); + setInitialLocalPreview(null); + }; + const imagePreviewUrl = localValue + ? URL.createObjectURL(localValue) + : initialLocalPreview || ''; + + return ( + handleChange(files[0])} + onReject={(files) => console.log('rejected files', files)} + maxSize={5 * 1024 ** 2} + accept={[MIME_TYPES.png, MIME_TYPES.jpeg]} + classNames={{ root: styles?.root, content: styles.dropzoneContent }} + activateOnClick={false} + openRef={openRef} + {...dropzoneProps} + > + {imagePreviewUrl ? ( + + + + + )} + + ); +} diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeGeneralFields.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeGeneralFields.tsx index 9519b4bc5..79a7c9f9d 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeGeneralFields.tsx +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeGeneralFields.tsx @@ -10,6 +10,7 @@ import { import { FColorInput } from '@/components/Forms/FColorInput'; import { Overlay } from '../../Invoices/InvoiceCustomize/Overlay'; import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils'; +import { BrandingCompanyLogoUploadField } from '@/containers/ElementCustomize/components/BrandingCompanyLogoUploadField'; export function CreditNoteCustomizeGeneralField() { const isTemplateNameFilled = useIsTemplateNamedFilled(); @@ -64,15 +65,23 @@ export function CreditNoteCustomizeGeneralField() { /> - - + - + style={{ marginBottom: 0 }} + > + + + + + diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx index 2a32b4412..a017599d5 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx @@ -1,16 +1,16 @@ // @ts-nocheck -import { Classes, Text } from '@blueprintjs/core'; +import { Classes } from '@blueprintjs/core'; import { FFormGroup, FInputGroup, FSwitch, FieldRequiredHint, - Group, Stack, } from '@/components'; import { FColorInput } from '@/components/Forms/FColorInput'; import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils'; import { Overlay } from '../../Invoices/InvoiceCustomize/Overlay'; +import { BrandingCompanyLogoUploadField } from '@/containers/ElementCustomize/components/BrandingCompanyLogoUploadField'; export function EstimateCustomizeGeneralField() { const isTemplateNameFilled = useIsTemplateNamedFilled(); @@ -65,15 +65,24 @@ export function EstimateCustomizeGeneralField() { /> - - + - + style={{ marginBottom: 0 }} + > + + + + + diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/CompanyLogoUpload.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/CompanyLogoUpload.tsx deleted file mode 100644 index f59f6e600..000000000 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/CompanyLogoUpload.tsx +++ /dev/null @@ -1,88 +0,0 @@ -// @ts-nocheck -import { useRef, useState } from 'react'; -import { Button, Intent } from '@blueprintjs/core'; -import clsx from 'classnames'; -import { Box, Icon, Stack } from '@/components'; -import { Dropzone, DropzoneProps } from '@/components/Dropzone'; -import { MIME_TYPES } from '@/components/Dropzone/mine-types'; -import { useUncontrolled } from '@/hooks/useUncontrolled'; -import styles from './CompanyLogoUpload.module.scss'; - -export interface CompanyLogoUploadProps { - initialValue?: File; - value?: File; - onChange?: (file: File) => void; - dropzoneProps?: DropzoneProps; - uploadIcon?: JSX.Element; - title?: string; - classNames?: Record; -} - -export function CompanyLogoUpload({ - initialValue, - value, - onChange, - dropzoneProps, - uploadIcon = , - title = 'Drag images here or click to select files', - classNames, -}: CompanyLogoUploadProps) { - const [localValue, handleChange] = useUncontrolled({ - value, - initialValue, - finalValue: null, - onChange, - }); - const openRef = useRef<() => void>(null); - - const handleRemove = () => { - handleChange(null); - }; - const imagePreviewUrl = localValue ? URL.createObjectURL(localValue) : ''; - - return ( - handleChange(files[0])} - onReject={(files) => console.log('rejected files', files)} - maxSize={5 * 1024 ** 2} - accept={[MIME_TYPES.png, MIME_TYPES.jpeg]} - classNames={{ root: styles?.root, content: styles.dropzoneContent }} - activateOnClick={false} - openRef={openRef} - {...dropzoneProps} - > - - {localValue ? ( - - - - - )} - - - ); -} diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx index 0efa1a591..11d789f05 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx @@ -12,8 +12,7 @@ import { FColorInput } from '@/components/Forms/FColorInput'; import { CreditCardIcon } from '@/icons/CreditCardIcon'; import { Overlay } from './Overlay'; import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils'; -import { CompanyLogoUpload } from './CompanyLogoUpload'; -import { useFormikContext } from 'formik'; +import { BrandingCompanyLogoUploadField } from '@/containers/ElementCustomize/components/BrandingCompanyLogoUploadField'; export function InvoiceCustomizeGeneralField() { const isTemplateNameFilled = useIsTemplateNamedFilled(); @@ -68,17 +67,23 @@ export function InvoiceCustomizeGeneralField() { /> - - + - + style={{ marginBottom: 0 }} + > + + - + + @@ -103,24 +108,9 @@ function InvoiceCustomizePaymentManage() { Accept payment methods - Manage + + Manage + ); } - -function CompanyLogoUploadField() { - const { setFieldValue } = useFormikContext(); - - return ( - - { - const imageUrl = file ? URL.createObjectURL(file) : ''; - - setFieldValue('companyLogoFile', file); - setFieldValue('companyLogo', imageUrl); - }} - /> - - ); -} diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx index b191b0875..0d37eddce 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx @@ -20,7 +20,7 @@ export interface InvoicePaperTemplateProps { secondaryColor?: string; showCompanyLogo?: boolean; - companyLogo?: string; + companyLogoUri?: string; showInvoiceNumber?: boolean; invoiceNumber?: string; @@ -95,7 +95,7 @@ export function InvoicePaperTemplate({ companyName = 'Bigcapital Technology, Inc.', showCompanyLogo = true, - companyLogo, + companyLogoUri, dueDate = 'September 3, 2024', dueDateLabel = 'Date due', @@ -185,7 +185,7 @@ export function InvoicePaperTemplate({ primaryColor={primaryColor} secondaryColor={secondaryColor} showCompanyLogo={showCompanyLogo} - companyLogo={companyLogo} + companyLogo={companyLogoUri} bigtitle={'Invoice'} > diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/constants.ts b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/constants.ts index 33b739dc5..e020c0d3e 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/constants.ts +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/constants.ts @@ -7,8 +7,8 @@ export const initialValues = { // Company logo. showCompanyLogo: true, - companyLogo: - 'https://cdn-development.mercury.com/demo-assets/avatars/mercury-demo-dark.png', + companyLogoKey: '', + companyLogoUri: '', // Top details. showInvoiceNumber: true, diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/types.ts b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/types.ts index f7ed6ea5e..6a2d6839e 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/types.ts +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/types.ts @@ -7,7 +7,8 @@ export interface InvoiceCustomizeValues extends BrandingTemplateValues { // Company Logo showCompanyLogo?: boolean; - companyLogo?: string; + companyLogoKey?: string; + companyLogoUri?: string; // Top details. showInvoiceNumber?: boolean; diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsGeneral.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsGeneral.tsx index ed3edaf22..94b1f29aa 100644 --- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsGeneral.tsx +++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsGeneral.tsx @@ -10,6 +10,7 @@ import { import { FColorInput } from '@/components/Forms/FColorInput'; import { Overlay } from '../../Invoices/InvoiceCustomize/Overlay'; import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils'; +import { BrandingCompanyLogoUploadField } from '@/containers/ElementCustomize/components/BrandingCompanyLogoUploadField'; export function PaymentReceivedCustomizeGeneralField() { const isTemplateNameFilled = useIsTemplateNamedFilled(); @@ -64,15 +65,24 @@ export function PaymentReceivedCustomizeGeneralField() { /> - - + - + style={{ marginBottom: 0 }} + > + + + + + diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsGeneral.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsGeneral.tsx index 81e7121a6..4e9e21993 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsGeneral.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsGeneral.tsx @@ -10,6 +10,7 @@ import { import { FColorInput } from '@/components/Forms/FColorInput'; import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils'; import { Overlay } from '../../Invoices/InvoiceCustomize/Overlay'; +import { BrandingCompanyLogoUploadField } from '@/containers/ElementCustomize/components/BrandingCompanyLogoUploadField'; export function ReceiptCustomizeGeneralField() { const isTemplateNameFilled = useIsTemplateNamedFilled(); @@ -64,15 +65,23 @@ export function ReceiptCustomizeGeneralField() { /> - - + - + style={{ marginBottom: 0 }} + > + + + + + diff --git a/packages/webapp/src/hooks/query/attachments.ts b/packages/webapp/src/hooks/query/attachments.ts index 4b92bbbcb..f7fb8887a 100644 --- a/packages/webapp/src/hooks/query/attachments.ts +++ b/packages/webapp/src/hooks/query/attachments.ts @@ -1,6 +1,16 @@ // @ts-nocheck import { useMutation } from 'react-query'; import useApiRequest from '../useRequest'; +import { transformToCamelCase } from '@/utils'; + +interface UploadAttachmentResponse { + createdAt: string; + id: number; + key: string; + mimeType: string; + originName: string; + size: number; +} /** * Uploads the given attachments. @@ -8,8 +18,11 @@ import useApiRequest from '../useRequest'; export function useUploadAttachments(props) { const apiRequest = useApiRequest(); - return useMutation( - (values) => apiRequest.post('attachments', values), + return useMutation( + (values) => + apiRequest + .post('attachments', values) + .then((res) => transformToCamelCase(res.data?.data)), props, ); } diff --git a/packages/webapp/src/utils/index.tsx b/packages/webapp/src/utils/index.tsx index 07e357515..5d5c0b35e 100644 --- a/packages/webapp/src/utils/index.tsx +++ b/packages/webapp/src/utils/index.tsx @@ -13,7 +13,6 @@ import jsCookie from 'js-cookie'; import { deepMapKeys } from './map-key-deep'; export * from './deep'; - export const getCookie = (name, defaultValue) => _.defaultTo(jsCookie.get(name), defaultValue); @@ -352,6 +351,14 @@ export const transformToForm = (obj, emptyInitialValues) => { ); }; +export function excludePrivateProps( + obj: Record, +): Record { + return Object.fromEntries( + Object.entries(obj).filter(([key, value]) => !key.startsWith('_')), + ); +} + export function inputIntent({ error, touched }) { return error && touched ? Intent.DANGER : ''; }