[WEB-310] dev: private bucket implementation (#5793)
* chore: migrations and backmigration to move attachments to file asset * chore: move attachments to file assets * chore: update migration file to include created by and updated by and size * chore: remove uninmport errors * chore: make size as float field * fix: file asset uploads * chore: asset uploads migration changes * chore: v2 assets endpoint * chore: remove unused imports * chore: issue attachments * chore: issue attachments * chore: workspace logo endpoints * chore: private bucket changes * chore: user asset endpoint * chore: add logo_url validation * chore: cover image urlk * chore: change asset max length * chore: pages endpoint * chore: store the storage_metadata only when none * chore: attachment asset apis * chore: update create private bucket * chore: make bucket private * chore: fix response of user uploads * fix: response of user uploads * fix: job to fix file asset uploads * fix: user asset endpoints * chore: avatar for user profile * chore: external apis user url endpoint * chore: upload workspace and user asset actions updated * chore: analytics endpoint * fix: analytics export * chore: avatar urls * chore: update user avatar instances * chore: avatar urls for assignees and creators * chore: bucket permission script * fix: all user avatr instances in the web app * chore: update project cover image logic * fix: issue attachment endpoint * chore: patch endpoint for issue attachment * chore: attachments * chore: change attachment storage class * chore: update issue attachment endpoints * fix: issue attachment * chore: update issue attachment implementation * chore: page asset endpoints * fix: web build errors * chore: attachments * chore: page asset urls * chore: comment and issue asset endpoints * chore: asset endpoints * chore: attachment endpoints * chore: bulk asset endpoint * chore: restore endpoint * chore: project assets endpoints * chore: asset url * chore: add delete asset endpoints * chore: fix asset upload endpoint * chore: update patch endpoints * chore: update patch endpoint * chore: update editor image handling * chore: asset restore endpoints * chore: avatar url for space assets * chore: space app assets migration * fix: space app urls * chore: space endpoints * fix: old editor images rendering logic * fix: issue archive and attachment activity * chore: asset deletes * chore: attachment delete * fix: issue attachment * fix: issue attachment get * chore: cover image url for projects * chore: remove duplicate py file * fix: url check function * chore: chore project cover asset delete * fix: migrations * chore: delete migration files * chore: update bucket * fix: build errors * chore: add asset url in intake attachment * chore: project cover fix * chore: update next.config * chore: delete old workspace logos * chore: workspace assets * chore: asset get for space * chore: update project modal * chore: remove unused imports * fix: space app editor helper * chore: update rich-text read-only editor * chore: create multiple column for entity identifiers * chore: update migrations * chore: remove entity identifier * fix: issue assets * chore: update maximum file size logic * chore: update editor max file size logic * fix: close modal after removing workspace logo * chore: update uploaded asstes' status post issue creation * chore: added file size limit to the space app * dev: add file size limit restriction on all endpoints * fix: remove old workspace logo and user avatar --------- Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
This commit is contained in:
parent
c9580ab794
commit
7e334203f1
241 changed files with 5326 additions and 2518 deletions
|
|
@ -1,3 +1,6 @@
|
|||
// helpers
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
|
||||
type Props = {
|
||||
logo: string | null | undefined;
|
||||
name: string | undefined;
|
||||
|
|
@ -11,9 +14,13 @@ export const WorkspaceLogo = (props: Props) => (
|
|||
} ${props.classNames ? props.classNames : ""}`}
|
||||
>
|
||||
{props.logo && props.logo !== "" ? (
|
||||
<img src={props.logo} className="absolute left-0 top-0 h-full w-full rounded object-cover" alt="Workspace Logo" />
|
||||
<img
|
||||
src={getFileURL(props.logo)}
|
||||
className="absolute left-0 top-0 h-full w-full rounded object-cover"
|
||||
alt="Workspace Logo"
|
||||
/>
|
||||
) : (
|
||||
props.name?.charAt(0) ?? "..."
|
||||
(props.name?.charAt(0) ?? "...")
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,10 +3,17 @@ import Link from "next/link";
|
|||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { Disclosure } from "@headlessui/react";
|
||||
// plane types
|
||||
import { IUser, IWorkspaceMember } from "@plane/types";
|
||||
// plane ui
|
||||
import { CustomSelect, PopoverMenu, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
import { ROLE } from "@/constants/workspace";
|
||||
// helpers
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
// hooks
|
||||
import { useMember, useUser, useUserPermissions } from "@/hooks/store";
|
||||
// plane web constants
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
export interface RowData {
|
||||
|
|
@ -29,33 +36,36 @@ type AccountTypeProps = {
|
|||
|
||||
export const NameColumn: React.FC<NameProps> = (props) => {
|
||||
const { rowData, workspaceSlug, isAdmin, currentUser, setRemoveMemberModal } = props;
|
||||
// derived values
|
||||
const { avatar_url, display_name, email, first_name, id, last_name } = rowData.member;
|
||||
|
||||
return (
|
||||
<Disclosure>
|
||||
{({}) => (
|
||||
<div className="relative group">
|
||||
<div className="flex items-center gap-x-4 gap-y-2 w-72 justify-between">
|
||||
<div className="flex items-center gap-x-4 gap-y-2 flex-1">
|
||||
{rowData.member.avatar && rowData.member.avatar.trim() !== "" ? (
|
||||
<Link href={`/${workspaceSlug}/profile/${rowData.member.id}`}>
|
||||
{avatar_url && avatar_url.trim() !== "" ? (
|
||||
<Link href={`/${workspaceSlug}/profile/${id}`}>
|
||||
<span className="relative flex h-6 w-6 items-center justify-center rounded-full p-4 capitalize text-white">
|
||||
<img
|
||||
src={rowData.member.avatar}
|
||||
src={getFileURL(avatar_url)}
|
||||
className="absolute left-0 top-0 h-full w-full rounded-full object-cover"
|
||||
alt={rowData.member.display_name || rowData.member.email}
|
||||
alt={display_name || email}
|
||||
/>
|
||||
</span>
|
||||
</Link>
|
||||
) : (
|
||||
<Link href={`/${workspaceSlug}/profile/${rowData.member.id}`}>
|
||||
<Link href={`/${workspaceSlug}/profile/${id}`}>
|
||||
<span className="relative flex h-6 w-6 items-center justify-center rounded-full bg-gray-700 p-4 capitalize text-white">
|
||||
{(rowData.member.email ?? rowData.member.display_name ?? "?")[0]}
|
||||
{(email ?? display_name ?? "?")[0]}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
{rowData.member.first_name} {rowData.member.last_name}
|
||||
{first_name} {last_name}
|
||||
</div>
|
||||
|
||||
{(isAdmin || rowData.member?.id === currentUser?.id) && (
|
||||
{(isAdmin || id === currentUser?.id) && (
|
||||
<PopoverMenu
|
||||
data={[""]}
|
||||
keyExtractor={(item) => item}
|
||||
|
|
@ -66,8 +76,7 @@ export const NameColumn: React.FC<NameProps> = (props) => {
|
|||
className="flex items-center gap-x-3 cursor-pointer"
|
||||
onClick={() => setRemoveMemberModal(rowData)}
|
||||
>
|
||||
<Trash2 className="size-3.5 align-middle" />{" "}
|
||||
{rowData.member?.id === currentUser?.id ? "Leave " : "Remove "}
|
||||
<Trash2 className="size-3.5 align-middle" /> {id === currentUser?.id ? "Leave " : "Remove "}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -14,29 +14,24 @@ import { WorkspaceImageUploadModal } from "@/components/core";
|
|||
import { WORKSPACE_UPDATED } from "@/constants/event-tracker";
|
||||
import { ORGANIZATION_SIZE } from "@/constants/workspace";
|
||||
// helpers
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
import { copyUrlToClipboard } from "@/helpers/string.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { DeleteWorkspaceSection } from "@/plane-web/components/workspace";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
// services
|
||||
import { FileService } from "@/services/file.service";
|
||||
|
||||
const defaultValues: Partial<IWorkspace> = {
|
||||
name: "",
|
||||
url: "",
|
||||
organization_size: "2-10",
|
||||
logo: null,
|
||||
logo_url: null,
|
||||
};
|
||||
|
||||
// services
|
||||
const fileService = new FileService();
|
||||
|
||||
export const WorkspaceDetails: FC = observer(() => {
|
||||
// states
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isImageRemoving, setIsImageRemoving] = useState(false);
|
||||
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
|
||||
// store hooks
|
||||
const { captureWorkspaceEvent } = useEventTracker();
|
||||
|
|
@ -53,6 +48,8 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||
} = useForm<IWorkspace>({
|
||||
defaultValues: { ...defaultValues, ...currentWorkspace },
|
||||
});
|
||||
// derived values
|
||||
const workspaceLogo = watch("logo_url");
|
||||
|
||||
const onSubmit = async (formData: IWorkspace) => {
|
||||
if (!currentWorkspace) return;
|
||||
|
|
@ -60,7 +57,6 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||
setIsLoading(true);
|
||||
|
||||
const payload: Partial<IWorkspace> = {
|
||||
logo: formData.logo,
|
||||
name: formData.name,
|
||||
organization_size: formData.organization_size,
|
||||
};
|
||||
|
|
@ -96,34 +92,26 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||
}, 300);
|
||||
};
|
||||
|
||||
const handleRemoveLogo = () => {
|
||||
const handleRemoveLogo = async () => {
|
||||
if (!currentWorkspace) return;
|
||||
|
||||
const url = currentWorkspace.logo;
|
||||
|
||||
if (!url) return;
|
||||
|
||||
setIsImageRemoving(true);
|
||||
|
||||
fileService.deleteFile(currentWorkspace.id, url).then(() => {
|
||||
updateWorkspace(currentWorkspace.slug, { logo: "" })
|
||||
.then(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Workspace picture removed successfully.",
|
||||
});
|
||||
setIsImageUploadModalOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: "There was some error in deleting your profile picture. Please try again.",
|
||||
});
|
||||
})
|
||||
.finally(() => setIsImageRemoving(false));
|
||||
});
|
||||
await updateWorkspace(currentWorkspace.slug, {
|
||||
logo_url: "",
|
||||
})
|
||||
.then(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Workspace picture removed successfully.",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: "There was some error in deleting your profile picture. Please try again.",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleCopyUrl = () => {
|
||||
|
|
@ -154,17 +142,15 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||
<>
|
||||
<Controller
|
||||
control={control}
|
||||
name="logo"
|
||||
name="logo_url"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<WorkspaceImageUploadModal
|
||||
isOpen={isImageUploadModalOpen}
|
||||
onClose={() => setIsImageUploadModalOpen(false)}
|
||||
isRemoving={isImageRemoving}
|
||||
handleRemove={handleRemoveLogo}
|
||||
onSuccess={(imageUrl) => {
|
||||
onChange(imageUrl);
|
||||
setIsImageUploadModalOpen(false);
|
||||
handleSubmit(onSubmit)();
|
||||
}}
|
||||
value={value}
|
||||
/>
|
||||
|
|
@ -174,10 +160,10 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||
<div className="flex gap-5 border-b border-custom-border-100 pb-7 items-start">
|
||||
<div className="flex flex-col gap-1">
|
||||
<button type="button" onClick={() => setIsImageUploadModalOpen(true)} disabled={!isAdmin}>
|
||||
{watch("logo") && watch("logo") !== null && watch("logo") !== "" ? (
|
||||
{workspaceLogo && workspaceLogo !== "" ? (
|
||||
<div className="relative mx-auto flex h-14 w-14">
|
||||
<img
|
||||
src={watch("logo")!}
|
||||
src={getFileURL(workspaceLogo)}
|
||||
className="absolute left-0 top-0 h-full w-full rounded-md object-cover"
|
||||
alt="Workspace Logo"
|
||||
/>
|
||||
|
|
@ -199,7 +185,7 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||
className="flex items-center gap-1.5 text-left text-xs font-medium text-custom-primary-100"
|
||||
onClick={() => setIsImageUploadModalOpen(true)}
|
||||
>
|
||||
{watch("logo") && watch("logo") !== null && watch("logo") !== "" ? (
|
||||
{workspaceLogo && workspaceLogo !== "" ? (
|
||||
<>
|
||||
<Pencil className="h-3 w-3" />
|
||||
Edit logo
|
||||
|
|
|
|||
|
|
@ -14,8 +14,11 @@ import { IWorkspace } from "@plane/types";
|
|||
// plane ui
|
||||
import { Avatar, Loader, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { GOD_MODE_URL, cn } from "@/helpers/common.helper";
|
||||
// helpers
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
// hooks
|
||||
import { useAppTheme, useUser, useUserPermissions, useUserProfile, useWorkspace } from "@/hooks/store";
|
||||
// plane web constants
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
import { WorkspaceLogo } from "../logo";
|
||||
|
||||
|
|
@ -110,7 +113,7 @@ export const SidebarDropdown = observer(() => {
|
|||
)}
|
||||
>
|
||||
<div className="flex-grow flex items-center gap-2 truncate">
|
||||
<WorkspaceLogo logo={activeWorkspace?.logo} name={activeWorkspace?.name} />
|
||||
<WorkspaceLogo logo={activeWorkspace?.logo_url} name={activeWorkspace?.name} />
|
||||
{!sidebarCollapsed && (
|
||||
<h4 className="truncate text-base font-medium text-custom-text-100">
|
||||
{activeWorkspace?.name ?? "Loading..."}
|
||||
|
|
@ -162,17 +165,17 @@ export const SidebarDropdown = observer(() => {
|
|||
<div className="flex items-center justify-start gap-2.5 truncate">
|
||||
<span
|
||||
className={`relative flex h-6 w-6 flex-shrink-0 items-center justify-center p-2 text-xs uppercase ${
|
||||
!workspace?.logo && "rounded bg-custom-primary-500 text-white"
|
||||
!workspace?.logo_url && "rounded bg-custom-primary-500 text-white"
|
||||
}`}
|
||||
>
|
||||
{workspace?.logo && workspace.logo !== "" ? (
|
||||
{workspace?.logo_url && workspace.logo_url !== "" ? (
|
||||
<img
|
||||
src={workspace.logo}
|
||||
src={getFileURL(workspace.logo_url)}
|
||||
className="absolute left-0 top-0 h-full w-full rounded object-cover"
|
||||
alt="Workspace Logo"
|
||||
/>
|
||||
) : (
|
||||
(workspace?.name?.charAt(0) ?? "...")
|
||||
(workspace?.name?.[0] ?? "...")
|
||||
)}
|
||||
</span>
|
||||
<h5
|
||||
|
|
@ -255,7 +258,7 @@ export const SidebarDropdown = observer(() => {
|
|||
<Menu.Button className="grid place-items-center outline-none" ref={setReferenceElement}>
|
||||
<Avatar
|
||||
name={currentUser?.display_name}
|
||||
src={currentUser?.avatar || undefined}
|
||||
src={getFileURL(currentUser?.avatar_url ?? "")}
|
||||
size={24}
|
||||
shape="square"
|
||||
className="!text-base"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue