mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-08 15:59:16 +00:00
refactor(logo): upload on submit form
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { type ProjectData } from 'ontime-types';
|
import { type ProjectData } from 'ontime-types';
|
||||||
import { ChangeEvent, useEffect, useRef } from 'react';
|
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
||||||
import { useFieldArray, useForm } from 'react-hook-form';
|
import { useFieldArray, useForm } from 'react-hook-form';
|
||||||
import { IoAdd, IoDownloadOutline, IoTrash } from 'react-icons/io5';
|
import { IoAdd, IoDownloadOutline, IoTrash } from 'react-icons/io5';
|
||||||
|
|
||||||
@@ -17,9 +17,16 @@ import * as Panel from '../../panel-utils/PanelUtils';
|
|||||||
|
|
||||||
import style from './SettingsPanel.module.scss';
|
import style from './SettingsPanel.module.scss';
|
||||||
|
|
||||||
|
type PendingLogo = {
|
||||||
|
file: File;
|
||||||
|
previewUrl: string;
|
||||||
|
uploadedFilename?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export default function ProjectData() {
|
export default function ProjectData() {
|
||||||
const { data, status } = useProjectData();
|
const { data, status } = useProjectData();
|
||||||
const { updateProjectData } = useUpdateProjectData();
|
const { updateProjectData } = useUpdateProjectData();
|
||||||
|
const [pendingLogo, setPendingLogo] = useState<PendingLogo | null>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@@ -45,13 +52,22 @@ export default function ProjectData() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// reset form values if data changes
|
// reset form values if data changes
|
||||||
|
// Release the browser-managed preview URL when the selection changes or this form unmounts.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
reset(data);
|
reset(data);
|
||||||
}
|
}
|
||||||
}, [data, reset]);
|
}, [data, reset]);
|
||||||
|
|
||||||
const handleUploadProjectLogo = async (event: ChangeEvent<HTMLInputElement>) => {
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (pendingLogo) {
|
||||||
|
URL.revokeObjectURL(pendingLogo.previewUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [pendingLogo]);
|
||||||
|
|
||||||
|
const handleUploadProjectLogo = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = event.target.files?.[0];
|
const file = event.target.files?.[0];
|
||||||
clearErrors('logo');
|
clearErrors('logo');
|
||||||
|
|
||||||
@@ -61,10 +77,9 @@ export default function ProjectData() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
validateLogo(file);
|
validateLogo(file);
|
||||||
const response = await uploadProjectLogo(file);
|
setPendingLogo({
|
||||||
|
file,
|
||||||
setValue('logo', response.data.logoFilename, {
|
previewUrl: URL.createObjectURL(file),
|
||||||
shouldDirty: true,
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = maybeAxiosError(error);
|
const message = maybeAxiosError(error);
|
||||||
@@ -86,6 +101,7 @@ export default function ProjectData() {
|
|||||||
const handleDeleteLogo = (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handleDeleteLogo = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
setPendingLogo(null);
|
||||||
setValue('logo', null, {
|
setValue('logo', null, {
|
||||||
shouldDirty: true,
|
shouldDirty: true,
|
||||||
});
|
});
|
||||||
@@ -95,10 +111,29 @@ export default function ProjectData() {
|
|||||||
append({ title: '', value: '', url: '' });
|
append({ title: '', value: '', url: '' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const uploadPendingLogo = async () => {
|
||||||
|
if (!pendingLogo) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingLogo.uploadedFilename) {
|
||||||
|
return pendingLogo.uploadedFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await uploadProjectLogo(pendingLogo.file);
|
||||||
|
const uploadedFilename = response.data.logoFilename;
|
||||||
|
setPendingLogo((currentPendingLogo) =>
|
||||||
|
currentPendingLogo?.file === pendingLogo.file ? { ...currentPendingLogo, uploadedFilename } : currentPendingLogo,
|
||||||
|
);
|
||||||
|
return uploadedFilename;
|
||||||
|
};
|
||||||
|
|
||||||
const onSubmit = async (formData: ProjectData) => {
|
const onSubmit = async (formData: ProjectData) => {
|
||||||
try {
|
try {
|
||||||
clearErrors();
|
clearErrors();
|
||||||
await updateProjectData(formData);
|
const logo = (await uploadPendingLogo()) ?? formData.logo;
|
||||||
|
await updateProjectData({ ...formData, logo });
|
||||||
|
setPendingLogo(null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = maybeAxiosError(error);
|
const message = maybeAxiosError(error);
|
||||||
setError('root', { message });
|
setError('root', { message });
|
||||||
@@ -107,10 +142,12 @@ export default function ProjectData() {
|
|||||||
|
|
||||||
// reset data to the initial state
|
// reset data to the initial state
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
|
setPendingLogo(null);
|
||||||
reset(data);
|
reset(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLoading = status === 'pending';
|
const isLoading = status === 'pending';
|
||||||
|
const logo = watch('logo');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel.Section as='form' onSubmit={handleSubmit(onSubmit)} onKeyDown={(event) => preventEscape(event, onReset)}>
|
<Panel.Section as='form' onSubmit={handleSubmit(onSubmit)} onKeyDown={(event) => preventEscape(event, onReset)}>
|
||||||
@@ -121,7 +158,12 @@ export default function ProjectData() {
|
|||||||
<Button onClick={onReset} disabled={isSubmitting || !isDirty}>
|
<Button onClick={onReset} disabled={isSubmitting || !isDirty}>
|
||||||
Revert to saved
|
Revert to saved
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='primary' type='submit' disabled={!isDirty || !isValid} loading={isSubmitting}>
|
<Button
|
||||||
|
variant='primary'
|
||||||
|
type='submit'
|
||||||
|
disabled={(!isDirty && !pendingLogo) || !isValid}
|
||||||
|
loading={isSubmitting}
|
||||||
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</Panel.InlineElements>
|
</Panel.InlineElements>
|
||||||
@@ -153,9 +195,9 @@ export default function ProjectData() {
|
|||||||
onChange={handleUploadProjectLogo}
|
onChange={handleUploadProjectLogo}
|
||||||
/>
|
/>
|
||||||
<Panel.Card className={style.uploadLogoCard}>
|
<Panel.Card className={style.uploadLogoCard}>
|
||||||
{watch('logo') ? (
|
{logo || pendingLogo ? (
|
||||||
<>
|
<>
|
||||||
<img src={`${projectLogoPath}/${watch('logo')}`} />
|
<img src={pendingLogo ? pendingLogo.previewUrl : `${projectLogoPath}/${logo}`} />
|
||||||
<div className={style.logoActions}>
|
<div className={style.logoActions}>
|
||||||
<Button disabled={isSubmitting} onClick={handleClickUpload} type='button'>
|
<Button disabled={isSubmitting} onClick={handleClickUpload} type='button'>
|
||||||
<IoDownloadOutline />
|
<IoDownloadOutline />
|
||||||
@@ -163,7 +205,7 @@ export default function ProjectData() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant='subtle-destructive'
|
variant='subtle-destructive'
|
||||||
disabled={isSubmitting || !watch('logo')}
|
disabled={isSubmitting || (!logo && !pendingLogo)}
|
||||||
onClick={handleDeleteLogo}
|
onClick={handleDeleteLogo}
|
||||||
type='button'
|
type='button'
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user