mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-14 11:50:31 +00:00
feat: Uploading company logo
This commit is contained in:
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}`
|
||||
: '';
|
||||
}
|
||||
}
|
||||
@@ -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<T> extends ElementCustomizeProps<T> {
|
||||
resource: string;
|
||||
@@ -41,7 +42,7 @@ export function BrandingTemplateForm<T extends BrandingTemplateValues>({
|
||||
|
||||
const initialValues = useBrandingTemplateFormInitialValues<T>(defaultValues);
|
||||
const [isUploading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
|
||||
// Uploads the attachments.
|
||||
const { mutateAsync: uploadAttachments } = useUploadAttachments({
|
||||
onSuccess: () => {
|
||||
@@ -53,38 +54,53 @@ export function BrandingTemplateForm<T extends BrandingTemplateValues>({
|
||||
// - Push the updated data.
|
||||
const handleFormSubmit = async (
|
||||
values: T,
|
||||
{ setSubmitting }: FormikHelpers<T>,
|
||||
{ setSubmitting, setFieldValue }: FormikHelpers<T>,
|
||||
) => {
|
||||
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<T extends BrandingTemplateValues>({
|
||||
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<T extends BrandingTemplateValues>({
|
||||
export const validationSchema = Yup.object().shape({
|
||||
templateName: Yup.string().required('Template Name is required'),
|
||||
});
|
||||
|
||||
|
||||
// Initial values - companyLogoKey, companyLogoUri
|
||||
// Form - _companyLogoFile, companyLogoKey, companyLogoUri
|
||||
// Request - companyLogoKey
|
||||
|
||||
@@ -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 = <T extends BrandingTemplateValues>(
|
||||
values: T,
|
||||
defaultValues: T,
|
||||
): EditPdfTemplateValues => {
|
||||
return {
|
||||
templateName: values.templateName,
|
||||
attributes: omit(values, ['templateName']),
|
||||
attributes: transformToForm(
|
||||
omit(values, commonExcludedAttrs),
|
||||
defaultValues,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export const transformToNewRequest = <T extends BrandingTemplateValues>(
|
||||
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';
|
||||
}
|
||||
return R.prop(resource, pairs) || 'Create Branding Template';
|
||||
};
|
||||
|
||||
@@ -2,4 +2,7 @@
|
||||
|
||||
export interface BrandingTemplateValues {
|
||||
templateName: string;
|
||||
|
||||
companyLogoKey?: string;
|
||||
companyLogoUri?: string;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Box, Group } from '@/components';
|
||||
import { Group } from '@/components';
|
||||
import { ElementCustomizeProvider } from './ElementCustomizeProvider';
|
||||
import {
|
||||
ElementCustomizeForm,
|
||||
|
||||
@@ -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 (
|
||||
<FFormGroup name={'companyLogo'} label={''} fastField>
|
||||
<CompanyLogoUpload
|
||||
initialPreview={values.companyLogoUri}
|
||||
onChange={(file) => {
|
||||
const imageUrl = file ? URL.createObjectURL(file) : '';
|
||||
|
||||
setFieldValue('_companyLogoFile', file);
|
||||
setFieldValue('companyLogoUri', imageUrl);
|
||||
}}
|
||||
/>
|
||||
</FFormGroup>
|
||||
);
|
||||
}
|
||||
@@ -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%;
|
||||
}
|
||||
@@ -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<string, string>;
|
||||
}
|
||||
|
||||
export function CompanyLogoUpload({
|
||||
initialPreview,
|
||||
initialValue,
|
||||
value,
|
||||
onChange,
|
||||
dropzoneProps,
|
||||
uploadIcon = <Icon icon="download" iconSize={26} />,
|
||||
title = 'Drag images here or click to select files',
|
||||
classNames,
|
||||
}: CompanyLogoUploadProps) {
|
||||
const [localValue, handleChange] = useUncontrolled<File | null>({
|
||||
value,
|
||||
initialValue,
|
||||
finalValue: null,
|
||||
onChange,
|
||||
});
|
||||
const [initialLocalPreview, setInitialLocalPreview] = useState<string | null>(
|
||||
initialPreview || null,
|
||||
);
|
||||
|
||||
const openRef = useRef<() => void>(null);
|
||||
|
||||
const handleRemove = () => {
|
||||
handleChange(null);
|
||||
setInitialLocalPreview(null);
|
||||
};
|
||||
const imagePreviewUrl = localValue
|
||||
? URL.createObjectURL(localValue)
|
||||
: initialLocalPreview || '';
|
||||
|
||||
return (
|
||||
<Dropzone
|
||||
onDrop={(files) => 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 ? (
|
||||
<span>
|
||||
<img src={imagePreviewUrl} alt="" className={styles.previewImage} />
|
||||
<Button
|
||||
minimal
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleRemove}
|
||||
icon={<Icon icon={'smallCross'} iconSize={16} />}
|
||||
className={styles?.removeButton}
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
<Stack spacing={10} align="center" className={styles.contentPrePreview}>
|
||||
{title && <span className={styles.title}>{title}</span>}
|
||||
<Button
|
||||
intent="none"
|
||||
onClick={() => openRef.current?.()}
|
||||
style={{ pointerEvents: 'all' }}
|
||||
minimal
|
||||
outlined
|
||||
small
|
||||
>
|
||||
{'Upload File'}
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
</Dropzone>
|
||||
);
|
||||
}
|
||||
@@ -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() {
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup name={'showCompanyLogo'} label={'Logo'} fastField>
|
||||
<FSwitch
|
||||
<Stack spacing={10}>
|
||||
<FFormGroup
|
||||
name={'showCompanyLogo'}
|
||||
label={'Display company logo in the paper'}
|
||||
style={{ fontSize: 14 }}
|
||||
large
|
||||
label={'Logo'}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<FSwitch
|
||||
name={'showCompanyLogo'}
|
||||
label={'Display company logo in the paper'}
|
||||
style={{ fontSize: 14 }}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<BrandingCompanyLogoUploadField />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Overlay>
|
||||
</Stack>
|
||||
|
||||
@@ -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() {
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup name={'showCompanyLogo'} label={'Logo'} fastField>
|
||||
<FSwitch
|
||||
<Stack spacing={10}>
|
||||
<FFormGroup
|
||||
name={'showCompanyLogo'}
|
||||
label={'Display company logo in the paper'}
|
||||
style={{ fontSize: 14 }}
|
||||
large
|
||||
label={'Logo'}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<FSwitch
|
||||
name={'showCompanyLogo'}
|
||||
label={'Display company logo in the paper'}
|
||||
style={{ fontSize: 14 }}
|
||||
large
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<BrandingCompanyLogoUploadField />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Overlay>
|
||||
</Stack>
|
||||
|
||||
@@ -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<string, string>;
|
||||
}
|
||||
|
||||
export function CompanyLogoUpload({
|
||||
initialValue,
|
||||
value,
|
||||
onChange,
|
||||
dropzoneProps,
|
||||
uploadIcon = <Icon icon="download" iconSize={26} />,
|
||||
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 (
|
||||
<Dropzone
|
||||
onDrop={(files) => 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}
|
||||
>
|
||||
<Stack
|
||||
spacing={12}
|
||||
align="center"
|
||||
className={clsx(styles.content, classNames?.content)}
|
||||
>
|
||||
{localValue ? (
|
||||
<Stack spacing={10} justify="center" align="center">
|
||||
<img src={imagePreviewUrl} alt="" className={styles.previewImage} />
|
||||
<Button
|
||||
minimal
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleRemove}
|
||||
icon={<Icon icon={'smallCross'} iconSize={16} />}
|
||||
className={styles?.removeButton}
|
||||
/>
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack spacing={10} align="center">
|
||||
{title && <span className={styles.title}>{title}</span>}
|
||||
<Button
|
||||
intent="none"
|
||||
onClick={() => openRef.current?.()}
|
||||
style={{ pointerEvents: 'all' }}
|
||||
minimal
|
||||
outlined
|
||||
small
|
||||
>
|
||||
{'Upload File'}
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Dropzone>
|
||||
);
|
||||
}
|
||||
@@ -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() {
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup name={'showCompanyLogo'} label={'Logo'} fastField>
|
||||
<FSwitch
|
||||
<Stack spacing={10}>
|
||||
<FFormGroup
|
||||
name={'showCompanyLogo'}
|
||||
label={'Display company logo in the paper'}
|
||||
style={{ fontSize: 14 }}
|
||||
large
|
||||
label={'Logo'}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<FSwitch
|
||||
name={'showCompanyLogo'}
|
||||
label={'Display company logo in the paper'}
|
||||
style={{ fontSize: 14 }}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<CompanyLogoUploadField />
|
||||
<BrandingCompanyLogoUploadField />
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<InvoiceCustomizePaymentManage />
|
||||
@@ -103,24 +108,9 @@ function InvoiceCustomizePaymentManage() {
|
||||
<Text>Accept payment methods</Text>
|
||||
</Group>
|
||||
|
||||
<a style={{ fontSize: 13 }} href={'#'}>Manage</a>
|
||||
<a style={{ fontSize: 13 }} href={'#'}>
|
||||
Manage
|
||||
</a>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
function CompanyLogoUploadField() {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
|
||||
return (
|
||||
<FFormGroup name={'companyLogo'} label={''} fastField>
|
||||
<CompanyLogoUpload
|
||||
onChange={(file) => {
|
||||
const imageUrl = file ? URL.createObjectURL(file) : '';
|
||||
|
||||
setFieldValue('companyLogoFile', file);
|
||||
setFieldValue('companyLogo', imageUrl);
|
||||
}}
|
||||
/>
|
||||
</FFormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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'}
|
||||
>
|
||||
<Stack spacing={24}>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -7,7 +7,8 @@ export interface InvoiceCustomizeValues extends BrandingTemplateValues {
|
||||
|
||||
// Company Logo
|
||||
showCompanyLogo?: boolean;
|
||||
companyLogo?: string;
|
||||
companyLogoKey?: string;
|
||||
companyLogoUri?: string;
|
||||
|
||||
// Top details.
|
||||
showInvoiceNumber?: boolean;
|
||||
|
||||
@@ -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() {
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup name={'showCompanyLogo'} label={'Logo'} fastField>
|
||||
<FSwitch
|
||||
<Stack spacing={10}>
|
||||
<FFormGroup
|
||||
name={'showCompanyLogo'}
|
||||
label={'Display company logo in the paper'}
|
||||
style={{ fontSize: 14 }}
|
||||
large
|
||||
label={'Logo'}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<FSwitch
|
||||
name={'showCompanyLogo'}
|
||||
label={'Display company logo in the paper'}
|
||||
style={{ fontSize: 14 }}
|
||||
large
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<BrandingCompanyLogoUploadField />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Overlay>
|
||||
</Stack>
|
||||
|
||||
@@ -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() {
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup name={'showCompanyLogo'} label={'Logo'} fastField>
|
||||
<FSwitch
|
||||
<Stack spacing={10}>
|
||||
<FFormGroup
|
||||
name={'showCompanyLogo'}
|
||||
label={'Display company logo in the paper'}
|
||||
style={{ fontSize: 14 }}
|
||||
large
|
||||
label={'Logo'}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<FSwitch
|
||||
name={'showCompanyLogo'}
|
||||
label={'Display company logo in the paper'}
|
||||
style={{ fontSize: 14 }}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<BrandingCompanyLogoUploadField />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Overlay>
|
||||
</Stack>
|
||||
|
||||
@@ -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<UploadAttachmentResponse>(
|
||||
(values) =>
|
||||
apiRequest
|
||||
.post('attachments', values)
|
||||
.then((res) => transformToCamelCase(res.data?.data)),
|
||||
props,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<string, any>,
|
||||
): Record<string, any> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).filter(([key, value]) => !key.startsWith('_')),
|
||||
);
|
||||
}
|
||||
|
||||
export function inputIntent({ error, touched }) {
|
||||
return error && touched ? Intent.DANGER : '';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user