forked from github/plane
chore: workspace roles (#3024)
* chore: workspace project roles for the current user * dev: workspace and project member * chore: store implementation for workspace user projects role * view changes for the project roles * connect workspace member's project roles to assigned --------- Co-authored-by: gurusainath <gurusainath007@gmail.com> Co-authored-by: rahulramesha <rahulramesham@gmail.com> Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com>
This commit is contained in:
parent
2a5ff3397f
commit
74ca187659
@ -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(),
|
||||
|
@ -11,6 +11,7 @@ from .project import (
|
||||
ProjectFavoritesViewSet,
|
||||
ProjectPublicCoverImagesEndpoint,
|
||||
ProjectDeployBoardViewSet,
|
||||
UserProjectRolesEndpoint,
|
||||
)
|
||||
from .user import (
|
||||
UserEndpoint,
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -68,6 +68,7 @@ export interface IBaseKanBanLayout {
|
||||
issueWithIds: any
|
||||
) => Promise<IIssue | undefined>;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
|
||||
}
|
||||
|
||||
type KanbanDragState = {
|
||||
@ -88,6 +89,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
currentStore,
|
||||
handleDragDrop,
|
||||
addIssuesToView,
|
||||
canEditPropertiesBasedOnProject,
|
||||
} = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
@ -105,7 +107,6 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { currentProjectRole } = userStore;
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
const issues = issueStore?.getIssues || {};
|
||||
const issueIds = issueStore?.getIssuesIds || [];
|
||||
@ -130,6 +131,15 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
const [dragState, setDragState] = useState<KanbanDragState>({});
|
||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
const canEditProperties = (projectId: string | undefined) => {
|
||||
const isEditingAllowedBasedOnProject =
|
||||
canEditPropertiesBasedOnProject && projectId ? canEditPropertiesBasedOnProject(projectId) : isEditingAllowed;
|
||||
|
||||
return enableInlineEditing && isEditingAllowedBasedOnProject;
|
||||
};
|
||||
|
||||
const onDragStart = (dragStart: DragStart) => {
|
||||
setDragState({
|
||||
draggedIssueId: dragStart.draggableId.split("__")[0],
|
||||
@ -285,7 +295,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
quickAddCallback={issueStore?.quickAddIssue}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
||||
isReadOnly={!enableInlineEditing || !isEditingAllowed}
|
||||
canEditProperties={canEditProperties}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
@ -327,14 +337,10 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={true}
|
||||
enableQuickIssueCreate={enableQuickAdd}
|
||||
isReadOnly={!enableInlineEditing || !isEditingAllowed}
|
||||
currentStore={currentStore}
|
||||
quickAddCallback={issueStore?.quickAddIssue}
|
||||
addIssuesToView={(issues) => {
|
||||
console.log("kanban existingIds", issues);
|
||||
|
||||
return Promise.resolve({} as IIssue);
|
||||
}}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
</DragDropContext>
|
||||
|
@ -20,7 +20,7 @@ interface IssueBlockProps {
|
||||
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
|
||||
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
isReadOnly: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
}
|
||||
|
||||
interface IssueDetailsBlockProps {
|
||||
@ -110,13 +110,15 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
handleIssues,
|
||||
quickActions,
|
||||
displayProperties,
|
||||
isReadOnly,
|
||||
canEditProperties,
|
||||
} = props;
|
||||
|
||||
let draggableId = issue.id;
|
||||
if (columnId) draggableId = `${draggableId}__${columnId}`;
|
||||
if (sub_group_id) draggableId = `${draggableId}__${sub_group_id}`;
|
||||
|
||||
const canEditIssueProperties = canEditProperties(issue.project);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Draggable draggableId={draggableId} index={index}>
|
||||
@ -143,7 +145,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
isReadOnly={isReadOnly}
|
||||
isReadOnly={!canEditIssueProperties}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -19,7 +19,7 @@ interface IssueBlocksListProps {
|
||||
customActionButton?: React.ReactElement
|
||||
) => React.ReactNode;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
isReadOnly: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
}
|
||||
|
||||
export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) => {
|
||||
@ -33,7 +33,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) =>
|
||||
handleIssues,
|
||||
quickActions,
|
||||
displayProperties,
|
||||
isReadOnly,
|
||||
canEditProperties,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
@ -57,7 +57,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) =>
|
||||
columnId={columnId}
|
||||
sub_group_id={sub_group_id}
|
||||
isDragDisabled={isDragDisabled}
|
||||
isReadOnly={isReadOnly}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@ -48,7 +48,7 @@ export interface IGroupByKanBan {
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
isReadOnly: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
}
|
||||
|
||||
const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
||||
@ -75,9 +75,9 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
||||
quickAddCallback,
|
||||
viewId,
|
||||
disableIssueCreation,
|
||||
isReadOnly,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
canEditProperties,
|
||||
} = props;
|
||||
|
||||
const verticalAlignPosition = (_list: any) =>
|
||||
@ -133,7 +133,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
isReadOnly={isReadOnly}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
) : (
|
||||
isDragDisabled && (
|
||||
@ -216,7 +216,7 @@ export interface IKanBan {
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
isReadOnly: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
}
|
||||
|
||||
export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
@ -244,9 +244,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
quickAddCallback,
|
||||
viewId,
|
||||
disableIssueCreation,
|
||||
isReadOnly,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
canEditProperties,
|
||||
} = props;
|
||||
|
||||
const { issueKanBanView: issueKanBanViewStore } = useMobxStore();
|
||||
@ -276,9 +276,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
isReadOnly={isReadOnly}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -305,9 +305,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
isReadOnly={isReadOnly}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -334,9 +334,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
isReadOnly={isReadOnly}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -363,9 +363,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
isReadOnly={isReadOnly}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -392,9 +392,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
isReadOnly={isReadOnly}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -421,9 +421,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
isReadOnly={isReadOnly}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -450,9 +450,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
isReadOnly={isReadOnly}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -10,6 +10,7 @@ import { IIssue } from "types";
|
||||
import { EIssueActions } from "../../types";
|
||||
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
|
||||
export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
@ -18,6 +19,7 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
|
||||
const {
|
||||
workspaceProfileIssues: profileIssuesStore,
|
||||
workspaceProfileIssuesFilter: profileIssueFiltersStore,
|
||||
workspaceMember: { currentWorkspaceUserProjectsRole },
|
||||
issueKanBanView: issueKanBanViewStore,
|
||||
} = useMobxStore();
|
||||
|
||||
@ -34,6 +36,12 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
|
||||
},
|
||||
};
|
||||
|
||||
const canEditPropertiesBasedOnProject = (projectId: string) => {
|
||||
const currentProjectRole = currentWorkspaceUserProjectsRole && currentWorkspaceUserProjectsRole[projectId];
|
||||
|
||||
return !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseKanBanRoot
|
||||
issueActions={issueActions}
|
||||
@ -43,6 +51,7 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
|
||||
showLoader={true}
|
||||
QuickActions={ProjectIssueQuickActions}
|
||||
currentStore={EProjectStore.PROFILE}
|
||||
canEditPropertiesBasedOnProject={canEditPropertiesBasedOnProject}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -95,7 +95,7 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
enableQuickIssueCreate: boolean;
|
||||
isReadOnly: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
@ -127,7 +127,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||
isDragStarted,
|
||||
disableIssueCreation,
|
||||
enableQuickIssueCreate,
|
||||
isReadOnly,
|
||||
canEditProperties,
|
||||
addIssuesToView,
|
||||
quickAddCallback,
|
||||
} = props;
|
||||
@ -186,7 +186,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||
projects={projects}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
isReadOnly={isReadOnly}
|
||||
canEditProperties={canEditProperties}
|
||||
addIssuesToView={addIssuesToView}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
@ -232,7 +232,7 @@ export interface IKanBanSwimLanes {
|
||||
data: IIssue,
|
||||
viewId?: string
|
||||
) => Promise<IIssue | undefined>;
|
||||
isReadOnly: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
}
|
||||
|
||||
export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
@ -257,7 +257,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
isDragStarted,
|
||||
disableIssueCreation,
|
||||
enableQuickIssueCreate,
|
||||
isReadOnly,
|
||||
canEditProperties,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
quickAddCallback,
|
||||
@ -402,7 +402,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isReadOnly={isReadOnly}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
@ -431,7 +431,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isReadOnly={isReadOnly}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
@ -460,7 +460,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isReadOnly={isReadOnly}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
@ -489,7 +489,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isReadOnly={isReadOnly}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
@ -518,7 +518,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isReadOnly={isReadOnly}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
@ -547,7 +547,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isReadOnly={isReadOnly}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
@ -576,7 +576,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isReadOnly={isReadOnly}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
@ -605,7 +605,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isReadOnly={isReadOnly}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
|
@ -58,6 +58,7 @@ interface IBaseListRoot {
|
||||
viewId?: string;
|
||||
currentStore: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
|
||||
}
|
||||
|
||||
export const BaseListRoot = observer((props: IBaseListRoot) => {
|
||||
@ -70,6 +71,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
||||
viewId,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
canEditPropertiesBasedOnProject,
|
||||
} = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
@ -90,6 +92,12 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
||||
const issues = issueStore?.getIssues;
|
||||
|
||||
const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issueStore?.viewFlags || {};
|
||||
const canEditProperties = (projectId: string | undefined) => {
|
||||
const isEditingAllowedBasedOnProject =
|
||||
canEditPropertiesBasedOnProject && projectId ? canEditPropertiesBasedOnProject(projectId) : isEditingAllowed;
|
||||
|
||||
return enableInlineEditing && isEditingAllowedBasedOnProject;
|
||||
};
|
||||
|
||||
const displayFilters = issueFilterStore?.issueFilters?.displayFilters;
|
||||
const group_by = displayFilters?.group_by || null;
|
||||
@ -147,7 +155,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
||||
viewId={viewId}
|
||||
quickAddCallback={issueStore?.quickAddIssue}
|
||||
enableIssueQuickAdd={!!enableQuickAdd}
|
||||
isReadonly={!enableInlineEditing || !isEditingAllowed}
|
||||
canEditProperties={canEditProperties}
|
||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
|
@ -14,11 +14,11 @@ interface IssueBlockProps {
|
||||
handleIssues: (issue: IIssue, action: EIssueActions) => void;
|
||||
quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
isReadonly?: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
}
|
||||
|
||||
export const IssueBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
const { columnId, issue, handleIssues, quickActions, displayProperties, isReadonly } = props;
|
||||
const { columnId, issue, handleIssues, quickActions, displayProperties, canEditProperties } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const updateIssue = (group_by: string | null, issueToUpdate: IIssue) => {
|
||||
@ -34,6 +34,8 @@ export const IssueBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const canEditIssueProperties = canEditProperties(issue.project);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="text-sm p-3 relative bg-custom-background-100 flex items-center gap-3">
|
||||
@ -61,7 +63,7 @@ export const IssueBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
<ListProperties
|
||||
columnId={columnId}
|
||||
issue={issue}
|
||||
isReadonly={isReadonly}
|
||||
isReadonly={!canEditIssueProperties}
|
||||
handleIssues={updateIssue}
|
||||
displayProperties={displayProperties}
|
||||
/>
|
||||
|
@ -10,14 +10,14 @@ interface Props {
|
||||
columnId: string;
|
||||
issueIds: IGroupedIssues | TUnGroupedIssues | any;
|
||||
issues: IIssueResponse;
|
||||
isReadonly?: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
handleIssues: (issue: IIssue, action: EIssueActions) => void;
|
||||
quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
}
|
||||
|
||||
export const IssueBlocksList: FC<Props> = (props) => {
|
||||
const { columnId, issueIds, issues, handleIssues, quickActions, displayProperties, isReadonly } = props;
|
||||
const { columnId, issueIds, issues, handleIssues, quickActions, displayProperties, canEditProperties } = props;
|
||||
|
||||
return (
|
||||
<div className="w-full h-full relative divide-y-[0.5px] divide-custom-border-200">
|
||||
@ -31,7 +31,7 @@ export const IssueBlocksList: FC<Props> = (props) => {
|
||||
issue={issues[issueId]}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
isReadonly={isReadonly}
|
||||
canEditProperties={canEditProperties}
|
||||
displayProperties={displayProperties}
|
||||
/>
|
||||
)
|
||||
|
@ -23,7 +23,7 @@ export interface IGroupByList {
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
enableIssueQuickAdd: boolean;
|
||||
showEmptyGroup?: boolean;
|
||||
isReadonly: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
@ -50,7 +50,7 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
|
||||
displayProperties,
|
||||
enableIssueQuickAdd,
|
||||
showEmptyGroup,
|
||||
isReadonly,
|
||||
canEditProperties,
|
||||
quickAddCallback,
|
||||
viewId,
|
||||
disableIssueCreation,
|
||||
@ -105,7 +105,7 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
isReadonly={isReadonly}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -134,7 +134,7 @@ export interface IList {
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
showEmptyGroup: boolean;
|
||||
enableIssueQuickAdd: boolean;
|
||||
isReadonly: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
states: IState[] | null;
|
||||
labels: IIssueLabel[] | null;
|
||||
members: IUserLite[] | null;
|
||||
@ -165,7 +165,7 @@ export const List: React.FC<IList> = (props) => {
|
||||
displayProperties,
|
||||
showEmptyGroup,
|
||||
enableIssueQuickAdd,
|
||||
isReadonly,
|
||||
canEditProperties,
|
||||
disableIssueCreation,
|
||||
states,
|
||||
stateGroups,
|
||||
@ -193,7 +193,7 @@ export const List: React.FC<IList> = (props) => {
|
||||
displayProperties={displayProperties}
|
||||
enableIssueQuickAdd={enableIssueQuickAdd}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
isReadonly={isReadonly}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
@ -215,7 +215,7 @@ export const List: React.FC<IList> = (props) => {
|
||||
displayProperties={displayProperties}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
enableIssueQuickAdd={enableIssueQuickAdd}
|
||||
isReadonly={isReadonly}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
@ -237,7 +237,7 @@ export const List: React.FC<IList> = (props) => {
|
||||
displayProperties={displayProperties}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
enableIssueQuickAdd={enableIssueQuickAdd}
|
||||
isReadonly={isReadonly}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
@ -259,7 +259,7 @@ export const List: React.FC<IList> = (props) => {
|
||||
displayProperties={displayProperties}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
enableIssueQuickAdd={enableIssueQuickAdd}
|
||||
isReadonly={isReadonly}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
@ -281,7 +281,7 @@ export const List: React.FC<IList> = (props) => {
|
||||
displayProperties={displayProperties}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
enableIssueQuickAdd={enableIssueQuickAdd}
|
||||
isReadonly={isReadonly}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
@ -303,7 +303,7 @@ export const List: React.FC<IList> = (props) => {
|
||||
displayProperties={displayProperties}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
enableIssueQuickAdd={enableIssueQuickAdd}
|
||||
isReadonly={isReadonly}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
@ -325,7 +325,7 @@ export const List: React.FC<IList> = (props) => {
|
||||
displayProperties={displayProperties}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
enableIssueQuickAdd={enableIssueQuickAdd}
|
||||
isReadonly={isReadonly}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
@ -347,7 +347,7 @@ export const List: React.FC<IList> = (props) => {
|
||||
displayProperties={displayProperties}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
enableIssueQuickAdd={enableIssueQuickAdd}
|
||||
isReadonly={isReadonly}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
|
@ -12,14 +12,18 @@ import { EIssueActions } from "../../types";
|
||||
import { BaseListRoot } from "../base-list-root";
|
||||
import { IProjectStore } from "store/project";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
|
||||
export const ProfileIssuesListLayout: FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, userId } = router.query as { workspaceSlug: string; userId: string };
|
||||
|
||||
// store
|
||||
const { workspaceProfileIssuesFilter: profileIssueFiltersStore, workspaceProfileIssues: profileIssuesStore } =
|
||||
useMobxStore();
|
||||
const {
|
||||
workspaceProfileIssuesFilter: profileIssueFiltersStore,
|
||||
workspaceProfileIssues: profileIssuesStore,
|
||||
workspaceMember: { currentWorkspaceUserProjectsRole },
|
||||
} = useMobxStore();
|
||||
|
||||
const issueActions = {
|
||||
[EIssueActions.UPDATE]: async (group_by: string | null, issue: IIssue) => {
|
||||
@ -36,6 +40,17 @@ export const ProfileIssuesListLayout: FC = observer(() => {
|
||||
|
||||
const getProjects = (projectStore: IProjectStore) => projectStore.workspaceProjects;
|
||||
|
||||
const canEditPropertiesBasedOnProject = (projectId: string) => {
|
||||
const currentProjectRole = currentWorkspaceUserProjectsRole && currentWorkspaceUserProjectsRole[projectId];
|
||||
|
||||
console.log(
|
||||
projectId,
|
||||
currentWorkspaceUserProjectsRole,
|
||||
!!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER
|
||||
);
|
||||
return !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseListRoot
|
||||
issueFilterStore={profileIssueFiltersStore}
|
||||
@ -44,6 +59,7 @@ export const ProfileIssuesListLayout: FC = observer(() => {
|
||||
issueActions={issueActions}
|
||||
getProjects={getProjects}
|
||||
currentStore={EProjectStore.PROFILE}
|
||||
canEditPropertiesBasedOnProject={canEditPropertiesBasedOnProject}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -16,6 +16,7 @@ import { IIssueUnGroupedStructure } from "store/issue";
|
||||
import { EIssueActions } from "../types";
|
||||
|
||||
import { EFilterType, TUnGroupedIssues } from "store/issues/types";
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
|
||||
type Props = {
|
||||
type?: TStaticViewTypes | null;
|
||||
@ -35,6 +36,7 @@ export const AllIssueLayoutRoot: React.FC<Props> = observer((props) => {
|
||||
globalViews: { fetchAllGlobalViews },
|
||||
workspaceGlobalIssues: { loader, getIssues, getIssuesIds, fetchIssues, updateIssue, removeIssue },
|
||||
workspaceGlobalIssuesFilter: { currentView, issueFilters, fetchFilters, updateFilters, setCurrentView },
|
||||
workspaceMember: { currentWorkspaceUserProjectsRole },
|
||||
} = useMobxStore();
|
||||
|
||||
useSWR(workspaceSlug ? `WORKSPACE_GLOBAL_VIEWS${workspaceSlug}` : null, async () => {
|
||||
@ -55,7 +57,13 @@ export const AllIssueLayoutRoot: React.FC<Props> = observer((props) => {
|
||||
}
|
||||
);
|
||||
|
||||
const isEditingAllowed = false;
|
||||
const canEditProperties = (projectId: string | undefined) => {
|
||||
if (!projectId) return false;
|
||||
|
||||
const currentProjectRole = currentWorkspaceUserProjectsRole && currentWorkspaceUserProjectsRole[projectId];
|
||||
|
||||
return !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
};
|
||||
|
||||
const issuesResponse = getIssues;
|
||||
const issueIds = (getIssuesIds ?? []) as TUnGroupedIssues;
|
||||
@ -123,7 +131,7 @@ export const AllIssueLayoutRoot: React.FC<Props> = observer((props) => {
|
||||
members={workspaceMembers?.map((m) => m.member)}
|
||||
labels={workspaceLabels || undefined}
|
||||
handleIssues={handleIssues}
|
||||
disableUserActions={isEditingAllowed}
|
||||
canEditProperties={canEditProperties}
|
||||
viewId={currentIssueView}
|
||||
/>
|
||||
</div>
|
||||
|
@ -34,10 +34,11 @@ interface IBaseSpreadsheetRoot {
|
||||
[EIssueActions.UPDATE]?: (issue: IIssue) => void;
|
||||
[EIssueActions.REMOVE]?: (issue: IIssue) => void;
|
||||
};
|
||||
canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
|
||||
}
|
||||
|
||||
export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
||||
const { issueFiltersStore, issueStore, viewId, QuickActions, issueActions } = props;
|
||||
const { issueFiltersStore, issueStore, viewId, QuickActions, issueActions, canEditPropertiesBasedOnProject } = props;
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
@ -49,9 +50,18 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
||||
user: userStore,
|
||||
} = useMobxStore();
|
||||
|
||||
const { enableInlineEditing, enableQuickAdd } = issueStore?.viewFlags || {};
|
||||
|
||||
const { currentProjectRole } = userStore;
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
const canEditProperties = (projectId: string | undefined) => {
|
||||
const isEditingAllowedBasedOnProject =
|
||||
canEditPropertiesBasedOnProject && projectId ? canEditPropertiesBasedOnProject(projectId) : isEditingAllowed;
|
||||
|
||||
return enableInlineEditing && isEditingAllowedBasedOnProject;
|
||||
};
|
||||
|
||||
const issuesResponse = issueStore.getIssues;
|
||||
const issueIds = (issueStore.getIssuesIds ?? []) as TUnGroupedIssues;
|
||||
|
||||
@ -106,10 +116,10 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
||||
labels={projectLabels || undefined}
|
||||
states={projectId ? projectStateStore.states?.[projectId.toString()] : undefined}
|
||||
handleIssues={handleIssues}
|
||||
disableUserActions={!isEditingAllowed}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={issueStore.quickAddIssue}
|
||||
viewId={viewId}
|
||||
enableQuickCreateIssue
|
||||
enableQuickCreateIssue={enableQuickAdd}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -8,7 +8,7 @@ import { IIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueLabe
|
||||
type Props = {
|
||||
displayFilters: IIssueDisplayFilterOptions;
|
||||
displayProperties: IIssueDisplayProperties;
|
||||
disableUserActions: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
expandedIssues: string[];
|
||||
handleDisplayFilterUpdate: (data: Partial<IIssueDisplayFilterOptions>) => void;
|
||||
handleUpdateIssue: (issue: IIssue, data: Partial<IIssue>) => void;
|
||||
@ -20,7 +20,7 @@ type Props = {
|
||||
|
||||
export const SpreadsheetColumnsList: React.FC<Props> = observer((props) => {
|
||||
const {
|
||||
disableUserActions,
|
||||
canEditProperties,
|
||||
displayFilters,
|
||||
displayProperties,
|
||||
expandedIssues,
|
||||
@ -43,7 +43,7 @@ export const SpreadsheetColumnsList: React.FC<Props> = observer((props) => {
|
||||
{displayProperties.state && (
|
||||
<SpreadsheetColumn
|
||||
displayFilters={displayFilters}
|
||||
disableUserActions={disableUserActions}
|
||||
canEditProperties={canEditProperties}
|
||||
expandedIssues={expandedIssues}
|
||||
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
|
||||
handleUpdateIssue={handleUpdateIssue}
|
||||
@ -55,7 +55,7 @@ export const SpreadsheetColumnsList: React.FC<Props> = observer((props) => {
|
||||
{displayProperties.priority && (
|
||||
<SpreadsheetColumn
|
||||
displayFilters={displayFilters}
|
||||
disableUserActions={disableUserActions}
|
||||
canEditProperties={canEditProperties}
|
||||
expandedIssues={expandedIssues}
|
||||
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
|
||||
handleUpdateIssue={handleUpdateIssue}
|
||||
@ -66,7 +66,7 @@ export const SpreadsheetColumnsList: React.FC<Props> = observer((props) => {
|
||||
{displayProperties.assignee && (
|
||||
<SpreadsheetColumn
|
||||
displayFilters={displayFilters}
|
||||
disableUserActions={disableUserActions}
|
||||
canEditProperties={canEditProperties}
|
||||
expandedIssues={expandedIssues}
|
||||
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
|
||||
handleUpdateIssue={handleUpdateIssue}
|
||||
@ -78,7 +78,7 @@ export const SpreadsheetColumnsList: React.FC<Props> = observer((props) => {
|
||||
{displayProperties.labels && (
|
||||
<SpreadsheetColumn
|
||||
displayFilters={displayFilters}
|
||||
disableUserActions={disableUserActions}
|
||||
canEditProperties={canEditProperties}
|
||||
expandedIssues={expandedIssues}
|
||||
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
|
||||
handleUpdateIssue={handleUpdateIssue}
|
||||
@ -90,7 +90,7 @@ export const SpreadsheetColumnsList: React.FC<Props> = observer((props) => {
|
||||
{displayProperties.start_date && (
|
||||
<SpreadsheetColumn
|
||||
displayFilters={displayFilters}
|
||||
disableUserActions={disableUserActions}
|
||||
canEditProperties={canEditProperties}
|
||||
expandedIssues={expandedIssues}
|
||||
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
|
||||
handleUpdateIssue={handleUpdateIssue}
|
||||
@ -101,7 +101,7 @@ export const SpreadsheetColumnsList: React.FC<Props> = observer((props) => {
|
||||
{displayProperties.due_date && (
|
||||
<SpreadsheetColumn
|
||||
displayFilters={displayFilters}
|
||||
disableUserActions={disableUserActions}
|
||||
canEditProperties={canEditProperties}
|
||||
expandedIssues={expandedIssues}
|
||||
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
|
||||
handleUpdateIssue={handleUpdateIssue}
|
||||
@ -112,7 +112,7 @@ export const SpreadsheetColumnsList: React.FC<Props> = observer((props) => {
|
||||
{displayProperties.estimate && isEstimateEnabled && (
|
||||
<SpreadsheetColumn
|
||||
displayFilters={displayFilters}
|
||||
disableUserActions={disableUserActions}
|
||||
canEditProperties={canEditProperties}
|
||||
expandedIssues={expandedIssues}
|
||||
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
|
||||
handleUpdateIssue={handleUpdateIssue}
|
||||
@ -123,7 +123,7 @@ export const SpreadsheetColumnsList: React.FC<Props> = observer((props) => {
|
||||
{displayProperties.created_on && (
|
||||
<SpreadsheetColumn
|
||||
displayFilters={displayFilters}
|
||||
disableUserActions={disableUserActions}
|
||||
canEditProperties={canEditProperties}
|
||||
expandedIssues={expandedIssues}
|
||||
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
|
||||
handleUpdateIssue={handleUpdateIssue}
|
||||
@ -134,7 +134,7 @@ export const SpreadsheetColumnsList: React.FC<Props> = observer((props) => {
|
||||
{displayProperties.updated_on && (
|
||||
<SpreadsheetColumn
|
||||
displayFilters={displayFilters}
|
||||
disableUserActions={disableUserActions}
|
||||
canEditProperties={canEditProperties}
|
||||
expandedIssues={expandedIssues}
|
||||
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
|
||||
handleUpdateIssue={handleUpdateIssue}
|
||||
@ -145,7 +145,7 @@ export const SpreadsheetColumnsList: React.FC<Props> = observer((props) => {
|
||||
{displayProperties.link && (
|
||||
<SpreadsheetColumn
|
||||
displayFilters={displayFilters}
|
||||
disableUserActions={disableUserActions}
|
||||
canEditProperties={canEditProperties}
|
||||
expandedIssues={expandedIssues}
|
||||
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
|
||||
handleUpdateIssue={handleUpdateIssue}
|
||||
@ -156,7 +156,7 @@ export const SpreadsheetColumnsList: React.FC<Props> = observer((props) => {
|
||||
{displayProperties.attachment_count && (
|
||||
<SpreadsheetColumn
|
||||
displayFilters={displayFilters}
|
||||
disableUserActions={disableUserActions}
|
||||
canEditProperties={canEditProperties}
|
||||
expandedIssues={expandedIssues}
|
||||
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
|
||||
handleUpdateIssue={handleUpdateIssue}
|
||||
@ -167,7 +167,7 @@ export const SpreadsheetColumnsList: React.FC<Props> = observer((props) => {
|
||||
{displayProperties.sub_issue_count && (
|
||||
<SpreadsheetColumn
|
||||
displayFilters={displayFilters}
|
||||
disableUserActions={disableUserActions}
|
||||
canEditProperties={canEditProperties}
|
||||
expandedIssues={expandedIssues}
|
||||
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
|
||||
handleUpdateIssue={handleUpdateIssue}
|
||||
|
@ -14,7 +14,7 @@ type Props = {
|
||||
handleToggleExpand: (issueId: string) => void;
|
||||
properties: IIssueDisplayProperties;
|
||||
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
disableUserActions: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
nestingLevel: number;
|
||||
};
|
||||
|
||||
@ -24,7 +24,7 @@ export const IssueColumn: React.FC<Props> = ({
|
||||
handleToggleExpand,
|
||||
properties,
|
||||
quickActions,
|
||||
disableUserActions,
|
||||
canEditProperties,
|
||||
nestingLevel,
|
||||
}) => {
|
||||
// router
|
||||
@ -76,7 +76,7 @@ export const IssueColumn: React.FC<Props> = ({
|
||||
{issue.project_detail?.identifier}-{issue.sequence_id}
|
||||
</span>
|
||||
|
||||
{!disableUserActions && (
|
||||
{canEditProperties(issue.project) && (
|
||||
<div className={`absolute top-0 left-2.5 hidden group-hover:block ${isMenuActive ? "!block" : ""}`}>
|
||||
{quickActions(issue, customActionButton)}
|
||||
</div>
|
||||
|
@ -12,8 +12,8 @@ type Props = {
|
||||
expandedIssues: string[];
|
||||
setExpandedIssues: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
properties: IIssueDisplayProperties;
|
||||
quickActions: (issue: IIssue,customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
disableUserActions: boolean;
|
||||
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
nestingLevel?: number;
|
||||
};
|
||||
|
||||
@ -23,7 +23,7 @@ export const SpreadsheetIssuesColumn: React.FC<Props> = ({
|
||||
setExpandedIssues,
|
||||
properties,
|
||||
quickActions,
|
||||
disableUserActions,
|
||||
canEditProperties,
|
||||
nestingLevel = 0,
|
||||
}) => {
|
||||
const handleToggleExpand = (issueId: string) => {
|
||||
@ -49,7 +49,7 @@ export const SpreadsheetIssuesColumn: React.FC<Props> = ({
|
||||
expanded={isExpanded}
|
||||
handleToggleExpand={handleToggleExpand}
|
||||
properties={properties}
|
||||
disableUserActions={disableUserActions}
|
||||
canEditProperties={canEditProperties}
|
||||
nestingLevel={nestingLevel}
|
||||
quickActions={quickActions}
|
||||
/>
|
||||
@ -66,7 +66,7 @@ export const SpreadsheetIssuesColumn: React.FC<Props> = ({
|
||||
setExpandedIssues={setExpandedIssues}
|
||||
properties={properties}
|
||||
quickActions={quickActions}
|
||||
disableUserActions={disableUserActions}
|
||||
canEditProperties={canEditProperties}
|
||||
nestingLevel={nestingLevel + 1}
|
||||
/>
|
||||
))}
|
||||
|
@ -32,7 +32,7 @@ import { IIssue, IIssueDisplayFilterOptions, IIssueLabel, IState, IUserLite, TIs
|
||||
import { SPREADSHEET_PROPERTY_DETAILS } from "constants/spreadsheet";
|
||||
|
||||
type Props = {
|
||||
disableUserActions: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
displayFilters: IIssueDisplayFilterOptions;
|
||||
expandedIssues: string[];
|
||||
handleDisplayFilterUpdate: (data: Partial<IIssueDisplayFilterOptions>) => void;
|
||||
@ -46,7 +46,7 @@ type Props = {
|
||||
|
||||
export const SpreadsheetColumn: React.FC<Props> = (props) => {
|
||||
const {
|
||||
disableUserActions,
|
||||
canEditProperties,
|
||||
displayFilters,
|
||||
expandedIssues,
|
||||
handleDisplayFilterUpdate,
|
||||
@ -160,78 +160,81 @@ export const SpreadsheetColumn: React.FC<Props> = (props) => {
|
||||
</div>
|
||||
|
||||
<div className="h-full min-w-[8rem] w-full">
|
||||
{issues?.map((issue) => (
|
||||
<div
|
||||
key={`${property}-${issue.id}`}
|
||||
className={`h-11 border-b-[0.5px] border-custom-border-200 ${
|
||||
disableUserActions ? "" : "cursor-pointer hover:bg-custom-background-80"
|
||||
}`}
|
||||
>
|
||||
{property === "state" ? (
|
||||
<SpreadsheetStateColumn
|
||||
disabled={disableUserActions}
|
||||
expandedIssues={expandedIssues}
|
||||
issue={issue}
|
||||
onChange={(data: Partial<IIssue>) => handleUpdateIssue(issue, data)}
|
||||
states={states}
|
||||
/>
|
||||
) : property === "priority" ? (
|
||||
<SpreadsheetPriorityColumn
|
||||
disabled={disableUserActions}
|
||||
expandedIssues={expandedIssues}
|
||||
issue={issue}
|
||||
onChange={(data: Partial<IIssue>) => handleUpdateIssue(issue, data)}
|
||||
/>
|
||||
) : property === "estimate" ? (
|
||||
<SpreadsheetEstimateColumn
|
||||
disabled={disableUserActions}
|
||||
expandedIssues={expandedIssues}
|
||||
issue={issue}
|
||||
onChange={(data: Partial<IIssue>) => handleUpdateIssue(issue, data)}
|
||||
/>
|
||||
) : property === "assignee" ? (
|
||||
<SpreadsheetAssigneeColumn
|
||||
disabled={disableUserActions}
|
||||
expandedIssues={expandedIssues}
|
||||
issue={issue}
|
||||
members={members}
|
||||
onChange={(data: Partial<IIssue>) => handleUpdateIssue(issue, data)}
|
||||
/>
|
||||
) : property === "labels" ? (
|
||||
<SpreadsheetLabelColumn
|
||||
disabled={disableUserActions}
|
||||
expandedIssues={expandedIssues}
|
||||
issue={issue}
|
||||
labels={labels}
|
||||
onChange={(data: Partial<IIssue>) => handleUpdateIssue(issue, data)}
|
||||
/>
|
||||
) : property === "start_date" ? (
|
||||
<SpreadsheetStartDateColumn
|
||||
disabled={disableUserActions}
|
||||
expandedIssues={expandedIssues}
|
||||
issue={issue}
|
||||
onChange={(data: Partial<IIssue>) => handleUpdateIssue(issue, data)}
|
||||
/>
|
||||
) : property === "due_date" ? (
|
||||
<SpreadsheetDueDateColumn
|
||||
disabled={disableUserActions}
|
||||
expandedIssues={expandedIssues}
|
||||
issue={issue}
|
||||
onChange={(data: Partial<IIssue>) => handleUpdateIssue(issue, data)}
|
||||
/>
|
||||
) : property === "created_on" ? (
|
||||
<SpreadsheetCreatedOnColumn expandedIssues={expandedIssues} issue={issue} />
|
||||
) : property === "updated_on" ? (
|
||||
<SpreadsheetUpdatedOnColumn expandedIssues={expandedIssues} issue={issue} />
|
||||
) : property === "link" ? (
|
||||
<SpreadsheetLinkColumn expandedIssues={expandedIssues} issue={issue} />
|
||||
) : property === "attachment_count" ? (
|
||||
<SpreadsheetAttachmentColumn expandedIssues={expandedIssues} issue={issue} />
|
||||
) : property === "sub_issue_count" ? (
|
||||
<SpreadsheetSubIssueColumn expandedIssues={expandedIssues} issue={issue} />
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
{issues?.map((issue) => {
|
||||
const disableUserActions = !canEditProperties(issue.project);
|
||||
return (
|
||||
<div
|
||||
key={`${property}-${issue.id}`}
|
||||
className={`h-11 border-b-[0.5px] border-custom-border-200 ${
|
||||
disableUserActions ? "" : "cursor-pointer hover:bg-custom-background-80"
|
||||
}`}
|
||||
>
|
||||
{property === "state" ? (
|
||||
<SpreadsheetStateColumn
|
||||
disabled={disableUserActions}
|
||||
expandedIssues={expandedIssues}
|
||||
issue={issue}
|
||||
onChange={(data: Partial<IIssue>) => handleUpdateIssue(issue, data)}
|
||||
states={states}
|
||||
/>
|
||||
) : property === "priority" ? (
|
||||
<SpreadsheetPriorityColumn
|
||||
disabled={disableUserActions}
|
||||
expandedIssues={expandedIssues}
|
||||
issue={issue}
|
||||
onChange={(data: Partial<IIssue>) => handleUpdateIssue(issue, data)}
|
||||
/>
|
||||
) : property === "estimate" ? (
|
||||
<SpreadsheetEstimateColumn
|
||||
disabled={disableUserActions}
|
||||
expandedIssues={expandedIssues}
|
||||
issue={issue}
|
||||
onChange={(data: Partial<IIssue>) => handleUpdateIssue(issue, data)}
|
||||
/>
|
||||
) : property === "assignee" ? (
|
||||
<SpreadsheetAssigneeColumn
|
||||
disabled={disableUserActions}
|
||||
expandedIssues={expandedIssues}
|
||||
issue={issue}
|
||||
members={members}
|
||||
onChange={(data: Partial<IIssue>) => handleUpdateIssue(issue, data)}
|
||||
/>
|
||||
) : property === "labels" ? (
|
||||
<SpreadsheetLabelColumn
|
||||
disabled={disableUserActions}
|
||||
expandedIssues={expandedIssues}
|
||||
issue={issue}
|
||||
labels={labels}
|
||||
onChange={(data: Partial<IIssue>) => handleUpdateIssue(issue, data)}
|
||||
/>
|
||||
) : property === "start_date" ? (
|
||||
<SpreadsheetStartDateColumn
|
||||
disabled={disableUserActions}
|
||||
expandedIssues={expandedIssues}
|
||||
issue={issue}
|
||||
onChange={(data: Partial<IIssue>) => handleUpdateIssue(issue, data)}
|
||||
/>
|
||||
) : property === "due_date" ? (
|
||||
<SpreadsheetDueDateColumn
|
||||
disabled={disableUserActions}
|
||||
expandedIssues={expandedIssues}
|
||||
issue={issue}
|
||||
onChange={(data: Partial<IIssue>) => handleUpdateIssue(issue, data)}
|
||||
/>
|
||||
) : property === "created_on" ? (
|
||||
<SpreadsheetCreatedOnColumn expandedIssues={expandedIssues} issue={issue} />
|
||||
) : property === "updated_on" ? (
|
||||
<SpreadsheetUpdatedOnColumn expandedIssues={expandedIssues} issue={issue} />
|
||||
) : property === "link" ? (
|
||||
<SpreadsheetLinkColumn expandedIssues={expandedIssues} issue={issue} />
|
||||
) : property === "attachment_count" ? (
|
||||
<SpreadsheetAttachmentColumn expandedIssues={expandedIssues} issue={issue} />
|
||||
) : property === "sub_issue_count" ? (
|
||||
<SpreadsheetSubIssueColumn expandedIssues={expandedIssues} issue={issue} />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -31,7 +31,7 @@ type Props = {
|
||||
viewId?: string
|
||||
) => Promise<IIssue | undefined>;
|
||||
viewId?: string;
|
||||
disableUserActions: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
enableQuickCreateIssue?: boolean;
|
||||
};
|
||||
|
||||
@ -48,7 +48,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
||||
handleIssues,
|
||||
quickAddCallback,
|
||||
viewId,
|
||||
disableUserActions,
|
||||
canEditProperties,
|
||||
enableQuickCreateIssue,
|
||||
} = props;
|
||||
// states
|
||||
@ -114,7 +114,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
||||
setExpandedIssues={setExpandedIssues}
|
||||
properties={displayProperties}
|
||||
quickActions={quickActions}
|
||||
disableUserActions={disableUserActions}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
@ -124,7 +124,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
||||
<SpreadsheetColumnsList
|
||||
displayFilters={displayFilters}
|
||||
displayProperties={displayProperties}
|
||||
disableUserActions={disableUserActions}
|
||||
canEditProperties={canEditProperties}
|
||||
expandedIssues={expandedIssues}
|
||||
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
|
||||
handleUpdateIssue={(issue, data) => handleIssues({ ...issue, ...data }, EIssueActions.UPDATE)}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ export class ProfileIssuesStore extends IssueBaseStore implements IProfileIssues
|
||||
return {
|
||||
enableQuickAdd: false,
|
||||
enableIssueCreation: false,
|
||||
enableInlineEditing: false,
|
||||
enableInlineEditing: true,
|
||||
};
|
||||
}
|
||||
|
||||
@ -233,8 +233,8 @@ export class ProfileIssuesStore extends IssueBaseStore implements IProfileIssues
|
||||
let _issues = { ...this.issues };
|
||||
if (!_issues) _issues = {};
|
||||
if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} };
|
||||
_issues[userId][this.currentUserIssueTab][userId] = {
|
||||
..._issues[userId][this.currentUserIssueTab][userId],
|
||||
_issues[userId][this.currentUserIssueTab][issueId] = {
|
||||
..._issues[userId][this.currentUserIssueTab][issueId],
|
||||
...data,
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
@ -52,7 +56,9 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore {
|
||||
// observables
|
||||
members: observable.ref,
|
||||
memberInvitations: observable.ref,
|
||||
workspaceUserProjectsRole: observable.ref,
|
||||
// actions
|
||||
fetchWorkspaceUserProjectsRole: action,
|
||||
fetchWorkspaceMembers: action,
|
||||
fetchWorkspaceMemberInvitations: action,
|
||||
updateMember: action,
|
||||
@ -64,6 +70,7 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore {
|
||||
workspaceMembers: computed,
|
||||
workspaceMemberInvitations: computed,
|
||||
workspaceMembersWithInvitations: computed,
|
||||
currentWorkspaceUserProjectsRole: computed,
|
||||
});
|
||||
|
||||
this.rootStore = _rootStore;
|
||||
@ -126,6 +133,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
|
||||
|
4
web/types/users.d.ts
vendored
4
web/types/users.d.ts
vendored
@ -162,6 +162,10 @@ export interface IUserProfileProjectSegregation {
|
||||
};
|
||||
}
|
||||
|
||||
export interface IUserProjectsRole {
|
||||
[project_id: string]: number;
|
||||
}
|
||||
|
||||
// export interface ICurrentUser {
|
||||
// id: readonly string;
|
||||
// avatar: string;
|
||||
|
Loading…
Reference in New Issue
Block a user