mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-17 05:13:32 +00:00
Upload corporate logo (#1314)
* feat: upload logo to project data --------- Co-authored-by: Carlos Valente <carlosvalente@pm.me>
This commit is contained in:
@@ -35,9 +35,9 @@ export function Paragraph({ children }: { children: ReactNode }) {
|
||||
return <p className={style.paragraph}>{children}</p>;
|
||||
}
|
||||
|
||||
export function Card({ children, ...props }: { children: ReactNode } & JSX.IntrinsicElements['div']) {
|
||||
export function Card({ children, className, ...props }: { children: ReactNode } & JSX.IntrinsicElements['div']) {
|
||||
return (
|
||||
<div className={style.card} {...props}>
|
||||
<div className={cx([style.card, className])} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useEffect } from 'react';
|
||||
import { ChangeEvent, useEffect, useRef } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Button, Input, Textarea } from '@chakra-ui/react';
|
||||
import { IoDownloadOutline } from '@react-icons/all-files/io5/IoDownloadOutline';
|
||||
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
||||
import { type ProjectData } from 'ontime-types';
|
||||
|
||||
import { postProjectData } from '../../../../common/api/project';
|
||||
import { projectLogoPath } from '../../../../common/api/constants';
|
||||
import { postProjectData, uploadProjectLogo } from '../../../../common/api/project';
|
||||
import { maybeAxiosError } from '../../../../common/api/utils';
|
||||
import useProjectData from '../../../../common/hooks-query/useProjectData';
|
||||
import { validateLogo } from '../../../../common/utils/uploadUtils';
|
||||
import * as Panel from '../../panel-utils/PanelUtils';
|
||||
|
||||
import style from './ProjectPanel.module.scss';
|
||||
@@ -17,8 +21,10 @@ export default function ProjectData() {
|
||||
handleSubmit,
|
||||
register,
|
||||
reset,
|
||||
formState: { isSubmitting, isValid, isDirty },
|
||||
formState: { isSubmitting, isValid, isDirty, errors },
|
||||
setError,
|
||||
watch,
|
||||
setValue,
|
||||
} = useForm({
|
||||
defaultValues: data,
|
||||
values: data,
|
||||
@@ -34,6 +40,40 @@ export default function ProjectData() {
|
||||
}
|
||||
}, [data, reset]);
|
||||
|
||||
const handleUploadProjectLogo = async (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
validateLogo(file);
|
||||
const response = await uploadProjectLogo(file);
|
||||
|
||||
setValue('projectLogo', response.data.logoFilename, {
|
||||
shouldDirty: true,
|
||||
});
|
||||
} catch (error) {
|
||||
const message = maybeAxiosError(error);
|
||||
setError('projectLogo', { message });
|
||||
}
|
||||
};
|
||||
|
||||
const { ref, ...projectLogoRest } = register('projectLogo');
|
||||
|
||||
const uploadInputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const handleClickUpload = () => {
|
||||
uploadInputRef.current?.click();
|
||||
};
|
||||
|
||||
const handleDeleteLogo = () => {
|
||||
setValue('projectLogo', null, {
|
||||
shouldDirty: true,
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = async (formData: ProjectData) => {
|
||||
try {
|
||||
await postProjectData(formData);
|
||||
@@ -86,6 +126,53 @@ export default function ProjectData() {
|
||||
{...register('title')}
|
||||
/>
|
||||
</label>
|
||||
<Panel.Section style={{ marginTop: 0 }}>
|
||||
<label>
|
||||
Project logo
|
||||
<Input
|
||||
variant='ontime-filled'
|
||||
size='sm'
|
||||
type='file'
|
||||
style={{ display: 'none' }}
|
||||
accept='image/*'
|
||||
{...projectLogoRest}
|
||||
ref={(e) => {
|
||||
ref(e);
|
||||
uploadInputRef.current = e;
|
||||
}}
|
||||
onChange={handleUploadProjectLogo}
|
||||
/>
|
||||
<Panel.Card className={style.uploadLogoCard}>
|
||||
{watch('projectLogo') ? (
|
||||
<>
|
||||
<img src={`${projectLogoPath}/${watch('projectLogo')}`} />
|
||||
<Button
|
||||
size='sm'
|
||||
variant='ontime-filled'
|
||||
isDisabled={isSubmitting || !watch('projectLogo')}
|
||||
leftIcon={<IoTrash />}
|
||||
onClick={handleDeleteLogo}
|
||||
type='button'
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
variant='ontime-filled'
|
||||
size='sm'
|
||||
isDisabled={isSubmitting}
|
||||
leftIcon={<IoDownloadOutline />}
|
||||
onClick={handleClickUpload}
|
||||
type='button'
|
||||
>
|
||||
Upload logo
|
||||
</Button>
|
||||
)}
|
||||
{errors?.projectLogo?.message && <Panel.Error>{errors.projectLogo.message}</Panel.Error>}
|
||||
</Panel.Card>
|
||||
</label>
|
||||
</Panel.Section>
|
||||
<label>
|
||||
Project description
|
||||
<Input
|
||||
|
||||
@@ -61,3 +61,26 @@
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.uploadLogoCard {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
background-color: $gray-1350;
|
||||
border: 1px solid $white-10;
|
||||
border-radius: 3px;
|
||||
|
||||
img {
|
||||
max-width: 250px;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user