pull from branch and resolve merge conflicts

This commit is contained in:
rahulramesha 2023-12-07 18:53:17 +05:30
commit d1b41bfbd0
83 changed files with 793 additions and 623 deletions

View File

@ -62,14 +62,14 @@ jobs:
runs-on: ubuntu-20.04
needs: [branch_build_setup]
env:
FRONTEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-ce:${{ needs.branch_build_setup.outputs.gh_branch_name }}
FRONTEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:${{ needs.branch_build_setup.outputs.gh_branch_name }}
steps:
- name: Set Frontend Docker Tag
run: |
if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-ce:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-ce:${{ github.event.release.tag_name }}
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:${{ github.event.release.tag_name }}
elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-ce:stable
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:stable
else
TAG=${{ env.FRONTEND_TAG }}
fi
@ -104,14 +104,14 @@ jobs:
runs-on: ubuntu-20.04
needs: [branch_build_setup]
env:
SPACE_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-space-ce:${{ needs.branch_build_setup.outputs.gh_branch_name }}
SPACE_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-space:${{ needs.branch_build_setup.outputs.gh_branch_name }}
steps:
- name: Set Space Docker Tag
run: |
if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space-ce:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-space-ce:${{ github.event.release.tag_name }}
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-space:${{ github.event.release.tag_name }}
elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space-ce:stable
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space:stable
else
TAG=${{ env.SPACE_TAG }}
fi
@ -146,14 +146,14 @@ jobs:
runs-on: ubuntu-20.04
needs: [branch_build_setup]
env:
BACKEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-ce:${{ needs.branch_build_setup.outputs.gh_branch_name }}
BACKEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:${{ needs.branch_build_setup.outputs.gh_branch_name }}
steps:
- name: Set Backend Docker Tag
run: |
if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-ce:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-ce:${{ github.event.release.tag_name }}
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:${{ github.event.release.tag_name }}
elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-ce:stable
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:stable
else
TAG=${{ env.BACKEND_TAG }}
fi
@ -188,14 +188,14 @@ jobs:
runs-on: ubuntu-20.04
needs: [branch_build_setup]
env:
PROXY_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-ce:${{ needs.branch_build_setup.outputs.gh_branch_name }}
PROXY_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:${{ needs.branch_build_setup.outputs.gh_branch_name }}
steps:
- name: Set Proxy Docker Tag
run: |
if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-ce:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-ce:${{ github.event.release.tag_name }}
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:${{ github.event.release.tag_name }}
elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-ce:stable
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:stable
else
TAG=${{ env.PROXY_TAG }}
fi

View File

@ -13,6 +13,7 @@ from plane.app.views import (
UserProjectInvitationsViewset,
ProjectPublicCoverImagesEndpoint,
ProjectDeployBoardViewSet,
UserProjectRolesEndpoint,
)
@ -74,6 +75,11 @@ urlpatterns = [
),
name="user-project-invitations",
),
path(
"users/me/workspaces/<str:slug>/project-roles/",
UserProjectRolesEndpoint.as_view(),
name="user-project-roles",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/join/<uuid:pk>/",
ProjectJoinEndpoint.as_view(),

View File

@ -11,6 +11,7 @@ from .project import (
ProjectFavoritesViewSet,
ProjectPublicCoverImagesEndpoint,
ProjectDeployBoardViewSet,
UserProjectRolesEndpoint,
)
from .user import (
UserEndpoint,

View File

@ -39,6 +39,7 @@ from plane.app.serializers import (
)
from plane.app.permissions import (
WorkspaceUserPermission,
ProjectBasePermission,
ProjectMemberPermission,
)
@ -165,7 +166,7 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
workspace__slug=slug,
is_active=True,
).select_related("member"),
to_attr='members_list'
to_attr="members_list",
)
)
.order_by("sort_order", "name")
@ -1049,3 +1050,20 @@ class ProjectDeployBoardViewSet(BaseViewSet):
serializer = ProjectDeployBoardSerializer(project_deploy_board)
return Response(serializer.data, status=status.HTTP_200_OK)
class UserProjectRolesEndpoint(BaseAPIView):
permission_classes = [
WorkspaceUserPermission,
]
def get(self, request, slug):
project_members = ProjectMember.objects.filter(
workspace__slug=slug,
member_id=request.user.id,
).values("project_id", "role")
project_members = {
str(member["project_id"]): member["role"] for member in project_members
}
return Response(project_members, status=status.HTTP_200_OK)

View File

@ -30,7 +30,7 @@ from plane.license.api.serializers import (
from plane.license.api.permissions import (
InstanceAdminPermission,
)
from plane.db.models import User
from plane.db.models import User, WorkspaceMember, ProjectMember
from plane.license.utils.encryption import encrypt_data
@ -221,6 +221,37 @@ class InstanceAdminSignInEndpoint(BaseAPIView):
is_password_autoset=False,
)
# if the current user is not using captain then add the current all users to workspace and projects
if user.email != "captain@plane.so":
# Add the current user also as a workspace member and project memeber to all the workspaces and projects
captain = User.objects.filter(email="captain@plane.so")
# Workspace members
workspace_members = WorkspaceMember.objects.filter(member=captain)
WorkspaceMember.objects.bulk_create(
[
WorkspaceMember(
workspace=member.workspace_id,
member=user,
role=member.role,
)
for member in workspace_members
],
batch_size=100,
)
# project members
project_members = ProjectMember.objects.filter(member=captain)
ProjectMember.objects.bulk_create(
[
ProjectMember(
workspace=member.workspace_id,
member=user,
role=member.role,
)
for member in project_members
],
batch_size=100,
)
# settings last active for the user
user.is_active = True
user.last_active = timezone.now()

View File

@ -67,7 +67,7 @@ services:
web:
<<: *app-env
platform: linux/amd64
image: makeplane/plane-frontend-ce:${APP_RELEASE:-latest}
image: makeplane/plane-frontend:${APP_RELEASE:-latest}
restart: unless-stopped
command: /usr/local/bin/start.sh web/server.js web
deploy:
@ -79,7 +79,7 @@ services:
space:
<<: *app-env
platform: linux/amd64
image: makeplane/plane-space-ce:${APP_RELEASE:-latest}
image: makeplane/plane-space:${APP_RELEASE:-latest}
restart: unless-stopped
command: /usr/local/bin/start.sh space/server.js space
deploy:
@ -92,7 +92,7 @@ services:
api:
<<: *app-env
platform: linux/amd64
image: makeplane/plane-backend-ce:${APP_RELEASE:-latest}
image: makeplane/plane-backend:${APP_RELEASE:-latest}
restart: unless-stopped
command: ./bin/takeoff
deploy:
@ -104,7 +104,7 @@ services:
worker:
<<: *app-env
platform: linux/amd64
image: makeplane/plane-backend-ce:${APP_RELEASE:-latest}
image: makeplane/plane-backend:${APP_RELEASE:-latest}
restart: unless-stopped
command: ./bin/worker
depends_on:
@ -115,7 +115,7 @@ services:
beat-worker:
<<: *app-env
platform: linux/amd64
image: makeplane/plane-backend-ce:${APP_RELEASE:-latest}
image: makeplane/plane-backend:${APP_RELEASE:-latest}
restart: unless-stopped
command: ./bin/beat
depends_on:
@ -150,7 +150,7 @@ services:
proxy:
<<: *app-env
platform: linux/amd64
image: makeplane/plane-proxy-ce:${APP_RELEASE:-latest}
image: makeplane/plane-proxy:${APP_RELEASE:-latest}
ports:
- ${NGINX_PORT}:80
depends_on:

View File

