feat: add cover image upload functionality with loading state in article creation form

This commit is contained in:
2026-04-11 00:02:01 -03:00
parent 7943527106
commit d99ab44ec2
2 changed files with 32 additions and 5 deletions

View File

@@ -54,6 +54,7 @@ function validateContentFile(file: File): string | null {
export const CreateArticleForm = () => { export const CreateArticleForm = () => {
const [coverImageFile, setCoverImageFile] = useState<File | null>(null); const [coverImageFile, setCoverImageFile] = useState<File | null>(null);
const [coverImageUploading, setCoverImageUploading] = useState(false);
const [contentFile, setContentFile] = useState<File | null>(null); const [contentFile, setContentFile] = useState<File | null>(null);
const coverImageUrlRef = useRef<string | null>(null); const coverImageUrlRef = useRef<string | null>(null);
@@ -78,10 +79,8 @@ export const CreateArticleForm = () => {
}, },
}); });
const title = useWatch({ const title = useWatch({ control: form.control, name: 'title' });
control: form.control, const coverImageUrl = useWatch({ control: form.control, name: 'coverImageUrl' });
name: 'title',
});
useEffect(() => { useEffect(() => {
if (!title) return; if (!title) return;
form.setValue('slug', slugify(title).toLowerCase()); form.setValue('slug', slugify(title).toLowerCase());
@@ -93,6 +92,7 @@ export const CreateArticleForm = () => {
coverImageUrlRef.current = null; coverImageUrlRef.current = null;
} }
setCoverImageFile(null); setCoverImageFile(null);
setCoverImageUploading(false);
setContentFile(null); setContentFile(null);
}, []); }, []);
@@ -128,10 +128,13 @@ export const CreateArticleForm = () => {
setCoverImageFile(file); setCoverImageFile(file);
if (!file) { if (!file) {
setCoverImageFile(null); setCoverImageFile(null);
setCoverImageUploading(false);
form.setValue('coverImageUrl', ''); form.setValue('coverImageUrl', '');
return; return;
} }
setCoverImageUploading(true);
const fileMetadataResult = await uploadFile(file); const fileMetadataResult = await uploadFile(file);
setCoverImageUploading(false);
if (!fileMetadataResult.ok) { if (!fileMetadataResult.ok) {
setCoverImageFile(null); setCoverImageFile(null);
form.setValue('coverImageUrl', ''); form.setValue('coverImageUrl', '');
@@ -255,6 +258,8 @@ export const CreateArticleForm = () => {
label='Cover image' label='Cover image'
description='PNG, JPG, GIF, WebP accepted' description='PNG, JPG, GIF, WebP accepted'
error={form.formState.errors.coverImageUrl?.message} error={form.formState.errors.coverImageUrl?.message}
previewUrl={coverImageUrl || undefined}
isUploading={coverImageUploading}
icon={ icon={
<Image <Image
src={ImageLogo} src={ImageLogo}

View File

@@ -6,6 +6,7 @@ import {
FieldDescription, FieldDescription,
FieldError, FieldError,
} from '@/ui/components/shadcn/field'; } from '@/ui/components/shadcn/field';
import { Spinner } from '@/ui/components/shadcn/spinner';
import { import {
FileUpload, FileUpload,
FileUploadDropzone, FileUploadDropzone,
@@ -29,6 +30,8 @@ export interface FileUploadFieldProps {
description?: string; description?: string;
error?: string; error?: string;
icon?: React.ReactNode; icon?: React.ReactNode;
previewUrl?: string;
isUploading?: boolean;
} }
export const FileUploadField: React.FC<FileUploadFieldProps> = ({ export const FileUploadField: React.FC<FileUploadFieldProps> = ({
@@ -41,6 +44,8 @@ export const FileUploadField: React.FC<FileUploadFieldProps> = ({
description, description,
error, error,
icon, icon,
previewUrl,
isUploading,
}) => { }) => {
const handleAccept = useCallback( const handleAccept = useCallback(
(files: File[]) => { (files: File[]) => {
@@ -90,7 +95,24 @@ export const FileUploadField: React.FC<FileUploadFieldProps> = ({
<FileUploadList> <FileUploadList>
{file && ( {file && (
<FileUploadItem value={file}> <FileUploadItem value={file}>
<FileUploadItemPreview /> <FileUploadItemPreview
render={
isUploading
? () => (
<Spinner className='size-5 text-muted-foreground' />
)
: previewUrl
? () => (
// eslint-disable-next-line @next/next/no-img-element
<img
src={previewUrl}
alt='Uploaded image'
className='size-full object-cover'
/>
)
: undefined
}
/>
<FileUploadItemMetadata size='sm' /> <FileUploadItemMetadata size='sm' />
<FileUploadItemDelete asChild> <FileUploadItemDelete asChild>
<Button <Button