From 1bd38ad4c7d84020829ff24b3f66e1eb0112f081 Mon Sep 17 00:00:00 2001
From: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
Date: Fri, 24 Nov 2023 13:23:46 +0530
Subject: [PATCH] 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
---
apiserver/plane/app/views/config.py | 2 +
web/components/core/image-picker-popover.tsx | 12 +-
web/components/core/modals/index.ts | 3 +-
.../core/modals/user-image-upload-modal.tsx | 199 ++++++++++++++++++
...l.tsx => workspace-image-upload-modal.tsx} | 102 ++++-----
.../issues/attachment/attachment-upload.tsx | 25 ++-
web/components/onboarding/user-details.tsx | 47 ++---
.../workspace/settings/workspace-details.tsx | 60 ++++--
web/components/workspace/sidebar-dropdown.tsx | 5 +-
web/constants/common.ts | 1 +
.../settings-layout/profile/sidebar.tsx | 111 +++++++++-
web/pages/profile/index.tsx | 32 +--
web/types/app.d.ts | 1 +
13 files changed, 470 insertions(+), 130 deletions(-)
create mode 100644 web/components/core/modals/user-image-upload-modal.tsx
rename web/components/core/modals/{image-upload-modal.tsx => workspace-image-upload-modal.tsx} (81%)
create mode 100644 web/constants/common.ts
diff --git a/apiserver/plane/app/views/config.py b/apiserver/plane/app/views/config.py
index a585fc82e..f42c853e2 100644
--- a/apiserver/plane/app/views/config.py
+++ b/apiserver/plane/app/views/config.py
@@ -102,4 +102,6 @@ class ConfigurationEndpoint(BaseAPIView):
)
)
+ data["file_size_limit"] = float(os.environ.get("FILE_SIZE_LIMIT", 5242880))
+
return Response(data, status=status.HTTP_200_OK)
diff --git a/web/components/core/image-picker-popover.tsx b/web/components/core/image-picker-popover.tsx
index 123eee6d2..eea18ce43 100644
--- a/web/components/core/image-picker-popover.tsx
+++ b/web/components/core/image-picker-popover.tsx
@@ -14,6 +14,8 @@ import { FileService } from "services/file.service";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
// components
import { Button, Input, Loader } from "@plane/ui";
+// constants
+import { MAX_FILE_SIZE } from "constants/common";
const tabOptions = [
{
@@ -58,8 +60,10 @@ export const ImagePickerPopover: React.FC = observer((props) => {
const router = useRouter();
const { workspaceSlug } = router.query;
- const { workspace: workspaceStore } = useMobxStore();
- const { currentWorkspace: workspaceDetails } = workspaceStore;
+ const {
+ workspace: { currentWorkspace },
+ appConfig: { envConfig },
+ } = useMobxStore();
const { data: unsplashImages, error: unsplashError } = useSWR(
`UNSPLASH_IMAGES_${searchParams}`,
@@ -86,7 +90,7 @@ export const ImagePickerPopover: React.FC = observer((props) => {
accept: {
"image/*": [".png", ".jpg", ".jpeg", ".svg", ".webp"],
},
- maxSize: 5 * 1024 * 1024,
+ maxSize: envConfig?.file_size_limit ?? MAX_FILE_SIZE,
});
const handleSubmit = async () => {
@@ -112,7 +116,7 @@ export const ImagePickerPopover: React.FC = observer((props) => {
if (isUnsplashImage) return;
- if (oldValue && workspaceDetails) fileService.deleteFile(workspaceDetails.id, oldValue);
+ if (oldValue && currentWorkspace) fileService.deleteFile(currentWorkspace.id, oldValue);
})
.catch((err) => {
console.log(err);
diff --git a/web/components/core/modals/index.ts b/web/components/core/modals/index.ts
index 5f55020e4..aa2c163a6 100644
--- a/web/components/core/modals/index.ts
+++ b/web/components/core/modals/index.ts
@@ -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";
diff --git a/web/components/core/modals/user-image-upload-modal.tsx b/web/components/core/modals/user-image-upload-modal.tsx
new file mode 100644
index 000000000..6358a4aee
--- /dev/null
+++ b/web/components/core/modals/user-image-upload-modal.tsx
@@ -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 = observer((props) => {
+ const { value, onSuccess, isOpen, onClose, isRemoving, handleDelete } = props;
+ // states
+ const [image, setImage] = useState(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 (
+
+
+
+ );
+});
diff --git a/web/components/core/modals/image-upload-modal.tsx b/web/components/core/modals/workspace-image-upload-modal.tsx
similarity index 81%
rename from web/components/core/modals/image-upload-modal.tsx
rename to web/components/core/modals/workspace-image-upload-modal.tsx
index a879b4705..a99c1445b 100644
--- a/web/components/core/modals/image-upload-modal.tsx
+++ b/web/components/core/modals/workspace-image-upload-modal.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useState } from "react";
+import React, { useState } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { useDropzone } from "react-dropzone";
@@ -7,89 +7,89 @@ import { Transition, Dialog } from "@headlessui/react";
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 = {
- value?: string | null;
- onClose: () => void;
+ handleRemove?: () => void;
isOpen: boolean;
- onSuccess: (url: string) => void;
isRemoving: boolean;
- handleDelete: () => void;
- userImage?: boolean;
+ onClose: () => void;
+ onSuccess: (url: string) => void;
+ value: string | null;
};
// services
const fileService = new FileService();
-export const ImageUploadModal: React.FC = observer((props) => {
- const { value, onSuccess, isOpen, onClose, isRemoving, handleDelete, userImage } = props;
-
+export const WorkspaceImageUploadModal: React.FC = observer((props) => {
+ const { value, onSuccess, isOpen, onClose, isRemoving, handleRemove } = props;
+ // states
const [image, setImage] = useState(null);
const [isImageUploading, setIsImageUploading] = useState(false);
-
+ // router
const router = useRouter();
const { workspaceSlug } = router.query;
- const { workspace: workspaceStore } = useMobxStore();
- const { currentWorkspace: workspaceDetails } = workspaceStore;
+ const { setToastAlert } = useToast();
- const onDrop = useCallback((acceptedFiles: File[]) => {
- setImage(acceptedFiles[0]);
- }, []);
+ const {
+ workspace: { currentWorkspace },
+ 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: 5 * 1024 * 1024,
+ maxSize: envConfig?.file_size_limit ?? MAX_FILE_SIZE,
+ multiple: false,
});
+ const handleClose = () => {
+ setImage(null);
+ setIsImageUploading(false);
+ onClose();
+ };
+
const handleSubmit = async () => {
- if (!image || (!workspaceSlug && router.pathname != "/onboarding")) return;
+ if (!image || (!workspaceSlug && router.pathname !== "/onboarding")) return;
+
setIsImageUploading(true);
+
const formData = new FormData();
formData.append("asset", image);
formData.append("attributes", JSON.stringify({}));
- if (userImage) {
- fileService
- .uploadUserFile(formData)
- .then((res) => {
- const imageUrl = res.asset;
+ if (!workspaceSlug) return;
- onSuccess(imageUrl);
- setIsImageUploading(false);
- setImage(null);
+ fileService
+ .uploadFile(workspaceSlug.toString(), formData)
+ .then((res) => {
+ const imageUrl = res.asset;
- if (value) fileService.deleteUserFile(value);
+ onSuccess(imageUrl);
+ setImage(null);
+
+ if (value && currentWorkspace) fileService.deleteFile(currentWorkspace.id, value);
+ })
+ .catch((err) =>
+ setToastAlert({
+ type: "error",
+ title: "Error!",
+ message: err?.error ?? "Something went wrong. Please try again.",
})
- .catch((err) => {
- console.error(err);
- });
- } else
- fileService
- .uploadFile(workspaceSlug as string, formData)
- .then((res) => {
- const imageUrl = res.asset;
- onSuccess(imageUrl);
- setIsImageUploading(false);
- setImage(null);
-
- if (value && workspaceDetails) fileService.deleteFile(workspaceDetails.id, value);
- })
- .catch((err) => {
- console.error(err);
- });
- };
-
- const handleClose = () => {
- setImage(null);
- onClose();
+ )
+ .finally(() => setIsImageUploading(false));
};
return (
@@ -172,11 +172,11 @@ export const ImageUploadModal: React.FC = observer((props) => {
File formats supported- .jpeg, .jpg, .png, .webp, .svg
-
-
+ )}
Cancel
diff --git a/web/components/issues/attachment/attachment-upload.tsx b/web/components/issues/attachment/attachment-upload.tsx
index 9f8b84479..c88654a79 100644
--- a/web/components/issues/attachment/attachment-upload.tsx
+++ b/web/components/issues/attachment/attachment-upload.tsx
@@ -1,7 +1,10 @@
import { useCallback, useState } from "react";
import { useRouter } from "next/router";
+import { observer } from "mobx-react-lite";
import { mutate } from "swr";
import { useDropzone } from "react-dropzone";
+// mobx store
+import { useMobxStore } from "lib/mobx/store-provider";
// services
import { IssueAttachmentService } from "services/issue";
// hooks
@@ -10,8 +13,8 @@ import useToast from "hooks/use-toast";
import { IIssueAttachment } from "types";
// fetch-keys
import { ISSUE_ATTACHMENTS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
-
-const maxFileSize = 5 * 1024 * 1024; // 5 MB
+// constants
+import { MAX_FILE_SIZE } from "constants/common";
type Props = {
disabled?: boolean;
@@ -19,14 +22,20 @@ type Props = {
const issueAttachmentService = new IssueAttachmentService();
-export const IssueAttachmentUpload: React.FC = ({ disabled = false }) => {
+export const IssueAttachmentUpload: React.FC = observer((props) => {
+ const { disabled = false } = props;
+ // states
const [isLoading, setIsLoading] = useState(false);
-
+ // router
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
const { setToastAlert } = useToast();
+ const {
+ appConfig: { envConfig },
+ } = useMobxStore();
+
const onDrop = useCallback((acceptedFiles: File[]) => {
if (!acceptedFiles[0] || !workspaceSlug) return;
@@ -70,13 +79,15 @@ export const IssueAttachmentUpload: React.FC = ({ disabled = false }) =>
const { getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({
onDrop,
- maxSize: maxFileSize,
+ maxSize: envConfig?.file_size_limit ?? MAX_FILE_SIZE,
multiple: false,
disabled: isLoading || disabled,
});
const fileError =
- fileRejections.length > 0 ? `Invalid file type or size (max ${maxFileSize / 1024 / 1024} MB)` : null;
+ fileRejections.length > 0
+ ? `Invalid file type or size (max ${envConfig?.file_size_limit ?? MAX_FILE_SIZE / 1024 / 1024} MB)`
+ : null;
return (
= ({ disabled = false }) =>
);
-};
+});
diff --git a/web/components/onboarding/user-details.tsx b/web/components/onboarding/user-details.tsx
index ff62657c0..6dff16c93 100644
--- a/web/components/onboarding/user-details.tsx
+++ b/web/components/onboarding/user-details.tsx
@@ -9,15 +9,13 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { Button, Input } from "@plane/ui";
import DummySidebar from "components/account/sidebar";
import OnboardingStepIndicator from "components/account/step-indicator";
+import { UserImageUploadModal } from "components/core";
// types
import { IUser } from "types";
-// constants
-import { TIME_ZONES } from "constants/timezones";
// services
import { FileService } from "services/file.service";
// assets
import IssuesSvg from "public/onboarding/onboarding-issues.svg";
-import { ImageUploadModal } from "components/core";
const defaultValues: Partial = {
first_name: "",
@@ -29,13 +27,7 @@ type Props = {
user?: IUser;
};
-// const timeZoneOptions = TIME_ZONES.map((timeZone) => ({
-// value: timeZone.value,
-// query: timeZone.label + " " + timeZone.value,
-// content: timeZone.label,
-// }));
-
-const useCases = [
+const USE_CASES = [
"Build Products",
"Manage Feedbacks",
"Service delivery",
@@ -43,15 +35,15 @@ const useCases = [
"Code Repository Integration",
"Bug Tracking",
"Test Case Management",
- "Rescource allocation",
+ "Resource allocation",
];
const fileService = new FileService();
export const UserDetails: React.FC = observer((props) => {
const { user } = props;
+ // states
const [isRemoving, setIsRemoving] = useState(false);
- // const [selectedUsecase, setSelectedUsecase] = useState();
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
const {
user: userStore,
@@ -100,19 +92,22 @@ export const UserDetails: React.FC = observer((props) => {
- setIsImageUploadModalOpen(false)}
- isRemoving={isRemoving}
- handleDelete={() => {
- handleDelete(getValues("avatar"));
- }}
- onSuccess={(url) => {
- setValue("avatar", url);
- setIsImageUploadModalOpen(false);
- }}
- value={watch("avatar") !== "" ? watch("avatar") : undefined}
- userImage
+ (
+ setIsImageUploadModalOpen(false)}
+ isRemoving={isRemoving}
+ handleDelete={() => handleDelete(getValues("avatar"))}
+ onSuccess={(url) => {
+ onChange(url);
+ setIsImageUploadModalOpen(false);
+ }}
+ value={value && value.trim() !== "" ? value : null}
+ />
+ )}
/>
@@ -189,7 +184,7 @@ export const UserDetails: React.FC
= observer((props) => {
name="use_case"
render={({ field: { value, onChange } }) => (
- {useCases.map((useCase) => (
+ {USE_CASES.map((useCase) => (
= {
name: "",
@@ -33,8 +34,6 @@ const fileService = new FileService();
export const WorkspaceDetails: FC = observer(() => {
// states
const [deleteWorkspaceModal, setDeleteWorkspaceModal] = useState(false);
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const [isImageUploading, setIsImageUploading] = useState(false);
const [isImageRemoving, setIsImageRemoving] = useState(false);
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
// store
@@ -51,7 +50,6 @@ export const WorkspaceDetails: FC = observer(() => {
control,
reset,
watch,
- setValue,
formState: { errors, isSubmitting },
} = useForm
({
defaultValues: { ...defaultValues, ...currentWorkspace },
@@ -78,8 +76,12 @@ export const WorkspaceDetails: FC = observer(() => {
.catch((err) => console.error(err));
};
- const handleDelete = (url: string | null | undefined) => {
- if (!currentWorkspace || !url) return;
+ const handleRemoveLogo = () => {
+ if (!currentWorkspace) return;
+
+ const url = currentWorkspace.logo;
+
+ if (!url) return;
setIsImageRemoving(true);
@@ -104,6 +106,17 @@ export const WorkspaceDetails: FC = observer(() => {
});
};
+ const handleCopyUrl = () => {
+ if (!currentWorkspace) return;
+
+ copyUrlToClipboard(`${currentWorkspace.slug}`).then(() => {
+ setToastAlert({
+ type: "success",
+ title: "Workspace URL copied to the clipboard.",
+ });
+ });
+ };
+
useEffect(() => {
if (currentWorkspace) reset({ ...currentWorkspace });
}, [currentWorkspace, reset]);
@@ -118,22 +131,27 @@ export const WorkspaceDetails: FC = observer(() => {
return (
<>
setDeleteWorkspaceModal(false)}
- data={currentWorkspace}
/>
- setIsImageUploadModalOpen(false)}
- isRemoving={isImageRemoving}
- handleDelete={() => handleDelete(currentWorkspace?.logo)}
- onSuccess={(imageUrl) => {
- setIsImageUploading(true);
- setValue("logo", imageUrl);
- setIsImageUploadModalOpen(false);
- handleSubmit(onSubmit)().then(() => setIsImageUploading(false));
- }}
- value={watch("logo")}
+ (
+ setIsImageUploadModalOpen(false)}
+ isRemoving={isImageRemoving}
+ handleRemove={handleRemoveLogo}
+ onSuccess={(imageUrl) => {
+ onChange(imageUrl);
+ setIsImageUploadModalOpen(false);
+ handleSubmit(onSubmit)();
+ }}
+ value={value}
+ />
+ )}
/>
@@ -156,9 +174,9 @@ export const WorkspaceDetails: FC = observer(() => {
{watch("name")}
-
{`${
+ {`${
typeof window !== "undefined" && window.location.origin.replace("http://", "").replace("https://", "")
- }/${currentWorkspace.slug}`}
+ }/${currentWorkspace.slug}`}
{
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
-
+
Workspace
{workspaces ? (
diff --git a/web/constants/common.ts b/web/constants/common.ts
new file mode 100644
index 000000000..3fac821fa
--- /dev/null
+++ b/web/constants/common.ts
@@ -0,0 +1 @@
+export const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
diff --git a/web/layouts/settings-layout/profile/sidebar.tsx b/web/layouts/settings-layout/profile/sidebar.tsx
index 325fd703a..6466f2b93 100644
--- a/web/layouts/settings-layout/profile/sidebar.tsx
+++ b/web/layouts/settings-layout/profile/sidebar.tsx
@@ -1,10 +1,18 @@
import Link from "next/link";
import { observer } from "mobx-react-lite";
-import { MoveLeft, Plus, UserPlus } from "lucide-react";
+import { Menu, Transition } from "@headlessui/react";
+import { LogIn, LogOut, MoveLeft, Plus, User, UserPlus } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
+// services
+import { AuthService } from "services/auth.service";
// ui
-import { Tooltip } from "@plane/ui";
+import { Avatar, Tooltip } from "@plane/ui";
+import { Fragment } from "react";
+import { mutate } from "swr";
+import { useRouter } from "next/router";
+import useToast from "hooks/use-toast";
+import { useTheme } from "next-themes";
const SIDEBAR_LINKS = [
{
@@ -21,12 +29,44 @@ const SIDEBAR_LINKS = [
},
];
+const authService = new AuthService();
+
export const ProfileLayoutSidebar = observer(() => {
+ const router = useRouter();
+
+ const { setTheme } = useTheme();
+
+ const { setToastAlert } = useToast();
+
const {
theme: { sidebarCollapsed, toggleSidebar },
workspace: { workspaces },
+ user: { currentUser, currentUserSettings },
} = useMobxStore();
+ // redirect url for normal mode
+ const redirectWorkspaceSlug =
+ currentUserSettings?.workspace?.last_workspace_slug ||
+ currentUserSettings?.workspace?.fallback_workspace_slug ||
+ "";
+
+ const handleSignOut = async () => {
+ await authService
+ .signOut()
+ .then(() => {
+ mutate("CURRENT_USER_DETAILS", null);
+ setTheme("system");
+ router.push("/");
+ })
+ .catch(() =>
+ setToastAlert({
+ type: "error",
+ title: "Error!",
+ message: "Failed to sign out. Please try again.",
+ })
+ );
+ };
+
return (
{
} ${sidebarCollapsed ? "left-0" : "-left-full md:left-0"}`}
>
+
+
+
+
+
+
+
+ {!sidebarCollapsed &&
My Profile
}
+
+
+
+ {!sidebarCollapsed && (
+
+
+
+ )}
+
+ {!sidebarCollapsed && (
+
+ )}
+
+
{SIDEBAR_LINKS.map((link) => (
diff --git a/web/pages/profile/index.tsx b/web/pages/profile/index.tsx
index 1af888110..583236f82 100644
--- a/web/pages/profile/index.tsx
+++ b/web/pages/profile/index.tsx
@@ -10,7 +10,7 @@ import useToast from "hooks/use-toast";
// layouts
import { ProfileSettingsLayout } from "layouts/settings-layout";
// components
-import { ImagePickerPopover, ImageUploadModal } from "components/core";
+import { ImagePickerPopover, UserImageUploadModal } from "components/core";
import { ProfileSettingsHeader } from "components/headers";
import { DeactivateAccountModal } from "components/account";
// ui
@@ -48,7 +48,6 @@ const ProfileSettingsPage: NextPageWithLayout = () => {
handleSubmit,
reset,
watch,
- setValue,
control,
formState: { errors, isSubmitting },
} = useForm
({ defaultValues });
@@ -151,18 +150,23 @@ const ProfileSettingsPage: NextPageWithLayout = () => {
return (
<>
- setIsImageUploadModalOpen(false)}
- isRemoving={isRemoving}
- handleDelete={() => handleDelete(myProfile?.avatar, true)}
- onSuccess={(url) => {
- setValue("avatar", url);
- handleSubmit(onSubmit)();
- setIsImageUploadModalOpen(false);
- }}
- value={watch("avatar") !== "" ? watch("avatar") : undefined}
- userImage
+ (
+ setIsImageUploadModalOpen(false)}
+ isRemoving={isRemoving}
+ handleDelete={() => handleDelete(myProfile?.avatar, true)}
+ onSuccess={(url) => {
+ onChange(url);
+ handleSubmit(onSubmit)();
+ setIsImageUploadModalOpen(false);
+ }}
+ value={value && value.trim() !== "" ? value : null}
+ />
+ )}
/>
setDeactivateAccountModal(false)} />
diff --git a/web/types/app.d.ts b/web/types/app.d.ts
index 05f0fc7e5..9c95538ff 100644
--- a/web/types/app.d.ts
+++ b/web/types/app.d.ts
@@ -4,6 +4,7 @@ export type NextPageWithLayout
= NextPage
& {
export interface IAppConfig {
email_password_login: boolean;
+ file_size_limit: number;
google_client_id: string | null;
github_app_name: string | null;
github_client_id: string | null;