feat: Upload company logo to invoice templates

This commit is contained in:
Ahmed Bouhuolia
2024-09-22 00:01:12 +02:00
parent 5e7cff0eb7
commit d16c57b63b
3 changed files with 158 additions and 15 deletions

View File

@@ -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.');
); }
} }
}; };

View File

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

View File

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