mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-23 16:19:49 +00:00
feat: Upload company logo to invoice templates
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
|
// @ts-nocheck
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
ElementCustomize,
|
ElementCustomize,
|
||||||
ElementCustomizeProps,
|
ElementCustomizeProps,
|
||||||
@@ -16,6 +18,7 @@ import {
|
|||||||
} from '@/hooks/query/pdf-templates';
|
} from '@/hooks/query/pdf-templates';
|
||||||
import { FormikHelpers } from 'formik';
|
import { FormikHelpers } from 'formik';
|
||||||
import { BrandingTemplateValues } from './types';
|
import { BrandingTemplateValues } from './types';
|
||||||
|
import { useUploadAttachments } from '@/hooks/query/attachments';
|
||||||
|
|
||||||
interface BrandingTemplateFormProps<T> extends ElementCustomizeProps<T> {
|
interface BrandingTemplateFormProps<T> extends ElementCustomizeProps<T> {
|
||||||
resource: string;
|
resource: string;
|
||||||
@@ -37,8 +40,21 @@ export function BrandingTemplateForm<T extends BrandingTemplateValues>({
|
|||||||
const { mutateAsync: editPdfTemplate } = useEditPdfTemplate();
|
const { mutateAsync: editPdfTemplate } = useEditPdfTemplate();
|
||||||
|
|
||||||
const initialValues = useBrandingTemplateFormInitialValues<T>(defaultValues);
|
const initialValues = useBrandingTemplateFormInitialValues<T>(defaultValues);
|
||||||
|
const [isUploading, setIsLoading] = useState<boolean>(false);
|
||||||
const handleFormSubmit = (values: T, { setSubmitting }: FormikHelpers<T>) => {
|
|
||||||
|
// Uploads the attachments.
|
||||||
|
const { mutateAsync: uploadAttachments } = useUploadAttachments({
|
||||||
|
onSuccess: () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Handles the form submitting.
|
||||||
|
// - Uploads the company logos.
|
||||||
|
// - Push the updated data.
|
||||||
|
const handleFormSubmit = async (
|
||||||
|
values: T,
|
||||||
|
{ setSubmitting }: FormikHelpers<T>,
|
||||||
|
) => {
|
||||||
const handleSuccess = (message: string) => {
|
const handleSuccess = (message: string) => {
|
||||||
AppToaster.show({ intent: Intent.SUCCESS, message });
|
AppToaster.show({ intent: Intent.SUCCESS, message });
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
@@ -49,26 +65,44 @@ export function BrandingTemplateForm<T extends BrandingTemplateValues>({
|
|||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
onError && onError();
|
onError && onError();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (values.companyLogoFile) {
|
||||||
|
isUploading(true);
|
||||||
|
const formData = new FormData();
|
||||||
|
const key = Date.now().toString();
|
||||||
|
|
||||||
|
formData.append('file', values.companyLogoFile);
|
||||||
|
formData.append('internalKey', key);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await uploadAttachments(formData);
|
||||||
|
setIsLoading(false);
|
||||||
|
} catch {
|
||||||
|
handleError('An error occurred while uploading company logo.');
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (templateId) {
|
if (templateId) {
|
||||||
const reqValues = transformToEditRequest(values);
|
const reqValues = transformToEditRequest(values);
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|
||||||
// Edit existing template
|
try {
|
||||||
editPdfTemplate({ templateId, values: reqValues })
|
await editPdfTemplate({ templateId, values: reqValues });
|
||||||
.then(() => handleSuccess('PDF template updated successfully!'))
|
handleSuccess('PDF template updated successfully!');
|
||||||
.catch(() =>
|
} catch {
|
||||||
handleError('An error occurred while updating the PDF template.'),
|
handleError('An error occurred while updating the PDF template.');
|
||||||
);
|
}
|
||||||
} else {
|
} else {
|
||||||
const reqValues = transformToNewRequest(values, resource);
|
const reqValues = transformToNewRequest(values, resource);
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|
||||||
// Create new template
|
try {
|
||||||
createPdfTemplate(reqValues)
|
await createPdfTemplate(reqValues);
|
||||||
.then(() => handleSuccess('PDF template created successfully!'))
|
handleSuccess('PDF template created successfully!');
|
||||||
.catch(() =>
|
} catch {
|
||||||
handleError('An error occurred while creating the PDF template.'),
|
handleError('An error occurred while creating the PDF template.');
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// @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,6 +12,8 @@ import { FColorInput } from '@/components/Forms/FColorInput';
|
|||||||
import { CreditCardIcon } from '@/icons/CreditCardIcon';
|
import { CreditCardIcon } from '@/icons/CreditCardIcon';
|
||||||
import { Overlay } from './Overlay';
|
import { Overlay } from './Overlay';
|
||||||
import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils';
|
import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils';
|
||||||
|
import { CompanyLogoUpload } from './CompanyLogoUpload';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
|
||||||
export function InvoiceCustomizeGeneralField() {
|
export function InvoiceCustomizeGeneralField() {
|
||||||
const isTemplateNameFilled = useIsTemplateNamedFilled();
|
const isTemplateNameFilled = useIsTemplateNamedFilled();
|
||||||
@@ -75,6 +77,8 @@ export function InvoiceCustomizeGeneralField() {
|
|||||||
fastField
|
fastField
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
|
<CompanyLogoUploadField />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<InvoiceCustomizePaymentManage />
|
<InvoiceCustomizePaymentManage />
|
||||||
@@ -99,7 +103,24 @@ function InvoiceCustomizePaymentManage() {
|
|||||||
<Text>Accept payment methods</Text>
|
<Text>Accept payment methods</Text>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<a href={'#'}>Manage</a>
|
<a style={{ fontSize: 13 }} href={'#'}>Manage</a>
|
||||||
</Group>
|
</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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user