@ -174,7 +174,7 @@ const IssueNavbar = observer(() => {
</div>
) : (
<div className="flex-shrink-0">
<Link href={`/login/?next_path=${router.asPath}`}>
<Link href={`/?next_path=${router.asPath}`}>
<Button variant="outline-primary">Sign in</Button>
</Link>
</div>

View File

@ -30,7 +30,7 @@ const useSignInRedirection = (): UseSignInRedirectionProps => {
if (isOnboard) {
// if next_path is provided, redirect the user to that url
if (next_path) router.push(next_path.toString());
else router.push("/login");
else router.push("/");
} else {
// if the user profile is not complete, redirect them to the onboarding page to complete their profile and then redirect them to the next path
if (next_path) router.push(`/onboarding?next_path=${next_path}`);

View File

@ -1,19 +1,28 @@
import { useEffect } from "react";
// next
import { NextPage } from "next";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
const Index: NextPage = () => {
// components
import { LoginView } from "components/views";
// store
import { RootStore } from "store/root";
import { useMobxStore } from "lib/mobx/store-provider";
const Index: NextPage = observer(() => {
const router = useRouter();
const { next_path } = router.query as { next_path: string };
const { next_path } = router.query;
const {
user: { currentUser },
}: RootStore = useMobxStore();
useEffect(() => {
if (next_path) router.push(`/login?next_path=${next_path}`);
else router.push(`/login`);
}, [router, next_path]);
if (next_path && currentUser?.onboarding_step?.profile_complete)
router.push(next_path.toString().replace(/[^a-zA-Z0-9\-._~:/?#[\]@!$&'()*+,;=]/g, ""));
}, [router, next_path, currentUser]);
return null;
};
return <LoginView />;
});
export default Index;

View File

@ -1,8 +0,0 @@
import React from "react";
// components
import { LoginView } from "components/views";
const LoginPage = () => <LoginView />;
export default LoginPage;

View File

@ -1,7 +1,9 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import { useTheme } from "next-themes";
import { Dialog, Transition } from "@headlessui/react";
import { Trash2 } from "lucide-react";
import { mutate } from "swr";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// ui
@ -27,6 +29,7 @@ export const DeactivateAccountModal: React.FC<Props> = (props) => {
const router = useRouter();
const { setToastAlert } = useToast();
const { setTheme } = useTheme();
const handleClose = () => {
setIsDeactivating(false);
@ -43,8 +46,10 @@ export const DeactivateAccountModal: React.FC<Props> = (props) => {
title: "Success!",
message: "Account deactivated successfully.",
});
handleClose();
mutate("CURRENT_USER_DETAILS", null);
setTheme("system");
router.push("/");
handleClose();
})
.catch((err) =>
setToastAlert({

View File

@ -34,6 +34,7 @@ export const CommandPalette: FC = observer(() => {
theme: { toggleSidebar },
user: { currentUser },
trackEvent: { setTrackElement },
projectIssues: { removeIssue },
} = useMobxStore();
const {
toggleCommandPaletteModal,
@ -218,11 +219,15 @@ export const CommandPalette: FC = observer(() => {
currentStore={createIssueStoreType}
/>
{issueId && issueDetails && (
{workspaceSlug && projectId && issueId && issueDetails && (
<DeleteIssueModal
handleClose={() => toggleDeleteIssueModal(false)}
isOpen={isDeleteIssueModalOpen}
data={issueDetails}
onSubmit={async () => {
await removeIssue(workspaceSlug.toString(), projectId.toString(), issueId.toString());
router.push(`/${workspaceSlug}/projects/${projectId}/issues`);
}}
/>
)}

View File

@ -57,12 +57,8 @@ export const UserImageUploadModal: React.FC<Props> = observer((props) => {
};
const handleSubmit = async () => {
console.log("Submit triggered");
if (!image) return;
console.log("Inside submit");
setIsImageUploading(true);
const formData = new FormData();

View File

@ -10,7 +10,7 @@ import { IssueService, IssueCommentService } from "services/issue";
// hooks
import useToast from "hooks/use-toast";
// types
import { IIssue, IIssueComment } from "types";
import { IIssue, IIssueActivity } from "types";
// fetch-keys
import { PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
@ -41,7 +41,7 @@ export const InboxIssueActivity: React.FC<Props> = observer(({ issueDetails }) =
const user = userStore.currentUser;
const handleCommentUpdate = async (commentId: string, data: Partial<IIssueComment>) => {
const handleCommentUpdate = async (commentId: string, data: Partial<any>) => {
if (!workspaceSlug || !projectId || !issueDetails.id || !user) return;
await issueCommentService
@ -86,7 +86,7 @@ export const InboxIssueActivity: React.FC<Props> = observer(({ issueDetails }) =
});
};
const handleAddComment = async (formData: IIssueComment) => {
const handleAddComment = async (formData: IIssueActivity) => {
if (!workspaceSlug || !issueDetails || !user) return;
await issueCommentService

View File

@ -37,13 +37,10 @@ export const InboxMainContent: React.FC = observer(() => {
const {
inboxIssues: inboxIssuesStore,
inboxIssueDetails: inboxIssueDetailsStore,
user: userStore,
user: { currentUser, currentProjectRole },
projectState: { states },
} = useMobxStore();
const user = userStore.currentUser;
const userRole = userStore.currentProjectRole;
const { reset, control, watch } = useForm<IIssue>({
defaultValues,
});
@ -156,7 +153,7 @@ export const InboxMainContent: React.FC = observer(() => {
</div>
);
const isAllowed = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER;
const isAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
return (
<>
@ -249,12 +246,17 @@ export const InboxMainContent: React.FC = observer(() => {
id: issueDetails.id,
}}
handleFormSubmit={submitChanges}
isAllowed={isAllowed || user?.id === issueDetails.created_by}
isAllowed={isAllowed || currentUser?.id === issueDetails.created_by}
/>
</div>
<IssueReaction projectId={projectId} workspaceSlug={workspaceSlug} issueId={issueDetails.id} />
{workspaceSlug && projectId && (
<IssueReaction
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
issueId={issueDetails.id}
/>
)}
<InboxIssueActivity issueDetails={issueDetails} />
</div>

View File

@ -11,12 +11,12 @@ import { Loader, Tooltip } from "@plane/ui";
// helpers
import { render24HourFormatTime, renderLongDateFormat, timeAgo } from "helpers/date-time.helper";
// types
import { IIssueActivity, IIssueComment } from "types";
import { IIssueActivity } from "types";
import { History } from "lucide-react";
type Props = {
activity: IIssueActivity[] | undefined;
handleCommentUpdate: (commentId: string, data: Partial<IIssueComment>) => Promise<void>;
handleCommentUpdate: (commentId: string, data: Partial<IIssueActivity>) => Promise<void>;
handleCommentDelete: (commentId: string) => Promise<void>;
showAccessSpecifier?: boolean;
};
@ -130,7 +130,7 @@ export const IssueActivitySection: React.FC<Props> = ({
return (
<div key={activityItem.id} className="mt-4">
<CommentCard
comment={activityItem as IIssueComment}
comment={activityItem as IIssueActivity}
handleCommentDeletion={handleCommentDelete}
onSubmit={handleCommentUpdate}
showAccessSpecifier={showAccessSpecifier}

View File

@ -11,17 +11,17 @@ import { Button } from "@plane/ui";
import { Globe2, Lock } from "lucide-react";
// types
import type { IIssueComment } from "types";
import type { IIssueActivity } from "types";
import useEditorSuggestions from "hooks/use-editor-suggestions";
const defaultValues: Partial<IIssueComment> = {
const defaultValues: Partial<IIssueActivity> = {
access: "INTERNAL",
comment_html: "",
};
type Props = {
disabled?: boolean;
onSubmit: (data: IIssueComment) => Promise<void>;
onSubmit: (data: IIssueActivity) => Promise<void>;
showAccessSpecifier?: boolean;
};
@ -59,9 +59,9 @@ export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit, showAc
formState: { isSubmitting },
handleSubmit,
reset,
} = useForm<IIssueComment>({ defaultValues });
} = useForm<IIssueActivity>({ defaultValues });
const handleAddComment = async (formData: IIssueComment) => {
const handleAddComment = async (formData: IIssueActivity) => {
if (!formData.comment_html || isSubmitting) return;
await onSubmit(formData).then(() => {
@ -96,7 +96,7 @@ export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit, showAc
onChange={(comment_json: Object, comment_html: string) => onCommentChange(comment_html)}
commentAccessSpecifier={
showAccessSpecifier
? { accessValue, onAccessChange, showAccessSpecifier, commentAccess }
? { accessValue: accessValue ?? "INTERNAL", onAccessChange, showAccessSpecifier, commentAccess }
: undefined
}
mentionSuggestions={editorSuggestions.mentionSuggestions}

View File

@ -14,16 +14,16 @@ import { LiteTextEditorWithRef, LiteReadOnlyEditorWithRef } from "@plane/lite-te
// helpers
import { timeAgo } from "helpers/date-time.helper";
// types
import type { IIssueComment } from "types";
import type { IIssueActivity } from "types";
import useEditorSuggestions from "hooks/use-editor-suggestions";
// services
const fileService = new FileService();
type Props = {
comment: IIssueComment;
comment: IIssueActivity;
handleCommentDeletion: (comment: string) => void;
onSubmit: (commentId: string, data: Partial<IIssueComment>) => void;
onSubmit: (commentId: string, data: Partial<IIssueActivity>) => void;
showAccessSpecifier?: boolean;
workspaceSlug: string;
};
@ -50,11 +50,11 @@ export const CommentCard: React.FC<Props> = ({
setFocus,
watch,
setValue,
} = useForm<IIssueComment>({
} = useForm<IIssueActivity>({
defaultValues: comment,
});
const onEnter = (formData: Partial<IIssueComment>) => {
const onEnter = (formData: Partial<IIssueActivity>) => {
if (isSubmitting) return;
setIsEditing(false);
@ -110,13 +110,10 @@ export const CommentCard: React.FC<Props> = ({
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
ref={editorRef}
value={watch("comment_html")}
value={watch("comment_html") ?? ""}
debouncedUpdatesEnabled={false}
customClassName="min-h-[50px] p-3 shadow-sm"
onChange={(comment_json: Object, comment_html: string) => {
setValue("comment_json", comment_json);
setValue("comment_html", comment_html);
}}
onChange={(comment_json: Object, comment_html: string) => setValue("comment_html", comment_html)}
mentionSuggestions={editorSuggestions.mentionSuggestions}
mentionHighlights={editorSuggestions.mentionHighlights}
/>
@ -147,7 +144,7 @@ export const CommentCard: React.FC<Props> = ({
)}
<LiteReadOnlyEditorWithRef
ref={showEditorRef}
value={comment.comment_html}
value={comment.comment_html ?? ""}
customClassName="text-xs border border-custom-border-200 bg-custom-background-100"
mentionHighlights={editorSuggestions.mentionHighlights}
/>

View File

@ -29,7 +29,10 @@ export const DeleteIssueModal: React.FC<Props> = (props) => {
const handleIssueDelete = async () => {
setIsDeleteLoading(true);
if (onSubmit) await onSubmit().finally(() => setIsDeleteLoading(false));
if (onSubmit)
await onSubmit()
.then(() => onClose())
.finally(() => setIsDeleteLoading(false));
};
return (

View File

@ -61,8 +61,6 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
description_html: issue.description_html,
});
console.log("in form", localIssueDescription);
useEffect(() => {
if (issue.id) {
setLocalIssueDescription({ id: issue.id, description_html: issue.description_html });

View File

@ -6,7 +6,7 @@ import { Dialog, Transition } from "@headlessui/react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// services
import { IssueService, IssueDraftService } from "services/issue";
import { IssueService } from "services/issue";
import { ModuleService } from "services/module.service";
// hooks
import useToast from "hooks/use-toast";

View File

@ -7,7 +7,7 @@ export * from "./delete-issue-modal";
export * from "./description-form";
export * from "./form";
export * from "./issue-layouts";
export * from "./issue-peek-overview";
export * from "./peek-overview";
export * from "./main-content";
export * from "./modal";
export * from "./parent-issues-list-modal";

View File

@ -31,9 +31,9 @@ interface IBaseCalendarRoot {
calendarViewStore: IIssueCalendarViewStore;
QuickActions: FC<IQuickActionProps>;
issueActions: {
[EIssueActions.DELETE]: (issue: IIssue) => void;
[EIssueActions.UPDATE]?: (issue: IIssue) => void;
[EIssueActions.REMOVE]?: (issue: IIssue) => void;
[EIssueActions.DELETE]: (issue: IIssue) => Promise<void>;
[EIssueActions.UPDATE]?: (issue: IIssue) => Promise<void>;
[EIssueActions.REMOVE]?: (issue: IIssue) => Promise<void>;
};
viewId?: string;
handleDragDrop: (source: any, destination: any, issues: any, issueWithIds: any) => void;
@ -64,9 +64,9 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
};
const handleIssues = useCallback(
(date: string, issue: IIssue, action: EIssueActions) => {
async (date: string, issue: IIssue, action: EIssueActions) => {
if (issueActions[action]) {
issueActions[action]!(issue);
await issueActions[action]!(issue);
}
},
[issueActions]
@ -108,8 +108,8 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
workspaceSlug={workspaceSlug.toString()}
projectId={peekProjectId.toString()}
issueId={peekIssueId.toString()}
handleIssue={(issueToUpdate) =>
handleIssues(issueToUpdate.target_date ?? "", issueToUpdate as IIssue, EIssueActions.UPDATE)
handleIssue={async (issueToUpdate) =>
await handleIssues(issueToUpdate.target_date ?? "", issueToUpdate as IIssue, EIssueActions.UPDATE)
}
/>
)}

View File

@ -14,46 +14,50 @@ export const CycleCalendarLayout: React.FC = observer(() => {
cycleIssues: cycleIssueStore,
cycleIssuesFilter: cycleIssueFilterStore,
cycleIssueCalendarView: cycleIssueCalendarViewStore,
calendarHelpers: calendarHelperStore,
calendarHelpers: { handleDragDrop: handleCalenderDragDrop },
} = useMobxStore();
const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query as {
workspaceSlug: string;
projectId: string;
cycleId: string;
};
const { workspaceSlug, projectId, cycleId } = router.query;
const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug || !cycleId) return;
cycleIssueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue, cycleId);
await cycleIssueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue, cycleId.toString());
},
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug || !cycleId) return;
cycleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, cycleId);
await cycleIssueStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id, cycleId.toString());
},
[EIssueActions.REMOVE]: async (issue: IIssue) => {
if (!workspaceSlug || !cycleId || !issue.bridge_id) return;
cycleIssueStore.removeIssueFromCycle(workspaceSlug, issue.project, cycleId, issue.id, issue.bridge_id);
if (!workspaceSlug || !cycleId || !projectId || !issue.bridge_id) return;
await cycleIssueStore.removeIssueFromCycle(
workspaceSlug.toString(),
issue.project,
cycleId.toString(),
issue.id,
issue.bridge_id
);
},
};
const handleDragDrop = (source: any, destination: any, issues: IIssue[], issueWithIds: any) => {
if (calendarHelperStore.handleDragDrop)
calendarHelperStore.handleDragDrop(
if (workspaceSlug && projectId && cycleId)
handleCalenderDragDrop(
source,
destination,
workspaceSlug,
projectId,
workspaceSlug.toString(),
projectId.toString(),
cycleIssueStore,
issues,
issueWithIds,
cycleId
cycleId.toString()
);
};
if (!cycleId) return null;
return (
<BaseCalendarRoot
issueStore={cycleIssueStore}
@ -61,7 +65,7 @@ export const CycleCalendarLayout: React.FC = observer(() => {
calendarViewStore={cycleIssueCalendarViewStore}
QuickActions={CycleIssueQuickActions}
issueActions={issueActions}
viewId={cycleId}
viewId={cycleId.toString()}
handleDragDrop={handleDragDrop}
/>
);

View File

@ -14,7 +14,7 @@ export const ModuleCalendarLayout: React.FC = observer(() => {
moduleIssues: moduleIssueStore,
moduleIssuesFilter: moduleIssueFilterStore,
moduleIssueCalendarView: moduleIssueCalendarViewStore,
calendarHelpers: calendarHelperStore,
calendarHelpers: { handleDragDrop: handleCalenderDragDrop },
} = useMobxStore();
const router = useRouter();
@ -25,32 +25,31 @@ export const ModuleCalendarLayout: React.FC = observer(() => {
};
const issueActions = {
[EIssueActions.UPDATE]: (issue: IIssue) => {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug || !moduleId) return;
moduleIssueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue, moduleId);
await moduleIssueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue, moduleId);
},
[EIssueActions.DELETE]: (issue: IIssue) => {
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug || !moduleId) return;
moduleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, moduleId);
await moduleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, moduleId);
},
[EIssueActions.REMOVE]: (issue: IIssue) => {
[EIssueActions.REMOVE]: async (issue: IIssue) => {
if (!workspaceSlug || !moduleId || !issue.bridge_id) return;
moduleIssueStore.removeIssueFromModule(workspaceSlug, issue.project, moduleId, issue.id, issue.bridge_id);
await moduleIssueStore.removeIssueFromModule(workspaceSlug, issue.project, moduleId, issue.id, issue.bridge_id);
},
};
const handleDragDrop = (source: any, destination: any, issues: IIssue[], issueWithIds: any) => {
if (calendarHelperStore.handleDragDrop)
calendarHelperStore.handleDragDrop(
source,
destination,
workspaceSlug,
projectId,
moduleIssueStore,
issues,
issueWithIds,
moduleId
);
handleCalenderDragDrop(
source,
destination,
workspaceSlug,
projectId,
moduleIssueStore,
issues,
issueWithIds,
moduleId
);
};
return (

View File

@ -10,35 +10,35 @@ import { useRouter } from "next/router";
export const CalendarLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { workspaceSlug, projectId } = router.query;
const {
projectIssues: issueStore,
issueCalendarView: issueCalendarViewStore,
projectIssuesFilter: projectIssueFiltersStore,
calendarHelpers: calendarHelperStore,
calendarHelpers: { handleDragDrop: handleCalenderDragDrop },
} = useMobxStore();
const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
issueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue);
await issueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
},
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
issueStore.removeIssue(workspaceSlug, issue.project, issue.id);
await issueStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id);
},
};
const handleDragDrop = (source: any, destination: any, issues: IIssue[], issueWithIds: any) => {
if (calendarHelperStore.handleDragDrop)
calendarHelperStore.handleDragDrop(
if (workspaceSlug && projectId)
handleCalenderDragDrop(
source,
destination,
workspaceSlug,
projectId,
workspaceSlug.toString(),
projectId.toString(),
issueStore,
issues,
issueWithIds

View File

@ -14,32 +14,32 @@ export const ProjectViewCalendarLayout: React.FC = observer(() => {
viewIssues: projectViewIssuesStore,
viewIssuesFilter: projectIssueViewFiltersStore,
projectViewIssueCalendarView: projectViewIssueCalendarViewStore,
calendarHelpers: calendarHelperStore,
calendarHelpers: { handleDragDrop: handleCalenderDragDrop },
} = useMobxStore();
const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { workspaceSlug, projectId } = router.query;
const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
projectViewIssuesStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
await projectViewIssuesStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
},
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
projectViewIssuesStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id);
await projectViewIssuesStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id);
},
};
const handleDragDrop = (source: any, destination: any, issues: IIssue[], issueWithIds: any) => {
if (calendarHelperStore.handleDragDrop)
calendarHelperStore.handleDragDrop(
if (workspaceSlug && projectId)
handleCalenderDragDrop(
source,
destination,
workspaceSlug,
projectId,
workspaceSlug.toString(),
projectId.toString(),
projectViewIssuesStore,
issues,
issueWithIds

View File

@ -1,6 +1,5 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
@ -26,7 +25,6 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
projectState: projectStateStore,
projectMember: { projectMembers },
projectViews: projectViewsStore,
projectViewFilters: projectViewFiltersStore,
viewIssuesFilter: { issueFilters, updateFilters },
} = useMobxStore();

View File

@ -54,11 +54,11 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
const issues = issueIds.map((id) => issuesResponse?.[id]);
const updateIssue = (issue: IIssue, payload: IBlockUpdateData) => {
const updateIssue = async (issue: IIssue, payload: IBlockUpdateData) => {
if (!workspaceSlug) return;
//Todo fix sort order in the structure
issueStore.updateIssue(
await issueStore.updateIssue(
workspaceSlug.toString(),
issue.project,
issue.id,
@ -101,9 +101,9 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
workspaceSlug={workspaceSlug.toString()}
projectId={peekProjectId.toString()}
issueId={peekIssueId.toString()}
handleIssue={(issueToUpdate) => {
handleIssue={async (issueToUpdate) => {
// TODO: update the logic here
updateIssue(issueToUpdate as IIssue, {});
await updateIssue(issueToUpdate as IIssue, {});
}}
/>
)}

View File

@ -52,9 +52,9 @@ export interface IBaseKanBanLayout {
kanbanViewStore: IIssueKanBanViewStore;
QuickActions: FC<IQuickActionProps>;
issueActions: {
[EIssueActions.DELETE]: (issue: IIssue) => void;
[EIssueActions.UPDATE]?: (issue: IIssue) => void;
[EIssueActions.REMOVE]?: (issue: IIssue) => void;
[EIssueActions.DELETE]: (issue: IIssue) => Promise<void>;
[EIssueActions.UPDATE]?: (issue: IIssue) => Promise<void>;
[EIssueActions.REMOVE]?: (issue: IIssue) => Promise<void>;
};
showLoader?: boolean;
viewId?: string;
@ -179,7 +179,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
const handleIssues = useCallback(
async (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => {
if (issueActions[action]) {
issueActions[action]!(issue);
await issueActions[action]!(issue);
}
},
[issueActions]
@ -351,8 +351,8 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
workspaceSlug={workspaceSlug.toString()}
projectId={peekProjectId.toString()}
issueId={peekIssueId.toString()}
handleIssue={(issueToUpdate) =>
handleIssues(sub_group_by, group_by, issueToUpdate as IIssue, EIssueActions.UPDATE)
handleIssue={async (issueToUpdate) =>
await handleIssues(sub_group_by, group_by, issueToUpdate as IIssue, EIssueActions.UPDATE)
}
/>
)}

View File

@ -109,8 +109,8 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
)}
<div
className={`min-h-[150px] ${
verticalAlignPosition(_list) ? `w-[0px] overflow-hidden` : `w-full transition-all`
className={`${
verticalAlignPosition(_list) ? `min-h-[150px] w-[0px] overflow-hidden` : `w-full transition-all`
}`}
>
<Droppable droppableId={`${getValueFromObject(_list, listKey) as string}__${sub_group_id}`}>
@ -122,7 +122,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
{...provided.droppableProps}
ref={provided.innerRef}
>
{issues ? (
{issues && !verticalAlignPosition(_list) ? (
<KanbanIssueBlocksList
sub_group_id={sub_group_id}
columnId={getValueFromObject(_list, listKey) as string}

View File

@ -22,7 +22,7 @@ export interface IAssigneesHeader {
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
export const Icon = ({ user }: any) => <Avatar name={user.display_name} src={user.avatar} size="base" />;
export const Icon = ({ user }: any) => <Avatar name={user.display_name} src={user.avatar} size="md" />;
export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => {
const {

View File

@ -17,11 +17,7 @@ export interface ICycleKanBanLayout {}
export const CycleKanBanLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query as {
workspaceSlug: string;
projectId: string;
cycleId: string;
};
const { workspaceSlug, projectId, cycleId } = router.query;
// store
const {
@ -35,15 +31,23 @@ export const CycleKanBanLayout: React.FC = observer(() => {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug || !cycleId) return;
cycleIssueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue, cycleId);
await cycleIssueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue, cycleId.toString());
},
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug || !cycleId) return;
cycleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, cycleId);
await cycleIssueStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id, cycleId.toString());
},
[EIssueActions.REMOVE]: async (issue: IIssue) => {
if (!workspaceSlug || !cycleId || !issue.bridge_id) return;
cycleIssueStore.removeIssueFromCycle(workspaceSlug, issue.project, cycleId, issue.id, issue.bridge_id);
await cycleIssueStore.removeIssueFromCycle(
workspaceSlug.toString(),
issue.project,
cycleId.toString(),
issue.id,
issue.bridge_id
);
},
};
@ -55,18 +59,18 @@ export const CycleKanBanLayout: React.FC = observer(() => {
issues: IIssueResponse | undefined,
issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
) => {
if (kanBanHelperStore.handleDragDrop)
if (workspaceSlug && projectId && cycleId)
return await kanBanHelperStore.handleDragDrop(
source,
destination,
workspaceSlug,
projectId,
workspaceSlug.toString(),
projectId.toString(),
cycleIssueStore,
subGroupBy,
groupBy,
issues,
issueWithIds,
cycleId
cycleId.toString()
);
};
@ -78,10 +82,12 @@ export const CycleKanBanLayout: React.FC = observer(() => {
kanbanViewStore={cycleIssueKanBanViewStore}
showLoader={true}
QuickActions={CycleIssueQuickActions}
viewId={cycleId}
viewId={cycleId?.toString() ?? ""}
currentStore={EProjectStore.CYCLE}
handleDragDrop={handleDragDrop}
addIssuesToView={(issues: string[]) => cycleIssueStore.addIssueToCycle(workspaceSlug, cycleId, issues)}
addIssuesToView={(issues: string[]) =>
cycleIssueStore.addIssueToCycle(workspaceSlug?.toString() ?? "", cycleId?.toString() ?? "", issues)
}
/>
);
});

View File

@ -14,7 +14,7 @@ export interface IKanBanLayout {}
export const DraftKanBanLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug } = router.query as { workspaceSlug: string };
const { workspaceSlug } = router.query;
const {
projectDraftIssues: issueStore,
@ -26,12 +26,12 @@ export const DraftKanBanLayout: React.FC = observer(() => {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
await issueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue);
await issueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
},
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
await issueStore.removeIssue(workspaceSlug, issue.project, issue.id);
await issueStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id);
},
};

View File

@ -17,11 +17,7 @@ export interface IModuleKanBanLayout {}
export const ModuleKanBanLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query as {
workspaceSlug: string;
projectId: string;
moduleId: string;
};
const { workspaceSlug, projectId, moduleId } = router.query;
// store
const {
@ -35,15 +31,23 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug || !moduleId) return;
moduleIssueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue, moduleId);
await moduleIssueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue, moduleId.toString());
},
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug || !moduleId) return;
moduleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, moduleId);
await moduleIssueStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id, moduleId.toString());
},
[EIssueActions.REMOVE]: async (issue: IIssue) => {
if (!workspaceSlug || !moduleId || !issue.bridge_id) return;
moduleIssueStore.removeIssueFromModule(workspaceSlug, issue.project, moduleId, issue.id, issue.bridge_id);
await moduleIssueStore.removeIssueFromModule(
workspaceSlug.toString(),
issue.project,
moduleId.toString(),
issue.id,
issue.bridge_id
);
},
};
@ -55,18 +59,18 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
issues: IIssueResponse | undefined,
issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
) => {
if (kanBanHelperStore.handleDragDrop)
if (workspaceSlug && projectId && moduleId)
return await kanBanHelperStore.handleDragDrop(
source,
destination,
workspaceSlug,
projectId,
workspaceSlug.toString(),
projectId.toString(),
moduleIssueStore,
subGroupBy,
groupBy,
issues,
issueWithIds,
moduleId
moduleId.toString()
);
};
return (
@ -77,10 +81,12 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
kanbanViewStore={moduleIssueKanBanViewStore}
showLoader={true}
QuickActions={ModuleIssueQuickActions}
viewId={moduleId}
viewId={moduleId?.toString() ?? ""}
currentStore={EProjectStore.MODULE}
handleDragDrop={handleDragDrop}
addIssuesToView={(issues: string[]) => moduleIssueStore.addIssueToModule(workspaceSlug, moduleId, issues)}
addIssuesToView={(issues: string[]) =>
moduleIssueStore.addIssueToModule(workspaceSlug?.toString() ?? "", moduleId?.toString() ?? "", issues)
}
/>
);
});

View File

@ -45,20 +45,18 @@ export const KanBanLayout: React.FC = observer(() => {
groupBy: string | null,
issues: IIssueResponse | undefined,
issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
) => {
if (kanBanHelperStore.handleDragDrop)
return await kanBanHelperStore.handleDragDrop(
source,
destination,
workspaceSlug,
projectId,
issueStore,
subGroupBy,
groupBy,
issues,
issueWithIds
);
};
) =>
await kanBanHelperStore.handleDragDrop(
source,
destination,
workspaceSlug,
projectId,
issueStore,
subGroupBy,
groupBy,
issues,
issueWithIds
);
return (
<BaseKanBanRoot

View File

@ -45,20 +45,18 @@ export const ProjectViewKanBanLayout: React.FC = observer(() => {
groupBy: string | null,
issues: IIssueResponse | undefined,
issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
) => {
if (kanBanHelperStore.handleDragDrop)
return await kanBanHelperStore.handleDragDrop(
source,
destination,
workspaceSlug,
projectId,
projectViewIssuesStore,
subGroupBy,
groupBy,
issues,
issueWithIds
);
};
) =>
await kanBanHelperStore.handleDragDrop(
source,
destination,
workspaceSlug,
projectId,
projectViewIssuesStore,
subGroupBy,
groupBy,
issues,
issueWithIds
);
return (
<BaseKanBanRoot

View File

@ -50,9 +50,9 @@ interface IBaseListRoot {
| IProfileIssuesStore;
QuickActions: FC<IQuickActionProps>;
issueActions: {
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => void;
[EIssueActions.UPDATE]?: (group_by: string | null, issue: IIssue) => void;
[EIssueActions.REMOVE]?: (group_by: string | null, issue: IIssue) => void;
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => Promise<void>;
[EIssueActions.UPDATE]?: (group_by: string | null, issue: IIssue) => Promise<void>;
[EIssueActions.REMOVE]?: (group_by: string | null, issue: IIssue) => Promise<void>;
};
getProjects: (projectStore: IProjectStore) => IProject[] | null;
viewId?: string;
@ -113,7 +113,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
const members = projectMembers?.map((m) => m.member) ?? null;
const handleIssues = async (issue: IIssue, action: EIssueActions) => {
if (issueActions[action]) {
issueActions[action]!(group_by, issue);
await issueActions[action]!(group_by, issue);
}
};
@ -168,7 +168,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
workspaceSlug={workspaceSlug.toString()}
projectId={peekProjectId.toString()}
issueId={peekIssueId.toString()}
handleIssue={(issueToUpdate) => handleIssues(issueToUpdate as IIssue, EIssueActions.UPDATE)}
handleIssue={async (issueToUpdate) => await handleIssues(issueToUpdate as IIssue, EIssueActions.UPDATE)}
/>
)}
</>

View File

@ -21,10 +21,10 @@ export const ArchivedIssueListLayout: FC = observer(() => {
useMobxStore();
const issueActions = {
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => {
[EIssueActions.DELETE]: async (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug || !projectId) return;
archivedIssueStore.removeIssue(workspaceSlug, projectId, issue.id);
await archivedIssueStore.removeIssue(workspaceSlug, projectId, issue.id);
},
};

View File

@ -22,17 +22,20 @@ export const CycleListLayout: React.FC = observer(() => {
const { cycleIssues: cycleIssueStore, cycleIssuesFilter: cycleIssueFilterStore } = useMobxStore();
const issueActions = {
[EIssueActions.UPDATE]: (group_by: string | null, issue: IIssue) => {
[EIssueActions.UPDATE]: async (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug || !cycleId) return;
cycleIssueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue, cycleId);
await cycleIssueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue, cycleId);
},
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => {
[EIssueActions.DELETE]: async (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug || !cycleId) return;
cycleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, cycleId);
await cycleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, cycleId);
},
[EIssueActions.REMOVE]: (group_by: string | null, issue: IIssue) => {
[EIssueActions.REMOVE]: async (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug || !cycleId || !issue.bridge_id) return;
cycleIssueStore.removeIssueFromCycle(workspaceSlug, issue.project, cycleId, issue.id, issue.bridge_id);
await cycleIssueStore.removeIssueFromCycle(workspaceSlug, issue.project, cycleId, issue.id, issue.bridge_id);
},
};
const getProjects = (projectStore: IProjectStore) => {

View File

@ -23,13 +23,15 @@ export const DraftIssueListLayout: FC = observer(() => {
const { projectDraftIssuesFilter: projectIssuesFilterStore, projectDraftIssues: projectIssuesStore } = useMobxStore();
const issueActions = {
[EIssueActions.UPDATE]: (group_by: string | null, issue: IIssue) => {
[EIssueActions.UPDATE]: async (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug || !projectId) return;
projectIssuesStore.updateIssue(workspaceSlug, projectId, issue.id, issue);
await projectIssuesStore.updateIssue(workspaceSlug, projectId, issue.id, issue);
},
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => {
[EIssueActions.DELETE]: async (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug || !projectId) return;
projectIssuesStore.removeIssue(workspaceSlug, projectId, issue.id);
await projectIssuesStore.removeIssue(workspaceSlug, projectId, issue.id);
},
};

View File

@ -22,17 +22,20 @@ export const ModuleListLayout: React.FC = observer(() => {
const { moduleIssues: moduleIssueStore, moduleIssuesFilter: moduleIssueFilterStore } = useMobxStore();
const issueActions = {
[EIssueActions.UPDATE]: (group_by: string | null, issue: IIssue) => {
[EIssueActions.UPDATE]: async (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug || !moduleId) return;
moduleIssueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue, moduleId);
await moduleIssueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue, moduleId);
},
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => {
[EIssueActions.DELETE]: async (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug || !moduleId) return;
moduleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, moduleId);
await moduleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, moduleId);
},
[EIssueActions.REMOVE]: (group_by: string | null, issue: IIssue) => {
[EIssueActions.REMOVE]: async (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug || !moduleId || !issue.bridge_id) return;
moduleIssueStore.removeIssueFromModule(workspaceSlug, issue.project, moduleId, issue.id, issue.bridge_id);
await moduleIssueStore.removeIssueFromModule(workspaceSlug, issue.project, moduleId, issue.id, issue.bridge_id);
},
};

View File

@ -23,13 +23,15 @@ export const ListLayout: FC = observer(() => {
const { projectIssuesFilter: projectIssuesFilterStore, projectIssues: projectIssuesStore } = useMobxStore();
const issueActions = {
[EIssueActions.UPDATE]: (group_by: string | null, issue: IIssue) => {
[EIssueActions.UPDATE]: async (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug || !projectId) return;
projectIssuesStore.updateIssue(workspaceSlug, projectId, issue.id, issue);
await projectIssuesStore.updateIssue(workspaceSlug, projectId, issue.id, issue);
},
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => {
[EIssueActions.DELETE]: async (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug || !projectId) return;
projectIssuesStore.removeIssue(workspaceSlug, projectId, issue.id);
await projectIssuesStore.removeIssue(workspaceSlug, projectId, issue.id);
},
};

View File

@ -26,13 +26,15 @@ export const ProjectViewListLayout: React.FC = observer(() => {
if (!workspaceSlug || !projectId) return null;
const issueActions = {
[EIssueActions.UPDATE]: (group_by: string | null, issue: IIssue) => {
[EIssueActions.UPDATE]: async (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug || !projectId) return;
projectViewIssueStore.updateIssue(workspaceSlug, projectId, issue.id, issue);
await projectViewIssueStore.updateIssue(workspaceSlug, projectId, issue.id, issue);
},
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => {
[EIssueActions.DELETE]: async (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug || !projectId) return;
projectViewIssueStore.removeIssue(workspaceSlug, projectId, issue.id);
await projectViewIssueStore.removeIssue(workspaceSlug, projectId, issue.id);
},
};

View File

@ -21,8 +21,8 @@ type Props = {
members?: IUserLite[] | undefined;
labels?: IIssueLabel[] | undefined;
states?: IState[] | undefined;
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
handleIssues: (issue: IIssue, action: EIssueActions) => void;
quickActions: (issue: IIssue, customActionButton: any) => React.ReactNode; // TODO: replace any with type
handleIssues: (issue: IIssue, action: EIssueActions) => Promise<void>;
openIssuesListModal?: (() => void) | null;
quickAddCallback?: (
workspaceSlug: string,
@ -189,7 +189,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
workspaceSlug={workspaceSlug.toString()}
projectId={peekProjectId.toString()}
issueId={peekIssueId.toString()}
handleIssue={(issueToUpdate: any) => handleIssues(issueToUpdate, EIssueActions.UPDATE)}
handleIssue={async (issueToUpdate: any) => await handleIssues(issueToUpdate, EIssueActions.UPDATE)}
/>
)}
</div>

View File

@ -1 +0,0 @@
export * from "./root";

View File

@ -1,22 +1,25 @@
// hooks
import useUserAuth from "hooks/use-user-auth";
import useIssueReaction from "hooks/use-issue-reaction";
// components
import { ReactionSelector } from "components/core";
// string helpers
import { renderEmoji } from "helpers/emoji.helper";
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// types
type Props = {
workspaceSlug?: string | string[];
projectId?: string | string[];
issueId?: string | string[];
workspaceSlug: string;
projectId: string;
issueId: string;
};
export const IssueReaction: React.FC<Props> = (props) => {
export const IssueReaction: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId, issueId } = props;
const { user } = useUserAuth();
const {
user: { currentUser },
} = useMobxStore();
const { reactions, groupedReactions, handleReactionCreate, handleReactionDelete } = useIssueReaction(
workspaceSlug,
@ -27,7 +30,7 @@ export const IssueReaction: React.FC<Props> = (props) => {
const handleReactionClick = (reaction: string) => {
if (!workspaceSlug || !projectId || !issueId) return;
const isSelected = reactions?.some((r) => r.actor === user?.id && r.reaction === reaction);
const isSelected = reactions?.some((r) => r.actor === currentUser?.id && r.reaction === reaction);
if (isSelected) {
handleReactionDelete(reaction);
@ -41,7 +44,7 @@ export const IssueReaction: React.FC<Props> = (props) => {
<ReactionSelector
size="md"
position="top"
value={reactions?.filter((reaction) => reaction.actor === user?.id).map((r) => r.reaction) || []}
value={reactions?.filter((reaction) => reaction.actor === currentUser?.id).map((r) => r.reaction) || []}
onSelect={handleReactionClick}
/>
@ -56,7 +59,7 @@ export const IssueReaction: React.FC<Props> = (props) => {
}}
key={reaction}
className={`flex items-center gap-1 text-custom-text-100 text-sm h-full px-2 py-1 rounded-md ${
reactions?.some((r) => r.actor === user?.id && r.reaction === reaction)
reactions?.some((r) => r.actor === currentUser?.id && r.reaction === reaction)
? "bg-custom-primary-100/10"
: "bg-custom-background-80"
}`}
@ -64,7 +67,7 @@ export const IssueReaction: React.FC<Props> = (props) => {
<span>{renderEmoji(reaction)}</span>
<span
className={
reactions?.some((r) => r.actor === user?.id && r.reaction === reaction)
reactions?.some((r) => r.actor === currentUser?.id && r.reaction === reaction)
? "text-custom-primary-100"
: ""
}
@ -76,4 +79,4 @@ export const IssueReaction: React.FC<Props> = (props) => {
)}
</div>
);
};
});

View File

@ -24,9 +24,10 @@ import { SubIssuesRoot } from "./sub-issues";
// ui
import { CustomMenu, LayersIcon, StateGroupIcon } from "@plane/ui";
// types
import { IIssue, IIssueComment } from "types";
import { IIssue, IIssueActivity } from "types";
// fetch-keys
import { PROJECT_ISSUES_ACTIVITY, SUB_ISSUES } from "constants/fetch-keys";
// constants
import { EUserWorkspaceRoles } from "constants/workspace";
type Props = {
@ -41,24 +42,22 @@ const issueCommentService = new IssueCommentService();
export const IssueMainContent: React.FC<Props> = observer((props) => {
const { issueDetails, submitChanges, uneditable = false } = props;
// states
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
// router
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
// toast alert
const { setToastAlert } = useToast();
// mobx store
const {
user: userStore,
user: { currentUser, currentProjectRole },
project: projectStore,
projectState: { states },
trackEvent: { postHogEventTracker },
workspace: { currentWorkspace }
} = useMobxStore();
const user = userStore.currentUser ?? undefined;
const userRole = userStore.currentProjectRole;
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : undefined;
const currentIssueState = projectId
? states[projectId.toString()]?.find((s) => s.id === issueDetails.state)
@ -67,7 +66,7 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
const { data: siblingIssues } = useSWR(
workspaceSlug && projectId && issueDetails?.parent ? SUB_ISSUES(issueDetails.parent) : null,
workspaceSlug && projectId && issueDetails?.parent
? () => issueService.subIssues(workspaceSlug as string, projectId as string, issueDetails.parent ?? "")
? () => issueService.subIssues(workspaceSlug.toString(), projectId.toString(), issueDetails.parent ?? "")
: null
);
const siblingIssuesList = siblingIssues?.sub_issues.filter((i) => i.id !== issueDetails.id);
@ -79,7 +78,7 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
: null
);
const handleCommentUpdate = async (commentId: string, data: Partial<IIssueComment>) => {
const handleCommentUpdate = async (commentId: string, data: Partial<IIssueActivity>) => {
if (!workspaceSlug || !projectId || !issueId) return;
await issueCommentService
@ -103,7 +102,7 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
};
const handleCommentDelete = async (commentId: string) => {
if (!workspaceSlug || !projectId || !issueId || !user) return;
if (!workspaceSlug || !projectId || !issueId || !currentUser) return;
mutateIssueActivity((prevData: any) => prevData?.filter((p: any) => p.id !== commentId), false);
@ -126,8 +125,8 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
);
};
const handleAddComment = async (formData: IIssueComment) => {
if (!workspaceSlug || !issueDetails || !user) return;
const handleAddComment = async (formData: IIssueActivity) => {
if (!workspaceSlug || !issueDetails || !currentUser) return;
await issueCommentService
.createIssueComment(workspaceSlug.toString(), issueDetails.project, issueDetails.id, formData)
@ -155,7 +154,7 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
);
};
const isAllowed = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER;
const isAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
return (
<>
@ -238,10 +237,16 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
isAllowed={isAllowed || !uneditable}
/>
<IssueReaction workspaceSlug={workspaceSlug} issueId={issueId} projectId={projectId} />
{workspaceSlug && projectId && (
<IssueReaction
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
issueId={issueDetails.id}
/>
)}
<div className="mt-2 space-y-2">
<SubIssuesRoot parentIssue={issueDetails} user={user} />
<SubIssuesRoot parentIssue={issueDetails} user={currentUser ?? undefined} />
</div>
</div>
<div className="flex flex-col gap-3 py-3">

View File

@ -2,32 +2,34 @@ import { FC } from "react";
import Link from "next/link";
import { History } from "lucide-react";
// packages
import { Tooltip } from "@plane/ui";
import { Loader, Tooltip } from "@plane/ui";
// components
import { ActivityIcon, ActivityMessage } from "components/core";
import { IssueCommentCard } from "./comment-card";
// helpers
import { render24HourFormatTime, renderLongDateFormat, timeAgo } from "helpers/date-time.helper";
// types
import { IIssueActivity, IUser } from "types";
interface IssueActivityCard {
interface IIssueActivityCard {
workspaceSlug: string;
projectId: string;
issueId: string;
user: any;
issueComments: any;
user: IUser | null;
issueActivity: IIssueActivity[] | null;
issueCommentUpdate: (comment: any) => void;
issueCommentRemove: (commentId: string) => void;
issueCommentReactionCreate: (commentId: string, reaction: string) => void;
issueCommentReactionRemove: (commentId: string, reaction: string) => void;
}
export const IssueActivityCard: FC<IssueActivityCard> = (props) => {
export const IssueActivityCard: FC<IIssueActivityCard> = (props) => {
const {
workspaceSlug,
projectId,
issueId,
user,
issueComments,
issueActivity,
issueCommentUpdate,
issueCommentRemove,
issueCommentReactionCreate,
@ -37,9 +39,9 @@ export const IssueActivityCard: FC<IssueActivityCard> = (props) => {
return (
<div className="flow-root">
<ul role="list" className="-mb-4">
{issueComments &&
issueComments.length > 0 &&
issueComments.map((activityItem: any, index: any) => {
{issueActivity ? (
issueActivity.length > 0 &&
issueActivity.map((activityItem, index) => {
// determines what type of action is performed
const message = activityItem.field ? <ActivityMessage activity={activityItem} /> : "created the issue.";
@ -47,7 +49,7 @@ export const IssueActivityCard: FC<IssueActivityCard> = (props) => {
return (
<li key={activityItem.id}>
<div className="relative pb-1">
{issueComments.length > 1 && index !== issueComments.length - 1 ? (
{issueActivity.length > 1 && index !== issueActivity.length - 1 ? (
<span
className="absolute top-5 left-5 -ml-[1.5px] h-full w-0.5 bg-custom-background-100"
aria-hidden="true"
@ -114,7 +116,7 @@ export const IssueActivityCard: FC<IssueActivityCard> = (props) => {
</div>
</li>
);
} else if ("comment_json" in activityItem)
} else if ("comment_html" in activityItem)
return (
<div key={activityItem.id} className="mt-4">
<IssueCommentCard
@ -131,7 +133,15 @@ export const IssueActivityCard: FC<IssueActivityCard> = (props) => {
/>
</div>
);
})}
})
) : (
<Loader className="space-y-3 mb-3">
<Loader.Item height="20px" />
<Loader.Item height="20px" />
<Loader.Item height="20px" />
<Loader.Item height="20px" />
</Loader>
)}
</ul>
</div>
);

View File

@ -12,20 +12,20 @@ import { IssueCommentReaction } from "./comment-reaction";
// helpers
import { timeAgo } from "helpers/date-time.helper";
// types
import type { IIssueComment } from "types";
import type { IIssueActivity, IUser } from "types";
// services
const fileService = new FileService();
type IIssueCommentCard = {
comment: IIssueComment;
comment: IIssueActivity;
handleCommentDeletion: (comment: string) => void;
onSubmit: (data: Partial<IIssueComment>) => void;
onSubmit: (data: Partial<IIssueActivity>) => void;
showAccessSpecifier?: boolean;
workspaceSlug: string;
projectId: string;
issueId: string;
user: any;
user: IUser | null;
issueCommentReactionCreate: (commentId: string, reaction: string) => void;
issueCommentReactionRemove: (commentId: string, reaction: string) => void;
};
@ -57,11 +57,11 @@ export const IssueCommentCard: React.FC<IIssueCommentCard> = (props) => {
setFocus,
watch,
setValue,
} = useForm<IIssueComment>({
} = useForm<IIssueActivity>({
defaultValues: comment,
});
const formSubmit = (formData: Partial<IIssueComment>) => {
const formSubmit = (formData: Partial<IIssueActivity>) => {
if (isSubmitting) return;
setIsEditing(false);
@ -119,13 +119,10 @@ export const IssueCommentCard: React.FC<IIssueCommentCard> = (props) => {
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
ref={editorRef}
value={watch("comment_html")}
value={watch("comment_html") ?? ""}
debouncedUpdatesEnabled={false}
customClassName="min-h-[50px] p-3 shadow-sm"
onChange={(comment_json: Object, comment_html: string) => {
setValue("comment_json", comment_json);
setValue("comment_html", comment_html);
}}
onChange={(comment_json: Object, comment_html: string) => setValue("comment_html", comment_html)}
mentionSuggestions={editorSuggestions.mentionSuggestions}
mentionHighlights={editorSuggestions.mentionHighlights}
/>
@ -158,7 +155,7 @@ export const IssueCommentCard: React.FC<IIssueCommentCard> = (props) => {
)}
<LiteReadOnlyEditorWithRef
ref={showEditorRef}
value={comment.comment_html}
value={comment.comment_html ?? ""}
customClassName="text-xs border border-custom-border-200 bg-custom-background-100"
mentionHighlights={editorSuggestions.mentionHighlights}
/>

View File

@ -11,16 +11,16 @@ import { LiteTextEditorWithRef } from "@plane/lite-text-editor";
// ui
import { Button } from "@plane/ui";
// types
import type { IIssueComment } from "types";
import type { IIssueActivity } from "types";
const defaultValues: Partial<IIssueComment> = {
const defaultValues: Partial<IIssueActivity> = {
access: "INTERNAL",
comment_html: "",
};
type IIssueCommentEditor = {
disabled?: boolean;
onSubmit: (data: IIssueComment) => Promise<void>;
onSubmit: (data: IIssueActivity) => Promise<void>;
showAccessSpecifier?: boolean;
};
@ -60,9 +60,9 @@ export const IssueCommentEditor: React.FC<IIssueCommentEditor> = (props) => {
formState: { isSubmitting },
handleSubmit,
reset,
} = useForm<IIssueComment>({ defaultValues });
} = useForm<IIssueActivity>({ defaultValues });
const handleAddComment = async (formData: IIssueComment) => {
const handleAddComment = async (formData: IIssueActivity) => {
if (!formData.comment_html || isSubmitting) return;
await onSubmit(formData).then(() => {
@ -99,7 +99,7 @@ export const IssueCommentEditor: React.FC<IIssueCommentEditor> = (props) => {
onChange={(comment_json: Object, comment_html: string) => onCommentChange(comment_html)}
commentAccessSpecifier={
showAccessSpecifier
? { accessValue, onAccessChange, showAccessSpecifier, commentAccess }
? { accessValue: accessValue ?? "INTERNAL", onAccessChange, showAccessSpecifier, commentAccess }
: undefined
}
submitButton={

View File

@ -2,7 +2,7 @@ import { FC } from "react";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
// components
import { IssueReaction } from "../reactions";
import { IssuePeekOverviewReactions } from "components/issues";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
// types
@ -49,7 +49,7 @@ export const IssueCommentReaction: FC<IIssueCommentReaction> = observer((props)
return (
<div>
<IssueReaction
<IssuePeekOverviewReactions
issueReactions={issueReactions}
user={user}
issueReactionCreate={handleCommentReactionCreate}

View File

@ -1,5 +1,5 @@
export * from "./view";
export * from "./card";
export * from "./comment-card";
export * from "./comment-editor";
export * from "./comment-reaction";
export * from "./view";

View File

@ -1,29 +1,30 @@
import { FC } from "react";
// components
import { IssueActivityCard } from "./card";
import { IssueCommentEditor } from "./comment-editor";
import { IssueActivityCard, IssueCommentEditor } from "components/issues";
// types
import { IIssueActivity, IUser } from "types";
interface IIssueComment {
type Props = {
workspaceSlug: string;
projectId: string;
issueId: string;
user: any;
issueComments: any;
user: IUser | null;
issueActivity: IIssueActivity[] | null;
issueCommentCreate: (comment: any) => void;
issueCommentUpdate: (comment: any) => void;
issueCommentRemove: (commentId: string) => void;
issueCommentReactionCreate: (commentId: string, reaction: string) => void;
issueCommentReactionRemove: (commentId: string, reaction: string) => void;
showCommentAccessSpecifier: boolean;
}
};
export const IssueComment: FC<IIssueComment> = (props) => {
export const IssueActivity: FC<Props> = (props) => {
const {
workspaceSlug,
projectId,
issueId,
user,
issueComments,
issueActivity,
issueCommentCreate,
issueCommentUpdate,
issueCommentRemove,
@ -47,7 +48,7 @@ export const IssueComment: FC<IIssueComment> = (props) => {
projectId={projectId}
issueId={issueId}
user={user}
issueComments={issueComments}
issueActivity={issueActivity}
issueCommentUpdate={issueCommentUpdate}
issueCommentRemove={issueCommentRemove}
issueCommentReactionCreate={issueCommentReactionCreate}

View File

@ -0,0 +1,6 @@
export * from "./activity";
export * from "./reactions";
export * from "./issue-detail";
export * from "./properties";
export * from "./root";
export * from "./view";

View File

@ -1,19 +1,22 @@
import { ChangeEvent, FC, useCallback, useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import debounce from "lodash/debounce";
// packages
import { RichTextEditor } from "@plane/rich-text-editor";
// components
import { TextArea } from "@plane/ui";
import { IssueReaction } from "./reactions";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import debounce from "lodash/debounce";
import useReloadConfirmations from "hooks/use-reload-confirmation";
import useEditorSuggestions from "hooks/use-editor-suggestions";
// components
import { IssuePeekOverviewReactions } from "components/issues";
// ui
import { TextArea } from "@plane/ui";
// types
import { IIssue } from "types";
import { IIssue, IUser } from "types";
// services
import { FileService } from "services/file.service";
import { useMobxStore } from "lib/mobx/store-provider";
// constants
import { EUserWorkspaceRoles } from "constants/workspace";
const fileService = new FileService();
@ -22,7 +25,7 @@ interface IPeekOverviewIssueDetails {
workspaceSlug: string;
issue: IIssue;
issueReactions: any;
user: any;
user: IUser | null;
issueUpdate: (issue: Partial<IIssue>) => void;
issueReactionCreate: (reaction: string) => void;
issueReactionRemove: (reaction: string) => void;
@ -89,7 +92,7 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
setLocalIssueDescription({ id: issue.id, description_html: issue.description_html });
setLocalTitleValue(issue.name);
}
}, [issue.id]);
}, [issue.id, issue.description_html, issue.name]);
const debouncedFormSave = debounce(async () => {
handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted"));
@ -104,7 +107,7 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
} else if (isSubmitting === "submitting") {
setShowAlert(true);
}
}, [isSubmitting, setShowAlert]);
}, [isSubmitting, setShowAlert, setIsSubmitting]);
// reset form values
useEffect(() => {
@ -165,7 +168,7 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
<Controller
name="description_html"
control={control}
render={({ field: { value, onChange } }) => (
render={({ field: { onChange } }) => (
<RichTextEditor
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug)}
@ -190,7 +193,7 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
)}
/>
</div>
<IssueReaction
<IssuePeekOverviewReactions
issueReactions={issueReactions}
user={user}
issueReactionCreate={issueReactionCreate}

View File

@ -47,8 +47,6 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
const {
user: { currentProjectRole },
cycleIssues: cycleIssueStore,
moduleIssues: { addIssueToModule },
issueDetail: { fetchPeekIssueDetails },
} = useMobxStore();

View File

@ -1,3 +1,3 @@
export * from "./preview";
export * from "./root";
export * from "./selector";
export * from "./preview";

View File

@ -1,16 +1,18 @@
import { FC } from "react";
// components
import { IssueReactionPreview, IssueReactionSelector } from "./";
import { IssueReactionPreview, IssueReactionSelector } from "components/issues";
// types
import { IUser } from "types";
interface IIssueReaction {
issueReactions: any;
user: any;
user: IUser | null;
issueReactionCreate: (reaction: string) => void;
issueReactionRemove: (reaction: string) => void;
position?: "top" | "bottom";
}
export const IssueReaction: FC<IIssueReaction> = (props) => {
export const IssuePeekOverviewReactions: FC<IIssueReaction> = (props) => {
const { issueReactions, user, issueReactionCreate, issueReactionRemove, position = "bottom" } = props;
const handleReaction = (reaction: string) => {

View File

@ -1,17 +1,17 @@
import { FC, Fragment, ReactNode, useEffect } from "react";
import { FC, Fragment, ReactNode, useCallback, useEffect } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import useToast from "hooks/use-toast";
// components
import { IssueView } from "./view";
import { IssueView } from "components/issues";
// helpers
import { copyUrlToClipboard } from "helpers/string.helper";
// types
import { IIssue } from "types";
// constants
import { EUserWorkspaceRoles } from "constants/workspace";
interface IIssuePeekOverview {
@ -30,27 +30,45 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
const { peekIssueId } = router.query;
const {
user: userStore,
issue: issueStore,
issueDetail: issueDetailStore,
archivedIssueDetail: archivedIssueDetailStore,
archivedIssues: archivedIssuesStore,
project: projectStore,
user: { currentProjectRole },
issue: { removeIssueFromStructure },
issueDetail: {
createIssueComment,
updateIssueComment,
removeIssueComment,
creationIssueCommentReaction,
removeIssueCommentReaction,
createIssueReaction,
removeIssueReaction,
createIssueSubscription,
removeIssueSubscription,
getIssue,
loader,
fetchPeekIssueDetails,
setPeekId,
fetchIssueActivity,
},
archivedIssueDetail: {
getIssue: getArchivedIssue,
loader: archivedIssueLoader,
fetchPeekIssueDetails: fetchArchivedPeekIssueDetails,
},
archivedIssues: { deleteArchivedIssue },
project: { currentProjectDetails },
} = useMobxStore();
const { setToastAlert } = useToast();
const fetchIssueDetail = async () => {
const fetchIssueDetail = useCallback(async () => {
if (workspaceSlug && projectId && peekIssueId) {
if (isArchived)
await archivedIssueDetailStore.fetchPeekIssueDetails(workspaceSlug, projectId, peekIssueId as string);
else await issueDetailStore.fetchPeekIssueDetails(workspaceSlug, projectId, peekIssueId as string);
if (isArchived) await fetchArchivedPeekIssueDetails(workspaceSlug, projectId, peekIssueId as string);
else await fetchPeekIssueDetails(workspaceSlug, projectId, peekIssueId as string);
}
};
}, [fetchArchivedPeekIssueDetails, fetchPeekIssueDetails, workspaceSlug, projectId, peekIssueId, isArchived]);
useEffect(() => {
fetchIssueDetail();
}, [workspaceSlug, projectId, peekIssueId]);
}, [workspaceSlug, projectId, peekIssueId, fetchIssueDetail]);
const handleCopyText = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
@ -72,44 +90,43 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
});
};
const issue = isArchived ? archivedIssueDetailStore.getIssue : issueDetailStore.getIssue;
const isLoading = isArchived ? archivedIssueDetailStore.loader : issueDetailStore.loader;
const issue = isArchived ? getArchivedIssue : getIssue;
const isLoading = isArchived ? archivedIssueLoader : loader;
const issueUpdate = (_data: Partial<IIssue>) => {
if (handleIssue) handleIssue(_data);
const issueUpdate = async (_data: Partial<IIssue>) => {
if (handleIssue) {
await handleIssue(_data);
fetchIssueActivity(workspaceSlug, projectId, issueId);
}
};
const issueReactionCreate = (reaction: string) =>
issueDetailStore.createIssueReaction(workspaceSlug, projectId, issueId, reaction);
const issueReactionCreate = (reaction: string) => createIssueReaction(workspaceSlug, projectId, issueId, reaction);
const issueReactionRemove = (reaction: string) =>
issueDetailStore.removeIssueReaction(workspaceSlug, projectId, issueId, reaction);
const issueReactionRemove = (reaction: string) => removeIssueReaction(workspaceSlug, projectId, issueId, reaction);
const issueCommentCreate = (comment: any) =>
issueDetailStore.createIssueComment(workspaceSlug, projectId, issueId, comment);
const issueCommentCreate = (comment: any) => createIssueComment(workspaceSlug, projectId, issueId, comment);
const issueCommentUpdate = (comment: any) =>
issueDetailStore.updateIssueComment(workspaceSlug, projectId, issueId, comment?.id, comment);
updateIssueComment(workspaceSlug, projectId, issueId, comment?.id, comment);
const issueCommentRemove = (commentId: string) =>
issueDetailStore.removeIssueComment(workspaceSlug, projectId, issueId, commentId);
const issueCommentRemove = (commentId: string) => removeIssueComment(workspaceSlug, projectId, issueId, commentId);
const issueCommentReactionCreate = (commentId: string, reaction: string) =>
issueDetailStore.creationIssueCommentReaction(workspaceSlug, projectId, issueId, commentId, reaction);
creationIssueCommentReaction(workspaceSlug, projectId, issueId, commentId, reaction);
const issueCommentReactionRemove = (commentId: string, reaction: string) =>
issueDetailStore.removeIssueCommentReaction(workspaceSlug, projectId, issueId, commentId, reaction);
removeIssueCommentReaction(workspaceSlug, projectId, issueId, commentId, reaction);
const issueSubscriptionCreate = () => issueDetailStore.createIssueSubscription(workspaceSlug, projectId, issueId);
const issueSubscriptionCreate = () => createIssueSubscription(workspaceSlug, projectId, issueId);
const issueSubscriptionRemove = () => issueDetailStore.removeIssueSubscription(workspaceSlug, projectId, issueId);
const issueSubscriptionRemove = () => removeIssueSubscription(workspaceSlug, projectId, issueId);
const handleDeleteIssue = async () => {
if (isArchived) await archivedIssuesStore.deleteArchivedIssue(workspaceSlug, projectId, issue!);
else await issueStore.removeIssueFromStructure(workspaceSlug, projectId, issue!);
if (isArchived) await deleteArchivedIssue(workspaceSlug, projectId, issue!);
else removeIssueFromStructure(workspaceSlug, projectId, issue!);
const { query } = router;
if (query.peekIssueId) {
issueDetailStore.setPeekId(null);
setPeekId(null);
delete query.peekIssueId;
delete query.peekProjectId;
router.push({
@ -119,7 +136,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
}
};
const userRole = userStore.currentProjectRole ?? EUserWorkspaceRoles.GUEST;
const userRole = currentProjectRole ?? EUserWorkspaceRoles.GUEST;
return (
<Fragment>
@ -144,7 +161,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
issueSubscriptionRemove={issueSubscriptionRemove}
handleDeleteIssue={handleDeleteIssue}
disableUserActions={[5, 10].includes(userRole)}
showCommentAccessSpecifier={projectStore.currentProjectDetails?.is_deployed}
showCommentAccessSpecifier={currentProjectDetails?.is_deployed}
>
{children}
</IssueView>

View File

@ -3,16 +3,21 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
import { MoveRight, MoveDiagonal, Bell, Link2, Trash2 } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { PeekOverviewIssueDetails } from "./issue-detail";
import { PeekOverviewProperties } from "./properties";
import { IssueComment } from "./activity";
import {
DeleteArchivedIssueModal,
DeleteIssueModal,
IssueActivity,
IssueUpdateStatus,
PeekOverviewIssueDetails,
PeekOverviewProperties,
} from "components/issues";
// ui
import { Button, CenterPanelIcon, CustomSelect, FullScreenPanelIcon, SidePanelIcon, Spinner } from "@plane/ui";
import { DeleteIssueModal, DeleteArchivedIssueModal, IssueUpdateStatus } from "components/issues/";
// types
import { IIssue } from "types";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
interface IIssueView {
workspaceSlug: string;
@ -41,7 +46,7 @@ interface IIssueView {
type TPeekModes = "side-peek" | "modal" | "full-screen";
const peekOptions: { key: TPeekModes; icon: any; title: string }[] = [
const PEEK_OPTIONS: { key: TPeekModes; icon: any; title: string }[] = [
{
key: "side-peek",
icon: SidePanelIcon,
@ -86,9 +91,12 @@ export const IssueView: FC<IIssueView> = observer((props) => {
} = props;
const router = useRouter();
const { peekIssueId } = router.query as { peekIssueId: string };
const { peekIssueId } = router.query;
const { user: userStore, issueDetail: issueDetailStore } = useMobxStore();
const {
user: { currentUser },
issueDetail: { fetchIssueSubscription, getIssueActivity, getIssueReactions, getIssueSubscription, setPeekId },
} = useMobxStore();
const [peekMode, setPeekMode] = useState<TPeekModes>("side-peek");
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
@ -96,7 +104,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
const updateRoutePeekId = () => {
if (issueId != peekIssueId) {
issueDetailStore.setPeekId(issueId);
setPeekId(issueId);
const { query } = router;
router.push({
pathname: router.pathname,
@ -104,10 +112,13 @@ export const IssueView: FC<IIssueView> = observer((props) => {
});
}
};
const removeRoutePeekId = () => {
const { query } = router;
if (query.peekIssueId) {
issueDetailStore.setPeekId(null);
setPeekId(null);
delete query.peekIssueId;
delete query.peekProjectId;
router.push({
@ -123,18 +134,16 @@ export const IssueView: FC<IIssueView> = observer((props) => {
: null,
async () => {
if (workspaceSlug && projectId && issueId && peekIssueId && issueId === peekIssueId) {
await issueDetailStore.fetchIssueSubscription(workspaceSlug, projectId, issueId);
await fetchIssueSubscription(workspaceSlug, projectId, issueId);
}
}
);
const issueReactions = issueDetailStore.getIssueReactions || [];
const issueComments = issueDetailStore.getIssueComments || [];
const issueSubscription = issueDetailStore.getIssueSubscription || [];
const issueReactions = getIssueReactions || [];
const issueActivity = getIssueActivity;
const issueSubscription = getIssueSubscription || [];
const user = userStore?.currentUser;
const currentMode = peekOptions.find((m) => m.key === peekMode);
const currentMode = PEEK_OPTIONS.find((m) => m.key === peekMode);
return (
<>
@ -198,7 +207,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
</button>
}
>
{peekOptions.map((mode) => (
{PEEK_OPTIONS.map((mode) => (
<CustomSelect.Option key={mode.key} value={mode.key}>
<div
className={`flex items-center gap-1.5 ${
@ -219,8 +228,8 @@ export const IssueView: FC<IIssueView> = observer((props) => {
<div className="flex items-center gap-x-4">
<IssueUpdateStatus isSubmitting={isSubmitting} />
<div className="flex items-center gap-4">
{issue?.created_by !== user?.id &&
!issue?.assignees.includes(user?.id ?? "") &&
{issue?.created_by !== currentUser?.id &&
!issue?.assignees.includes(currentUser?.id ?? "") &&
!router.pathname.includes("[archivedIssueId]") && (
<Button
size="sm"
@ -269,7 +278,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
issue={issue}
issueUpdate={issueUpdate}
issueReactions={issueReactions}
user={user}
user={currentUser}
issueReactionCreate={issueReactionCreate}
issueReactionRemove={issueReactionRemove}
/>
@ -280,12 +289,12 @@ export const IssueView: FC<IIssueView> = observer((props) => {
disableUserActions={disableUserActions}
/>
<IssueComment
<IssueActivity
workspaceSlug={workspaceSlug}
projectId={projectId}
issueId={issueId}
user={user}
issueComments={issueComments}
user={currentUser}
issueActivity={issueActivity}
issueCommentCreate={issueCommentCreate}
issueCommentUpdate={issueCommentUpdate}
issueCommentRemove={issueCommentRemove}
@ -305,17 +314,17 @@ export const IssueView: FC<IIssueView> = observer((props) => {
issue={issue}
issueReactions={issueReactions}
issueUpdate={issueUpdate}
user={user}
user={currentUser}
issueReactionCreate={issueReactionCreate}
issueReactionRemove={issueReactionRemove}
/>
<IssueComment
<IssueActivity
workspaceSlug={workspaceSlug}
projectId={projectId}
issueId={issueId}
user={user}
issueComments={issueComments}
user={currentUser}
issueActivity={issueActivity}
issueCommentCreate={issueCommentCreate}
issueCommentUpdate={issueCommentUpdate}
issueCommentRemove={issueCommentRemove}

View File

@ -81,11 +81,10 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<ILinkDetails | null>(null);
const {
user: userStore,
user: { currentUser, currentProjectRole },
projectState: { states },
projectIssues: { removeIssue },
} = useMobxStore();
const user = userStore.currentUser;
const userRole = userStore.currentProjectRole;
const router = useRouter();
const { workspaceSlug, projectId, issueId, inboxIssueId } = router.query;
@ -102,7 +101,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
const handleCycleChange = useCallback(
(cycleId: string) => {
if (!workspaceSlug || !projectId || !issueDetail || !user) return;
if (!workspaceSlug || !projectId || !issueDetail || !currentUser) return;
issueService
.addIssueToCycle(workspaceSlug as string, projectId as string, cycleId, {
@ -112,12 +111,12 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
mutate(ISSUE_DETAILS(issueId as string));
});
},
[workspaceSlug, projectId, issueId, issueDetail, user]
[workspaceSlug, projectId, issueId, issueDetail, currentUser]
);
const handleModuleChange = useCallback(
(moduleId: string) => {
if (!workspaceSlug || !projectId || !issueDetail || !user) return;
if (!workspaceSlug || !projectId || !issueDetail || !currentUser) return;
moduleService
.addIssuesToModule(workspaceSlug as string, projectId as string, moduleId, {
@ -127,7 +126,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
mutate(ISSUE_DETAILS(issueId as string));
});
},
[workspaceSlug, projectId, issueId, issueDetail, user]
[workspaceSlug, projectId, issueId, issueDetail, currentUser]
);
const handleCreateLink = async (formData: IIssueLink) => {
@ -249,7 +248,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
setLinkModal(true);
};
const isAllowed = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER;
const isAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
const currentIssueState = projectId
? states[projectId.toString()]?.find((s) => s.id === issueDetail?.state)
@ -268,8 +267,16 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
createIssueLink={handleCreateLink}
updateIssueLink={handleUpdateLink}
/>
{issueDetail && (
<DeleteIssueModal handleClose={() => setDeleteIssueModal(false)} isOpen={deleteIssueModal} data={issueDetail} />
{workspaceSlug && projectId && issueDetail && (
<DeleteIssueModal
handleClose={() => setDeleteIssueModal(false)}
isOpen={deleteIssueModal}
data={issueDetail}
onSubmit={async () => {
await removeIssue(workspaceSlug.toString(), projectId.toString(), issueDetail.id);
router.push(`/${workspaceSlug}/projects/${projectId}/issues`);
}}
/>
)}
<div className="h-full w-full flex flex-col divide-y-2 divide-custom-border-200 overflow-hidden">
<div className="flex items-center justify-between px-5 pb-3">
@ -288,8 +295,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
</h4>
</div>
<div className="flex flex-wrap items-center gap-2">
{issueDetail?.created_by !== user?.id &&
!issueDetail?.assignees.includes(user?.id ?? "") &&
{issueDetail?.created_by !== currentUser?.id &&
!issueDetail?.assignees.includes(currentUser?.id ?? "") &&
!router.pathname.includes("[archivedIssueId]") &&
(fieldsToShow.includes("all") || fieldsToShow.includes("subscribe")) && (
<Button
@ -631,7 +638,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
</div>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("link")) && (
<div className={`min-h-[116px] py-1 text-xs ${uneditable ? "opacity-60" : ""}`}>
<div className={`py-1 text-xs ${uneditable ? "opacity-60" : ""}`}>
<div className="flex items-center justify-between gap-2">
<h4>Links</h4>
{isAllowed && (
@ -647,21 +654,23 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
</button>
)}
</div>
<div className="mt-2 space-y-2">
{issueDetail?.issue_link && issueDetail.issue_link.length > 0 ? (
<LinksList
links={issueDetail.issue_link}
handleDeleteLink={handleDeleteLink}
handleEditLink={handleEditLink}
userAuth={{
isGuest: userRole === 5,
isViewer: userRole === 10,
isMember: userRole === 15,
isOwner: userRole === 20,
}}
/>
) : null}
</div>
{issueDetail?.issue_link && issueDetail.issue_link.length > 0 && (
<div className="mt-2 space-y-2">
{
<LinksList
links={issueDetail.issue_link}
handleDeleteLink={handleDeleteLink}
handleEditLink={handleEditLink}
userAuth={{
isGuest: currentProjectRole === 5,
isViewer: currentProjectRole === 10,
isMember: currentProjectRole === 15,
isOwner: currentProjectRole === 20,
}}
/>
}
</div>
)}
</div>
)}
</div>

View File

@ -65,7 +65,7 @@ export const SubIssues: React.FC<ISubIssues> = ({
workspaceSlug={workspaceSlug}
projectId={peekProjectId.toString()}
issueId={peekIssueId.toString()}
handleIssue={(issueToUpdate) => handleUpdateIssue(issue, { ...issue, ...issueToUpdate })}
handleIssue={async (issueToUpdate) => await handleUpdateIssue(issue, { ...issue, ...issueToUpdate })}
/>
)}
<div>
@ -117,17 +117,16 @@ export const SubIssues: React.FC<ISubIssues> = ({
workspaceSlug={workspaceSlug}
parentIssue={parentIssue}
issue={issue}
user={user}
editable={editable}
/>
</div>
<div className="flex-shrink-0 text-sm">
<CustomMenu width="auto" ellipsis>
<CustomMenu width="auto" placement="bottom-end" ellipsis>
{editable && (
<CustomMenu.MenuItem onClick={() => handleIssueCrudOperation("edit", parentIssue?.id, issue)}>
<div className="flex items-center justify-start gap-2">
<Pencil width={14} strokeWidth={2} />
<div className="flex items-center gap-2">
<Pencil className="h-3.5 w-3.5" strokeWidth={2} />
<span>Edit issue</span>
</div>
</CustomMenu.MenuItem>
@ -135,8 +134,8 @@ export const SubIssues: React.FC<ISubIssues> = ({
{editable && (
<CustomMenu.MenuItem onClick={() => handleIssueCrudOperation("delete", parentIssue?.id, issue)}>
<div className="flex items-center justify-start gap-2">
<Trash width={14} strokeWidth={2} />
<div className="flex items-center gap-2">
<Trash className="h-3.5 w-3.5" strokeWidth={2} />
<span>Delete issue</span>
</div>
</CustomMenu.MenuItem>
@ -145,8 +144,8 @@ export const SubIssues: React.FC<ISubIssues> = ({
<CustomMenu.MenuItem
onClick={() => copyText(`${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`)}
>
<div className="flex items-center justify-start gap-2">
<LinkIcon width={14} strokeWidth={2} />
<div className="flex items-center gap-2">
<LinkIcon className="h-3.5 w-3.5" strokeWidth={2} />
<span>Copy issue link</span>
</div>
</CustomMenu.MenuItem>

View File

@ -1,15 +1,11 @@
import React from "react";
import { observer } from "mobx-react-lite";
import { mutate } from "swr";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// services
import { IssueService } from "services/issue";
// components
import { ViewDueDateSelect, ViewStartDateSelect } from "components/issues";
import { PrioritySelect } from "components/project";
// types
import { IUser, IIssue, IState } from "types";
import { IIssue, IState } from "types";
// fetch-keys
import { SUB_ISSUES } from "constants/fetch-keys";
import { IssuePropertyAssignee, IssuePropertyState } from "../issue-layouts/properties";
@ -18,19 +14,14 @@ export interface IIssueProperty {
workspaceSlug: string;
parentIssue: IIssue;
issue: IIssue;
user: IUser | undefined;
editable: boolean;
}
// services
const issueService = new IssueService();
export const IssueProperty: React.FC<IIssueProperty> = observer((props) => {
const { workspaceSlug, parentIssue, issue, user, editable } = props;
const { issueFilter: issueFilterStore } = useMobxStore();
const displayProperties = issueFilterStore.userDisplayProperties ?? {};
export const IssueProperty: React.FC<IIssueProperty> = (props) => {
const { workspaceSlug, parentIssue, issue, editable } = props;
const handlePriorityChange = (data: any) => {
partialUpdateIssue({ priority: data });
@ -77,63 +68,30 @@ export const IssueProperty: React.FC<IIssueProperty> = observer((props) => {
};
return (
<div className="relative flex items-center gap-1">
{displayProperties.priority && (
<div className="flex-shrink-0">
<PrioritySelect
value={issue.priority}
onChange={handlePriorityChange}
hideDropdownArrow
disabled={!editable}
/>
</div>
)}
<div className="relative flex items-center gap-2">
<div className="flex-shrink-0">
<PrioritySelect value={issue.priority} onChange={handlePriorityChange} hideDropdownArrow disabled={!editable} />
</div>
{displayProperties.state && (
<div className="flex-shrink-0">
<IssuePropertyState
projectId={issue?.project_detail?.id || null}
value={issue?.state_detail || null}
onChange={(data) => handleStateChange(data)}
disabled={false}
hideDropdownArrow
/>
</div>
)}
<div className="flex-shrink-0">
<IssuePropertyState
projectId={issue?.project_detail?.id || null}
value={issue?.state || null}
onChange={(data) => handleStateChange(data)}
disabled={false}
hideDropdownArrow
/>
</div>
{displayProperties.start_date && issue.start_date && (
<div className="flex-shrink-0 w-[104px]">
<ViewStartDateSelect
issue={issue}
onChange={(val) => partialUpdateIssue({ start_date: val })}
disabled={!editable}
/>
</div>
)}
{displayProperties.due_date && issue.target_date && (
<div className="flex-shrink-0 w-[104px]">
{user && (
<ViewDueDateSelect
issue={issue}
onChange={(val) => partialUpdateIssue({ target_date: val })}
disabled={!editable}
/>
)}
</div>
)}
{displayProperties.assignee && (
<div className="flex-shrink-0">
<IssuePropertyAssignee
projectId={issue?.project_detail?.id || null}
value={issue?.assignees || null}
hideDropdownArrow
onChange={(val) => handleAssigneeChange(val)}
disabled={false}
/>
</div>
)}
<div className="flex-shrink-0">
<IssuePropertyAssignee
projectId={issue?.project_detail?.id || null}
value={issue?.assignees || null}
hideDropdownArrow
onChange={(val) => handleAssigneeChange(val)}
disabled={false}
/>
</div>
</div>
);
});
};

View File

@ -45,11 +45,10 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
const { parentIssue, user } = props;
const {
user: userStore,
user: { currentProjectRole },
issue: { updateIssueStructure },
projectIssues: { updateIssue },
projectIssues: { updateIssue, removeIssue },
} = useMobxStore();
const userRole = userStore.currentProjectRole;
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
@ -177,7 +176,7 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
[updateIssueStructure, projectId, updateIssue, user, workspaceSlug]
);
const isEditable = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER;
const isEditable = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
const mutateSubIssues = (parentIssueId: string | null) => {
if (parentIssueId) mutate(SUB_ISSUES(parentIssueId));
@ -261,7 +260,7 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
</>
}
buttonClassName="whitespace-nowrap"
// position="left"
placement="bottom-end"
noBorder
noChevron
>
@ -297,7 +296,7 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
</>
}
buttonClassName="whitespace-nowrap"
// position="left"
placement="bottom-end"
noBorder
noChevron
>
@ -356,7 +355,8 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
</>
)}
{isEditable &&
issueCrudOperation?.delete?.toggle &&
workspaceSlug &&
projectId &&
issueCrudOperation?.delete?.issueId &&
issueCrudOperation?.delete?.issue && (
<DeleteIssueModal
@ -366,6 +366,13 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
handleIssueCrudOperation("delete", null, null);
}}
data={issueCrudOperation?.delete?.issue}
onSubmit={async () => {
await removeIssue(
workspaceSlug.toString(),
projectId.toString(),
issueCrudOperation?.delete?.issue?.id ?? ""
);
}}
/>
)}
</>

View File

@ -1,4 +1,4 @@
import React, {useState } from "react";
import React, { useState } from "react";
import { useRouter } from "next/router";
import { mutate } from "swr";
import { useTheme } from "next-themes";
@ -67,8 +67,10 @@ export const SwitchOrDeleteAccountModal: React.FC<Props> = (props) => {
title: "Success!",
message: "Account deleted successfully.",
});
handleClose();
mutate("CURRENT_USER_DETAILS", null);
setTheme("system");
router.push("/");
handleClose();
})
.catch((err) =>
setToastAlert({

View File

@ -27,8 +27,11 @@ export const ProfileIssuesPage = observer((props: IProfileIssuesPage) => {
const {
workspaceProfileIssues: { loader, getIssues, fetchIssues },
workspaceProfileIssuesFilter: { issueFilters, fetchFilters },
workspaceMember: { currentWorkspaceUserProjectsRole },
}: RootStore = useMobxStore();
console.log("currentWorkspaceUserProjectsRole", currentWorkspaceUserProjectsRole);
useSWR(
workspaceSlug && userId ? `CURRENT_WORKSPACE_PROFILE_ISSUES_${workspaceSlug}_${userId}_${type}` : null,
async () => {

View File

@ -19,7 +19,7 @@ export const WorkspaceAuthWrapper: FC<IWorkspaceAuthWrapper> = observer((props)
user: { currentWorkspaceMemberInfo, hasPermissionToCurrentWorkspace, fetchUserWorkspaceInfo },
project: { fetchProjects },
workspace: { fetchWorkspaceLabels },
workspaceMember: { fetchWorkspaceMembers },
workspaceMember: { fetchWorkspaceMembers, fetchWorkspaceUserProjectsRole },
} = useMobxStore();
// router
@ -45,6 +45,11 @@ export const WorkspaceAuthWrapper: FC<IWorkspaceAuthWrapper> = observer((props)
workspaceSlug ? `WORKSPACE_LABELS_${workspaceSlug}` : null,
workspaceSlug ? () => fetchWorkspaceLabels(workspaceSlug.toString()) : null
);
// fetch workspace user projects role
useSWR(
workspaceSlug ? `WORKSPACE_PROJECTS_ROLE_${workspaceSlug}` : null,
workspaceSlug ? () => fetchWorkspaceUserProjectsRole(workspaceSlug.toString()) : null
);
// while data is being loaded
if (!currentWorkspaceMemberInfo && hasPermissionToCurrentWorkspace === undefined) {

View File

@ -26,7 +26,7 @@ import { NextPageWithLayout } from "types/app";
import { IPage, IIssue } from "types";
// fetch-keys
import { PAGE_DETAILS, PROJECT_ISSUES_LIST } from "constants/fetch-keys";
import { IssuePeekOverview } from "components/issues/issue-peek-overview";
import { IssuePeekOverview } from "components/issues/peek-overview";
import { IssueService } from "services/issue";
import useToast from "hooks/use-toast";
import useReloadConfirmations from "hooks/use-reload-confirmation";

View File

@ -1,6 +1,6 @@
import { APIService } from "services/api.service";
// types
import { IIssueComment } from "types";
import { IIssueActivity } from "types";
// helper
import { API_BASE_URL } from "helpers/common.helper";
@ -21,7 +21,7 @@ export class IssueCommentService extends APIService {
workspaceSlug: string,
projectId: string,
issueId: string,
data: Partial<IIssueComment>
data: Partial<IIssueActivity>
): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/comments/`, data)
.then((response) => response?.data)
@ -35,7 +35,7 @@ export class IssueCommentService extends APIService {
projectId: string,
issueId: string,
commentId: string,
data: Partial<IIssueComment>
data: Partial<IIssueActivity>
): Promise<any> {
return this.patch(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/comments/${commentId}/`,

View File

@ -2,7 +2,7 @@ import { API_BASE_URL } from "helpers/common.helper";
// services
import { APIService } from "services/api.service";
// types
import type { IssueReaction, IssueCommentReaction, IssueReactionForm, IssueCommentReactionForm } from "types";
import type { IssueCommentReaction, IssueReactionForm, IssueCommentReactionForm, IIssueReaction } from "types";
export class IssueReactionService extends APIService {
constructor() {
@ -22,7 +22,7 @@ export class IssueReactionService extends APIService {
});
}
async listIssueReactions(workspaceSlug: string, projectId: string, issueId: string): Promise<IssueReaction[]> {
async listIssueReactions(workspaceSlug: string, projectId: string, issueId: string): Promise<IIssueReaction[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/reactions/`)
.then((response) => response?.data)
.catch((error) => {

View File

@ -13,6 +13,7 @@ import {
IProductUpdateResponse,
IWorkspaceBulkInviteFormData,
IWorkspaceViewProps,
IUserProjectsRole,
} from "types";
import { IWorkspaceView } from "types/workspace-views";
// store
@ -267,4 +268,12 @@ export class WorkspaceService extends APIService {
throw error?.response?.data;
});
}
async getWorkspaceUserProjectsRole(workspaceSlug: string): Promise<IUserProjectsRole> {
return this.get(`/api/users/me/workspaces/${workspaceSlug}/project-roles/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}

View File

@ -173,7 +173,7 @@ export class ArchivedIssueDetailStore implements IArchivedIssueDetailStore {
try {
const issueResponse = await this.archivedIssueService.retrieveArchivedIssue(workspaceSlug, projectId, issueId);
await this.rootStore.issueDetail.fetchIssueReactions(workspaceSlug, projectId, issueId);
await this.rootStore.issueDetail.fetchIssueComments(workspaceSlug, projectId, issueId);
await this.rootStore.issueDetail.fetchIssueActivity(workspaceSlug, projectId, issueId);
runInAction(() => {
this.loader = false;

View File

@ -34,7 +34,6 @@ export class TrackEventStore implements ITrackEventStore {
group?: { isGrouping: boolean | null; groupType: string | null; gorupId: string | null } | null
) => {
try {
console.log("POSTHOG_EVENT: ", eventName);
let extras: any = {
workspace_name: this.rootStore.workspace.currentWorkspace?.name ?? "",
workspace_id: this.rootStore.workspace.currentWorkspace?.id ?? "",
@ -70,9 +69,8 @@ export class TrackEventStore implements ITrackEventStore {
element: this.trackElement ?? "",
});
}
console.log(payload);
} catch (error) {
console.log(error);
throw error;
}
this.setTrackElement("");
};

View File

@ -4,7 +4,7 @@ import { IssueService, IssueReactionService, IssueCommentService } from "service
import { NotificationService } from "services/notification.service";
// types
import { RootStore } from "../root";
import { IIssue } from "types";
import type { IIssue, IIssueActivity } from "types";
// constants
import { groupReactionEmojis } from "constants/issue";
@ -16,18 +16,18 @@ export interface IIssueDetailStore {
issues: {
[issueId: string]: IIssue;
};
issue_reactions: {
issueReactions: {
[issueId: string]: any;
};
issue_comments: {
[issueId: string]: any;
issueActivity: {
[issueId: string]: IIssueActivity[];
};
issue_comment_reactions: {
issueCommentReactions: {
[issueId: string]: {
[comment_id: string]: any;
};
};
issue_subscription: {
issueSubscription: {
[issueId: string]: any;
};
@ -36,7 +36,7 @@ export interface IIssueDetailStore {
// computed
getIssue: IIssue | null;
getIssueReactions: any | null;
getIssueComments: any | null;
getIssueActivity: IIssueActivity[] | null;
getIssueCommentReactions: any | null;
getIssueSubscription: any | null;
@ -51,7 +51,7 @@ export interface IIssueDetailStore {
createIssueReaction: (workspaceSlug: string, projectId: string, issueId: string, reaction: string) => Promise<void>;
removeIssueReaction: (workspaceSlug: string, projectId: string, issueId: string, reaction: string) => Promise<void>;
fetchIssueComments: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
fetchIssueActivity: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
createIssueComment: (workspaceSlug: string, projectId: string, issueId: string, data: any) => Promise<void>;
updateIssueComment: (
workspaceSlug: string,
@ -96,16 +96,16 @@ export class IssueDetailStore implements IIssueDetailStore {
issues: {
[issueId: string]: IIssue;
} = {};
issue_reactions: {
issueReactions: {
[issueId: string]: any;
} = {};
issue_comments: {
issueActivity: {
[issueId: string]: IIssueActivity[];
} = {};
issueCommentReactions: {
[issueId: string]: any;
} = {};
issue_comment_reactions: {
[issueId: string]: any;
} = {};
issue_subscription: {
issueSubscription: {
[issueId: string]: any;
} = {};
@ -125,14 +125,14 @@ export class IssueDetailStore implements IIssueDetailStore {
peekId: observable.ref,
issues: observable.ref,
issue_reactions: observable.ref,
issue_comments: observable.ref,
issue_comment_reactions: observable.ref,
issue_subscription: observable.ref,
issueReactions: observable.ref,
issueActivity: observable.ref,
issueCommentReactions: observable.ref,
issueSubscription: observable.ref,
getIssue: computed,
getIssueReactions: computed,
getIssueComments: computed,
getIssueActivity: computed,
getIssueCommentReactions: computed,
getIssueSubscription: computed,
@ -147,7 +147,7 @@ export class IssueDetailStore implements IIssueDetailStore {
createIssueReaction: action,
removeIssueReaction: action,
fetchIssueComments: action,
fetchIssueActivity: action,
createIssueComment: action,
updateIssueComment: action,
removeIssueComment: action,
@ -196,25 +196,25 @@ export class IssueDetailStore implements IIssueDetailStore {
get getIssueReactions() {
if (!this.peekId) return null;
const _reactions = this.issue_reactions[this.peekId];
const _reactions = this.issueReactions[this.peekId];
return _reactions || null;
}
get getIssueComments() {
get getIssueActivity() {
if (!this.peekId) return null;
const _comments = this.issue_comments[this.peekId];
return _comments || null;
const activity = this.issueActivity[this.peekId];
return activity || null;
}
get getIssueCommentReactions() {
if (!this.peekId) return null;
const _commentReactions = this.issue_comment_reactions[this.peekId];
const _commentReactions = this.issueCommentReactions[this.peekId];
return _commentReactions || null;
}
get getIssueSubscription() {
if (!this.peekId) return null;
const _commentSubscription = this.issue_subscription[this.peekId];
const _commentSubscription = this.issueSubscription[this.peekId];
return _commentSubscription || null;
}
@ -292,7 +292,7 @@ export class IssueDetailStore implements IIssueDetailStore {
const issueDetailsResponse = await this.issueService.retrieve(workspaceSlug, projectId, issueId);
await this.fetchIssueReactions(workspaceSlug, projectId, issueId);
await this.fetchIssueComments(workspaceSlug, projectId, issueId);
await this.fetchIssueActivity(workspaceSlug, projectId, issueId);
runInAction(() => {
this.loader = false;
@ -319,13 +319,13 @@ export class IssueDetailStore implements IIssueDetailStore {
try {
const _reactions = await this.issueReactionService.listIssueReactions(workspaceSlug, projectId, issueId);
const _issue_reactions = {
...this.issue_reactions,
const _issueReactions = {
...this.issueReactions,
[issueId]: groupReactionEmojis(_reactions),
};
runInAction(() => {
this.issue_reactions = _issue_reactions;
this.issueReactions = _issueReactions;
});
} catch (error) {
console.warn("error creating the issue reaction", error);
@ -346,15 +346,15 @@ export class IssueDetailStore implements IIssueDetailStore {
};
runInAction(() => {
this.issue_reactions = {
...this.issue_reactions,
this.issueReactions = {
...this.issueReactions,
[issueId]: _currentReactions,
};
});
} catch (error) {
runInAction(() => {
this.issue_reactions = {
...this.issue_reactions,
this.issueReactions = {
...this.issueReactions,
[issueId]: _currentReactions,
};
});
@ -375,8 +375,8 @@ export class IssueDetailStore implements IIssueDetailStore {
};
runInAction(() => {
this.issue_reactions = {
...this.issue_reactions,
this.issueReactions = {
...this.issueReactions,
[issueId]: _currentReactions,
};
});
@ -385,8 +385,8 @@ export class IssueDetailStore implements IIssueDetailStore {
}
} catch (error) {
runInAction(() => {
this.issue_reactions = {
...this.issue_reactions,
this.issueReactions = {
...this.issueReactions,
[issueId]: _currentReactions,
};
});
@ -395,24 +395,25 @@ export class IssueDetailStore implements IIssueDetailStore {
}
};
// comments
fetchIssueComments = async (workspaceSlug: string, projectId: string, issueId: string) => {
fetchIssueActivity = async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
const _issueCommentResponse = await this.issueService.getIssueActivities(workspaceSlug, projectId, issueId);
const issueActivityResponse = await this.issueService.getIssueActivities(workspaceSlug, projectId, issueId);
const _issueComments = {
...this.issue_comments,
[issueId]: [..._issueCommentResponse],
...this.issueActivity,
[issueId]: [...issueActivityResponse],
};
runInAction(() => {
this.issue_comments = _issueComments;
this.issueActivity = _issueComments;
});
} catch (error) {
console.warn("error creating the issue comment", error);
throw error;
}
};
// comments
createIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, data: any) => {
try {
const _issueCommentResponse = await this.issueCommentService.createIssueComment(
@ -423,18 +424,19 @@ export class IssueDetailStore implements IIssueDetailStore {
);
const _issueComments = {
...this.issue_comments,
[issueId]: [...this.issue_comments[issueId], _issueCommentResponse],
...this.issueActivity,
[issueId]: [...this.issueActivity[issueId], _issueCommentResponse],
};
runInAction(() => {
this.issue_comments = _issueComments;
this.issueActivity = _issueComments;
});
} catch (error) {
console.warn("error creating the issue comment", error);
throw error;
}
};
updateIssueComment = async (
workspaceSlug: string,
projectId: string,
@ -452,31 +454,32 @@ export class IssueDetailStore implements IIssueDetailStore {
);
const _issueComments = {
...this.issue_comments,
[issueId]: this.issue_comments[issueId].map((comment: any) =>
...this.issueActivity,
[issueId]: this.issueActivity[issueId].map((comment) =>
comment.id === commentId ? _issueCommentResponse : comment
),
};
runInAction(() => {
this.issue_comments = _issueComments;
this.issueActivity = _issueComments;
});
} catch (error) {
console.warn("error updating the issue comment", error);
throw error;
}
};
removeIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => {
try {
const _issueComments = {
...this.issue_comments,
[issueId]: this.issue_comments[issueId].filter((comment: any) => comment.id != commentId),
...this.issueActivity,
[issueId]: this.issueActivity[issueId]?.filter((comment) => comment.id != commentId),
};
await this.issueCommentService.deleteIssueComment(workspaceSlug, projectId, issueId, commentId);
runInAction(() => {
this.issue_comments = _issueComments;
this.issueActivity = _issueComments;
});
} catch (error) {
console.warn("error removing the issue comment", error);
@ -484,27 +487,28 @@ export class IssueDetailStore implements IIssueDetailStore {
}
};
// comment reaction
// comment reactions
fetchIssueCommentReactions = async (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => {
try {
const _reactions = await this.issueReactionService.listIssueCommentReactions(workspaceSlug, projectId, commentId);
const _issue_comment_reactions = {
...this.issue_comment_reactions,
const _issueCommentReactions = {
...this.issueCommentReactions,
[issueId]: {
...this.issue_comment_reactions[issueId],
...this.issueCommentReactions[issueId],
[commentId]: groupReactionEmojis(_reactions),
},
};
runInAction(() => {
this.issue_comment_reactions = _issue_comment_reactions;
this.issueCommentReactions = _issueCommentReactions;
});
} catch (error) {
console.warn("error removing the issue comment", error);
throw error;
}
};
creationIssueCommentReaction = async (
workspaceSlug: string,
projectId: string,
@ -530,22 +534,23 @@ export class IssueDetailStore implements IIssueDetailStore {
[reaction]: [..._currentReactions?.[reaction], { ..._reaction }],
};
const _issue_comment_reactions = {
...this.issue_comment_reactions,
const _issueCommentReactions = {
...this.issueCommentReactions,
[issueId]: {
...this.issue_comment_reactions[issueId],
...this.issueCommentReactions[issueId],
[commentId]: _currentReactions,
},
};
runInAction(() => {
this.issue_comment_reactions = _issue_comment_reactions;
this.issueCommentReactions = _issueCommentReactions;
});
} catch (error) {
console.warn("error removing the issue comment", error);
throw error;
}
};
removeIssueCommentReaction = async (
workspaceSlug: string,
projectId: string,
@ -565,16 +570,16 @@ export class IssueDetailStore implements IIssueDetailStore {
[reaction]: [..._currentReactions?.[reaction].filter((r: any) => r.actor !== user.id)],
};
const _issue_comment_reactions = {
...this.issue_comment_reactions,
const _issueCommentReactions = {
...this.issueCommentReactions,
[issueId]: {
...this.issue_comment_reactions[issueId],
...this.issueCommentReactions[issueId],
[commentId]: _currentReactions,
},
};
runInAction(() => {
this.issue_comment_reactions = _issue_comment_reactions;
this.issueCommentReactions = _issueCommentReactions;
});
await this.issueReactionService.deleteIssueCommentReaction(workspaceSlug, projectId, commentId, reaction);
@ -585,7 +590,7 @@ export class IssueDetailStore implements IIssueDetailStore {
}
};
// subscription
// subscriptions
fetchIssueSubscription = async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
const _subscription = await this.notificationService.getIssueNotificationSubscriptionStatus(
@ -594,13 +599,13 @@ export class IssueDetailStore implements IIssueDetailStore {
issueId
);
const _issue_subscription = {
...this.issue_subscription,
const _issueSubscription = {
...this.issueSubscription,
[issueId]: _subscription,
};
runInAction(() => {
this.issue_subscription = _issue_subscription;
this.issueSubscription = _issueSubscription;
});
} catch (error) {
console.warn("error fetching the issue subscription", error);
@ -611,13 +616,13 @@ export class IssueDetailStore implements IIssueDetailStore {
try {
await this.notificationService.subscribeToIssueNotifications(workspaceSlug, projectId, issueId);
const _issue_subscription = {
...this.issue_subscription,
const _issueSubscription = {
...this.issueSubscription,
[issueId]: { subscribed: true },
};
runInAction(() => {
this.issue_subscription = _issue_subscription;
this.issueSubscription = _issueSubscription;
});
} catch (error) {
console.warn("error creating the issue subscription", error);
@ -626,13 +631,13 @@ export class IssueDetailStore implements IIssueDetailStore {
};
removeIssueSubscription = async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
const _issue_subscription = {
...this.issue_subscription,
const _issueSubscription = {
...this.issueSubscription,
[issueId]: { subscribed: false },
};
runInAction(() => {
this.issue_subscription = _issue_subscription;
this.issueSubscription = _issueSubscription;
});
await this.notificationService.unsubscribeFromIssueNotifications(workspaceSlug, projectId, issueId);

View File

@ -374,6 +374,9 @@ class UserStore implements IUserStore {
deactivateAccount = async () => {
try {
await this.userService.deactivateAccount();
this.currentUserError = null;
this.currentUser = null;
this.isUserLoggedIn = false;
} catch (error) {
throw error;
}

View File

@ -1,7 +1,7 @@
import { action, computed, observable, makeObservable, runInAction } from "mobx";
import { RootStore } from "../root";
// types
import { IWorkspaceMember, IWorkspaceMemberInvitation, IWorkspaceBulkInviteFormData } from "types";
import { IWorkspaceMember, IWorkspaceMemberInvitation, IWorkspaceBulkInviteFormData, IUserProjectsRole } from "types";
// services
import { WorkspaceService } from "services/workspace.service";
@ -13,7 +13,9 @@ export interface IWorkspaceMemberStore {
// observables
members: { [workspaceSlug: string]: IWorkspaceMember[] }; // workspaceSlug: members[]
memberInvitations: { [workspaceSlug: string]: IWorkspaceMemberInvitation[] };
workspaceUserProjectsRole: { [workspaceSlug: string]: IUserProjectsRole } | undefined;
// actions
fetchWorkspaceUserProjectsRole: (workspaceSlug: string) => Promise<IUserProjectsRole>;
fetchWorkspaceMembers: (workspaceSlug: string) => Promise<void>;
fetchWorkspaceMemberInvitations: (workspaceSlug: string) => Promise<IWorkspaceMemberInvitation[]>;
updateMember: (workspaceSlug: string, memberId: string, data: Partial<IWorkspaceMember>) => Promise<void>;
@ -29,6 +31,7 @@ export interface IWorkspaceMemberStore {
workspaceMembers: IWorkspaceMember[] | null;
workspaceMemberInvitations: IWorkspaceMemberInvitation[] | null;
workspaceMembersWithInvitations: any[] | null;
currentWorkspaceUserProjectsRole: IUserProjectsRole | undefined;
}
export class WorkspaceMemberStore implements IWorkspaceMemberStore {
@ -38,6 +41,7 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore {
// observables
members: { [workspaceSlug: string]: IWorkspaceMember[] } = {};
memberInvitations: { [workspaceSlug: string]: IWorkspaceMemberInvitation[] } = {};
workspaceUserProjectsRole: { [workspaceSlug: string]: IUserProjectsRole } | undefined = undefined;
// services
workspaceService;
// root store
@ -53,6 +57,7 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore {
members: observable.ref,
memberInvitations: observable.ref,
// actions
fetchWorkspaceUserProjectsRole: action,
fetchWorkspaceMembers: action,
fetchWorkspaceMemberInvitations: action,
updateMember: action,
@ -64,6 +69,7 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore {
workspaceMembers: computed,
workspaceMemberInvitations: computed,
workspaceMembersWithInvitations: computed,
currentWorkspaceUserProjectsRole: computed,
});
this.rootStore = _rootStore;
@ -126,6 +132,36 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore {
];
}
/**
* computed value provides the workspace user projects role
*/
get currentWorkspaceUserProjectsRole() {
if (!this.rootStore.workspace.workspaceSlug) return undefined;
return this.workspaceUserProjectsRole?.[this.rootStore.workspace.workspaceSlug];
}
/**
* fetch workspace user projects role using workspace slug
* @param workspaceSlug
*/
fetchWorkspaceUserProjectsRole = async (workspaceSlug: string) => {
try {
const _workspaceUserProjectsRole = { ...this.workspaceUserProjectsRole };
if (!_workspaceUserProjectsRole[workspaceSlug]) _workspaceUserProjectsRole[workspaceSlug] = {};
const response = await this.workspaceService.getWorkspaceUserProjectsRole(workspaceSlug);
_workspaceUserProjectsRole[workspaceSlug] = response;
runInAction(() => {
this.workspaceUserProjectsRole = _workspaceUserProjectsRole;
});
return response;
} catch (error) {
throw error;
}
};
/**
* fetch workspace members using workspace slug
* @param workspaceSlug

15
web/types/issues.d.ts vendored
View File

@ -181,18 +181,20 @@ export interface IIssueLabelTree extends IIssueLabel {
}
export interface IIssueActivity {
access?: "EXTERNAL" | "INTERNAL";
actor: string;
actor_detail: IUserLite;
attachments: any[];
comment: string;
comment?: string;
comment_html?: string;
comment_stripped?: string;
created_at: Date;
created_by: string;
field: string | null;
id: string;
issue: string | null;
issue_comment: string | null;
issue_comment?: string | null;
issue_detail: {
description: any;
description_html: string;
id: string;
name: string;
@ -212,13 +214,6 @@ export interface IIssueActivity {
workspace_detail?: IWorkspaceLite;
}
export interface IIssueComment extends IIssueActivity {
access: "EXTERNAL" | "INTERNAL";
comment_html: string;
comment_json: any;
comment_stripped: string;
}
export interface IIssueLite {
id: string;
name: string;

View File

@ -1,14 +1,17 @@
export interface IssueReaction {
id: string;
created_at: Date;
updated_at: Date;
reaction: string;
created_by: string;
updated_by: string;
project: string;
workspace: string;
import { IUserLite } from "./users";
export interface IIssueReaction {
actor: string;
actor_detail: IUserLite;
created_at: Date;
created_by: string;
id: string;
issue: string;
project: string;
reaction: string;
updated_at: Date;
updated_by: string;
workspace: string;
}
export interface IssueReactionForm {

View File

@ -162,6 +162,10 @@ export interface IUserProfileProjectSegregation {
};
}
export interface IUserProjectsRole {
[project_id: string]: boolean;
}
// export interface ICurrentUser {
// id: readonly string;
// avatar: string;

View File

@ -2797,7 +2797,7 @@
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^18.2.39", "@types/react@^18.2.42":
"@types/react@*", "@types/react@18.2.42", "@types/react@^18.2.39", "@types/react@^18.2.42":
version "18.2.42"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.42.tgz#6f6b11a904f6d96dda3c2920328a97011a00aba7"
integrity sha512-c1zEr96MjakLYus/wPnuWDo1/zErfdU9rNsIGmE+NV71nx88FG9Ttgo5dqorXTu/LImX2f63WBP986gJkMPNbA==