forked from github/plane
fix: sub-issue properties not rendering and other sub-issue bugs (#3020)
* fix: sub-issue properties not rendering * fix: delete sub-issue * fix: delete issue modal on command k and the issue details page
This commit is contained in:
parent
c6e2effa65
commit
5fdd2ac366
@ -34,6 +34,7 @@ export const CommandPalette: FC = observer(() => {
|
|||||||
theme: { toggleSidebar },
|
theme: { toggleSidebar },
|
||||||
user: { currentUser },
|
user: { currentUser },
|
||||||
trackEvent: { setTrackElement },
|
trackEvent: { setTrackElement },
|
||||||
|
projectIssues: { removeIssue },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
const {
|
const {
|
||||||
toggleCommandPaletteModal,
|
toggleCommandPaletteModal,
|
||||||
@ -218,11 +219,15 @@ export const CommandPalette: FC = observer(() => {
|
|||||||
currentStore={createIssueStoreType}
|
currentStore={createIssueStoreType}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{issueId && issueDetails && (
|
{workspaceSlug && projectId && issueId && issueDetails && (
|
||||||
<DeleteIssueModal
|
<DeleteIssueModal
|
||||||
handleClose={() => toggleDeleteIssueModal(false)}
|
handleClose={() => toggleDeleteIssueModal(false)}
|
||||||
isOpen={isDeleteIssueModalOpen}
|
isOpen={isDeleteIssueModalOpen}
|
||||||
data={issueDetails}
|
data={issueDetails}
|
||||||
|
onSubmit={async () => {
|
||||||
|
await removeIssue(workspaceSlug.toString(), projectId.toString(), issueId.toString());
|
||||||
|
router.push(`/${workspaceSlug}/projects/${projectId}/issues`);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -57,12 +57,8 @@ export const UserImageUploadModal: React.FC<Props> = observer((props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
console.log("Submit triggered");
|
|
||||||
|
|
||||||
if (!image) return;
|
if (!image) return;
|
||||||
|
|
||||||
console.log("Inside submit");
|
|
||||||
|
|
||||||
setIsImageUploading(true);
|
setIsImageUploading(true);
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
@ -29,7 +29,10 @@ export const DeleteIssueModal: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const handleIssueDelete = async () => {
|
const handleIssueDelete = async () => {
|
||||||
setIsDeleteLoading(true);
|
setIsDeleteLoading(true);
|
||||||
if (onSubmit) await onSubmit().finally(() => setIsDeleteLoading(false));
|
if (onSubmit)
|
||||||
|
await onSubmit()
|
||||||
|
.then(() => onClose())
|
||||||
|
.finally(() => setIsDeleteLoading(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -61,8 +61,6 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
|
|||||||
description_html: issue.description_html,
|
description_html: issue.description_html,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("in form", localIssueDescription);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (issue.id) {
|
if (issue.id) {
|
||||||
setLocalIssueDescription({ id: issue.id, description_html: issue.description_html });
|
setLocalIssueDescription({ id: issue.id, description_html: issue.description_html });
|
||||||
|
@ -81,11 +81,10 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<ILinkDetails | null>(null);
|
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<ILinkDetails | null>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
user: userStore,
|
user: { currentUser, currentProjectRole },
|
||||||
projectState: { states },
|
projectState: { states },
|
||||||
|
projectIssues: { removeIssue },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
const user = userStore.currentUser;
|
|
||||||
const userRole = userStore.currentProjectRole;
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, issueId, inboxIssueId } = router.query;
|
const { workspaceSlug, projectId, issueId, inboxIssueId } = router.query;
|
||||||
@ -102,7 +101,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
const handleCycleChange = useCallback(
|
const handleCycleChange = useCallback(
|
||||||
(cycleId: string) => {
|
(cycleId: string) => {
|
||||||
if (!workspaceSlug || !projectId || !issueDetail || !user) return;
|
if (!workspaceSlug || !projectId || !issueDetail || !currentUser) return;
|
||||||
|
|
||||||
issueService
|
issueService
|
||||||
.addIssueToCycle(workspaceSlug as string, projectId as string, cycleId, {
|
.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));
|
mutate(ISSUE_DETAILS(issueId as string));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[workspaceSlug, projectId, issueId, issueDetail, user]
|
[workspaceSlug, projectId, issueId, issueDetail, currentUser]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleModuleChange = useCallback(
|
const handleModuleChange = useCallback(
|
||||||
(moduleId: string) => {
|
(moduleId: string) => {
|
||||||
if (!workspaceSlug || !projectId || !issueDetail || !user) return;
|
if (!workspaceSlug || !projectId || !issueDetail || !currentUser) return;
|
||||||
|
|
||||||
moduleService
|
moduleService
|
||||||
.addIssuesToModule(workspaceSlug as string, projectId as string, moduleId, {
|
.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));
|
mutate(ISSUE_DETAILS(issueId as string));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[workspaceSlug, projectId, issueId, issueDetail, user]
|
[workspaceSlug, projectId, issueId, issueDetail, currentUser]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCreateLink = async (formData: IIssueLink) => {
|
const handleCreateLink = async (formData: IIssueLink) => {
|
||||||
@ -249,7 +248,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
setLinkModal(true);
|
setLinkModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAllowed = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER;
|
const isAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||||
|
|
||||||
const currentIssueState = projectId
|
const currentIssueState = projectId
|
||||||
? states[projectId.toString()]?.find((s) => s.id === issueDetail?.state)
|
? states[projectId.toString()]?.find((s) => s.id === issueDetail?.state)
|
||||||
@ -268,8 +267,16 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
createIssueLink={handleCreateLink}
|
createIssueLink={handleCreateLink}
|
||||||
updateIssueLink={handleUpdateLink}
|
updateIssueLink={handleUpdateLink}
|
||||||
/>
|
/>
|
||||||
{issueDetail && (
|
{workspaceSlug && projectId && issueDetail && (
|
||||||
<DeleteIssueModal handleClose={() => setDeleteIssueModal(false)} isOpen={deleteIssueModal} data={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="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">
|
<div className="flex items-center justify-between px-5 pb-3">
|
||||||
@ -288,8 +295,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{issueDetail?.created_by !== user?.id &&
|
{issueDetail?.created_by !== currentUser?.id &&
|
||||||
!issueDetail?.assignees.includes(user?.id ?? "") &&
|
!issueDetail?.assignees.includes(currentUser?.id ?? "") &&
|
||||||
!router.pathname.includes("[archivedIssueId]") &&
|
!router.pathname.includes("[archivedIssueId]") &&
|
||||||
(fieldsToShow.includes("all") || fieldsToShow.includes("subscribe")) && (
|
(fieldsToShow.includes("all") || fieldsToShow.includes("subscribe")) && (
|
||||||
<Button
|
<Button
|
||||||
@ -654,10 +661,10 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
handleDeleteLink={handleDeleteLink}
|
handleDeleteLink={handleDeleteLink}
|
||||||
handleEditLink={handleEditLink}
|
handleEditLink={handleEditLink}
|
||||||
userAuth={{
|
userAuth={{
|
||||||
isGuest: userRole === 5,
|
isGuest: currentProjectRole === 5,
|
||||||
isViewer: userRole === 10,
|
isViewer: currentProjectRole === 10,
|
||||||
isMember: userRole === 15,
|
isMember: currentProjectRole === 15,
|
||||||
isOwner: userRole === 20,
|
isOwner: currentProjectRole === 20,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -117,17 +117,16 @@ export const SubIssues: React.FC<ISubIssues> = ({
|
|||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
parentIssue={parentIssue}
|
parentIssue={parentIssue}
|
||||||
issue={issue}
|
issue={issue}
|
||||||
user={user}
|
|
||||||
editable={editable}
|
editable={editable}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-shrink-0 text-sm">
|
<div className="flex-shrink-0 text-sm">
|
||||||
<CustomMenu width="auto" ellipsis>
|
<CustomMenu width="auto" placement="bottom-end" ellipsis>
|
||||||
{editable && (
|
{editable && (
|
||||||
<CustomMenu.MenuItem onClick={() => handleIssueCrudOperation("edit", parentIssue?.id, issue)}>
|
<CustomMenu.MenuItem onClick={() => handleIssueCrudOperation("edit", parentIssue?.id, issue)}>
|
||||||
<div className="flex items-center justify-start gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Pencil width={14} strokeWidth={2} />
|
<Pencil className="h-3.5 w-3.5" strokeWidth={2} />
|
||||||
<span>Edit issue</span>
|
<span>Edit issue</span>
|
||||||
</div>
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
@ -135,8 +134,8 @@ export const SubIssues: React.FC<ISubIssues> = ({
|
|||||||
|
|
||||||
{editable && (
|
{editable && (
|
||||||
<CustomMenu.MenuItem onClick={() => handleIssueCrudOperation("delete", parentIssue?.id, issue)}>
|
<CustomMenu.MenuItem onClick={() => handleIssueCrudOperation("delete", parentIssue?.id, issue)}>
|
||||||
<div className="flex items-center justify-start gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Trash width={14} strokeWidth={2} />
|
<Trash className="h-3.5 w-3.5" strokeWidth={2} />
|
||||||
<span>Delete issue</span>
|
<span>Delete issue</span>
|
||||||
</div>
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
@ -145,8 +144,8 @@ export const SubIssues: React.FC<ISubIssues> = ({
|
|||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem
|
||||||
onClick={() => copyText(`${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`)}
|
onClick={() => copyText(`${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-start gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<LinkIcon width={14} strokeWidth={2} />
|
<LinkIcon className="h-3.5 w-3.5" strokeWidth={2} />
|
||||||
<span>Copy issue link</span>
|
<span>Copy issue link</span>
|
||||||
</div>
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
// mobx store
|
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
|
||||||
// services
|
// services
|
||||||
import { IssueService } from "services/issue";
|
import { IssueService } from "services/issue";
|
||||||
// components
|
// components
|
||||||
import { ViewDueDateSelect, ViewStartDateSelect } from "components/issues";
|
|
||||||
import { PrioritySelect } from "components/project";
|
import { PrioritySelect } from "components/project";
|
||||||
// types
|
// types
|
||||||
import { IUser, IIssue, IState } from "types";
|
import { IIssue, IState } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { SUB_ISSUES } from "constants/fetch-keys";
|
import { SUB_ISSUES } from "constants/fetch-keys";
|
||||||
import { IssuePropertyAssignee, IssuePropertyState } from "../issue-layouts/properties";
|
import { IssuePropertyAssignee, IssuePropertyState } from "../issue-layouts/properties";
|
||||||
@ -18,19 +14,14 @@ export interface IIssueProperty {
|
|||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
parentIssue: IIssue;
|
parentIssue: IIssue;
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
user: IUser | undefined;
|
|
||||||
editable: boolean;
|
editable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// services
|
// services
|
||||||
const issueService = new IssueService();
|
const issueService = new IssueService();
|
||||||
|
|
||||||
export const IssueProperty: React.FC<IIssueProperty> = observer((props) => {
|
export const IssueProperty: React.FC<IIssueProperty> = (props) => {
|
||||||
const { workspaceSlug, parentIssue, issue, user, editable } = props;
|
const { workspaceSlug, parentIssue, issue, editable } = props;
|
||||||
|
|
||||||
const { issueFilter: issueFilterStore } = useMobxStore();
|
|
||||||
|
|
||||||
const displayProperties = issueFilterStore.userDisplayProperties ?? {};
|
|
||||||
|
|
||||||
const handlePriorityChange = (data: any) => {
|
const handlePriorityChange = (data: any) => {
|
||||||
partialUpdateIssue({ priority: data });
|
partialUpdateIssue({ priority: data });
|
||||||
@ -77,53 +68,21 @@ export const IssueProperty: React.FC<IIssueProperty> = observer((props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex items-center gap-1">
|
<div className="relative flex items-center gap-2">
|
||||||
{displayProperties.priority && (
|
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<PrioritySelect
|
<PrioritySelect value={issue.priority} onChange={handlePriorityChange} hideDropdownArrow disabled={!editable} />
|
||||||
value={issue.priority}
|
|
||||||
onChange={handlePriorityChange}
|
|
||||||
hideDropdownArrow
|
|
||||||
disabled={!editable}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{displayProperties.state && (
|
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<IssuePropertyState
|
<IssuePropertyState
|
||||||
projectId={issue?.project_detail?.id || null}
|
projectId={issue?.project_detail?.id || null}
|
||||||
value={issue?.state_detail || null}
|
value={issue?.state || null}
|
||||||
onChange={(data) => handleStateChange(data)}
|
onChange={(data) => handleStateChange(data)}
|
||||||
disabled={false}
|
disabled={false}
|
||||||
hideDropdownArrow
|
hideDropdownArrow
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<div className="flex-shrink-0">
|
||||||
<IssuePropertyAssignee
|
<IssuePropertyAssignee
|
||||||
projectId={issue?.project_detail?.id || null}
|
projectId={issue?.project_detail?.id || null}
|
||||||
@ -133,7 +92,6 @@ export const IssueProperty: React.FC<IIssueProperty> = observer((props) => {
|
|||||||
disabled={false}
|
disabled={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
@ -45,11 +45,10 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
|
|||||||
const { parentIssue, user } = props;
|
const { parentIssue, user } = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
user: userStore,
|
user: { currentProjectRole },
|
||||||
issue: { updateIssueStructure },
|
issue: { updateIssueStructure },
|
||||||
projectIssues: { updateIssue },
|
projectIssues: { updateIssue, removeIssue },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
const userRole = userStore.currentProjectRole;
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
@ -177,7 +176,7 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
|
|||||||
[updateIssueStructure, projectId, updateIssue, user, workspaceSlug]
|
[updateIssueStructure, projectId, updateIssue, user, workspaceSlug]
|
||||||
);
|
);
|
||||||
|
|
||||||
const isEditable = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER;
|
const isEditable = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||||
|
|
||||||
const mutateSubIssues = (parentIssueId: string | null) => {
|
const mutateSubIssues = (parentIssueId: string | null) => {
|
||||||
if (parentIssueId) mutate(SUB_ISSUES(parentIssueId));
|
if (parentIssueId) mutate(SUB_ISSUES(parentIssueId));
|
||||||
@ -261,7 +260,7 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
buttonClassName="whitespace-nowrap"
|
buttonClassName="whitespace-nowrap"
|
||||||
// position="left"
|
placement="bottom-end"
|
||||||
noBorder
|
noBorder
|
||||||
noChevron
|
noChevron
|
||||||
>
|
>
|
||||||
@ -297,7 +296,7 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
buttonClassName="whitespace-nowrap"
|
buttonClassName="whitespace-nowrap"
|
||||||
// position="left"
|
placement="bottom-end"
|
||||||
noBorder
|
noBorder
|
||||||
noChevron
|
noChevron
|
||||||
>
|
>
|
||||||
@ -356,7 +355,8 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{isEditable &&
|
{isEditable &&
|
||||||
issueCrudOperation?.delete?.toggle &&
|
workspaceSlug &&
|
||||||
|
projectId &&
|
||||||
issueCrudOperation?.delete?.issueId &&
|
issueCrudOperation?.delete?.issueId &&
|
||||||
issueCrudOperation?.delete?.issue && (
|
issueCrudOperation?.delete?.issue && (
|
||||||
<DeleteIssueModal
|
<DeleteIssueModal
|
||||||
@ -366,6 +366,13 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
|
|||||||
handleIssueCrudOperation("delete", null, null);
|
handleIssueCrudOperation("delete", null, null);
|
||||||
}}
|
}}
|
||||||
data={issueCrudOperation?.delete?.issue}
|
data={issueCrudOperation?.delete?.issue}
|
||||||
|
onSubmit={async () => {
|
||||||
|
await removeIssue(
|
||||||
|
workspaceSlug.toString(),
|
||||||
|
projectId.toString(),
|
||||||
|
issueCrudOperation?.delete?.issue?.id ?? ""
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
Loading…
Reference in New Issue
Block a user