chore: update user file assets endpoint (#438)

* chore: new service for user assets

* chore: update user file assets endpoint
This commit is contained in:
Aaryan Khandelwal 2023-03-15 11:00:42 +05:30 committed by GitHub
parent dbd6de0988
commit bfab4865cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 149 additions and 78 deletions

View File

@ -19,11 +19,16 @@ type TImageUploadModalProps = {
onClose: () => void;
isOpen: boolean;
onSuccess: (url: string) => void;
userImage?: boolean;
};
export const ImageUploadModal: React.FC<TImageUploadModalProps> = (props) => {
const { value, onSuccess, isOpen, onClose } = props;
export const ImageUploadModal: React.FC<TImageUploadModalProps> = ({
value,
onSuccess,
isOpen,
onClose,
userImage,
}) => {
const [image, setImage] = useState<File | null>(null);
const [isImageUploading, setIsImageUploading] = useState(false);
@ -46,22 +51,34 @@ export const ImageUploadModal: React.FC<TImageUploadModalProps> = (props) => {
const handleSubmit = async () => {
setIsImageUploading(true);
if (image === null || !workspaceSlug) return;
if (!image || !workspaceSlug) return;
const formData = new FormData();
formData.append("asset", image);
formData.append("attributes", JSON.stringify({}));
fileServices
.uploadFile(workspaceSlug as string, formData)
.then((res) => {
const imageUrl = res.asset;
onSuccess(imageUrl);
setIsImageUploading(false);
})
.catch((err) => {
console.error(err);
});
if (userImage) {
fileServices
.uploadUserFile(formData)
.then((res) => {
const imageUrl = res.asset;
onSuccess(imageUrl);
setIsImageUploading(false);
})
.catch((err) => {
console.error(err);
});
} else
fileServices
.uploadFile(workspaceSlug as string, formData)
.then((res) => {
const imageUrl = res.asset;
onSuccess(imageUrl);
setIsImageUploading(false);
})
.catch((err) => {
console.error(err);
});
};
const handleClose = () => {
@ -109,11 +126,10 @@ export const ImageUploadModal: React.FC<TImageUploadModalProps> = (props) => {
: ""
}`}
>
{image !== null || (value && value !== null && value !== "") ? (
{image !== null || (value && value !== "") ? (
<>
<button
type="button"
onClick={openFileDialog}
className="absolute top-0 right-0 z-40 translate-x-1/2 -translate-y-1/2 rounded bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600"
>
Edit

View File

@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react";
import Link from "next/link";
import Image from "next/image";
import { useRouter } from "next/router";
import useSWR from "swr";
@ -11,15 +10,14 @@ import { useForm } from "react-hook-form";
// lib
import { requiredAuth } from "lib/auth";
// services
import projectService from "services/project.service";
import fileService from "services/file.service";
import userService from "services/user.service";
import workspaceService from "services/workspace.service";
// hooks
import useUser from "hooks/use-user";
import useToast from "hooks/use-toast";
// layouts
import AppLayout from "layouts/app-layout";
// services
import userService from "services/user.service";
import workspaceService from "services/workspace.service";
// components
import { ImageUploadModal } from "components/core";
// ui
@ -28,18 +26,16 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons
import {
ChevronRightIcon,
ClipboardDocumentListIcon,
PencilIcon,
RectangleStackIcon,
UserIcon,
UserPlusIcon,
XMarkIcon,
} from "@heroicons/react/24/outline";
// types
import type { NextPage, GetServerSidePropsContext } from "next";
import type { IIssue, IUser } from "types";
import type { IUser } from "types";
// fetch-keys
import { USER_ISSUE, USER_WORKSPACE_INVITATIONS, PROJECTS_LIST } from "constants/fetch-keys";
import { USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys";
const defaultValues: Partial<IUser> = {
avatar: "",
@ -50,12 +46,9 @@ const defaultValues: Partial<IUser> = {
const Profile: NextPage = () => {
const [isEditing, setIsEditing] = useState(false);
const [isRemoving, setIsRemoving] = useState(false);
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
const {
query: { workspaceSlug },
} = useRouter();
const {
register,
handleSubmit,
@ -68,65 +61,79 @@ const Profile: NextPage = () => {
const { setToastAlert } = useToast();
const { user: myProfile, mutateUser } = useUser();
const { data: myIssues } = useSWR<IIssue[]>(
myProfile && workspaceSlug ? USER_ISSUE(workspaceSlug as string) : null,
myProfile && workspaceSlug ? () => userService.userIssues(workspaceSlug as string) : null
);
const { data: invitations } = useSWR(USER_WORKSPACE_INVITATIONS, () =>
workspaceService.userWorkspaceInvitations()
);
const { data: projects } = useSWR(
workspaceSlug ? PROJECTS_LIST(workspaceSlug as string) : null,
() => (workspaceSlug ? () => projectService.getProjects(workspaceSlug as string) : null)
);
useEffect(() => {
reset({ ...defaultValues, ...myProfile });
}, [myProfile, reset]);
const onSubmit = (formData: IUser) => {
const payload: Partial<IUser> = {
id: formData.id,
first_name: formData.first_name,
last_name: formData.last_name,
avatar: formData.avatar,
};
userService
.updateUser(payload)
.then((response) => {
.then((res) => {
mutateUser((prevData) => {
if (!prevData) return prevData;
return { ...prevData, user: { ...payload, ...response } };
});
return { ...prevData, user: { ...payload, ...res } };
}, false);
setIsEditing(false);
setToastAlert({
title: "Success",
type: "success",
message: "Profile updated successfully",
title: "Success!",
message: "Profile updated successfully.",
});
})
.catch((error) => {
console.log(error);
.catch(() => {
setToastAlert({
type: "error",
title: "Error!",
message: "There was some error in updating your profile. Please try again.",
});
});
};
const handleDelete = (url: string | null | undefined, updateUser: boolean = false) => {
if (!url) return;
setIsRemoving(true);
const index = url.indexOf(".com");
const asset = url.substring(index + 5);
fileService.deleteUserFile(asset).then(() => {
if (updateUser)
userService
.updateUser({ avatar: "" })
.then((res) => {
setIsRemoving(false);
setToastAlert({
type: "success",
title: "Success!",
message: "Profile picture removed successfully.",
});
mutateUser((prevData) => {
if (!prevData) return prevData;
return { ...prevData, user: res };
}, false);
})
.catch(() => {
setIsRemoving(false);
setToastAlert({
type: "error",
title: "Error!",
message: "There was some error in deleting your profile picture. Please try again.",
});
});
});
};
const quickLinks = [
{
icon: RectangleStackIcon,
title: "My Issues",
number: myIssues?.length ?? 0,
description: "View the list of issues assigned to you for this workspace.",
href: `/${workspaceSlug}/me/my-issues`,
},
{
icon: ClipboardDocumentListIcon,
title: "My Projects",
number: projects?.length ?? 0,
description: "View the list of projects of the workspace.",
href: `/${workspaceSlug}/projects`,
},
{
icon: UserPlusIcon,
title: "Workspace Invitations",
@ -147,11 +154,13 @@ const Profile: NextPage = () => {
isOpen={isImageUploadModalOpen}
onClose={() => setIsImageUploadModalOpen(false)}
onSuccess={(url) => {
handleDelete(myProfile?.avatar);
setValue("avatar", url);
handleSubmit(onSubmit)();
setIsImageUploadModalOpen(false);
}}
value={watch("avatar") !== "" ? watch("avatar") : undefined}
userImage
/>
<div className="w-full space-y-5">
<Breadcrumbs>
@ -197,15 +206,26 @@ const Profile: NextPage = () => {
<br />
Supported file types are .jpg and .png.
</p>
<Button
type="button"
className="mt-4"
onClick={() => {
setIsImageUploadModalOpen(true);
}}
>
Upload
</Button>
<div className="flex items-center gap-2">
<Button
type="button"
className="mt-4"
onClick={() => setIsImageUploadModalOpen(true)}
>
Upload new
</Button>
{myProfile.avatar && myProfile.avatar !== "" && (
<Button
type="button"
className="mt-4"
theme="danger"
onClick={() => handleDelete(myProfile.avatar, true)}
disabled={isRemoving}
>
{isRemoving ? "Removing..." : "Remove"}
</Button>
)}
</div>
</div>
</div>
<form className="space-y-5" onSubmit={handleSubmit(onSubmit)}>
@ -259,7 +279,7 @@ const Profile: NextPage = () => {
</section>
<section>
<h2 className="mb-3 text-xl font-medium">Quick Links</h2>
<div className="grid grid-cols-3 gap-5">
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
{quickLinks.map((item, index) => (
<Link key={index} href={item.href}>
<a className="group rounded-lg bg-secondary p-5 duration-300 hover:bg-theme">

View File

@ -81,7 +81,8 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
const cycleStatus =
cycleDetails?.start_date && cycleDetails?.end_date
? getDateRangeStatus(cycleDetails?.start_date, cycleDetails?.end_date) : "";
? getDateRangeStatus(cycleDetails?.start_date, cycleDetails?.end_date)
: "";
const { data: cycleIssues } = useSWR<CycleIssueResponse[]>(
workspaceSlug && projectId && cycleId ? CYCLE_ISSUES(cycleId as string) : null,

View File

@ -14,10 +14,11 @@ import { LinkIcon } from "@heroicons/react/24/outline";
import { requiredWorkspaceAdmin } from "lib/auth";
// services
import workspaceService from "services/workspace.service";
// layouts
import AppLayout from "layouts/app-layout";
import fileService from "services/file.service";
// hooks
import useToast from "hooks/use-toast";
// layouts
import AppLayout from "layouts/app-layout";
// components
import { ImageUploadModal } from "components/core";
import { DeleteWorkspaceModal } from "components/workspace";
@ -96,6 +97,15 @@ const WorkspaceSettings: NextPage<UserAuth> = (props) => {
.catch((err) => console.error(err));
};
const handleDelete = (url: string | null | undefined) => {
if (!url) return;
const index = url.indexOf(".com");
const asset = url.substring(index + 5);
fileService.deleteFile(asset);
};
return (
<AppLayout
memberType={props}
@ -114,6 +124,7 @@ const WorkspaceSettings: NextPage<UserAuth> = (props) => {
onClose={() => setIsImageUploadModalOpen(false)}
onSuccess={(imageUrl) => {
setIsImageUploading(true);
handleDelete(activeWorkspace?.logo);
setValue("logo", imageUrl);
setIsImageUploadModalOpen(false);
handleSubmit(onSubmit)().then(() => setIsImageUploading(false));

View File

@ -40,6 +40,30 @@ class FileServices extends APIService {
});
}
async deleteFile(asset: string): Promise<any> {
return this.delete(`/api/workspaces/file-assets/${asset}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async uploadUserFile(file: FormData): Promise<any> {
return this.mediaUpload(`/api/users/file-assets/`, file)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteUserFile(asset: string): Promise<any> {
return this.delete(`/api/users/file-assets/${asset}`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getUnsplashImages(page: number = 1, query?: string): Promise<UnSplashImage[]> {
const clientId = process.env.NEXT_PUBLIC_UNSPLASH_ACCESS;
const url = query
@ -50,9 +74,7 @@ class FileServices extends APIService {
method: "get",
url,
})
.then((response) => {
return response?.data?.results ?? response?.data;
})
.then((response) => response?.data?.results ?? response?.data)
.catch((error) => {
throw error?.response?.data;
});

View File

@ -12,7 +12,8 @@
"NEXT_PUBLIC_SENTRY_ENVIRONMENT",
"NEXT_PUBLIC_GITHUB_APP_NAME",
"NEXT_PUBLIC_ENABLE_SENTRY",
"NEXT_PUBLIC_ENABLE_OAUTH"
"NEXT_PUBLIC_ENABLE_OAUTH",
"NEXT_PUBLIC_UNSPLASH_ACCESS"
],
"pipeline": {
"build": {