mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge pull request #30 from dakshesh14/main
fix: mutation error for project detail, feat: protecting all routes wrapper
This commit is contained in:
commit
ba869c37db
4
apps/app/.env.example
Normal file
4
apps/app/.env.example
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
NEXT_PUBLIC_API_BASE_URL = "<-- endpoint goes here -->"
|
||||||
|
NEXT_PUBLIC_GOOGLE_CLIENTID = "<-- google client id goes here -->"
|
||||||
|
NEXT_PUBLIC_GITHUB_ID = "<-- github id goes here -->"
|
||||||
|
NEXT_PUBLIC_APP_ENVIRONMENT=development
|
@ -7,7 +7,7 @@ import { useForm } from "react-hook-form";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Combobox, Dialog, Transition } from "@headlessui/react";
|
import { Combobox, Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issuesServices from "lib/services/issues.services";
|
import issuesServices from "lib/services/issues.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
// icons
|
// icons
|
||||||
|
@ -8,7 +8,7 @@ import { SubmitHandler, useForm } from "react-hook-form";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Combobox, Dialog, Transition } from "@headlessui/react";
|
import { Combobox, Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issuesServices from "lib/services/issues.services";
|
import issuesServices from "lib/services/issues.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
import useTheme from "lib/hooks/useTheme";
|
import useTheme from "lib/hooks/useTheme";
|
||||||
@ -22,7 +22,7 @@ import {
|
|||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
// components
|
// components
|
||||||
import ShortcutsModal from "components/command-palette/shortcuts";
|
import ShortcutsModal from "components/command-palette/shortcuts";
|
||||||
import CreateProjectModal from "components/project/CreateProjectModal";
|
import CreateProjectModal from "components/project/create-project-modal";
|
||||||
import CreateUpdateIssuesModal from "components/project/issues/CreateUpdateIssueModal";
|
import CreateUpdateIssuesModal from "components/project/issues/CreateUpdateIssueModal";
|
||||||
import CreateUpdateCycleModal from "components/project/cycles/CreateUpdateCyclesModal";
|
import CreateUpdateCycleModal from "components/project/cycles/CreateUpdateCyclesModal";
|
||||||
// ui
|
// ui
|
||||||
|
@ -20,7 +20,7 @@ import { Button, Select, TextArea } from "ui";
|
|||||||
import { ChevronDownIcon, CheckIcon } from "@heroicons/react/20/solid";
|
import { ChevronDownIcon, CheckIcon } from "@heroicons/react/20/solid";
|
||||||
|
|
||||||
// types
|
// types
|
||||||
import { ProjectMember, WorkspaceMember } from "types";
|
import { IProjectMemberInvitation } from "types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -28,6 +28,11 @@ type Props = {
|
|||||||
members: any[];
|
members: any[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ProjectMember = IProjectMemberInvitation & {
|
||||||
|
member_id: string;
|
||||||
|
user_id: string;
|
||||||
|
};
|
||||||
|
|
||||||
const defaultValues: Partial<ProjectMember> = {
|
const defaultValues: Partial<ProjectMember> = {
|
||||||
email: "",
|
email: "",
|
||||||
message: "",
|
message: "",
|
||||||
@ -49,7 +54,7 @@ const SendProjectInvitationModal: React.FC<Props> = ({ isOpen, setIsOpen, member
|
|||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { data: people } = useSWR<WorkspaceMember[]>(
|
const { data: people } = useSWR(
|
||||||
activeWorkspace ? WORKSPACE_MEMBERS(activeWorkspace.slug) : null,
|
activeWorkspace ? WORKSPACE_MEMBERS(activeWorkspace.slug) : null,
|
||||||
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null,
|
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null,
|
||||||
{
|
{
|
||||||
|
@ -9,19 +9,26 @@ import useToast from "lib/hooks/useToast";
|
|||||||
// icons
|
// icons
|
||||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||||||
// ui
|
// ui
|
||||||
import { Button } from "ui";
|
import { Button, Input } from "ui";
|
||||||
// types
|
// types
|
||||||
import type { IProject } from "types";
|
import type { IProject } from "types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
onClose: () => void;
|
||||||
data?: IProject;
|
data: IProject | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ConfirmProjectDeletion: React.FC<Props> = ({ isOpen, setIsOpen, data }) => {
|
const ConfirmProjectDeletion: React.FC<Props> = ({ isOpen, data, onClose }) => {
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
|
|
||||||
|
const [selectedProject, setSelectedProject] = useState<IProject | null>(null);
|
||||||
|
|
||||||
|
const [confirmProjectName, setConfirmProjectName] = useState("");
|
||||||
|
const [confirmDeleteMyProject, setConfirmDeleteMyProject] = useState(false);
|
||||||
|
|
||||||
|
const canDelete = confirmProjectName === data?.name && confirmDeleteMyProject;
|
||||||
|
|
||||||
const { activeWorkspace, mutateProjects } = useUser();
|
const { activeWorkspace, mutateProjects } = useUser();
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
@ -29,13 +36,18 @@ const ConfirmProjectDeletion: React.FC<Props> = ({ isOpen, setIsOpen, data }) =>
|
|||||||
const cancelButtonRef = useRef(null);
|
const cancelButtonRef = useRef(null);
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setIsOpen(false);
|
|
||||||
setIsDeleteLoading(false);
|
setIsDeleteLoading(false);
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setConfirmProjectName("");
|
||||||
|
setConfirmDeleteMyProject(false);
|
||||||
|
clearTimeout(timer);
|
||||||
|
}, 350);
|
||||||
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeletion = async () => {
|
const handleDeletion = async () => {
|
||||||
setIsDeleteLoading(true);
|
setIsDeleteLoading(true);
|
||||||
if (!data || !activeWorkspace) return;
|
if (!data || !activeWorkspace || !canDelete) return;
|
||||||
await projectService
|
await projectService
|
||||||
.deleteProject(activeWorkspace.slug, data.id)
|
.deleteProject(activeWorkspace.slug, data.id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -54,8 +66,14 @@ const ConfirmProjectDeletion: React.FC<Props> = ({ isOpen, setIsOpen, data }) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
data && setIsOpen(true);
|
if (data) setSelectedProject(data);
|
||||||
}, [data, setIsOpen]);
|
else {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setSelectedProject(null);
|
||||||
|
clearTimeout(timer);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={isOpen} as={React.Fragment}>
|
<Transition.Root show={isOpen} as={React.Fragment}>
|
||||||
@ -104,11 +122,48 @@ const ConfirmProjectDeletion: React.FC<Props> = ({ isOpen, setIsOpen, data }) =>
|
|||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
Are you sure you want to delete project - {`"`}
|
Are you sure you want to delete project - {`"`}
|
||||||
<span className="italic">{data?.name}</span>
|
<span className="italic">{selectedProject?.name}</span>
|
||||||
{`"`} ? All of the data related to the project will be permanently
|
{`"`} ? All of the data related to the project will be permanently
|
||||||
removed. This action cannot be undone.
|
removed. This action cannot be undone.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="h-0.5 bg-gray-200 my-3" />
|
||||||
|
<div className="mt-3">
|
||||||
|
<p className="text-sm">
|
||||||
|
Enter the project name{" "}
|
||||||
|
<span className="font-semibold">{selectedProject?.name}</span> to
|
||||||
|
continue:
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Project name"
|
||||||
|
className="mt-2"
|
||||||
|
value={confirmProjectName}
|
||||||
|
onChange={(e) => {
|
||||||
|
setConfirmProjectName(e.target.value);
|
||||||
|
}}
|
||||||
|
name="projectName"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3">
|
||||||
|
<p className="text-sm">
|
||||||
|
To confirm, type <span className="font-semibold">delete my project</span>{" "}
|
||||||
|
below:
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter 'delete my project'"
|
||||||
|
className="mt-2"
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.value === "delete my project") {
|
||||||
|
setConfirmDeleteMyProject(true);
|
||||||
|
} else {
|
||||||
|
setConfirmDeleteMyProject(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
name="projectName"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -117,7 +172,7 @@ const ConfirmProjectDeletion: React.FC<Props> = ({ isOpen, setIsOpen, data }) =>
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleDeletion}
|
onClick={handleDeletion}
|
||||||
theme="danger"
|
theme="danger"
|
||||||
disabled={isDeleteLoading}
|
disabled={isDeleteLoading || !canDelete}
|
||||||
className="inline-flex sm:ml-3"
|
className="inline-flex sm:ml-3"
|
||||||
>
|
>
|
||||||
{isDeleteLoading ? "Deleting..." : "Delete"}
|
{isDeleteLoading ? "Deleting..." : "Delete"}
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
// swr
|
// swr
|
||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
// react hook form
|
// react hook form
|
||||||
@ -8,6 +8,10 @@ import { Dialog, Transition } from "@headlessui/react";
|
|||||||
// services
|
// services
|
||||||
import projectServices from "lib/services/project.service";
|
import projectServices from "lib/services/project.service";
|
||||||
import workspaceService from "lib/services/workspace.service";
|
import workspaceService from "lib/services/workspace.service";
|
||||||
|
// common
|
||||||
|
import { createSimilarString } from "constants/common";
|
||||||
|
// constants
|
||||||
|
import { NETWORK_CHOICES } from "constants/";
|
||||||
// fetch keys
|
// fetch keys
|
||||||
import { PROJECTS_LIST, WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
import { PROJECTS_LIST, WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
||||||
// hooks
|
// hooks
|
||||||
@ -15,21 +19,19 @@ import useUser from "lib/hooks/useUser";
|
|||||||
import useToast from "lib/hooks/useToast";
|
import useToast from "lib/hooks/useToast";
|
||||||
// ui
|
// ui
|
||||||
import { Button, Input, TextArea, Select } from "ui";
|
import { Button, Input, TextArea, Select } from "ui";
|
||||||
// common
|
|
||||||
import { debounce } from "constants/common";
|
|
||||||
// types
|
// types
|
||||||
import { IProject, WorkspaceMember } from "types";
|
import { IProject } from "types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const NETWORK_CHOICES = { "0": "Secret", "2": "Public" };
|
|
||||||
|
|
||||||
const defaultValues: Partial<IProject> = {
|
const defaultValues: Partial<IProject> = {
|
||||||
name: "",
|
name: "",
|
||||||
|
identifier: "",
|
||||||
description: "",
|
description: "",
|
||||||
|
network: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const IsGuestCondition: React.FC<{
|
const IsGuestCondition: React.FC<{
|
||||||
@ -60,11 +62,16 @@ const CreateProjectModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
|
|||||||
|
|
||||||
const { activeWorkspace, user } = useUser();
|
const { activeWorkspace, user } = useUser();
|
||||||
|
|
||||||
const { data: workspaceMembers } = useSWR<WorkspaceMember[]>(
|
const { data: workspaceMembers } = useSWR(
|
||||||
activeWorkspace ? WORKSPACE_MEMBERS(activeWorkspace.slug) : null,
|
activeWorkspace ? WORKSPACE_MEMBERS(activeWorkspace.slug) : null,
|
||||||
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null
|
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null,
|
||||||
|
{
|
||||||
|
shouldRetryOnError: false,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [recommendedIdentifier, setRecommendedIdentifier] = useState<string[]>([]);
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const [isChangeIdentifierRequired, setIsChangeIdentifierRequired] = useState(true);
|
const [isChangeIdentifierRequired, setIsChangeIdentifierRequired] = useState(true);
|
||||||
@ -75,12 +82,11 @@ const CreateProjectModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
|
|||||||
handleSubmit,
|
handleSubmit,
|
||||||
reset,
|
reset,
|
||||||
setError,
|
setError,
|
||||||
|
clearErrors,
|
||||||
watch,
|
watch,
|
||||||
setValue,
|
setValue,
|
||||||
} = useForm<IProject>({
|
} = useForm<IProject>({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
reValidateMode: "onChange",
|
|
||||||
mode: "all",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = async (formData: IProject) => {
|
const onSubmit = async (formData: IProject) => {
|
||||||
@ -111,6 +117,7 @@ const CreateProjectModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
|
|||||||
handleClose();
|
handleClose();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
err = err.data;
|
||||||
Object.keys(err).map((key) => {
|
Object.keys(err).map((key) => {
|
||||||
const errorMessages = err[key];
|
const errorMessages = err[key];
|
||||||
setError(key as keyof IProject, {
|
setError(key as keyof IProject, {
|
||||||
@ -123,22 +130,6 @@ const CreateProjectModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
|
|||||||
const projectName = watch("name") ?? "";
|
const projectName = watch("name") ?? "";
|
||||||
const projectIdentifier = watch("identifier") ?? "";
|
const projectIdentifier = watch("identifier") ?? "";
|
||||||
|
|
||||||
const checkIdentifier = (slug: string, value: string) => {
|
|
||||||
projectServices.checkProjectIdentifierAvailability(slug, value).then((response) => {
|
|
||||||
console.log(response);
|
|
||||||
if (response.exists) setError("identifier", { message: "Identifier already exists" });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
const checkIdentifierAvailability = useCallback(debounce(checkIdentifier, 1500), []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (projectName && isChangeIdentifierRequired) {
|
|
||||||
setValue("identifier", projectName.replace(/ /g, "-").toUpperCase().substring(0, 3));
|
|
||||||
}
|
|
||||||
}, [projectName, projectIdentifier, setValue, isChangeIdentifierRequired]);
|
|
||||||
|
|
||||||
if (workspaceMembers) {
|
if (workspaceMembers) {
|
||||||
const isMember = workspaceMembers.find((member) => member.member.id === user?.id);
|
const isMember = workspaceMembers.find((member) => member.member.id === user?.id);
|
||||||
const isGuest = workspaceMembers.find(
|
const isGuest = workspaceMembers.find(
|
||||||
@ -148,6 +139,30 @@ const CreateProjectModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
|
|||||||
if ((!isMember || isGuest) && isOpen) return <IsGuestCondition setIsOpen={setIsOpen} />;
|
if ((!isMember || isGuest) && isOpen) return <IsGuestCondition setIsOpen={setIsOpen} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (projectName && isChangeIdentifierRequired) {
|
||||||
|
setValue("identifier", projectName.replace(/ /g, "").toUpperCase().substring(0, 3));
|
||||||
|
}
|
||||||
|
}, [projectName, projectIdentifier, setValue, isChangeIdentifierRequired]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!projectName) return;
|
||||||
|
const suggestedIdentifier = createSimilarString(
|
||||||
|
projectName.replace(/ /g, "").toUpperCase().substring(0, 3)
|
||||||
|
);
|
||||||
|
|
||||||
|
setRecommendedIdentifier([
|
||||||
|
suggestedIdentifier + Math.floor(Math.random() * 101),
|
||||||
|
suggestedIdentifier + Math.floor(Math.random() * 101),
|
||||||
|
projectIdentifier.toUpperCase().substring(0, 3) + Math.floor(Math.random() * 101),
|
||||||
|
projectIdentifier.toUpperCase().substring(0, 3) + Math.floor(Math.random() * 101),
|
||||||
|
]);
|
||||||
|
}, [errors.identifier]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => setIsChangeIdentifierRequired(true);
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={isOpen} as={React.Fragment}>
|
<Transition.Root show={isOpen} as={React.Fragment}>
|
||||||
<Dialog as="div" className="relative z-10" onClose={handleClose}>
|
<Dialog as="div" className="relative z-10" onClose={handleClose}>
|
||||||
@ -234,11 +249,7 @@ const CreateProjectModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
|
|||||||
placeholder="Enter Project Identifier"
|
placeholder="Enter Project Identifier"
|
||||||
error={errors.identifier}
|
error={errors.identifier}
|
||||||
register={register}
|
register={register}
|
||||||
onChange={(e: any) => {
|
onChange={() => setIsChangeIdentifierRequired(false)}
|
||||||
setIsChangeIdentifierRequired(false);
|
|
||||||
if (!activeWorkspace || !e.target.value) return;
|
|
||||||
checkIdentifierAvailability(activeWorkspace.slug, e.target.value);
|
|
||||||
}}
|
|
||||||
validations={{
|
validations={{
|
||||||
required: "Identifier is required",
|
required: "Identifier is required",
|
||||||
minLength: {
|
minLength: {
|
||||||
@ -251,6 +262,27 @@ const CreateProjectModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{errors.identifier && (
|
||||||
|
<div className="mt-2">
|
||||||
|
<p>Ops! Identifier is already taken. Try one of the following:</p>
|
||||||
|
<div className="flex gap-x-2">
|
||||||
|
{recommendedIdentifier.map((identifier) => (
|
||||||
|
<button
|
||||||
|
key={identifier}
|
||||||
|
type="button"
|
||||||
|
className="text-sm text-gray-500 hover:text-gray-700 border p-2 py-0.5 rounded"
|
||||||
|
onClick={() => {
|
||||||
|
clearErrors("identifier");
|
||||||
|
setValue("identifier", identifier);
|
||||||
|
setIsChangeIdentifierRequired(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{identifier}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -4,7 +4,7 @@ import { mutate } from "swr";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import cycleService from "lib/services/cycles.services";
|
import cycleService from "lib/services/cycles.service";
|
||||||
// fetch api
|
// fetch api
|
||||||
import { CYCLE_LIST } from "constants/fetch-keys";
|
import { CYCLE_LIST } from "constants/fetch-keys";
|
||||||
// hooks
|
// hooks
|
||||||
|
@ -6,7 +6,7 @@ import { useForm } from "react-hook-form";
|
|||||||
// headless
|
// headless
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import cycleService from "lib/services/cycles.services";
|
import cycleService from "lib/services/cycles.service";
|
||||||
// fetch keys
|
// fetch keys
|
||||||
import { CYCLE_LIST } from "constants/fetch-keys";
|
import { CYCLE_LIST } from "constants/fetch-keys";
|
||||||
// hooks
|
// hooks
|
||||||
|
@ -7,7 +7,7 @@ import { Combobox, Dialog, Transition } from "@headlessui/react";
|
|||||||
// ui
|
// ui
|
||||||
import { Button } from "ui";
|
import { Button } from "ui";
|
||||||
// services
|
// services
|
||||||
import issuesServices from "lib/services/issues.services";
|
import issuesServices from "lib/services/issues.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
import useToast from "lib/hooks/useToast";
|
import useToast from "lib/hooks/useToast";
|
||||||
|
@ -7,7 +7,7 @@ import useSWR, { mutate } from "swr";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Disclosure, Transition, Menu } from "@headlessui/react";
|
import { Disclosure, Transition, Menu } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import cycleServices from "lib/services/cycles.services";
|
import cycleServices from "lib/services/cycles.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
// components
|
// components
|
||||||
@ -22,7 +22,7 @@ import type { CycleViewProps as Props, CycleIssueResponse, IssueResponse } from
|
|||||||
import { CYCLE_ISSUES } from "constants/fetch-keys";
|
import { CYCLE_ISSUES } from "constants/fetch-keys";
|
||||||
// constants
|
// constants
|
||||||
import { renderShortNumericDateFormat } from "constants/common";
|
import { renderShortNumericDateFormat } from "constants/common";
|
||||||
import issuesServices from "lib/services/issues.services";
|
import issuesServices from "lib/services/issues.service";
|
||||||
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
|
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
|
||||||
import { Draggable } from "react-beautiful-dnd";
|
import { Draggable } from "react-beautiful-dnd";
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ import useSWR from "swr";
|
|||||||
import type { DropResult } from "react-beautiful-dnd";
|
import type { DropResult } from "react-beautiful-dnd";
|
||||||
import { DragDropContext } from "react-beautiful-dnd";
|
import { DragDropContext } from "react-beautiful-dnd";
|
||||||
// services
|
// services
|
||||||
import stateServices from "lib/services/state.services";
|
import stateServices from "lib/services/state.service";
|
||||||
import issuesServices from "lib/services/issues.services";
|
import issuesServices from "lib/services/issues.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
// fetching keys
|
// fetching keys
|
||||||
@ -20,7 +20,7 @@ import CreateUpdateIssuesModal from "components/project/issues/CreateUpdateIssue
|
|||||||
// ui
|
// ui
|
||||||
import { Spinner } from "ui";
|
import { Spinner } from "ui";
|
||||||
// types
|
// types
|
||||||
import type { IState, IIssue, Properties, NestedKeyOf, ProjectMember } from "types";
|
import type { IState, IIssue, Properties, NestedKeyOf, IProjectMember } from "types";
|
||||||
import ConfirmIssueDeletion from "../ConfirmIssueDeletion";
|
import ConfirmIssueDeletion from "../ConfirmIssueDeletion";
|
||||||
import { TrashIcon } from "@heroicons/react/24/outline";
|
import { TrashIcon } from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ type Props = {
|
|||||||
groupedByIssues: {
|
groupedByIssues: {
|
||||||
[key: string]: IIssue[];
|
[key: string]: IIssue[];
|
||||||
};
|
};
|
||||||
members: ProjectMember[] | undefined;
|
members: IProjectMember[] | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BoardView: React.FC<Props> = ({ properties, selectedGroup, groupedByIssues, members }) => {
|
const BoardView: React.FC<Props> = ({ properties, selectedGroup, groupedByIssues, members }) => {
|
||||||
|
@ -4,7 +4,7 @@ import { mutate } from "swr";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import stateServices from "lib/services/state.services";
|
import stateServices from "lib/services/state.service";
|
||||||
// fetch api
|
// fetch api
|
||||||
import { STATE_LIST } from "constants/fetch-keys";
|
import { STATE_LIST } from "constants/fetch-keys";
|
||||||
// hooks
|
// hooks
|
||||||
@ -43,7 +43,7 @@ const ConfirmStateDeletion: React.FC<Props> = ({ isOpen, setIsOpen, data }) => {
|
|||||||
mutate<IState[]>(
|
mutate<IState[]>(
|
||||||
STATE_LIST(data.project),
|
STATE_LIST(data.project),
|
||||||
(prevData) => prevData?.filter((state) => state.id !== data?.id),
|
(prevData) => prevData?.filter((state) => state.id !== data?.id),
|
||||||
false,
|
false
|
||||||
);
|
);
|
||||||
handleClose();
|
handleClose();
|
||||||
})
|
})
|
||||||
@ -98,18 +98,15 @@ const ConfirmStateDeletion: React.FC<Props> = ({ isOpen, setIsOpen, data }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||||
<Dialog.Title
|
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-900">
|
||||||
as="h3"
|
|
||||||
className="text-lg font-medium leading-6 text-gray-900"
|
|
||||||
>
|
|
||||||
Delete State
|
Delete State
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
Are you sure you want to delete state - {`"`}
|
Are you sure you want to delete state - {`"`}
|
||||||
<span className="italic">{data?.name}</span>
|
<span className="italic">{data?.name}</span>
|
||||||
{`"`} ? All of the data related to the state will be
|
{`"`} ? All of the data related to the state will be permanently removed.
|
||||||
permanently removed. This action cannot be undone.
|
This action cannot be undone.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,7 +8,7 @@ import { TwitterPicker } from "react-color";
|
|||||||
// headless
|
// headless
|
||||||
import { Dialog, Popover, Transition } from "@headlessui/react";
|
import { Dialog, Popover, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import stateService from "lib/services/state.services";
|
import stateService from "lib/services/state.service";
|
||||||
// fetch keys
|
// fetch keys
|
||||||
import { STATE_LIST } from "constants/fetch-keys";
|
import { STATE_LIST } from "constants/fetch-keys";
|
||||||
// hooks
|
// hooks
|
||||||
|
@ -6,7 +6,7 @@ import { Dialog, Transition } from "@headlessui/react";
|
|||||||
// fetching keys
|
// fetching keys
|
||||||
import { PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
import { PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
||||||
// services
|
// services
|
||||||
import issueServices from "lib/services/issues.services";
|
import issueServices from "lib/services/issues.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
import useToast from "lib/hooks/useToast";
|
import useToast from "lib/hooks/useToast";
|
||||||
|
@ -11,7 +11,7 @@ import useUser from "lib/hooks/useUser";
|
|||||||
import { PROJECT_MEMBERS } from "constants/fetch-keys";
|
import { PROJECT_MEMBERS } from "constants/fetch-keys";
|
||||||
// types
|
// types
|
||||||
import type { Control } from "react-hook-form";
|
import type { Control } from "react-hook-form";
|
||||||
import type { IIssue, WorkspaceMember } from "types";
|
import type { IIssue } from "types";
|
||||||
import { UserIcon } from "@heroicons/react/24/outline";
|
import { UserIcon } from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
import { SearchListbox } from "ui";
|
import { SearchListbox } from "ui";
|
||||||
@ -23,7 +23,7 @@ type Props = {
|
|||||||
const SelectAssignee: React.FC<Props> = ({ control }) => {
|
const SelectAssignee: React.FC<Props> = ({ control }) => {
|
||||||
const { activeWorkspace, activeProject } = useUser();
|
const { activeWorkspace, activeProject } = useUser();
|
||||||
|
|
||||||
const { data: people } = useSWR<WorkspaceMember[]>(
|
const { data: people } = useSWR(
|
||||||
activeWorkspace && activeProject ? PROJECT_MEMBERS(activeProject.id) : null,
|
activeWorkspace && activeProject ? PROJECT_MEMBERS(activeProject.id) : null,
|
||||||
activeWorkspace && activeProject
|
activeWorkspace && activeProject
|
||||||
? () => projectServices.projectMembers(activeWorkspace.slug, activeProject.id)
|
? () => projectServices.projectMembers(activeWorkspace.slug, activeProject.id)
|
||||||
|
@ -6,7 +6,7 @@ import { useForm, Controller } from "react-hook-form";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Listbox, Transition } from "@headlessui/react";
|
import { Listbox, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issuesServices from "lib/services/issues.services";
|
import issuesServices from "lib/services/issues.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
// fetching keys
|
// fetching keys
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
// headless
|
// headless
|
||||||
import { Dialog, Menu, Transition } from "@headlessui/react";
|
import { Dialog, Menu, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issuesServices from "lib/services/issues.services";
|
import issuesServices from "lib/services/issues.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
import useToast from "lib/hooks/useToast";
|
import useToast from "lib/hooks/useToast";
|
||||||
|
@ -10,14 +10,14 @@ import { Listbox, Transition } from "@headlessui/react";
|
|||||||
// icons
|
// icons
|
||||||
import { PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
|
import { PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import { IIssue, IssueResponse, NestedKeyOf, Properties, WorkspaceMember } from "types";
|
import { IIssue, IssueResponse, NestedKeyOf, Properties } from "types";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
// fetch keys
|
// fetch keys
|
||||||
import { PRIORITIES } from "constants/";
|
import { PRIORITIES } from "constants/";
|
||||||
import { PROJECT_ISSUES_LIST, WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
import { PROJECT_ISSUES_LIST, WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
||||||
// services
|
// services
|
||||||
import issuesServices from "lib/services/issues.services";
|
import issuesServices from "lib/services/issues.service";
|
||||||
import workspaceService from "lib/services/workspace.service";
|
import workspaceService from "lib/services/workspace.service";
|
||||||
// constants
|
// constants
|
||||||
import { addSpaceIfCamelCase, classNames, renderShortNumericDateFormat } from "constants/common";
|
import { addSpaceIfCamelCase, classNames, renderShortNumericDateFormat } from "constants/common";
|
||||||
@ -60,7 +60,7 @@ const ListView: React.FC<Props> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data: people } = useSWR<WorkspaceMember[]>(
|
const { data: people } = useSWR(
|
||||||
activeWorkspace ? WORKSPACE_MEMBERS : null,
|
activeWorkspace ? WORKSPACE_MEMBERS : null,
|
||||||
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null
|
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null
|
||||||
);
|
);
|
||||||
|
@ -6,8 +6,8 @@ import { Listbox, Transition } from "@headlessui/react";
|
|||||||
// react hook form
|
// react hook form
|
||||||
import { useForm, Controller, UseFormWatch } from "react-hook-form";
|
import { useForm, Controller, UseFormWatch } from "react-hook-form";
|
||||||
// services
|
// services
|
||||||
import stateServices from "lib/services/state.services";
|
import stateServices from "lib/services/state.service";
|
||||||
import issuesServices from "lib/services/issues.services";
|
import issuesServices from "lib/services/issues.service";
|
||||||
import workspaceService from "lib/services/workspace.service";
|
import workspaceService from "lib/services/workspace.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
@ -40,14 +40,7 @@ import {
|
|||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import type { Control } from "react-hook-form";
|
import type { Control } from "react-hook-form";
|
||||||
import type {
|
import type { IIssue, IIssueLabels, IssueResponse, IState, NestedKeyOf } from "types";
|
||||||
IIssue,
|
|
||||||
IIssueLabels,
|
|
||||||
IssueResponse,
|
|
||||||
IState,
|
|
||||||
NestedKeyOf,
|
|
||||||
WorkspaceMember,
|
|
||||||
} from "types";
|
|
||||||
import { TwitterPicker } from "react-color";
|
import { TwitterPicker } from "react-color";
|
||||||
import IssuesListModal from "components/project/issues/IssuesListModal";
|
import IssuesListModal from "components/project/issues/IssuesListModal";
|
||||||
|
|
||||||
@ -84,7 +77,7 @@ const IssueDetailSidebar: React.FC<Props> = ({
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: people } = useSWR<WorkspaceMember[]>(
|
const { data: people } = useSWR(
|
||||||
activeWorkspace ? WORKSPACE_MEMBERS(activeWorkspace.slug) : null,
|
activeWorkspace ? WORKSPACE_MEMBERS(activeWorkspace.slug) : null,
|
||||||
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null
|
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null
|
||||||
);
|
);
|
||||||
|
@ -4,7 +4,7 @@ import { mutate } from "swr";
|
|||||||
// react hook form
|
// react hook form
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
// services
|
// services
|
||||||
import issuesServices from "lib/services/issues.services";
|
import issuesServices from "lib/services/issues.service";
|
||||||
// fetch keys
|
// fetch keys
|
||||||
import { PROJECT_ISSUES_COMMENTS } from "constants/fetch-keys";
|
import { PROJECT_ISSUES_COMMENTS } from "constants/fetch-keys";
|
||||||
// components
|
// components
|
||||||
|
@ -8,7 +8,7 @@ import useUser from "lib/hooks/useUser";
|
|||||||
import { addSpaceIfCamelCase, classNames } from "constants/common";
|
import { addSpaceIfCamelCase, classNames } from "constants/common";
|
||||||
import { STATE_LIST } from "constants/fetch-keys";
|
import { STATE_LIST } from "constants/fetch-keys";
|
||||||
// services
|
// services
|
||||||
import stateServices from "lib/services/state.services";
|
import stateServices from "lib/services/state.service";
|
||||||
// ui
|
// ui
|
||||||
import { Listbox, Transition } from "@headlessui/react";
|
import { Listbox, Transition } from "@headlessui/react";
|
||||||
// types
|
// types
|
||||||
|
@ -28,7 +28,7 @@ type Props = {
|
|||||||
slug: string;
|
slug: string;
|
||||||
invitationsRespond: string[];
|
invitationsRespond: string[];
|
||||||
handleInvitation: (project_invitation: any, action: "accepted" | "withdraw") => void;
|
handleInvitation: (project_invitation: any, action: "accepted" | "withdraw") => void;
|
||||||
setDeleteProject: React.Dispatch<React.SetStateAction<IProject | undefined>>;
|
setDeleteProject: (id: string | null) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProjectMemberInvitations: React.FC<Props> = ({
|
const ProjectMemberInvitations: React.FC<Props> = ({
|
||||||
@ -100,7 +100,7 @@ const ProjectMemberInvitations: React.FC<Props> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="h-7 w-7 p-1 grid place-items-center rounded hover:bg-gray-200 duration-300 outline-none"
|
className="h-7 w-7 p-1 grid place-items-center rounded hover:bg-gray-200 duration-300 outline-none"
|
||||||
onClick={() => setDeleteProject(project)}
|
onClick={() => setDeleteProject(project.id)}
|
||||||
>
|
>
|
||||||
<TrashIcon className="h-4 w-4 text-red-500" />
|
<TrashIcon className="h-4 w-4 text-red-500" />
|
||||||
</button>
|
</button>
|
||||||
|
@ -15,7 +15,7 @@ import { Button } from "ui";
|
|||||||
// icons
|
// icons
|
||||||
import { CheckIcon, ChevronDownIcon } from "@heroicons/react/24/outline";
|
import { CheckIcon, ChevronDownIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import { IProject, WorkspaceMember } from "types";
|
import { IProject } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ type Props = {
|
|||||||
const ControlSettings: React.FC<Props> = ({ control, isSubmitting }) => {
|
const ControlSettings: React.FC<Props> = ({ control, isSubmitting }) => {
|
||||||
const { activeWorkspace } = useUser();
|
const { activeWorkspace } = useUser();
|
||||||
|
|
||||||
const { data: people } = useSWR<WorkspaceMember[]>(
|
const { data: people } = useSWR(
|
||||||
activeWorkspace ? WORKSPACE_MEMBERS : null,
|
activeWorkspace ? WORKSPACE_MEMBERS : null,
|
||||||
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null
|
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null
|
||||||
);
|
);
|
||||||
|
@ -7,7 +7,7 @@ import { Controller, SubmitHandler, useForm } from "react-hook-form";
|
|||||||
// react-color
|
// react-color
|
||||||
import { TwitterPicker } from "react-color";
|
import { TwitterPicker } from "react-color";
|
||||||
// services
|
// services
|
||||||
import issuesServices from "lib/services/issues.services";
|
import issuesServices from "lib/services/issues.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
// headless ui
|
// headless ui
|
||||||
|
@ -14,7 +14,7 @@ import { Button, Input, TextArea, Select } from "ui";
|
|||||||
// hooks
|
// hooks
|
||||||
import useToast from "lib/hooks/useToast";
|
import useToast from "lib/hooks/useToast";
|
||||||
// types
|
// types
|
||||||
import { WorkspaceMember } from "types";
|
import { IWorkspaceMemberInvitation } from "types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -30,7 +30,7 @@ const ROLE = {
|
|||||||
20: "Admin",
|
20: "Admin",
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultValues: Partial<WorkspaceMember> = {
|
const defaultValues: Partial<IWorkspaceMemberInvitation> = {
|
||||||
email: "",
|
email: "",
|
||||||
role: 5,
|
role: 5,
|
||||||
message: "",
|
message: "",
|
||||||
@ -57,7 +57,7 @@ const SendWorkspaceInvitationModal: React.FC<Props> = ({
|
|||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
reset,
|
reset,
|
||||||
} = useForm<WorkspaceMember>({
|
} = useForm<IWorkspaceMemberInvitation>({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
reValidateMode: "onChange",
|
reValidateMode: "onChange",
|
||||||
mode: "all",
|
mode: "all",
|
||||||
|
@ -3,10 +3,10 @@ import Image from "next/image";
|
|||||||
// react
|
// react
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
// types
|
// types
|
||||||
import { IWorkspaceInvitation } from "types";
|
import { IWorkspaceMemberInvitation } from "types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
invitation: IWorkspaceInvitation;
|
invitation: IWorkspaceMemberInvitation;
|
||||||
invitationsRespond: string[];
|
invitationsRespond: string[];
|
||||||
handleInvitation: any;
|
handleInvitation: any;
|
||||||
};
|
};
|
||||||
|
@ -65,6 +65,8 @@ export const PROJECT_MEMBERS = (workspaceSlug: string, projectId: string) =>
|
|||||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/`;
|
`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/`;
|
||||||
export const PROJECT_MEMBER_DETAIL = (workspaceSlug: string, projectId: string, memberId: string) =>
|
export const PROJECT_MEMBER_DETAIL = (workspaceSlug: string, projectId: string, memberId: string) =>
|
||||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/${memberId}/`;
|
`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/${memberId}/`;
|
||||||
|
export const PROJECT_VIEW_ENDPOINT = (workspaceSlug: string, projectId: string) =>
|
||||||
|
`/api/workspaces/${workspaceSlug}/projects/${projectId}/project-views/`;
|
||||||
|
|
||||||
export const PROJECT_INVITATIONS = (workspaceSlug: string, projectId: string) =>
|
export const PROJECT_INVITATIONS = (workspaceSlug: string, projectId: string) =>
|
||||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/invitations/`;
|
`/api/workspaces/${workspaceSlug}/projects/${projectId}/invitations/`;
|
||||||
|
@ -207,3 +207,12 @@ export const cosineSimilarity = (a: string, b: string) => {
|
|||||||
|
|
||||||
return dotProduct / Math.sqrt(magnitudeA * magnitudeB);
|
return dotProduct / Math.sqrt(magnitudeA * magnitudeB);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createSimilarString = (str: string) => {
|
||||||
|
const shuffled = str
|
||||||
|
.split("")
|
||||||
|
.sort(() => Math.random() - 0.5)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
return shuffled;
|
||||||
|
};
|
||||||
|
@ -7,7 +7,7 @@ export const WORKSPACE_INVITATIONS = "WORKSPACE_INVITATIONS";
|
|||||||
export const WORKSPACE_INVITATION = "WORKSPACE_INVITATION";
|
export const WORKSPACE_INVITATION = "WORKSPACE_INVITATION";
|
||||||
|
|
||||||
export const PROJECTS_LIST = (workspaceSlug: string) => `PROJECTS_LIST_${workspaceSlug}`;
|
export const PROJECTS_LIST = (workspaceSlug: string) => `PROJECTS_LIST_${workspaceSlug}`;
|
||||||
export const PROJECT_DETAILS = "PROJECT_DETAILS";
|
export const PROJECT_DETAILS = (projectId: string) => `PROJECT_DETAILS_${projectId}`;
|
||||||
|
|
||||||
export const PROJECT_MEMBERS = (projectId: string) => `PROJECT_MEMBERS_${projectId}`;
|
export const PROJECT_MEMBERS = (projectId: string) => `PROJECT_MEMBERS_${projectId}`;
|
||||||
export const PROJECT_INVITATIONS = "PROJECT_INVITATIONS";
|
export const PROJECT_INVITATIONS = "PROJECT_INVITATIONS";
|
||||||
|
@ -6,3 +6,5 @@ export const ROLE = {
|
|||||||
15: "Member",
|
15: "Member",
|
||||||
20: "Admin",
|
20: "Admin",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const NETWORK_CHOICES = { "0": "Secret", "2": "Public" };
|
||||||
|
@ -2,3 +2,5 @@ export const TOGGLE_SIDEBAR = "TOGGLE_SIDEBAR";
|
|||||||
export const REHYDRATE_THEME = "REHYDRATE_THEME";
|
export const REHYDRATE_THEME = "REHYDRATE_THEME";
|
||||||
export const SET_ISSUE_VIEW = "SET_ISSUE_VIEW";
|
export const SET_ISSUE_VIEW = "SET_ISSUE_VIEW";
|
||||||
export const SET_GROUP_BY_PROPERTY = "SET_GROUP_BY_PROPERTY";
|
export const SET_GROUP_BY_PROPERTY = "SET_GROUP_BY_PROPERTY";
|
||||||
|
export const SET_ORDER_BY_PROPERTY = "SET_ORDER_BY_PROPERTY";
|
||||||
|
export const SET_FILTER_ISSUES = "SET_FILTER_ISSUES";
|
||||||
|
@ -5,6 +5,8 @@ import {
|
|||||||
REHYDRATE_THEME,
|
REHYDRATE_THEME,
|
||||||
SET_ISSUE_VIEW,
|
SET_ISSUE_VIEW,
|
||||||
SET_GROUP_BY_PROPERTY,
|
SET_GROUP_BY_PROPERTY,
|
||||||
|
SET_ORDER_BY_PROPERTY,
|
||||||
|
SET_FILTER_ISSUES,
|
||||||
} from "constants/theme.context.constants";
|
} from "constants/theme.context.constants";
|
||||||
// components
|
// components
|
||||||
import ToastAlert from "components/toast-alert";
|
import ToastAlert from "components/toast-alert";
|
||||||
@ -12,30 +14,30 @@ import ToastAlert from "components/toast-alert";
|
|||||||
export const themeContext = createContext<ContextType>({} as ContextType);
|
export const themeContext = createContext<ContextType>({} as ContextType);
|
||||||
|
|
||||||
// types
|
// types
|
||||||
import type { IIssue, NestedKeyOf } from "types";
|
import type { IIssue, NestedKeyOf, ProjectViewTheme as Theme } from "types";
|
||||||
|
|
||||||
type Theme = {
|
|
||||||
collapsed: boolean;
|
|
||||||
issueView: "list" | "kanban" | null;
|
|
||||||
groupByProperty: NestedKeyOf<IIssue> | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ReducerActionType = {
|
type ReducerActionType = {
|
||||||
type:
|
type:
|
||||||
| typeof TOGGLE_SIDEBAR
|
| typeof TOGGLE_SIDEBAR
|
||||||
| typeof REHYDRATE_THEME
|
| typeof REHYDRATE_THEME
|
||||||
| typeof SET_ISSUE_VIEW
|
| typeof SET_ISSUE_VIEW
|
||||||
|
| typeof SET_ORDER_BY_PROPERTY
|
||||||
|
| typeof SET_FILTER_ISSUES
|
||||||
| typeof SET_GROUP_BY_PROPERTY;
|
| typeof SET_GROUP_BY_PROPERTY;
|
||||||
payload?: Partial<Theme>;
|
payload?: Partial<Theme>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ContextType = {
|
type ContextType = {
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
|
orderBy: NestedKeyOf<IIssue> | null;
|
||||||
issueView: "list" | "kanban" | null;
|
issueView: "list" | "kanban" | null;
|
||||||
groupByProperty: NestedKeyOf<IIssue> | null;
|
groupByProperty: NestedKeyOf<IIssue> | null;
|
||||||
|
filterIssue: "activeIssue" | "backlogIssue" | null;
|
||||||
toggleCollapsed: () => void;
|
toggleCollapsed: () => void;
|
||||||
setIssueView: (display: "list" | "kanban") => void;
|
setIssueView: (display: "list" | "kanban") => void;
|
||||||
setGroupByProperty: (property: NestedKeyOf<IIssue> | null) => void;
|
setGroupByProperty: (property: NestedKeyOf<IIssue> | null) => void;
|
||||||
|
setOrderBy: (property: NestedKeyOf<IIssue> | null) => void;
|
||||||
|
setFilterIssue: (property: "activeIssue" | "backlogIssue" | null) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateType = Theme;
|
type StateType = Theme;
|
||||||
@ -45,6 +47,8 @@ export const initialState: StateType = {
|
|||||||
collapsed: false,
|
collapsed: false,
|
||||||
issueView: "list",
|
issueView: "list",
|
||||||
groupByProperty: null,
|
groupByProperty: null,
|
||||||
|
orderBy: null,
|
||||||
|
filterIssue: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const reducer: ReducerFunctionType = (state, action) => {
|
export const reducer: ReducerFunctionType = (state, action) => {
|
||||||
@ -87,6 +91,28 @@ export const reducer: ReducerFunctionType = (state, action) => {
|
|||||||
...newState,
|
...newState,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case SET_ORDER_BY_PROPERTY: {
|
||||||
|
const newState = {
|
||||||
|
...state,
|
||||||
|
orderBy: payload?.orderBy || null,
|
||||||
|
};
|
||||||
|
localStorage.setItem("theme", JSON.stringify(newState));
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...newState,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case SET_FILTER_ISSUES: {
|
||||||
|
const newState = {
|
||||||
|
...state,
|
||||||
|
filterIssue: payload?.filterIssue || null,
|
||||||
|
};
|
||||||
|
localStorage.setItem("theme", JSON.stringify(newState));
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...newState,
|
||||||
|
};
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@ -120,6 +146,24 @@ export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const setOrderBy = useCallback((property: NestedKeyOf<IIssue> | null) => {
|
||||||
|
dispatch({
|
||||||
|
type: SET_ORDER_BY_PROPERTY,
|
||||||
|
payload: {
|
||||||
|
orderBy: property,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setFilterIssue = useCallback((property: "activeIssue" | "backlogIssue" | null) => {
|
||||||
|
dispatch({
|
||||||
|
type: SET_FILTER_ISSUES,
|
||||||
|
payload: {
|
||||||
|
filterIssue: property,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: REHYDRATE_THEME,
|
type: REHYDRATE_THEME,
|
||||||
@ -135,6 +179,10 @@ export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||||||
setIssueView,
|
setIssueView,
|
||||||
groupByProperty: state.groupByProperty,
|
groupByProperty: state.groupByProperty,
|
||||||
setGroupByProperty,
|
setGroupByProperty,
|
||||||
|
orderBy: state.orderBy,
|
||||||
|
setOrderBy,
|
||||||
|
filterIssue: state.filterIssue,
|
||||||
|
setFilterIssue,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ToastAlert />
|
<ToastAlert />
|
||||||
|
@ -6,9 +6,9 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
// services
|
// services
|
||||||
import userService from "lib/services/user.service";
|
import userService from "lib/services/user.service";
|
||||||
import issuesServices from "lib/services/issues.services";
|
import issuesServices from "lib/services/issues.service";
|
||||||
import stateServices from "lib/services/state.services";
|
import stateServices from "lib/services/state.service";
|
||||||
import sprintsServices from "lib/services/cycles.services";
|
import sprintsServices from "lib/services/cycles.service";
|
||||||
import projectServices from "lib/services/project.service";
|
import projectServices from "lib/services/project.service";
|
||||||
import workspaceService from "lib/services/workspace.service";
|
import workspaceService from "lib/services/workspace.service";
|
||||||
// constants
|
// constants
|
||||||
|
@ -8,7 +8,7 @@ import useUser from "lib/hooks/useUser";
|
|||||||
import Container from "layouts/Container";
|
import Container from "layouts/Container";
|
||||||
import Sidebar from "layouts/Navbar/Sidebar";
|
import Sidebar from "layouts/Navbar/Sidebar";
|
||||||
// components
|
// components
|
||||||
import CreateProjectModal from "components/project/CreateProjectModal";
|
import CreateProjectModal from "components/project/create-project-modal";
|
||||||
// types
|
// types
|
||||||
import type { Props } from "./types";
|
import type { Props } from "./types";
|
||||||
|
|
||||||
|
@ -10,8 +10,6 @@ import authenticationService from "lib/services/authentication.service";
|
|||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
import useTheme from "lib/hooks/useTheme";
|
import useTheme from "lib/hooks/useTheme";
|
||||||
import useToast from "lib/hooks/useToast";
|
import useToast from "lib/hooks/useToast";
|
||||||
// components
|
|
||||||
import CreateProjectModal from "components/project/CreateProjectModal";
|
|
||||||
// headless ui
|
// headless ui
|
||||||
import { Dialog, Disclosure, Menu, Transition } from "@headlessui/react";
|
import { Dialog, Disclosure, Menu, Transition } from "@headlessui/react";
|
||||||
// icons
|
// icons
|
||||||
@ -108,7 +106,6 @@ const userLinks = [
|
|||||||
|
|
||||||
const Sidebar: React.FC = () => {
|
const Sidebar: React.FC = () => {
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||||
const [isCreateProjectModal, setCreateProjectModal] = useState(false);
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@ -124,7 +121,6 @@ const Sidebar: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="h-full">
|
<nav className="h-full">
|
||||||
<CreateProjectModal isOpen={isCreateProjectModal} setIsOpen={setCreateProjectModal} />
|
|
||||||
<Transition.Root show={sidebarOpen} as={React.Fragment}>
|
<Transition.Root show={sidebarOpen} as={React.Fragment}>
|
||||||
<Dialog as="div" className="relative z-40 md:hidden" onClose={setSidebarOpen}>
|
<Dialog as="div" className="relative z-40 md:hidden" onClose={setSidebarOpen}>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
@ -557,7 +553,13 @@ const Sidebar: React.FC = () => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="group flex justify-center items-center gap-2 w-full rounded-md p-2 text-sm bg-theme text-white"
|
className="group flex justify-center items-center gap-2 w-full rounded-md p-2 text-sm bg-theme text-white"
|
||||||
onClick={() => setCreateProjectModal(true)}
|
onClick={() => {
|
||||||
|
const e = new KeyboardEvent("keydown", {
|
||||||
|
ctrlKey: true,
|
||||||
|
key: "p",
|
||||||
|
});
|
||||||
|
document.dispatchEvent(e);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<PlusIcon className="h-5 w-5" />
|
<PlusIcon className="h-5 w-5" />
|
||||||
{!sidebarCollapse && "Create Project"}
|
{!sidebarCollapse && "Create Project"}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { useState } from "react";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useTheme from "./useTheme";
|
import useTheme from "./useTheme";
|
||||||
import useUser from "./useUser";
|
import useUser from "./useUser";
|
||||||
@ -7,14 +6,19 @@ import { groupBy, orderArrayBy } from "constants/common";
|
|||||||
// constants
|
// constants
|
||||||
import { PRIORITIES } from "constants/";
|
import { PRIORITIES } from "constants/";
|
||||||
// types
|
// types
|
||||||
import type { IssueResponse, IIssue, NestedKeyOf } from "types";
|
import type { IssueResponse, IIssue } from "types";
|
||||||
|
|
||||||
const useIssuesFilter = (projectIssues?: IssueResponse) => {
|
const useIssuesFilter = (projectIssues?: IssueResponse) => {
|
||||||
const { issueView, setIssueView, groupByProperty, setGroupByProperty } = useTheme();
|
const {
|
||||||
|
issueView,
|
||||||
const [orderBy, setOrderBy] = useState<NestedKeyOf<IIssue> | null>(null);
|
setIssueView,
|
||||||
|
groupByProperty,
|
||||||
const [filterIssue, setFilterIssue] = useState<"activeIssue" | "backlogIssue" | null>(null);
|
setGroupByProperty,
|
||||||
|
orderBy,
|
||||||
|
setOrderBy,
|
||||||
|
filterIssue,
|
||||||
|
setFilterIssue,
|
||||||
|
} = useTheme();
|
||||||
|
|
||||||
const { states } = useUser();
|
const { states } = useUser();
|
||||||
|
|
||||||
@ -52,29 +56,29 @@ const useIssuesFilter = (projectIssues?: IssueResponse) => {
|
|||||||
|
|
||||||
if (filterIssue !== null) {
|
if (filterIssue !== null) {
|
||||||
if (filterIssue === "activeIssue") {
|
if (filterIssue === "activeIssue") {
|
||||||
groupedByIssues = Object.keys(groupedByIssues).reduce((acc, key) => {
|
const filteredStates = states?.filter(
|
||||||
const value = groupedByIssues[key];
|
(state) => state.group === "started" || state.group === "unstarted"
|
||||||
const filteredValue = value.filter(
|
);
|
||||||
(issue) =>
|
groupedByIssues = Object.fromEntries(
|
||||||
issue.state_detail.group === "started" || issue.state_detail.group === "unstarted"
|
filteredStates
|
||||||
|
?.sort((a, b) => a.sequence - b.sequence)
|
||||||
|
?.map((state) => [
|
||||||
|
state.name,
|
||||||
|
projectIssues?.results.filter((issue) => issue.state === state.id) ?? [],
|
||||||
|
]) ?? []
|
||||||
);
|
);
|
||||||
if (filteredValue.length > 0) {
|
|
||||||
acc[key] = filteredValue;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {} as typeof groupedByIssues);
|
|
||||||
} else if (filterIssue === "backlogIssue") {
|
} else if (filterIssue === "backlogIssue") {
|
||||||
groupedByIssues = Object.keys(groupedByIssues).reduce((acc, key) => {
|
const filteredStates = states?.filter(
|
||||||
const value = groupedByIssues[key];
|
(state) => state.group === "backlog" || state.group === "cancelled"
|
||||||
const filteredValue = value.filter(
|
);
|
||||||
(issue) =>
|
groupedByIssues = Object.fromEntries(
|
||||||
issue.state_detail.group === "backlog" || issue.state_detail.group === "cancelled"
|
filteredStates
|
||||||
|
?.sort((a, b) => a.sequence - b.sequence)
|
||||||
|
?.map((state) => [
|
||||||
|
state.name,
|
||||||
|
projectIssues?.results.filter((issue) => issue.state === state.id) ?? [],
|
||||||
|
]) ?? []
|
||||||
);
|
);
|
||||||
if (filteredValue.length > 0) {
|
|
||||||
acc[key] = filteredValue;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {} as typeof groupedByIssues);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import useSWR from "swr";
|
|||||||
// api routes
|
// api routes
|
||||||
import { ISSUE_PROPERTIES_ENDPOINT } from "constants/api-routes";
|
import { ISSUE_PROPERTIES_ENDPOINT } from "constants/api-routes";
|
||||||
// services
|
// services
|
||||||
import issueServices from "lib/services/issues.services";
|
import issueServices from "lib/services/issues.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "./useUser";
|
import useUser from "./useUser";
|
||||||
// types
|
// types
|
||||||
|
@ -10,9 +10,12 @@ import {
|
|||||||
PROJECT_MEMBERS,
|
PROJECT_MEMBERS,
|
||||||
PROJECT_MEMBER_DETAIL,
|
PROJECT_MEMBER_DETAIL,
|
||||||
USER_PROJECT_INVITATIONS,
|
USER_PROJECT_INVITATIONS,
|
||||||
|
PROJECT_VIEW_ENDPOINT,
|
||||||
} from "constants/api-routes";
|
} from "constants/api-routes";
|
||||||
// services
|
// services
|
||||||
import APIService from "lib/services/api.service";
|
import APIService from "lib/services/api.service";
|
||||||
|
// types
|
||||||
|
import type { IProject, IProjectMember, IProjectMemberInvitation, ProjectViewTheme } from "types";
|
||||||
|
|
||||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||||
|
|
||||||
@ -21,8 +24,8 @@ class ProjectServices extends APIService {
|
|||||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||||
}
|
}
|
||||||
|
|
||||||
async createProject(workspace_slug: string, data: any): Promise<any> {
|
async createProject(workspacSlug: string, data: Partial<IProject>): Promise<IProject> {
|
||||||
return this.post(PROJECTS_ENDPOINT(workspace_slug), data)
|
return this.post(PROJECTS_ENDPOINT(workspacSlug), data)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -45,8 +48,8 @@ class ProjectServices extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProjects(workspace_slug: string): Promise<any> {
|
async getProjects(workspacSlug: string): Promise<IProject[]> {
|
||||||
return this.get(PROJECTS_ENDPOINT(workspace_slug))
|
return this.get(PROJECTS_ENDPOINT(workspacSlug))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -55,8 +58,8 @@ class ProjectServices extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProject(workspace_slug: string, project_id: string): Promise<any> {
|
async getProject(workspacSlug: string, projectId: string): Promise<IProject> {
|
||||||
return this.get(PROJECT_DETAIL(workspace_slug, project_id))
|
return this.get(PROJECT_DETAIL(workspacSlug, projectId))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -65,8 +68,12 @@ class ProjectServices extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateProject(workspace_slug: string, project_id: string, data: any): Promise<any> {
|
async updateProject(
|
||||||
return this.patch(PROJECT_DETAIL(workspace_slug, project_id), data)
|
workspacSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: Partial<IProject>
|
||||||
|
): Promise<IProject> {
|
||||||
|
return this.patch(PROJECT_DETAIL(workspacSlug, projectId), data)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -75,8 +82,8 @@ class ProjectServices extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteProject(workspace_slug: string, project_id: string): Promise<any> {
|
async deleteProject(workspacSlug: string, projectId: string): Promise<any> {
|
||||||
return this.delete(PROJECT_DETAIL(workspace_slug, project_id))
|
return this.delete(PROJECT_DETAIL(workspacSlug, projectId))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -85,8 +92,8 @@ class ProjectServices extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async inviteProject(workspace_slug: string, project_id: string, data: any): Promise<any> {
|
async inviteProject(workspacSlug: string, projectId: string, data: any): Promise<any> {
|
||||||
return this.post(INVITE_PROJECT(workspace_slug, project_id), data)
|
return this.post(INVITE_PROJECT(workspacSlug, projectId), data)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -95,8 +102,8 @@ class ProjectServices extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async joinProject(workspace_slug: string, data: any): Promise<any> {
|
async joinProject(workspacSlug: string, data: any): Promise<any> {
|
||||||
return this.post(JOIN_PROJECT(workspace_slug), data)
|
return this.post(JOIN_PROJECT(workspacSlug), data)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -115,8 +122,8 @@ class ProjectServices extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async projectMembers(workspace_slug: string, project_id: string): Promise<any> {
|
async projectMembers(workspacSlug: string, projectId: string): Promise<IProjectMember[]> {
|
||||||
return this.get(PROJECT_MEMBERS(workspace_slug, project_id))
|
return this.get(PROJECT_MEMBERS(workspacSlug, projectId))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -126,12 +133,12 @@ class ProjectServices extends APIService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateProjectMember(
|
async updateProjectMember(
|
||||||
workspace_slug: string,
|
workspacSlug: string,
|
||||||
project_id: string,
|
projectId: string,
|
||||||
memberId: string,
|
memberId: string,
|
||||||
data: any
|
data: Partial<IProjectMember>
|
||||||
): Promise<any> {
|
): Promise<IProjectMember> {
|
||||||
return this.put(PROJECT_MEMBER_DETAIL(workspace_slug, project_id, memberId), data)
|
return this.put(PROJECT_MEMBER_DETAIL(workspacSlug, projectId, memberId), data)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -141,11 +148,11 @@ class ProjectServices extends APIService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async deleteProjectMember(
|
async deleteProjectMember(
|
||||||
workspace_slug: string,
|
workspacSlug: string,
|
||||||
project_id: string,
|
projectId: string,
|
||||||
memberId: string
|
memberId: string
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
return this.delete(PROJECT_MEMBER_DETAIL(workspace_slug, project_id, memberId))
|
return this.delete(PROJECT_MEMBER_DETAIL(workspacSlug, projectId, memberId))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -154,8 +161,11 @@ class ProjectServices extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async projectInvitations(workspace_slug: string, project_id: string): Promise<any> {
|
async projectInvitations(
|
||||||
return this.get(PROJECT_INVITATIONS(workspace_slug, project_id))
|
workspacSlug: string,
|
||||||
|
projectId: string
|
||||||
|
): Promise<IProjectMemberInvitation[]> {
|
||||||
|
return this.get(PROJECT_INVITATIONS(workspacSlug, projectId))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -165,11 +175,11 @@ class ProjectServices extends APIService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateProjectInvitation(
|
async updateProjectInvitation(
|
||||||
workspace_slug: string,
|
workspacSlug: string,
|
||||||
project_id: string,
|
projectId: string,
|
||||||
invitation_id: string
|
invitationId: string
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
return this.put(PROJECT_INVITATION_DETAIL(workspace_slug, project_id, invitation_id))
|
return this.put(PROJECT_INVITATION_DETAIL(workspacSlug, projectId, invitationId))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -177,12 +187,27 @@ class ProjectServices extends APIService {
|
|||||||
throw error?.response?.data;
|
throw error?.response?.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteProjectInvitation(
|
async deleteProjectInvitation(
|
||||||
workspace_slug: string,
|
workspacSlug: string,
|
||||||
project_id: string,
|
projectId: string,
|
||||||
invitation_id: string
|
invitationId: string
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
return this.delete(PROJECT_INVITATION_DETAIL(workspace_slug, project_id, invitation_id))
|
return this.delete(PROJECT_INVITATION_DETAIL(workspacSlug, projectId, invitationId))
|
||||||
|
.then((response) => {
|
||||||
|
return response?.data;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
throw error?.response?.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async setProjectView(
|
||||||
|
workspacSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: ProjectViewTheme
|
||||||
|
): Promise<any> {
|
||||||
|
await this.patch(PROJECT_VIEW_ENDPOINT(workspacSlug, projectId), data)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
|
@ -17,12 +17,15 @@ import APIService from "lib/services/api.service";
|
|||||||
|
|
||||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||||
|
|
||||||
|
// types
|
||||||
|
import { IWorkspace, IWorkspaceMember, IWorkspaceMemberInvitation } from "types";
|
||||||
|
|
||||||
class WorkspaceService extends APIService {
|
class WorkspaceService extends APIService {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||||
}
|
}
|
||||||
|
|
||||||
async userWorkspaces(): Promise<any> {
|
async userWorkspaces(): Promise<IWorkspace[]> {
|
||||||
return this.get(USER_WORKSPACES)
|
return this.get(USER_WORKSPACES)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
@ -32,7 +35,7 @@ class WorkspaceService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async createWorkspace(data: any): Promise<any> {
|
async createWorkspace(data: Partial<IWorkspace>): Promise<IWorkspace> {
|
||||||
return this.post(WORKSPACES_ENDPOINT, data)
|
return this.post(WORKSPACES_ENDPOINT, data)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
@ -42,17 +45,8 @@ class WorkspaceService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateWorkspace(workspace_slug: string, data: any): Promise<any> {
|
async updateWorkspace(workspaceSlug: string, data: Partial<IWorkspace>): Promise<IWorkspace> {
|
||||||
return this.patch(WORKSPACE_DETAIL(workspace_slug), data)
|
return this.patch(WORKSPACE_DETAIL(workspaceSlug), data)
|
||||||
.then((response) => {
|
|
||||||
return response?.data;
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
throw error?.response?.data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async deleteWorkspace(workspace_slug: string): Promise<any> {
|
|
||||||
return this.delete(WORKSPACE_DETAIL(workspace_slug))
|
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -61,8 +55,8 @@ class WorkspaceService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async inviteWorkspace(workspace_slug: string, data: any): Promise<any> {
|
async deleteWorkspace(workspaceSlug: string): Promise<any> {
|
||||||
return this.post(INVITE_WORKSPACE(workspace_slug), data)
|
return this.delete(WORKSPACE_DETAIL(workspaceSlug))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -70,8 +64,19 @@ class WorkspaceService extends APIService {
|
|||||||
throw error?.response?.data;
|
throw error?.response?.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async joinWorkspace(workspace_slug: string, InvitationId: string, data: any): Promise<any> {
|
|
||||||
return this.post(JOIN_WORKSPACE(workspace_slug, InvitationId), data, {
|
async inviteWorkspace(workspaceSlug: string, data: any): Promise<any> {
|
||||||
|
return this.post(INVITE_WORKSPACE(workspaceSlug), data)
|
||||||
|
.then((response) => {
|
||||||
|
return response?.data;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
throw error?.response?.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async joinWorkspace(workspaceSlug: string, InvitationId: string, data: any): Promise<any> {
|
||||||
|
return this.post(JOIN_WORKSPACE(workspaceSlug, InvitationId), data, {
|
||||||
headers: {},
|
headers: {},
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@ -92,7 +97,7 @@ class WorkspaceService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async userWorkspaceInvitations(): Promise<any> {
|
async userWorkspaceInvitations(): Promise<IWorkspaceMemberInvitation[]> {
|
||||||
return this.get(USER_WORKSPACE_INVITATIONS)
|
return this.get(USER_WORKSPACE_INVITATIONS)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
@ -102,8 +107,8 @@ class WorkspaceService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async workspaceMembers(workspace_slug: string): Promise<any> {
|
async workspaceMembers(workspaceSlug: string): Promise<IWorkspaceMember[]> {
|
||||||
return this.get(WORKSPACE_MEMBERS(workspace_slug))
|
return this.get(WORKSPACE_MEMBERS(workspaceSlug))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -112,8 +117,12 @@ class WorkspaceService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateWorkspaceMember(workspace_slug: string, memberId: string, data: any): Promise<any> {
|
async updateWorkspaceMember(
|
||||||
return this.put(WORKSPACE_MEMBER_DETAIL(workspace_slug, memberId), data)
|
workspaceSlug: string,
|
||||||
|
memberId: string,
|
||||||
|
data: Partial<IWorkspaceMember>
|
||||||
|
): Promise<IWorkspaceMember> {
|
||||||
|
return this.put(WORKSPACE_MEMBER_DETAIL(workspaceSlug, memberId), data)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -122,8 +131,8 @@ class WorkspaceService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteWorkspaceMember(workspace_slug: string, memberId: string): Promise<any> {
|
async deleteWorkspaceMember(workspaceSlug: string, memberId: string): Promise<any> {
|
||||||
return this.delete(WORKSPACE_MEMBER_DETAIL(workspace_slug, memberId))
|
return this.delete(WORKSPACE_MEMBER_DETAIL(workspaceSlug, memberId))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -132,8 +141,8 @@ class WorkspaceService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async workspaceInvitations(workspace_slug: string): Promise<any> {
|
async workspaceInvitations(workspaceSlug: string): Promise<IWorkspaceMemberInvitation[]> {
|
||||||
return this.get(WORKSPACE_INVITATIONS(workspace_slug))
|
return this.get(WORKSPACE_INVITATIONS(workspaceSlug))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -142,8 +151,8 @@ class WorkspaceService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWorkspaceInvitation(invitation_id: string): Promise<any> {
|
async getWorkspaceInvitation(invitationId: string): Promise<IWorkspaceMemberInvitation> {
|
||||||
return this.get(USER_WORKSPACE_INVITATION(invitation_id), { headers: {} })
|
return this.get(USER_WORKSPACE_INVITATION(invitationId), { headers: {} })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -152,8 +161,11 @@ class WorkspaceService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateWorkspaceInvitation(workspace_slug: string, invitation_id: string): Promise<any> {
|
async updateWorkspaceInvitation(
|
||||||
return this.put(WORKSPACE_INVITATION_DETAIL(workspace_slug, invitation_id))
|
workspaceSlug: string,
|
||||||
|
invitationId: string
|
||||||
|
): Promise<IWorkspaceMemberInvitation> {
|
||||||
|
return this.put(WORKSPACE_INVITATION_DETAIL(workspaceSlug, invitationId))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
@ -161,8 +173,9 @@ class WorkspaceService extends APIService {
|
|||||||
throw error?.response?.data;
|
throw error?.response?.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async deleteWorkspaceInvitations(workspace_slug: string, invitation_id: string): Promise<any> {
|
|
||||||
return this.delete(WORKSPACE_INVITATION_DETAIL(workspace_slug, invitation_id))
|
async deleteWorkspaceInvitations(workspaceSlug: string, invitationId: string): Promise<any> {
|
||||||
|
return this.delete(WORKSPACE_INVITATION_DETAIL(workspaceSlug, invitationId))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
|
@ -8,6 +8,8 @@ import { useForm } from "react-hook-form";
|
|||||||
import workspaceService from "lib/services/workspace.service";
|
import workspaceService from "lib/services/workspace.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
|
// hoc
|
||||||
|
import withAuth from "lib/hoc/withAuthWrapper";
|
||||||
// layouts
|
// layouts
|
||||||
import DefaultLayout from "layouts/DefaultLayout";
|
import DefaultLayout from "layouts/DefaultLayout";
|
||||||
// ui
|
// ui
|
||||||
@ -144,4 +146,4 @@ const CreateWorkspace: NextPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CreateWorkspace;
|
export default withAuth(CreateWorkspace);
|
||||||
|
@ -11,6 +11,8 @@ import userService from "lib/services/user.service";
|
|||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
// constants
|
// constants
|
||||||
import { USER_WORKSPACE_INVITATIONS } from "constants/api-routes";
|
import { USER_WORKSPACE_INVITATIONS } from "constants/api-routes";
|
||||||
|
// hoc
|
||||||
|
import withAuth from "lib/hoc/withAuthWrapper";
|
||||||
// layouts
|
// layouts
|
||||||
import DefaultLayout from "layouts/DefaultLayout";
|
import DefaultLayout from "layouts/DefaultLayout";
|
||||||
// components
|
// components
|
||||||
@ -20,7 +22,7 @@ import { Button, Spinner, EmptySpace, EmptySpaceItem } from "ui";
|
|||||||
// icons
|
// icons
|
||||||
import { CubeIcon, PlusIcon } from "@heroicons/react/24/outline";
|
import { CubeIcon, PlusIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import type { IWorkspaceInvitation } from "types";
|
import type { IWorkspaceMemberInvitation } from "types";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
const OnBoard: NextPage = () => {
|
const OnBoard: NextPage = () => {
|
||||||
@ -30,13 +32,12 @@ const OnBoard: NextPage = () => {
|
|||||||
|
|
||||||
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
|
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
|
||||||
|
|
||||||
const { data: invitations, mutate } = useSWR<IWorkspaceInvitation[]>(
|
const { data: invitations, mutate } = useSWR(USER_WORKSPACE_INVITATIONS, () =>
|
||||||
USER_WORKSPACE_INVITATIONS,
|
workspaceService.userWorkspaceInvitations()
|
||||||
() => workspaceService.userWorkspaceInvitations()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleInvitation = (
|
const handleInvitation = (
|
||||||
workspace_invitation: IWorkspaceInvitation,
|
workspace_invitation: IWorkspaceMemberInvitation,
|
||||||
action: "accepted" | "withdraw"
|
action: "accepted" | "withdraw"
|
||||||
) => {
|
) => {
|
||||||
if (action === "accepted") {
|
if (action === "accepted") {
|
||||||
@ -204,4 +205,4 @@ const OnBoard: NextPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default OnBoard;
|
export default withAuth(OnBoard);
|
||||||
|
@ -18,7 +18,9 @@ import { USER_ISSUE } from "constants/fetch-keys";
|
|||||||
import { classNames } from "constants/common";
|
import { classNames } from "constants/common";
|
||||||
// services
|
// services
|
||||||
import userService from "lib/services/user.service";
|
import userService from "lib/services/user.service";
|
||||||
import issuesServices from "lib/services/issues.services";
|
import issuesServices from "lib/services/issues.service";
|
||||||
|
// hoc
|
||||||
|
import withAuth from "lib/hoc/withAuthWrapper";
|
||||||
// components
|
// components
|
||||||
import ChangeStateDropdown from "components/project/issues/my-issues/ChangeStateDropdown";
|
import ChangeStateDropdown from "components/project/issues/my-issues/ChangeStateDropdown";
|
||||||
// icons
|
// icons
|
||||||
@ -278,4 +280,4 @@ const MyIssues: NextPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MyIssues;
|
export default withAuth(MyIssues);
|
||||||
|
@ -8,15 +8,17 @@ import { useForm } from "react-hook-form";
|
|||||||
import Dropzone, { useDropzone } from "react-dropzone";
|
import Dropzone, { useDropzone } from "react-dropzone";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
|
// hoc
|
||||||
|
import withAuth from "lib/hoc/withAuthWrapper";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/AppLayout";
|
import AppLayout from "layouts/AppLayout";
|
||||||
// services
|
// services
|
||||||
import userService from "lib/services/user.service";
|
import userService from "lib/services/user.service";
|
||||||
import fileServices from "lib/services/file.services";
|
import fileServices from "lib/services/file.service";
|
||||||
// ui
|
// ui
|
||||||
import { BreadcrumbItem, Breadcrumbs, Button, Input, Spinner } from "ui";
|
import { BreadcrumbItem, Breadcrumbs, Button, Input, Spinner } from "ui";
|
||||||
// types
|
// types
|
||||||
import type { IIssue, IUser, IWorkspaceInvitation } from "types";
|
import type { IIssue, IUser, IWorkspaceMemberInvitation } from "types";
|
||||||
import {
|
import {
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
ClipboardDocumentListIcon,
|
ClipboardDocumentListIcon,
|
||||||
@ -79,8 +81,9 @@ const Profile: NextPage = () => {
|
|||||||
myProfile ? () => userService.userIssues() : null
|
myProfile ? () => userService.userIssues() : null
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: invitations } = useSWR<IWorkspaceInvitation[]>(USER_WORKSPACE_INVITATIONS, () =>
|
const { data: invitations } = useSWR<IWorkspaceMemberInvitation[]>(
|
||||||
workspaceService.userWorkspaceInvitations()
|
USER_WORKSPACE_INVITATIONS,
|
||||||
|
() => workspaceService.userWorkspaceInvitations()
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -307,4 +310,4 @@ const Profile: NextPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Profile;
|
export default withAuth(Profile);
|
||||||
|
@ -5,12 +5,14 @@ import type { NextPage } from "next";
|
|||||||
// swr
|
// swr
|
||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
// services
|
// services
|
||||||
import issuesServices from "lib/services/issues.services";
|
import issuesServices from "lib/services/issues.service";
|
||||||
import sprintService from "lib/services/cycles.services";
|
import sprintService from "lib/services/cycles.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
// fetching keys
|
// fetching keys
|
||||||
import { CYCLE_ISSUES, CYCLE_LIST } from "constants/fetch-keys";
|
import { CYCLE_ISSUES, CYCLE_LIST } from "constants/fetch-keys";
|
||||||
|
// hoc
|
||||||
|
import withAuth from "lib/hoc/withAuthWrapper";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/AppLayout";
|
import AppLayout from "layouts/AppLayout";
|
||||||
// components
|
// components
|
||||||
@ -258,4 +260,4 @@ const ProjectSprints: NextPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProjectSprints;
|
export default withAuth(ProjectSprints);
|
||||||
|
@ -11,7 +11,7 @@ import { Controller, useForm } from "react-hook-form";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Disclosure, Menu, Tab, Transition } from "@headlessui/react";
|
import { Disclosure, Menu, Tab, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issuesServices from "lib/services/issues.services";
|
import issuesServices from "lib/services/issues.service";
|
||||||
// fetch keys
|
// fetch keys
|
||||||
import {
|
import {
|
||||||
PROJECT_ISSUES_ACTIVITY,
|
PROJECT_ISSUES_ACTIVITY,
|
||||||
@ -21,6 +21,8 @@ import {
|
|||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
|
// hoc
|
||||||
|
import withAuth from "lib/hoc/withAuthWrapper";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/AppLayout";
|
import AppLayout from "layouts/AppLayout";
|
||||||
// components
|
// components
|
||||||
@ -606,4 +608,4 @@ const IssueDetail: NextPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IssueDetail;
|
export default withAuth(IssueDetail);
|
||||||
|
@ -34,7 +34,7 @@ import HeaderButton from "ui/HeaderButton";
|
|||||||
import { ChevronDownIcon, ListBulletIcon, RectangleStackIcon } from "@heroicons/react/24/outline";
|
import { ChevronDownIcon, ListBulletIcon, RectangleStackIcon } from "@heroicons/react/24/outline";
|
||||||
import { PlusIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
|
import { PlusIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
|
||||||
// types
|
// types
|
||||||
import type { IIssue, Properties, NestedKeyOf, ProjectMember } from "types";
|
import type { IIssue, Properties, NestedKeyOf } from "types";
|
||||||
|
|
||||||
const groupByOptions: Array<{ name: string; key: NestedKeyOf<IIssue> | null }> = [
|
const groupByOptions: Array<{ name: string; key: NestedKeyOf<IIssue> | null }> = [
|
||||||
{ name: "State", key: "state_detail.name" },
|
{ name: "State", key: "state_detail.name" },
|
||||||
@ -86,7 +86,7 @@ const ProjectIssues: NextPage = () => {
|
|||||||
projectId as string
|
projectId as string
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: members } = useSWR<ProjectMember[]>(
|
const { data: members } = useSWR(
|
||||||
activeWorkspace && activeProject
|
activeWorkspace && activeProject
|
||||||
? PROJECT_MEMBERS(activeWorkspace.slug, activeProject.id)
|
? PROJECT_MEMBERS(activeWorkspace.slug, activeProject.id)
|
||||||
: null,
|
: null,
|
||||||
|
@ -14,6 +14,8 @@ import useUser from "lib/hooks/useUser";
|
|||||||
import useToast from "lib/hooks/useToast";
|
import useToast from "lib/hooks/useToast";
|
||||||
// fetching keys
|
// fetching keys
|
||||||
import { PROJECT_MEMBERS, PROJECT_INVITATIONS } from "constants/fetch-keys";
|
import { PROJECT_MEMBERS, PROJECT_INVITATIONS } from "constants/fetch-keys";
|
||||||
|
// hoc
|
||||||
|
import withAuth from "lib/hoc/withAuthWrapper";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/AppLayout";
|
import AppLayout from "layouts/AppLayout";
|
||||||
// components
|
// components
|
||||||
@ -109,8 +111,7 @@ const ProjectMembers: NextPage = () => {
|
|||||||
selectedRemoveMember
|
selectedRemoveMember
|
||||||
);
|
);
|
||||||
mutateMembers(
|
mutateMembers(
|
||||||
(prevData: any[]) =>
|
(prevData) => prevData?.filter((item: any) => item.id !== selectedRemoveMember),
|
||||||
prevData?.filter((item: any) => item.id !== selectedRemoveMember),
|
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -121,8 +122,7 @@ const ProjectMembers: NextPage = () => {
|
|||||||
selectedInviteRemoveMember
|
selectedInviteRemoveMember
|
||||||
);
|
);
|
||||||
mutateInvitations(
|
mutateInvitations(
|
||||||
(prevData: any[]) =>
|
(prevData) => prevData?.filter((item: any) => item.id !== selectedInviteRemoveMember),
|
||||||
prevData?.filter((item: any) => item.id !== selectedInviteRemoveMember),
|
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -313,4 +313,4 @@ const ProjectMembers: NextPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProjectMembers;
|
export default withAuth(ProjectMembers);
|
||||||
|
@ -9,6 +9,8 @@ import useSWR, { mutate } from "swr";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
// headless ui
|
// headless ui
|
||||||
import { Tab } from "@headlessui/react";
|
import { Tab } from "@headlessui/react";
|
||||||
|
// hoc
|
||||||
|
import withAuth from "lib/hoc/withAuthWrapper";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/AppLayout";
|
import AppLayout from "layouts/AppLayout";
|
||||||
// service
|
// service
|
||||||
@ -27,9 +29,12 @@ import type { IProject, IWorkspace } from "types";
|
|||||||
const defaultValues: Partial<IProject> = {
|
const defaultValues: Partial<IProject> = {
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
|
identifier: "",
|
||||||
|
network: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProjectSettings: NextPage = () => {
|
const ProjectSettings: NextPage = () => {
|
||||||
|
// FIXME: instead of using dynamic import inside component use it outside
|
||||||
const GeneralSettings = dynamic(() => import("components/project/settings/GeneralSettings"), {
|
const GeneralSettings = dynamic(() => import("components/project/settings/GeneralSettings"), {
|
||||||
loading: () => <p>Loading...</p>,
|
loading: () => <p>Loading...</p>,
|
||||||
ssr: false,
|
ssr: false,
|
||||||
@ -70,7 +75,7 @@ const ProjectSettings: NextPage = () => {
|
|||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { data: projectDetails } = useSWR<IProject>(
|
const { data: projectDetails } = useSWR<IProject>(
|
||||||
activeWorkspace && projectId ? PROJECT_DETAILS : null,
|
activeWorkspace && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
||||||
activeWorkspace
|
activeWorkspace
|
||||||
? () => projectServices.getProject(activeWorkspace.slug, projectId as string)
|
? () => projectServices.getProject(activeWorkspace.slug, projectId as string)
|
||||||
: null
|
: null
|
||||||
@ -87,7 +92,7 @@ const ProjectSettings: NextPage = () => {
|
|||||||
}, [projectDetails, reset]);
|
}, [projectDetails, reset]);
|
||||||
|
|
||||||
const onSubmit = async (formData: IProject) => {
|
const onSubmit = async (formData: IProject) => {
|
||||||
if (!activeWorkspace) return;
|
if (!activeWorkspace || !projectId) return;
|
||||||
const payload: Partial<IProject> = {
|
const payload: Partial<IProject> = {
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
network: formData.network,
|
network: formData.network,
|
||||||
@ -99,7 +104,11 @@ const ProjectSettings: NextPage = () => {
|
|||||||
await projectServices
|
await projectServices
|
||||||
.updateProject(activeWorkspace.slug, projectId as string, payload)
|
.updateProject(activeWorkspace.slug, projectId as string, payload)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
mutate<IProject>(PROJECT_DETAILS, (prevData) => ({ ...prevData, ...res }), false);
|
mutate<IProject>(
|
||||||
|
PROJECT_DETAILS(projectId as string),
|
||||||
|
(prevData) => ({ ...prevData, ...res }),
|
||||||
|
false
|
||||||
|
);
|
||||||
mutate<IProject[]>(
|
mutate<IProject[]>(
|
||||||
PROJECTS_LIST(activeWorkspace.slug),
|
PROJECTS_LIST(activeWorkspace.slug),
|
||||||
(prevData) => {
|
(prevData) => {
|
||||||
@ -181,4 +190,4 @@ const ProjectSettings: NextPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProjectSettings;
|
export default withAuth(ProjectSettings);
|
||||||
|
@ -1,28 +1,32 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useState } from "react";
|
||||||
// next
|
// next
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
|
// hoc
|
||||||
|
import withAuth from "lib/hoc/withAuthWrapper";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/AppLayout";
|
import AppLayout from "layouts/AppLayout";
|
||||||
// components
|
// components
|
||||||
import CreateProjectModal from "components/project/CreateProjectModal";
|
import ProjectMemberInvitations from "components/project/memberInvitations";
|
||||||
import ConfirmProjectDeletion from "components/project/ConfirmProjectDeletion";
|
import ConfirmProjectDeletion from "components/project/confirm-project-deletion";
|
||||||
// ui
|
// ui
|
||||||
import { Button, Spinner } from "ui";
|
import {
|
||||||
// types
|
Button,
|
||||||
import { IProject } from "types";
|
Spinner,
|
||||||
|
HeaderButton,
|
||||||
|
Breadcrumbs,
|
||||||
|
BreadcrumbItem,
|
||||||
|
EmptySpace,
|
||||||
|
EmptySpaceItem,
|
||||||
|
} from "ui";
|
||||||
// services
|
// services
|
||||||
import projectService from "lib/services/project.service";
|
import projectService from "lib/services/project.service";
|
||||||
import ProjectMemberInvitations from "components/project/memberInvitations";
|
// icons
|
||||||
import { ClipboardDocumentListIcon, PlusIcon } from "@heroicons/react/24/outline";
|
import { ClipboardDocumentListIcon, PlusIcon } from "@heroicons/react/24/outline";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "ui/Breadcrumbs";
|
|
||||||
import { EmptySpace, EmptySpaceItem } from "ui/EmptySpace";
|
|
||||||
import HeaderButton from "ui/HeaderButton";
|
|
||||||
|
|
||||||
const Projects: NextPage = () => {
|
const Projects: NextPage = () => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [deleteProject, setDeleteProject] = useState<string | null>(null);
|
||||||
const [deleteProject, setDeleteProject] = useState<IProject | undefined>();
|
|
||||||
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
|
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
|
||||||
|
|
||||||
const { projects, activeWorkspace, mutateProjects } = useUser();
|
const { projects, activeWorkspace, mutateProjects } = useUser();
|
||||||
@ -52,21 +56,12 @@ const Projects: NextPage = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isOpen) return;
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
setDeleteProject(undefined);
|
|
||||||
clearTimeout(timer);
|
|
||||||
}, 300);
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout>
|
<AppLayout>
|
||||||
<CreateProjectModal isOpen={isOpen && !deleteProject} setIsOpen={setIsOpen} />
|
|
||||||
<ConfirmProjectDeletion
|
<ConfirmProjectDeletion
|
||||||
isOpen={isOpen && !!deleteProject}
|
isOpen={!!deleteProject}
|
||||||
setIsOpen={setIsOpen}
|
onClose={() => setDeleteProject(null)}
|
||||||
data={deleteProject}
|
data={projects?.find((item) => item.id === deleteProject) ?? null}
|
||||||
/>
|
/>
|
||||||
{projects ? (
|
{projects ? (
|
||||||
<>
|
<>
|
||||||
@ -87,7 +82,10 @@ const Projects: NextPage = () => {
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
Icon={PlusIcon}
|
Icon={PlusIcon}
|
||||||
action={() => setIsOpen(true)}
|
action={() => {
|
||||||
|
const e = new KeyboardEvent("keydown", { key: "p", ctrlKey: true });
|
||||||
|
document.dispatchEvent(e);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</EmptySpace>
|
</EmptySpace>
|
||||||
</div>
|
</div>
|
||||||
@ -98,7 +96,14 @@ const Projects: NextPage = () => {
|
|||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
<div className="flex items-center justify-between cursor-pointer w-full">
|
<div className="flex items-center justify-between cursor-pointer w-full">
|
||||||
<h2 className="text-2xl font-medium">Projects</h2>
|
<h2 className="text-2xl font-medium">Projects</h2>
|
||||||
<HeaderButton Icon={PlusIcon} label="Add Project" onClick={() => setIsOpen(true)} />
|
<HeaderButton
|
||||||
|
Icon={PlusIcon}
|
||||||
|
label="Add Project"
|
||||||
|
onClick={() => {
|
||||||
|
const e = new KeyboardEvent("keydown", { key: "p", ctrlKey: true });
|
||||||
|
document.dispatchEvent(e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{projects.map((item) => (
|
{projects.map((item) => (
|
||||||
@ -129,4 +134,4 @@ const Projects: NextPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Projects;
|
export default withAuth(Projects);
|
||||||
|
@ -35,12 +35,12 @@ const WorkspaceInvitation: NextPage = () => {
|
|||||||
|
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
|
||||||
const { data: invitationDetail, error } = useSWR(
|
const { data: invitationDetail, error } = useSWR(invitationId && WORKSPACE_INVITATION, () =>
|
||||||
invitationId && WORKSPACE_INVITATION,
|
invitationId ? workspaceService.getWorkspaceInvitation(invitationId as string) : null
|
||||||
() => invitationId && workspaceService.getWorkspaceInvitation(invitationId as string)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleAccept = () => {
|
const handleAccept = () => {
|
||||||
|
if (!invitationDetail) return;
|
||||||
workspaceService
|
workspaceService
|
||||||
.joinWorkspace(invitationDetail.workspace.slug, invitationDetail.id, {
|
.joinWorkspace(invitationDetail.workspace.slug, invitationDetail.id, {
|
||||||
accepted: true,
|
accepted: true,
|
||||||
|
@ -7,7 +7,9 @@ import { useForm } from "react-hook-form";
|
|||||||
import Dropzone from "react-dropzone";
|
import Dropzone from "react-dropzone";
|
||||||
// services
|
// services
|
||||||
import workspaceService from "lib/services/workspace.service";
|
import workspaceService from "lib/services/workspace.service";
|
||||||
import fileServices from "lib/services/file.services";
|
import fileServices from "lib/services/file.service";
|
||||||
|
// hoc
|
||||||
|
import withAuth from "lib/hoc/withAuthWrapper";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/AppLayout";
|
import AppLayout from "layouts/AppLayout";
|
||||||
|
|
||||||
@ -232,4 +234,4 @@ const WorkspaceSettings = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default WorkspaceSettings;
|
export default withAuth(WorkspaceSettings);
|
||||||
|
16
apps/app/types/invitation.d.ts
vendored
16
apps/app/types/invitation.d.ts
vendored
@ -1,16 +0,0 @@
|
|||||||
import { IWorkspace } from "./";
|
|
||||||
|
|
||||||
export interface IWorkspaceInvitation {
|
|
||||||
id: string;
|
|
||||||
workspace: IWorkspace;
|
|
||||||
created_at: Date;
|
|
||||||
updated_at: Date;
|
|
||||||
email: string;
|
|
||||||
accepted: boolean;
|
|
||||||
token: string;
|
|
||||||
message: string;
|
|
||||||
responded_at: Date;
|
|
||||||
role: number;
|
|
||||||
created_by: null;
|
|
||||||
updated_by: null;
|
|
||||||
}
|
|
43
apps/app/types/projects.d.ts
vendored
43
apps/app/types/projects.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
import type { IWorkspace } from "./";
|
import type { IUserLite, IWorkspace } from "./";
|
||||||
|
|
||||||
export interface IProject {
|
export interface IProject {
|
||||||
id: string;
|
id: string;
|
||||||
@ -15,3 +15,44 @@ export interface IProject {
|
|||||||
created_by: string;
|
created_by: string;
|
||||||
updated_by: string;
|
updated_by: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProjectViewTheme = {
|
||||||
|
collapsed: boolean;
|
||||||
|
issueView: "list" | "kanban" | null;
|
||||||
|
groupByProperty: NestedKeyOf<IIssue> | null;
|
||||||
|
filterIssue: "activeIssue" | "backlogIssue" | null;
|
||||||
|
orderBy: NestedKeyOf<IIssue> | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IProjectMember {
|
||||||
|
member: IUserLite;
|
||||||
|
project: IProject;
|
||||||
|
workspace: IWorkspace;
|
||||||
|
comment: string;
|
||||||
|
role: 5 | 10 | 15 | 20;
|
||||||
|
view_props: ProjectViewTheme;
|
||||||
|
|
||||||
|
created_at: Date;
|
||||||
|
updated_at: Date;
|
||||||
|
created_by: string;
|
||||||
|
updated_by: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProjectMemberInvitation {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
project: IProject;
|
||||||
|
workspace: IWorkspace;
|
||||||
|
|
||||||
|
email: string;
|
||||||
|
accepted: boolean;
|
||||||
|
token: string;
|
||||||
|
message: string;
|
||||||
|
responded_at: Date;
|
||||||
|
role: 5 | 10 | 15 | 20;
|
||||||
|
|
||||||
|
created_at: Date;
|
||||||
|
updated_at: Date;
|
||||||
|
created_by: string;
|
||||||
|
updated_by: string;
|
||||||
|
}
|
||||||
|
8
apps/app/types/users.d.ts
vendored
8
apps/app/types/users.d.ts
vendored
@ -16,3 +16,11 @@ export interface IUser {
|
|||||||
token: string;
|
token: string;
|
||||||
[...rest: string]: any;
|
[...rest: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IUserLite {
|
||||||
|
readonly id: string;
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
|
email: string;
|
||||||
|
avatar: string;
|
||||||
|
}
|
||||||
|
30
apps/app/types/workspace.d.ts
vendored
30
apps/app/types/workspace.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
import type { IUser } from "./";
|
import type { IUser, IUserLite } from "./";
|
||||||
|
|
||||||
export interface IWorkspace {
|
export interface IWorkspace {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
@ -13,26 +13,26 @@ export interface IWorkspace {
|
|||||||
company_size: number;
|
company_size: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkspaceMember {
|
export interface IWorkspaceMemberInvitation {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
email: string;
|
email: string;
|
||||||
|
accepted: boolean;
|
||||||
|
token: string;
|
||||||
message: string;
|
message: string;
|
||||||
|
responded_at: Date;
|
||||||
role: 5 | 10 | 15 | 20;
|
role: 5 | 10 | 15 | 20;
|
||||||
member: IUser;
|
workspace: IWorkspace;
|
||||||
workspace: IWorkspace | string;
|
}
|
||||||
|
|
||||||
|
export interface IWorkspaceMember {
|
||||||
|
readonly id: string;
|
||||||
|
user: IUserLite;
|
||||||
|
workspace: IWorkspace;
|
||||||
|
member: IUserLite;
|
||||||
|
role: 5 | 10 | 15 | 20;
|
||||||
|
company_role: string | null;
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
created_by: string;
|
created_by: string;
|
||||||
updated_by: string;
|
updated_by: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProjectMember {
|
|
||||||
readonly id: string;
|
|
||||||
readonly project: string;
|
|
||||||
email: string;
|
|
||||||
message: string;
|
|
||||||
role: 5 | 10 | 15 | 20;
|
|
||||||
member: any;
|
|
||||||
member_id: string;
|
|
||||||
user_id: string;
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user