-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: image upload modals, file size limit added to config (#2868)
* chore: add file size limit as config in the config api * refactor: image upload modals --------- Co-authored-by: Aaryan Khandelwal <[email protected]>
- Loading branch information
1 parent
af3267a
commit 1bd38ad
Showing
13 changed files
with
470 additions
and
130 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
export * from "./bulk-delete-issues-modal"; | ||
export * from "./existing-issues-list-modal"; | ||
export * from "./gpt-assistant-modal"; | ||
export * from "./image-upload-modal"; | ||
export * from "./link-modal"; | ||
export * from "./user-image-upload-modal"; | ||
export * from "./workspace-image-upload-modal"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
import React, { useState } from "react"; | ||
import { observer } from "mobx-react-lite"; | ||
import { useDropzone } from "react-dropzone"; | ||
import { Transition, Dialog } from "@headlessui/react"; | ||
// mobx store | ||
import { useMobxStore } from "lib/mobx/store-provider"; | ||
// services | ||
import { FileService } from "services/file.service"; | ||
// hooks | ||
import useToast from "hooks/use-toast"; | ||
// ui | ||
import { Button } from "@plane/ui"; | ||
// icons | ||
import { UserCircle2 } from "lucide-react"; | ||
// constants | ||
import { MAX_FILE_SIZE } from "constants/common"; | ||
|
||
type Props = { | ||
handleDelete?: () => void; | ||
isOpen: boolean; | ||
isRemoving: boolean; | ||
onClose: () => void; | ||
onSuccess: (url: string) => void; | ||
value: string | null; | ||
}; | ||
|
||
// services | ||
const fileService = new FileService(); | ||
|
||
export const UserImageUploadModal: React.FC<Props> = observer((props) => { | ||
const { value, onSuccess, isOpen, onClose, isRemoving, handleDelete } = props; | ||
// states | ||
const [image, setImage] = useState<File | null>(null); | ||
const [isImageUploading, setIsImageUploading] = useState(false); | ||
|
||
const { setToastAlert } = useToast(); | ||
|
||
const { | ||
appConfig: { envConfig }, | ||
} = useMobxStore(); | ||
|
||
const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]); | ||
|
||
const { getRootProps, getInputProps, isDragActive, fileRejections } = useDropzone({ | ||
onDrop, | ||
accept: { | ||
"image/*": [".png", ".jpg", ".jpeg", ".svg", ".webp"], | ||
}, | ||
maxSize: envConfig?.file_size_limit ?? MAX_FILE_SIZE, | ||
multiple: false, | ||
}); | ||
|
||
const handleClose = () => { | ||
setImage(null); | ||
setIsImageUploading(false); | ||
onClose(); | ||
}; | ||
|
||
const handleSubmit = async () => { | ||
console.log("Submit triggered"); | ||
|
||
if (!image) return; | ||
|
||
console.log("Inside submit"); | ||
|
||
setIsImageUploading(true); | ||
|
||
const formData = new FormData(); | ||
formData.append("asset", image); | ||
formData.append("attributes", JSON.stringify({})); | ||
|
||
fileService | ||
.uploadUserFile(formData) | ||
.then((res) => { | ||
const imageUrl = res.asset; | ||
|
||
onSuccess(imageUrl); | ||
setImage(null); | ||
|
||
if (value) fileService.deleteUserFile(value); | ||
}) | ||
.catch((err) => | ||
setToastAlert({ | ||
type: "error", | ||
title: "Error!", | ||
message: err?.error ?? "Something went wrong. Please try again.", | ||
}) | ||
) | ||
.finally(() => setIsImageUploading(false)); | ||
}; | ||
|
||
return ( | ||
<Transition.Root show={isOpen} as={React.Fragment}> | ||
<Dialog as="div" className="relative z-30" onClose={handleClose}> | ||
<Transition.Child | ||
as={React.Fragment} | ||
enter="ease-out duration-300" | ||
enterFrom="opacity-0" | ||
enterTo="opacity-100" | ||
leave="ease-in duration-200" | ||
leaveFrom="opacity-100" | ||
leaveTo="opacity-0" | ||
> | ||
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" /> | ||
</Transition.Child> | ||
|
||
<div className="fixed inset-0 z-30 overflow-y-auto"> | ||
<div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0"> | ||
<Transition.Child | ||
as={React.Fragment} | ||
enter="ease-out duration-300" | ||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" | ||
enterTo="opacity-100 translate-y-0 sm:scale-100" | ||
leave="ease-in duration-200" | ||
leaveFrom="opacity-100 translate-y-0 sm:scale-100" | ||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" | ||
> | ||
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 px-5 py-8 text-left shadow-custom-shadow-md transition-all sm:w-full sm:max-w-xl sm:p-6"> | ||
<div className="space-y-5"> | ||
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100"> | ||
Upload Image | ||
</Dialog.Title> | ||
<div className="space-y-3"> | ||
<div className="flex items-center justify-center gap-3"> | ||
<div | ||
{...getRootProps()} | ||
className={`relative grid h-80 w-80 cursor-pointer place-items-center rounded-lg p-12 text-center focus:outline-none focus:ring-2 focus:ring-custom-primary focus:ring-offset-2 ${ | ||
(image === null && isDragActive) || !value | ||
? "border-2 border-dashed border-custom-border-200 hover:bg-custom-background-90" | ||
: "" | ||
}`} | ||
> | ||
{image !== null || (value && value !== "") ? ( | ||
<> | ||
<button | ||
type="button" | ||
className="absolute top-0 right-0 z-40 translate-x-1/2 -translate-y-1/2 rounded bg-custom-background-90 px-2 py-0.5 text-xs font-medium text-custom-text-200" | ||
> | ||
Edit | ||
</button> | ||
<img | ||
src={image ? URL.createObjectURL(image) : value ? value : ""} | ||
alt="image" | ||
className="absolute top-0 left-0 h-full w-full object-cover rounded-md" | ||
/> | ||
</> | ||
) : ( | ||
<div> | ||
<UserCircle2 className="mx-auto h-16 w-16 text-custom-text-200" /> | ||
<span className="mt-2 block text-sm font-medium text-custom-text-200"> | ||
{isDragActive ? "Drop image here to upload" : "Drag & drop image here"} | ||
</span> | ||
</div> | ||
)} | ||
|
||
<input {...getInputProps()} type="text" /> | ||
</div> | ||
</div> | ||
{fileRejections.length > 0 && ( | ||
<p className="text-sm text-red-500"> | ||
{fileRejections[0].errors[0].code === "file-too-large" | ||
? "The image size cannot exceed 5 MB." | ||
: "Please upload a file in a valid format."} | ||
</p> | ||
)} | ||
</div> | ||
</div> | ||
<p className="my-4 text-custom-text-200 text-sm"> | ||
File formats supported- .jpeg, .jpg, .png, .webp, .svg | ||
</p> | ||
<div className="flex items-center justify-between"> | ||
{handleDelete && ( | ||
<Button variant="danger" size="sm" onClick={handleDelete} disabled={!value}> | ||
{isRemoving ? "Removing..." : "Remove"} | ||
</Button> | ||
)} | ||
<div className="flex items-center gap-2"> | ||
<Button variant="neutral-primary" size="sm" onClick={handleClose}> | ||
Cancel | ||
</Button> | ||
<Button | ||
variant="primary" | ||
size="sm" | ||
onClick={handleSubmit} | ||
disabled={!image} | ||
loading={isImageUploading} | ||
> | ||
{isImageUploading ? "Uploading..." : "Upload & Save"} | ||
</Button> | ||
</div> | ||
</div> | ||
</Dialog.Panel> | ||
</Transition.Child> | ||
</div> | ||
</div> | ||
</Dialog> | ||
</Transition.Root> | ||
); | ||
}); |
Oops, something went wrong.