mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
676355d673
refractor: added types for cycle in IIssue, improved common function used for grouping, made custom hook for my issue filter
253 lines
9.1 KiB
TypeScript
253 lines
9.1 KiB
TypeScript
import React, { useEffect, useState } from "react";
|
|
// next
|
|
import Image from "next/image";
|
|
// react hook form
|
|
import { useForm } from "react-hook-form";
|
|
// headless ui
|
|
import { Tab } from "@headlessui/react";
|
|
// react dropzone
|
|
import Dropzone from "react-dropzone";
|
|
// services
|
|
import workspaceService from "lib/services/workspace.service";
|
|
import fileServices from "lib/services/file.service";
|
|
// hoc
|
|
import withAuth from "lib/hoc/withAuthWrapper";
|
|
// layouts
|
|
import AppLayout from "layouts/app-layout";
|
|
// hooks
|
|
import useUser from "lib/hooks/useUser";
|
|
import useToast from "lib/hooks/useToast";
|
|
// components
|
|
import ConfirmWorkspaceDeletion from "components/workspace/confirm-workspace-deletion";
|
|
// ui
|
|
import { Spinner, Button, Input, Select } from "ui";
|
|
import { BreadcrumbItem, Breadcrumbs } from "ui/Breadcrumbs";
|
|
// types
|
|
import type { IWorkspace } from "types";
|
|
|
|
const defaultValues: Partial<IWorkspace> = {
|
|
name: "",
|
|
};
|
|
|
|
const WorkspaceSettings = () => {
|
|
const { activeWorkspace, mutateWorkspaces } = useUser();
|
|
|
|
const { setToastAlert } = useToast();
|
|
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
|
|
const [image, setImage] = useState<File | null>(null);
|
|
const [isImageUploading, setIsImageUploading] = useState(false);
|
|
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
reset,
|
|
watch,
|
|
setValue,
|
|
formState: { errors, isSubmitting },
|
|
} = useForm<IWorkspace>({
|
|
defaultValues: { ...defaultValues, ...activeWorkspace },
|
|
});
|
|
|
|
useEffect(() => {
|
|
activeWorkspace && reset({ ...activeWorkspace });
|
|
}, [activeWorkspace, reset]);
|
|
|
|
const onSubmit = async (formData: IWorkspace) => {
|
|
if (!activeWorkspace) return;
|
|
const payload: Partial<IWorkspace> = {
|
|
logo: formData.logo,
|
|
name: formData.name,
|
|
company_size: formData.company_size,
|
|
};
|
|
await workspaceService
|
|
.updateWorkspace(activeWorkspace.slug, payload)
|
|
.then(async (res) => {
|
|
await mutateWorkspaces((workspaces) => {
|
|
return (workspaces ?? []).map((workspace) => {
|
|
if (workspace.slug === activeWorkspace.slug) {
|
|
return {
|
|
...workspace,
|
|
...res,
|
|
};
|
|
}
|
|
return workspace;
|
|
});
|
|
}, false);
|
|
setToastAlert({
|
|
title: "Success",
|
|
type: "success",
|
|
message: "Workspace updated successfully",
|
|
});
|
|
})
|
|
.catch((err) => console.log(err));
|
|
};
|
|
|
|
return (
|
|
<AppLayout
|
|
meta={{
|
|
title: "Plane - Workspace Settings",
|
|
}}
|
|
breadcrumbs={
|
|
<Breadcrumbs>
|
|
<BreadcrumbItem title={`${activeWorkspace?.name ?? "Workspace"} Settings`} />
|
|
</Breadcrumbs>
|
|
}
|
|
>
|
|
<ConfirmWorkspaceDeletion
|
|
isOpen={isOpen}
|
|
onClose={() => {
|
|
setIsOpen(false);
|
|
}}
|
|
data={activeWorkspace ?? null}
|
|
/>
|
|
<div className="space-y-5">
|
|
{activeWorkspace ? (
|
|
<div className="space-y-8">
|
|
<Tab.Group>
|
|
<Tab.List className="flex items-center gap-3">
|
|
{["General", "Actions"].map((tab, index) => (
|
|
<Tab
|
|
key={index}
|
|
className={({ selected }) =>
|
|
`text-md leading-6 text-gray-900 px-4 py-1 rounded outline-none ${
|
|
selected ? "bg-gray-700 text-white" : "hover:bg-gray-200"
|
|
} duration-300`
|
|
}
|
|
>
|
|
{tab}
|
|
</Tab>
|
|
))}
|
|
</Tab.List>
|
|
<Tab.Panels>
|
|
<Tab.Panel>
|
|
<div className="grid grid-cols-2 gap-6">
|
|
<div className="w-full space-y-3">
|
|
<Dropzone
|
|
multiple={false}
|
|
accept={{
|
|
"image/*": [],
|
|
}}
|
|
onDrop={(files) => {
|
|
setImage(files[0]);
|
|
}}
|
|
>
|
|
{({ getRootProps, getInputProps }) => (
|
|
<div>
|
|
<input {...getInputProps()} />
|
|
<div className="text-gray-500 mb-2">Logo</div>
|
|
<div>
|
|
<div className="h-60 bg-blue-50" {...getRootProps()}>
|
|
{((watch("logo") &&
|
|
watch("logo") !== null &&
|
|
watch("logo") !== "") ||
|
|
(image && image !== null)) && (
|
|
<div className="relative flex mx-auto h-60">
|
|
<Image
|
|
src={image ? URL.createObjectURL(image) : watch("logo") ?? ""}
|
|
alt="Workspace Logo"
|
|
objectFit="cover"
|
|
layout="fill"
|
|
priority
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<p className="text-sm text-gray-500 mt-2">
|
|
Max file size is 500kb. Supported file types are .jpg and .png.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Dropzone>
|
|
<div>
|
|
<Button
|
|
onClick={() => {
|
|
if (image === null) return;
|
|
setIsImageUploading(true);
|
|
const formData = new FormData();
|
|
formData.append("asset", image);
|
|
formData.append("attributes", JSON.stringify({}));
|
|
fileServices
|
|
.uploadFile(formData)
|
|
.then((response) => {
|
|
const imageUrl = response.asset;
|
|
setValue("logo", imageUrl);
|
|
handleSubmit(onSubmit)();
|
|
setIsImageUploading(false);
|
|
})
|
|
.catch((err) => {
|
|
setIsImageUploading(false);
|
|
});
|
|
}}
|
|
>
|
|
{isImageUploading ? "Uploading..." : "Upload"}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-3">
|
|
<div>
|
|
<Input
|
|
id="name"
|
|
name="name"
|
|
label="Name"
|
|
placeholder="Name"
|
|
autoComplete="off"
|
|
register={register}
|
|
error={errors.name}
|
|
validations={{
|
|
required: "Name is required",
|
|
}}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Select
|
|
id="company_size"
|
|
name="company_size"
|
|
label="How large is your company?"
|
|
options={[
|
|
{ value: 5, label: "5" },
|
|
{ value: 10, label: "10" },
|
|
{ value: 25, label: "25" },
|
|
{ value: 50, label: "50" },
|
|
]}
|
|
/>
|
|
</div>
|
|
<div className="text-right">
|
|
<Button onClick={handleSubmit(onSubmit)} disabled={isSubmitting}>
|
|
{isSubmitting ? "Updating..." : "Update"}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Tab.Panel>
|
|
<Tab.Panel>
|
|
<div className="space-y-3">
|
|
<h2 className="text-2xl text-red-500 font-semibold">Danger Zone</h2>
|
|
<p className="w-full md:w-1/2">
|
|
The danger zone of the workspace delete page is a critical area that requires
|
|
careful consideration and attention. When deleting a workspace, all of the
|
|
data and resources within that workspace will be permanently removed and
|
|
cannot be recovered.
|
|
</p>
|
|
<Button theme="danger" onClick={() => setIsOpen(true)}>
|
|
Delete the workspace
|
|
</Button>
|
|
</div>
|
|
</Tab.Panel>
|
|
</Tab.Panels>
|
|
</Tab.Group>
|
|
</div>
|
|
) : (
|
|
<div className="h-full w-full grid place-items-center px-4 sm:px-0">
|
|
<Spinner />
|
|
</div>
|
|
)}
|
|
</div>
|
|
</AppLayout>
|
|
);
|
|
};
|
|
|
|
export default withAuth(WorkspaceSettings);
|