chore: replace workspace slug and project id with anchor

This commit is contained in:
Aaryan Khandelwal 2024-06-04 16:52:55 +05:30
parent dc436f60fa
commit 1c3a27deaa
26 changed files with 383 additions and 459 deletions

View File

@ -1,8 +1,8 @@
type Props = { type Props = {
children: React.ReactNode; children: React.ReactNode;
params: { params: {
workspace_slug: string; workspaceSlug: string;
project_id: string; projectId: string;
}; };
}; };

View File

@ -12,14 +12,14 @@ const publishService = new PublishService();
type Props = { type Props = {
params: { params: {
workspace_slug: string; workspaceSlug: string;
project_id: string; projectId: string;
}; };
}; };
const ProjectIssuesPage = (props: Props) => { const ProjectIssuesPage = (props: Props) => {
const { params } = props; const { params } = props;
const { workspace_slug, project_id } = params; const { workspaceSlug, projectId } = params;
// states // states
const [error, setError] = useState(false); const [error, setError] = useState(false);
// params // params
@ -28,9 +28,9 @@ const ProjectIssuesPage = (props: Props) => {
const peekId = searchParams.get("peekId") || undefined; const peekId = searchParams.get("peekId") || undefined;
useEffect(() => { useEffect(() => {
if (!workspace_slug || !project_id) return; if (!workspaceSlug || !projectId) return;
publishService publishService
.fetchAnchorFromOldDetails(workspace_slug, project_id) .fetchAnchorFromOldDetails(workspaceSlug, projectId)
.then((res) => { .then((res) => {
let url = `/${res.anchor}`; let url = `/${res.anchor}`;
const params = new URLSearchParams(); const params = new URLSearchParams();
@ -40,7 +40,7 @@ const ProjectIssuesPage = (props: Props) => {
navigate(url); navigate(url);
}) })
.catch(() => setError(true)); .catch(() => setError(true));
}, [board, peekId, project_id, workspace_slug]); }, [board, peekId, projectId, workspaceSlug]);
if (error) notFound(); if (error) notFound();

View File

@ -1,4 +1,3 @@
import { observer } from "mobx-react";
import Image from "next/image"; import Image from "next/image";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
// components // components

View File

@ -16,7 +16,8 @@ const PageDetailsLayout = (props: Props) => {
const { anchor } = params; const { anchor } = params;
// store hooks // store hooks
const { fetchPublishSettings } = usePublishList(); const { fetchPublishSettings } = usePublishList();
const { id, workspace_detail, project } = usePublish(anchor); const publishSettings = usePublish(anchor);
const { id, workspace_detail, project } = publishSettings;
useSWR(anchor ? `PUBLISH_SETTINGS_${anchor}` : null, anchor ? () => fetchPublishSettings(anchor) : null); useSWR(anchor ? `PUBLISH_SETTINGS_${anchor}` : null, anchor ? () => fetchPublishSettings(anchor) : null);
@ -25,7 +26,7 @@ const PageDetailsLayout = (props: Props) => {
return ( return (
<div className="relative flex h-screen min-h-[500px] w-screen flex-col overflow-hidden"> <div className="relative flex h-screen min-h-[500px] w-screen flex-col overflow-hidden">
<div className="relative flex h-[60px] flex-shrink-0 select-none items-center border-b border-custom-border-300 bg-custom-sidebar-background-100"> <div className="relative flex h-[60px] flex-shrink-0 select-none items-center border-b border-custom-border-300 bg-custom-sidebar-background-100">
<IssueNavbar workspaceSlug={workspace_detail.slug} projectId={project} /> <IssueNavbar publishSettings={publishSettings} />
</div> </div>
<div className="relative h-full w-full overflow-hidden bg-custom-background-90">{children}</div> <div className="relative h-full w-full overflow-hidden bg-custom-background-90">{children}</div>
<a <a

View File

@ -10,18 +10,19 @@ import { IssueBlockState } from "@/components/issues/board-views/block-state";
// helpers // helpers
import { queryParamGenerator } from "@/helpers/query-param-generator"; import { queryParamGenerator } from "@/helpers/query-param-generator";
// hooks // hooks
import { useIssueDetails, useProject } from "@/hooks/store"; import { useIssueDetails, usePublish } from "@/hooks/store";
// interfaces // interfaces
import { IIssue } from "@/types/issue"; import { IIssue } from "@/types/issue";
type IssueKanBanBlockProps = { type IssueKanBanBlockProps = {
anchor: string;
issue: IIssue; issue: IIssue;
workspaceSlug: string;
projectId: string;
params: any; params: any;
}; };
export const IssueKanBanBlock: FC<IssueKanBanBlockProps> = observer((props) => { export const IssueKanBanBlock: FC<IssueKanBanBlockProps> = observer((props) => {
const { anchor, issue } = props;
// router
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
// query params // query params
@ -29,23 +30,21 @@ export const IssueKanBanBlock: FC<IssueKanBanBlockProps> = observer((props) => {
const state = searchParams.get("state") || undefined; const state = searchParams.get("state") || undefined;
const priority = searchParams.get("priority") || undefined; const priority = searchParams.get("priority") || undefined;
const labels = searchParams.get("labels") || undefined; const labels = searchParams.get("labels") || undefined;
// props // store hooks
const { workspaceSlug, projectId, issue } = props; const { project_details } = usePublish(anchor);
// hooks
const { project } = useProject();
const { setPeekId } = useIssueDetails(); const { setPeekId } = useIssueDetails();
const handleBlockClick = () => { const handleBlockClick = () => {
setPeekId(issue.id); setPeekId(issue.id);
const { queryParam } = queryParamGenerator({ board, peekId: issue.id, priority, state, labels }); const { queryParam } = queryParamGenerator({ board, peekId: issue.id, priority, state, labels });
router.push(`/${workspaceSlug}/${projectId}?${queryParam}`); router.push(`/issues/${anchor}?${queryParam}`);
}; };
return ( return (
<div className="flex flex-col gap-1.5 space-y-2 rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 px-3 py-2 text-sm shadow-custom-shadow-2xs"> <div className="flex flex-col gap-1.5 space-y-2 rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 px-3 py-2 text-sm shadow-custom-shadow-2xs">
{/* id */} {/* id */}
<div className="break-words text-xs text-custom-text-300"> <div className="break-words text-xs text-custom-text-300">
{project?.identifier}-{issue?.sequence_id} {project_details?.identifier}-{issue?.sequence_id}
</div> </div>
{/* name */} {/* name */}

View File

@ -9,39 +9,31 @@ import { IssueKanBanHeader } from "@/components/issues/board-views/kanban/header
import { Icon } from "@/components/ui"; import { Icon } from "@/components/ui";
// mobx hook // mobx hook
import { useIssue } from "@/hooks/store"; import { useIssue } from "@/hooks/store";
// interfaces
import { IIssueState, IIssue } from "@/types/issue";
type IssueKanbanViewProps = { type IssueKanbanViewProps = {
workspaceSlug: string; anchor: string;
projectId: string;
}; };
export const IssueKanbanView: FC<IssueKanbanViewProps> = observer((props) => { export const IssueKanbanView: FC<IssueKanbanViewProps> = observer((props) => {
const { workspaceSlug, projectId } = props; const { anchor } = props;
// store hooks // store hooks
const { states, getFilteredIssuesByState } = useIssue(); const { states, getFilteredIssuesByState } = useIssue();
return ( return (
<div className="relative flex h-full w-full gap-3 overflow-hidden overflow-x-auto"> <div className="relative flex h-full w-full gap-3 overflow-hidden overflow-x-auto">
{states && {states?.map((state) => {
states.length > 0 && const issues = getFilteredIssuesByState(state.id);
states.map((_state: IIssueState) => (
<div key={_state.id} className="relative flex h-full w-[340px] flex-shrink-0 flex-col"> return (
<div key={state.id} className="relative flex h-full w-[340px] flex-shrink-0 flex-col">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<IssueKanBanHeader state={_state} /> <IssueKanBanHeader state={state} />
</div> </div>
<div className="hide-vertical-scrollbar h-full w-full overflow-hidden overflow-y-auto"> <div className="hide-vertical-scrollbar h-full w-full overflow-hidden overflow-y-auto">
{getFilteredIssuesByState(_state.id) && getFilteredIssuesByState(_state.id).length > 0 ? ( {issues && issues.length > 0 ? (
<div className="space-y-3 px-2 pb-2"> <div className="space-y-3 px-2 pb-2">
{getFilteredIssuesByState(_state.id).map((_issue: IIssue) => ( {issues.map((issue) => (
<IssueKanBanBlock <IssueKanBanBlock key={issue.id} anchor={anchor} issue={issue} params={{}} />
key={_issue.id}
issue={_issue}
workspaceSlug={workspaceSlug}
projectId={projectId}
params={{}}
/>
))} ))}
</div> </div>
) : ( ) : (
@ -52,7 +44,8 @@ export const IssueKanbanView: FC<IssueKanbanViewProps> = observer((props) => {
)} )}
</div> </div>
</div> </div>
))} );
})}
</div> </div>
); );
}); });

View File

@ -10,28 +10,27 @@ import { IssueBlockState } from "@/components/issues/board-views/block-state";
// helpers // helpers
import { queryParamGenerator } from "@/helpers/query-param-generator"; import { queryParamGenerator } from "@/helpers/query-param-generator";
// hook // hook
import { useIssueDetails, useProject } from "@/hooks/store"; import { useIssueDetails, usePublish } from "@/hooks/store";
// interfaces // interfaces
import { IIssue } from "@/types/issue"; import { IIssue } from "@/types/issue";
// store // store
type IssueListBlockProps = { type IssueListBlockProps = {
anchor: string;
issue: IIssue; issue: IIssue;
workspaceSlug: string;
projectId: string;
}; };
export const IssueListBlock: FC<IssueListBlockProps> = observer((props) => { export const IssueListBlock: FC<IssueListBlockProps> = observer((props) => {
const { workspaceSlug, projectId, issue } = props; const { anchor, issue } = props;
const searchParams = useSearchParams(); const searchParams = useSearchParams();
// query params // query params
const board = searchParams.get("board") || undefined; const board = searchParams.get("board") || undefined;
const state = searchParams.get("state") || undefined; const state = searchParams.get("state") || undefined;
const priority = searchParams.get("priority") || undefined; const priority = searchParams.get("priority") || undefined;
const labels = searchParams.get("labels") || undefined; const labels = searchParams.get("labels") || undefined;
// store // store hooks
const { project } = useProject();
const { setPeekId } = useIssueDetails(); const { setPeekId } = useIssueDetails();
const { project_details } = usePublish(anchor);
// router // router
const router = useRouter(); const router = useRouter();
@ -39,7 +38,7 @@ export const IssueListBlock: FC<IssueListBlockProps> = observer((props) => {
setPeekId(issue.id); setPeekId(issue.id);
const { queryParam } = queryParamGenerator({ board, peekId: issue.id, priority, state, labels }); const { queryParam } = queryParamGenerator({ board, peekId: issue.id, priority, state, labels });
router.push(`/${workspaceSlug}/${projectId}?${queryParam}`); router.push(`/issues/${anchor}?${queryParam}`);
}; };
return ( return (
@ -47,7 +46,7 @@ export const IssueListBlock: FC<IssueListBlockProps> = observer((props) => {
<div className="relative flex w-full flex-grow items-center gap-3 overflow-hidden"> <div className="relative flex w-full flex-grow items-center gap-3 overflow-hidden">
{/* id */} {/* id */}
<div className="flex-shrink-0 text-xs font-medium text-custom-text-300"> <div className="flex-shrink-0 text-xs font-medium text-custom-text-300">
{project?.identifier}-{issue?.sequence_id} {project_details?.identifier}-{issue?.sequence_id}
</div> </div>
{/* name */} {/* name */}
<div onClick={handleBlockClick} className="flex-grow cursor-pointer truncate text-sm font-medium"> <div onClick={handleBlockClick} className="flex-grow cursor-pointer truncate text-sm font-medium">

View File

@ -6,37 +6,36 @@ import { IssueListBlock } from "@/components/issues/board-views/list/block";
import { IssueListHeader } from "@/components/issues/board-views/list/header"; import { IssueListHeader } from "@/components/issues/board-views/list/header";
// mobx hook // mobx hook
import { useIssue } from "@/hooks/store"; import { useIssue } from "@/hooks/store";
// types
import { IIssueState, IIssue } from "@/types/issue";
type IssueListViewProps = { type IssueListViewProps = {
workspaceSlug: string; anchor: string;
projectId: string;
}; };
export const IssueListView: FC<IssueListViewProps> = observer((props) => { export const IssueListView: FC<IssueListViewProps> = observer((props) => {
const { workspaceSlug, projectId } = props; const { anchor } = props;
// store hooks // store hooks
const { states, getFilteredIssuesByState } = useIssue(); const { states, getFilteredIssuesByState } = useIssue();
return ( return (
<> <>
{states && {states?.map((state) => {
states.length > 0 && const issues = getFilteredIssuesByState(state.id);
states.map((_state: IIssueState) => (
<div key={_state.id} className="relative w-full"> return (
<IssueListHeader state={_state} /> <div key={state.id} className="relative w-full">
{getFilteredIssuesByState(_state.id) && getFilteredIssuesByState(_state.id).length > 0 ? ( <IssueListHeader state={state} />
{issues && issues.length > 0 ? (
<div className="divide-y divide-custom-border-200"> <div className="divide-y divide-custom-border-200">
{getFilteredIssuesByState(_state.id).map((_issue: IIssue) => ( {issues.map((issue) => (
<IssueListBlock key={_issue.id} issue={_issue} workspaceSlug={workspaceSlug} projectId={projectId} /> <IssueListBlock key={issue.id} anchor={anchor} issue={issue} />
))} ))}
</div> </div>
) : ( ) : (
<div className="bg-custom-background-100 p-3 text-sm text-custom-text-400">No issues.</div> <div className="bg-custom-background-100 p-3 text-sm text-custom-text-400">No issues.</div>
)} )}
</div> </div>
))} );
})}
</> </>
); );
}); });

View File

@ -5,24 +5,24 @@ import cloneDeep from "lodash/cloneDeep";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
// hooks // hooks
import { useIssue, useIssueFilter } from "@/hooks/store"; import { useIssue, useIssueFilter, usePublish } from "@/hooks/store";
// store // store
import { TIssueQueryFilters } from "@/types/issue"; import { TIssueQueryFilters } from "@/types/issue";
// components // components
import { AppliedFiltersList } from "./filters-list"; import { AppliedFiltersList } from "./filters-list";
type TIssueAppliedFilters = { type TIssueAppliedFilters = {
workspaceSlug: string; anchor: string;
projectId: string;
}; };
export const IssueAppliedFilters: FC<TIssueAppliedFilters> = observer((props) => { export const IssueAppliedFilters: FC<TIssueAppliedFilters> = observer((props) => {
const { anchor } = props;
// router
const router = useRouter(); const router = useRouter();
// props // store hooks
const { workspaceSlug, projectId } = props;
// hooks
const { issueFilters, initIssueFilters, updateIssueFilters } = useIssueFilter(); const { issueFilters, initIssueFilters, updateIssueFilters } = useIssueFilter();
const { states, labels } = useIssue(); const { states, labels } = useIssue();
const { project: projectID } = usePublish(anchor);
const activeLayout = issueFilters?.display_filters?.layout || undefined; const activeLayout = issueFilters?.display_filters?.layout || undefined;
const userFilters = issueFilters?.filters || {}; const userFilters = issueFilters?.filters || {};
@ -46,30 +46,30 @@ export const IssueAppliedFilters: FC<TIssueAppliedFilters> = observer((props) =>
if (labels.length > 0) params = { ...params, labels: labels.join(",") }; if (labels.length > 0) params = { ...params, labels: labels.join(",") };
params = new URLSearchParams(params).toString(); params = new URLSearchParams(params).toString();
router.push(`/${workspaceSlug}/${projectId}?${params}`); router.push(`/issues/${anchor}?${params}`);
}, },
[workspaceSlug, projectId, activeLayout, issueFilters, router] [activeLayout, anchor, issueFilters, router]
); );
const handleFilters = useCallback( const handleFilters = useCallback(
(key: keyof TIssueQueryFilters, value: string | null) => { (key: keyof TIssueQueryFilters, value: string | null) => {
if (!projectId) return; if (!projectID) return;
let newValues = cloneDeep(issueFilters?.filters?.[key]) ?? []; let newValues = cloneDeep(issueFilters?.filters?.[key]) ?? [];
if (value === null) newValues = []; if (value === null) newValues = [];
else if (newValues.includes(value)) newValues.splice(newValues.indexOf(value), 1); else if (newValues.includes(value)) newValues.splice(newValues.indexOf(value), 1);
updateIssueFilters(projectId, "filters", key, newValues); updateIssueFilters(projectID, "filters", key, newValues);
updateRouteParams(key, newValues); updateRouteParams(key, newValues);
}, },
[projectId, issueFilters, updateIssueFilters, updateRouteParams] [projectID, issueFilters, updateIssueFilters, updateRouteParams]
); );
const handleRemoveAllFilters = () => { const handleRemoveAllFilters = () => {
if (!projectId) return; if (!projectID) return;
initIssueFilters(projectId, { initIssueFilters(projectID, {
display_filters: { layout: activeLayout || "list" }, display_filters: { layout: activeLayout || "list" },
filters: { filters: {
state: [], state: [],
@ -78,7 +78,7 @@ export const IssueAppliedFilters: FC<TIssueAppliedFilters> = observer((props) =>
}, },
}); });
router.push(`/${workspaceSlug}/${projectId}?${`board=${activeLayout || "list"}`}`); router.push(`/issues/${anchor}?${`board=${activeLayout || "list"}`}`);
}; };
if (Object.keys(appliedFilters).length === 0) return null; if (Object.keys(appliedFilters).length === 0) return null;

View File

@ -8,7 +8,7 @@ import { TOAST_TYPE, setToast } from "@plane/ui";
// editor components // editor components
import { LiteTextEditor } from "@/components/editor/lite-text-editor"; import { LiteTextEditor } from "@/components/editor/lite-text-editor";
// hooks // hooks
import { useIssueDetails, useProject, useUser } from "@/hooks/store"; import { useIssueDetails, usePublish, useUser } from "@/hooks/store";
// types // types
import { Comment } from "@/types/issue"; import { Comment } from "@/types/issue";
@ -17,22 +17,18 @@ const defaultValues: Partial<Comment> = {
}; };
type Props = { type Props = {
anchor: string;
disabled?: boolean; disabled?: boolean;
workspaceSlug: string;
projectId: string;
}; };
export const AddComment: React.FC<Props> = observer((props) => { export const AddComment: React.FC<Props> = observer((props) => {
// const { disabled = false } = props; const { anchor } = props;
const { workspaceSlug, projectId } = props;
// refs // refs
const editorRef = useRef<EditorRefApi>(null); const editorRef = useRef<EditorRefApi>(null);
// store hooks // store hooks
const { workspace } = useProject();
const { peekId: issueId, addIssueComment } = useIssueDetails(); const { peekId: issueId, addIssueComment } = useIssueDetails();
const { data: currentUser } = useUser(); const { data: currentUser } = useUser();
// derived values const { workspaceSlug, workspace: workspaceID } = usePublish(anchor);
const workspaceId = workspace?.id;
// form info // form info
const { const {
handleSubmit, handleSubmit,
@ -43,9 +39,9 @@ export const AddComment: React.FC<Props> = observer((props) => {
} = useForm<Comment>({ defaultValues }); } = useForm<Comment>({ defaultValues });
const onSubmit = async (formData: Comment) => { const onSubmit = async (formData: Comment) => {
if (!workspaceSlug || !projectId || !issueId || isSubmitting || !formData.comment_html) return; if (!anchor || !issueId || isSubmitting || !formData.comment_html) return;
await addIssueComment(workspaceSlug, projectId, issueId, formData) await addIssueComment(anchor, issueId, formData)
.then(() => { .then(() => {
reset(defaultValues); reset(defaultValues);
editorRef.current?.clearEditor(); editorRef.current?.clearEditor();
@ -71,8 +67,8 @@ export const AddComment: React.FC<Props> = observer((props) => {
onEnterKeyPress={(e) => { onEnterKeyPress={(e) => {
if (currentUser) handleSubmit(onSubmit)(e); if (currentUser) handleSubmit(onSubmit)(e);
}} }}
workspaceId={workspaceId as string} workspaceId={workspaceID?.toString() ?? ""}
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug?.toString() ?? ""}
ref={editorRef} ref={editorRef}
initialValue={ initialValue={
!value || value === "" || (typeof value === "object" && Object.keys(value).length === 0) !value || value === "" || (typeof value === "object" && Object.keys(value).length === 0)

View File

@ -10,25 +10,23 @@ import { CommentReactions } from "@/components/issues/peek-overview";
// helpers // helpers
import { timeAgo } from "@/helpers/date-time.helper"; import { timeAgo } from "@/helpers/date-time.helper";
// hooks // hooks
import { useIssueDetails, useProject, useUser } from "@/hooks/store"; import { useIssueDetails, usePublish, useUser } from "@/hooks/store";
import useIsInIframe from "@/hooks/use-is-in-iframe"; import useIsInIframe from "@/hooks/use-is-in-iframe";
// types // types
import { Comment } from "@/types/issue"; import { Comment } from "@/types/issue";
type Props = { type Props = {
workspaceSlug: string; anchor: string;
comment: Comment; comment: Comment;
}; };
export const CommentCard: React.FC<Props> = observer((props) => { export const CommentCard: React.FC<Props> = observer((props) => {
const { comment, workspaceSlug } = props; const { anchor, comment } = props;
// store hooks // store hooks
const { workspace } = useProject();
const { peekId, deleteIssueComment, updateIssueComment } = useIssueDetails(); const { peekId, deleteIssueComment, updateIssueComment } = useIssueDetails();
const { data: currentUser } = useUser(); const { data: currentUser } = useUser();
const { workspaceSlug, workspace: workspaceID } = usePublish(anchor);
const isInIframe = useIsInIframe(); const isInIframe = useIsInIframe();
// derived values
const workspaceId = workspace?.id;
// states // states
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
@ -45,13 +43,13 @@ export const CommentCard: React.FC<Props> = observer((props) => {
}); });
const handleDelete = () => { const handleDelete = () => {
if (!workspaceSlug || !peekId) return; if (!anchor || !peekId) return;
deleteIssueComment(workspaceSlug, comment.project, peekId, comment.id); deleteIssueComment(anchor, peekId, comment.id);
}; };
const handleCommentUpdate = async (formData: Comment) => { const handleCommentUpdate = async (formData: Comment) => {
if (!workspaceSlug || !peekId) return; if (!anchor || !peekId) return;
updateIssueComment(workspaceSlug, comment.project, peekId, comment.id, formData); updateIssueComment(anchor, peekId, comment.id, formData);
setIsEditing(false); setIsEditing(false);
editorRef.current?.setEditorValue(formData.comment_html); editorRef.current?.setEditorValue(formData.comment_html);
showEditorRef.current?.setEditorValue(formData.comment_html); showEditorRef.current?.setEditorValue(formData.comment_html);
@ -103,8 +101,8 @@ export const CommentCard: React.FC<Props> = observer((props) => {
name="comment_html" name="comment_html"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<LiteTextEditor <LiteTextEditor
workspaceId={workspaceId as string} workspaceId={workspaceID?.toString() ?? ""}
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug?.toString() ?? ""}
onEnterKeyPress={handleSubmit(handleCommentUpdate)} onEnterKeyPress={handleSubmit(handleCommentUpdate)}
ref={editorRef} ref={editorRef}
initialValue={value} initialValue={value}
@ -135,7 +133,7 @@ export const CommentCard: React.FC<Props> = observer((props) => {
</form> </form>
<div className={`${isEditing ? "hidden" : ""}`}> <div className={`${isEditing ? "hidden" : ""}`}>
<LiteTextReadOnlyEditor ref={showEditorRef} initialValue={comment.comment_html} /> <LiteTextReadOnlyEditor ref={showEditorRef} initialValue={comment.comment_html} />
<CommentReactions commentId={comment.id} projectId={comment.project} workspaceSlug={workspaceSlug} /> <CommentReactions anchor={anchor} commentId={comment.id} />
</div> </div>
</div> </div>
</div> </div>

View File

@ -13,12 +13,12 @@ import { useIssueDetails, useUser } from "@/hooks/store";
import useIsInIframe from "@/hooks/use-is-in-iframe"; import useIsInIframe from "@/hooks/use-is-in-iframe";
type Props = { type Props = {
anchor: string;
commentId: string; commentId: string;
projectId: string;
workspaceSlug: string;
}; };
export const CommentReactions: React.FC<Props> = observer((props) => { export const CommentReactions: React.FC<Props> = observer((props) => {
const { anchor, commentId } = props;
const router = useRouter(); const router = useRouter();
const pathName = usePathname(); const pathName = usePathname();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
@ -28,7 +28,6 @@ export const CommentReactions: React.FC<Props> = observer((props) => {
const priority = searchParams.get("priority") || undefined; const priority = searchParams.get("priority") || undefined;
const labels = searchParams.get("labels") || undefined; const labels = searchParams.get("labels") || undefined;
const { commentId, projectId, workspaceSlug } = props;
// hooks // hooks
const { addCommentReaction, removeCommentReaction, details, peekId } = useIssueDetails(); const { addCommentReaction, removeCommentReaction, details, peekId } = useIssueDetails();
const { data: user } = useUser(); const { data: user } = useUser();
@ -40,13 +39,13 @@ export const CommentReactions: React.FC<Props> = observer((props) => {
const userReactions = commentReactions?.filter((r) => r?.actor_detail?.id === user?.id); const userReactions = commentReactions?.filter((r) => r?.actor_detail?.id === user?.id);
const handleAddReaction = (reactionHex: string) => { const handleAddReaction = (reactionHex: string) => {
if (!workspaceSlug || !projectId || !peekId) return; if (!anchor || !peekId) return;
addCommentReaction(workspaceSlug, projectId, peekId, commentId, reactionHex); addCommentReaction(anchor, peekId, commentId, reactionHex);
}; };
const handleRemoveReaction = (reactionHex: string) => { const handleRemoveReaction = (reactionHex: string) => {
if (!workspaceSlug || !projectId || !peekId) return; if (!anchor || !peekId) return;
removeCommentReaction(workspaceSlug, projectId, peekId, commentId, reactionHex); removeCommentReaction(anchor, peekId, commentId, reactionHex);
}; };
const handleReactionClick = (reactionHex: string) => { const handleReactionClick = (reactionHex: string) => {

View File

@ -11,14 +11,13 @@ import {
import { IIssue } from "@/types/issue"; import { IIssue } from "@/types/issue";
type Props = { type Props = {
anchor: string;
handleClose: () => void; handleClose: () => void;
issueDetails: IIssue | undefined; issueDetails: IIssue | undefined;
workspaceSlug: string;
projectId: string;
}; };
export const FullScreenPeekView: React.FC<Props> = observer((props) => { export const FullScreenPeekView: React.FC<Props> = observer((props) => {
const { handleClose, issueDetails, workspaceSlug, projectId } = props; const { anchor, handleClose, issueDetails } = props;
return ( return (
<div className="grid h-full w-full grid-cols-10 divide-x divide-custom-border-200 overflow-hidden"> <div className="grid h-full w-full grid-cols-10 divide-x divide-custom-border-200 overflow-hidden">
@ -36,11 +35,7 @@ export const FullScreenPeekView: React.FC<Props> = observer((props) => {
<div className="my-5 h-[1] w-full border-t border-custom-border-200" /> <div className="my-5 h-[1] w-full border-t border-custom-border-200" />
{/* issue activity/comments */} {/* issue activity/comments */}
<div className="w-full pb-5"> <div className="w-full pb-5">
<PeekOverviewIssueActivity <PeekOverviewIssueActivity anchor={anchor} issueDetails={issueDetails} />
issueDetails={issueDetails}
workspaceSlug={workspaceSlug}
projectId={projectId}
/>
</div> </div>
</div> </div>
) : ( ) : (

View File

@ -13,13 +13,12 @@ import useIsInIframe from "@/hooks/use-is-in-iframe";
import { IIssue } from "@/types/issue"; import { IIssue } from "@/types/issue";
type Props = { type Props = {
anchor: string;
issueDetails: IIssue; issueDetails: IIssue;
workspaceSlug: string;
projectId: string;
}; };
export const PeekOverviewIssueActivity: React.FC<Props> = observer((props) => { export const PeekOverviewIssueActivity: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId } = props; const { anchor } = props;
// router // router
const pathname = usePathname(); const pathname = usePathname();
// store // store
@ -33,35 +32,33 @@ export const PeekOverviewIssueActivity: React.FC<Props> = observer((props) => {
return ( return (
<div className="pb-10"> <div className="pb-10">
<h4 className="font-medium">Comments</h4> <h4 className="font-medium">Comments</h4>
{workspaceSlug && ( <div className="mt-4">
<div className="mt-4"> <div className="space-y-4">
<div className="space-y-4"> {comments.map((comment) => (
{comments.map((comment: any) => ( <CommentCard key={comment.id} anchor={anchor} comment={comment} />
<CommentCard key={comment.id} comment={comment} workspaceSlug={workspaceSlug?.toString()} /> ))}
))}
</div>
{!isInIframe &&
(currentUser ? (
<>
{canComment && (
<div className="mt-4">
<AddComment disabled={!currentUser} workspaceSlug={workspaceSlug} projectId={projectId} />
</div>
)}
</>
) : (
<div className="mt-4 flex items-center justify-between gap-2 rounded border border-custom-border-300 bg-custom-background-80 px-2 py-2.5">
<p className="flex gap-2 overflow-hidden break-words text-sm text-custom-text-200">
<Icon iconName="lock" className="!text-sm" />
Sign in to add your comment
</p>
<Link href={`/?next_path=${pathname}`}>
<Button variant="primary">Sign in</Button>
</Link>
</div>
))}
</div> </div>
)} {!isInIframe &&
(currentUser ? (
<>
{canComment && (
<div className="mt-4">
<AddComment anchor={anchor} disabled={!currentUser} />
</div>
)}
</>
) : (
<div className="mt-4 flex items-center justify-between gap-2 rounded border border-custom-border-300 bg-custom-background-80 px-2 py-2.5">
<p className="flex gap-2 overflow-hidden break-words text-sm text-custom-text-200">
<Icon iconName="lock" className="!text-sm" />
Sign in to add your comment
</p>
<Link href={`/?next_path=${pathname}`}>
<Button variant="primary">Sign in</Button>
</Link>
</div>
))}
</div>
</div> </div>
); );
}); });

View File

@ -9,7 +9,7 @@ import useIsInIframe from "@/hooks/use-is-in-iframe";
// }; // };
export const IssueReactions: React.FC = () => { export const IssueReactions: React.FC = () => {
const { workspace_slug: workspaceSlug, project_id: projectId } = useParams<any>(); const { workspaceSlug, projectId } = useParams<any>();
const { canVote, canReact } = useProject(); const { canVote, canReact } = useProject();
const isInIframe = useIsInIframe(); const isInIframe = useIsInIframe();

View File

@ -10,13 +10,12 @@ import { FullScreenPeekView, SidePeekView } from "@/components/issues/peek-overv
import { useIssue, useIssueDetails } from "@/hooks/store"; import { useIssue, useIssueDetails } from "@/hooks/store";
type TIssuePeekOverview = { type TIssuePeekOverview = {
workspaceSlug: string; anchor: string;
projectId: string;
peekId: string; peekId: string;
}; };
export const IssuePeekOverview: FC<TIssuePeekOverview> = observer((props) => { export const IssuePeekOverview: FC<TIssuePeekOverview> = observer((props) => {
const { workspaceSlug, projectId, peekId } = props; const { anchor, peekId } = props;
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
// query params // query params
@ -34,21 +33,23 @@ export const IssuePeekOverview: FC<TIssuePeekOverview> = observer((props) => {
const issueDetails = issueDetailStore.peekId && peekId ? issueDetailStore.details[peekId.toString()] : undefined; const issueDetails = issueDetailStore.peekId && peekId ? issueDetailStore.details[peekId.toString()] : undefined;
useEffect(() => { useEffect(() => {
if (workspaceSlug && projectId && peekId && issueStore.issues && issueStore.issues.length > 0) { if (anchor && peekId && issueStore.issues && issueStore.issues.length > 0) {
if (!issueDetails) { if (!issueDetails) {
issueDetailStore.fetchIssueDetails(workspaceSlug.toString(), projectId.toString(), peekId.toString()); issueDetailStore.fetchIssueDetails(anchor, peekId.toString());
} }
} }
}, [workspaceSlug, projectId, issueDetailStore, issueDetails, peekId, issueStore.issues]); }, [anchor, issueDetailStore, issueDetails, peekId, issueStore.issues]);
const handleClose = () => { const handleClose = () => {
issueDetailStore.setPeekId(null); issueDetailStore.setPeekId(null);
let queryParams: any = { board: board }; let queryParams: any = {
board,
};
if (priority && priority.length > 0) queryParams = { ...queryParams, priority: priority }; if (priority && priority.length > 0) queryParams = { ...queryParams, priority: priority };
if (state && state.length > 0) queryParams = { ...queryParams, state: state }; if (state && state.length > 0) queryParams = { ...queryParams, state: state };
if (labels && labels.length > 0) queryParams = { ...queryParams, labels: labels }; if (labels && labels.length > 0) queryParams = { ...queryParams, labels: labels };
queryParams = new URLSearchParams(queryParams).toString(); queryParams = new URLSearchParams(queryParams).toString();
router.push(`/${workspaceSlug}/${projectId}?${queryParams}`); router.push(`/issues/${anchor}?${queryParams}`);
}; };
useEffect(() => { useEffect(() => {
@ -80,12 +81,7 @@ export const IssuePeekOverview: FC<TIssuePeekOverview> = observer((props) => {
leaveTo="translate-x-full" leaveTo="translate-x-full"
> >
<Dialog.Panel className="fixed right-0 top-0 z-20 h-full w-1/2 bg-custom-background-100 shadow-custom-shadow-sm"> <Dialog.Panel className="fixed right-0 top-0 z-20 h-full w-1/2 bg-custom-background-100 shadow-custom-shadow-sm">
<SidePeekView <SidePeekView anchor={anchor} handleClose={handleClose} issueDetails={issueDetails} />
handleClose={handleClose}
issueDetails={issueDetails}
workspaceSlug={workspaceSlug}
projectId={projectId}
/>
</Dialog.Panel> </Dialog.Panel>
</Transition.Child> </Transition.Child>
</Dialog> </Dialog>
@ -119,20 +115,10 @@ export const IssuePeekOverview: FC<TIssuePeekOverview> = observer((props) => {
}`} }`}
> >
{issueDetailStore.peekMode === "modal" && ( {issueDetailStore.peekMode === "modal" && (
<SidePeekView <SidePeekView anchor={anchor} handleClose={handleClose} issueDetails={issueDetails} />
handleClose={handleClose}
issueDetails={issueDetails}
workspaceSlug={workspaceSlug}
projectId={projectId}
/>
)} )}
{issueDetailStore.peekMode === "full" && ( {issueDetailStore.peekMode === "full" && (
<FullScreenPeekView <FullScreenPeekView anchor={anchor} handleClose={handleClose} issueDetails={issueDetails} />
handleClose={handleClose}
issueDetails={issueDetails}
workspaceSlug={workspaceSlug}
projectId={projectId}
/>
)} )}
</div> </div>
</Dialog.Panel> </Dialog.Panel>

View File

@ -7,22 +7,21 @@ import {
PeekOverviewIssueDetails, PeekOverviewIssueDetails,
PeekOverviewIssueProperties, PeekOverviewIssueProperties,
} from "@/components/issues/peek-overview"; } from "@/components/issues/peek-overview";
// hooks // store hooks
import { useProject } from "@/hooks/store"; import { usePublish } from "@/hooks/store";
// types // types
import { IIssue } from "@/types/issue"; import { IIssue } from "@/types/issue";
type Props = { type Props = {
anchor: string;
handleClose: () => void; handleClose: () => void;
issueDetails: IIssue | undefined; issueDetails: IIssue | undefined;
workspaceSlug: string;
projectId: string;
}; };
export const SidePeekView: React.FC<Props> = observer((props) => { export const SidePeekView: React.FC<Props> = observer((props) => {
const { handleClose, issueDetails, workspaceSlug, projectId } = props; const { anchor, handleClose, issueDetails } = props;
// store hooks
const { settings } = useProject(); const { comments } = usePublish(anchor);
return ( return (
<div className="flex h-full w-full flex-col overflow-hidden"> <div className="flex h-full w-full flex-col overflow-hidden">
@ -42,13 +41,9 @@ export const SidePeekView: React.FC<Props> = observer((props) => {
{/* divider */} {/* divider */}
<div className="my-5 h-[1] w-full border-t border-custom-border-200" /> <div className="my-5 h-[1] w-full border-t border-custom-border-200" />
{/* issue activity/comments */} {/* issue activity/comments */}
{settings?.comments && ( {comments && (
<div className="w-full pb-5"> <div className="w-full pb-5">
<PeekOverviewIssueActivity <PeekOverviewIssueActivity anchor={anchor} issueDetails={issueDetails} />
issueDetails={issueDetails}
workspaceSlug={workspaceSlug}
projectId={projectId}
/>
</div> </div>
)} )}
</div> </div>

View File

@ -37,12 +37,11 @@ export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props)
const { loader, issues, error, fetchPublicIssues } = useIssue(); const { loader, issues, error, fetchPublicIssues } = useIssue();
const issueDetailStore = useIssueDetails(); const issueDetailStore = useIssueDetails();
// derived values // derived values
const { workspace_detail, project } = publishSettings; const { anchor } = publishSettings;
const workspaceSlug = workspace_detail?.slug;
useSWR( useSWR(
workspaceSlug && project ? `WORKSPACE_PROJECT_PUBLIC_ISSUES_${workspaceSlug}_${project}` : null, anchor ? `PUBLIC_ISSUES_${anchor}` : null,
workspaceSlug && project ? () => fetchPublicIssues(workspaceSlug, project, { states, priority, labels }) : null anchor ? () => fetchPublicIssues(anchor, { states, priority, labels }) : null
); );
useEffect(() => { useEffect(() => {
@ -54,11 +53,11 @@ export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props)
// derived values // derived values
const activeLayout = issueFilters?.display_filters?.layout || undefined; const activeLayout = issueFilters?.display_filters?.layout || undefined;
if (!workspaceSlug || !project) return null; if (!anchor) return null;
return ( return (
<div className="relative h-full w-full overflow-hidden"> <div className="relative h-full w-full overflow-hidden">
{peekId && <IssuePeekOverview workspaceSlug={workspaceSlug} projectId={project} peekId={peekId} />} {peekId && <IssuePeekOverview anchor={anchor} peekId={peekId} />}
{loader && !issues ? ( {loader && !issues ? (
<div className="py-10 text-center text-sm text-custom-text-100">Loading...</div> <div className="py-10 text-center text-sm text-custom-text-100">Loading...</div>
@ -80,16 +79,16 @@ export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props)
activeLayout && ( activeLayout && (
<div className="relative flex h-full w-full flex-col overflow-hidden"> <div className="relative flex h-full w-full flex-col overflow-hidden">
{/* applied filters */} {/* applied filters */}
<IssueAppliedFilters workspaceSlug={workspaceSlug} projectId={project} /> <IssueAppliedFilters anchor={anchor} />
{activeLayout === "list" && ( {activeLayout === "list" && (
<div className="relative h-full w-full overflow-y-auto"> <div className="relative h-full w-full overflow-y-auto">
<IssueListView workspaceSlug={workspaceSlug} projectId={project} /> <IssueListView anchor={anchor} />
</div> </div>
)} )}
{activeLayout === "kanban" && ( {activeLayout === "kanban" && (
<div className="relative mx-auto h-full w-full p-5"> <div className="relative mx-auto h-full w-full p-5">
<IssueKanbanView workspaceSlug={workspaceSlug} projectId={project} /> <IssueKanbanView anchor={anchor} />
</div> </div>
)} )}
{activeLayout === "calendar" && <IssueCalendarView />} {activeLayout === "calendar" && <IssueCalendarView />}

View File

@ -1,14 +1,16 @@
import { API_BASE_URL } from "@/helpers/common.helper"; import { API_BASE_URL } from "@/helpers/common.helper";
// services // services
import { APIService } from "@/services/api.service"; import { APIService } from "@/services/api.service";
// types
import { TIssuesResponse } from "@/types/issue";
class IssueService extends APIService { class IssueService extends APIService {
constructor() { constructor() {
super(API_BASE_URL); super(API_BASE_URL);
} }
async getPublicIssues(workspace_slug: string, project_slug: string, params: any): Promise<any> { async fetchPublicIssues(anchor: string, params: any): Promise<TIssuesResponse> {
return this.get(`/api/public/workspaces/${workspace_slug}/project-boards/${project_slug}/issues/`, { return this.get(`/api/public/anchor/${anchor}/issues/`, {
params, params,
}) })
.then((response) => response?.data) .then((response) => response?.data)
@ -17,115 +19,88 @@ class IssueService extends APIService {
}); });
} }
async getIssueById(workspaceSlug: string, projectId: string, issueId: string): Promise<any> { async getIssueById(anchor: string, issueID: string): Promise<any> {
return this.get(`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/`) return this.get(`/api/public/anchor/${anchor}/issues/${issueID}/`)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;
}); });
} }
async getIssueVotes(workspaceSlug: string, projectId: string, issueId: string): Promise<any> { async getIssueVotes(anchor: string, issueID: string): Promise<any> {
return this.get(`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/votes/`) return this.get(`/api/public/anchor/${anchor}/issues/${issueID}/votes/`)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;
}); });
} }
async createIssueVote(workspaceSlug: string, projectId: string, issueId: string, data: any): Promise<any> { async createIssueVote(anchor: string, issueID: string, data: any): Promise<any> {
return this.post( return this.post(`/api/public/anchor/${anchor}/issues/${issueID}/votes/`, data)
`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/votes/`,
data
)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;
}); });
} }
async deleteIssueVote(workspaceSlug: string, projectId: string, issueId: string): Promise<any> { async deleteIssueVote(anchor: string, issueID: string): Promise<any> {
return this.delete(`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/votes/`) return this.delete(`/api/public/anchor/${anchor}/issues/${issueID}/votes/`)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;
}); });
} }
async getIssueReactions(workspaceSlug: string, projectId: string, issueId: string): Promise<any> { async getIssueReactions(anchor: string, issueID: string): Promise<any> {
return this.get(`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/reactions/`) return this.get(`/api/public/anchor/${anchor}/issues/${issueID}/reactions/`)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;
}); });
} }
async createIssueReaction(workspaceSlug: string, projectId: string, issueId: string, data: any): Promise<any> { async createIssueReaction(anchor: string, issueID: string, data: any): Promise<any> {
return this.post( return this.post(`/api/public/anchor/${anchor}/issues/${issueID}/reactions/`, data)
`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/reactions/`,
data
)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;
}); });
} }
async deleteIssueReaction( async deleteIssueReaction(anchor: string, issueID: string, reactionId: string): Promise<any> {
workspaceSlug: string, return this.delete(`/api/public/anchor/${anchor}/issues/${issueID}/reactions/${reactionId}/`)
projectId: string,
issueId: string,
reactionId: string
): Promise<any> {
return this.delete(
`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/reactions/${reactionId}/`
)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;
}); });
} }
async getIssueComments(workspaceSlug: string, projectId: string, issueId: string): Promise<any> { async getIssueComments(anchor: string, issueID: string): Promise<any> {
return this.get(`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/comments/`) return this.get(`/api/public/anchor/${anchor}/issues/${issueID}/comments/`)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;
}); });
} }
async createIssueComment(workspaceSlug: string, projectId: string, issueId: string, data: any): Promise<any> { async createIssueComment(anchor: string, issueID: string, data: any): Promise<any> {
return this.post( return this.post(`/api/public/anchor/${anchor}/issues/${issueID}/comments/`, data)
`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/comments/`,
data
)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;
}); });
} }
async updateIssueComment( async updateIssueComment(anchor: string, issueID: string, commentId: string, data: any): Promise<any> {
workspaceSlug: string, return this.patch(`/api/public/anchor/${anchor}/issues/${issueID}/comments/${commentId}/`, data)
projectId: string,
issueId: string,
commentId: string,
data: any
): Promise<any> {
return this.patch(
`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/comments/${commentId}/`,
data
)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;
}); });
} }
async deleteIssueComment(workspaceSlug: string, projectId: string, issueId: string, commentId: string): Promise<any> { async deleteIssueComment(anchor: string, issueID: string, commentId: string): Promise<any> {
return this.delete( return this.delete(`/api/public/anchor/${anchor}/issues/${issueID}/comments/${commentId}/`)
`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/comments/${commentId}/`
)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;
@ -133,32 +108,21 @@ class IssueService extends APIService {
} }
async createCommentReaction( async createCommentReaction(
workspaceSlug: string, anchor: string,
projectId: string,
commentId: string, commentId: string,
data: { data: {
reaction: string; reaction: string;
} }
): Promise<any> { ): Promise<any> {
return this.post( return this.post(`/api/public/anchor/${anchor}/comments/${commentId}/reactions/`, data)
`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/comments/${commentId}/reactions/`,
data
)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;
}); });
} }
async deleteCommentReaction( async deleteCommentReaction(anchor: string, commentId: string, reactionHex: string): Promise<any> {
workspaceSlug: string, return this.delete(`/api/public/anchor/${anchor}/comments/${commentId}/reactions/${reactionHex}/`)
projectId: string,
commentId: string,
reactionHex: string
): Promise<any> {
return this.delete(
`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/comments/${commentId}/reactions/${reactionHex}/`
)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;

View File

@ -9,16 +9,16 @@ export class ProjectMemberService extends APIService {
super(API_BASE_URL); super(API_BASE_URL);
} }
async fetchProjectMembers(workspaceSlug: string, projectId: string): Promise<IProjectMembership[]> { async fetchProjectMembers(anchor: string): Promise<IProjectMembership[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/`) return this.get(`/api/anchor/${anchor}/members/`)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response?.data; throw error?.response?.data;
}); });
} }
async getProjectMember(workspaceSlug: string, projectId: string, memberId: string): Promise<IProjectMember> { async getProjectMember(anchor: string, memberID: string): Promise<IProjectMember> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/${memberId}/`) return this.get(`/api/anchor/${anchor}/members/${memberID}/`)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response?.data; throw error?.response?.data;

View File

@ -10,108 +10,102 @@ import { IIssue, IPeekMode, IVote } from "@/types/issue";
export interface IIssueDetailStore { export interface IIssueDetailStore {
loader: boolean; loader: boolean;
error: any; error: any;
// peek info // observables
peekId: string | null; peekId: string | null;
peekMode: IPeekMode; peekMode: IPeekMode;
details: { details: {
[key: string]: IIssue; [key: string]: IIssue;
}; };
// peek actions // actions
setPeekId: (issueId: string | null) => void; setPeekId: (issueID: string | null) => void;
setPeekMode: (mode: IPeekMode) => void; setPeekMode: (mode: IPeekMode) => void;
// issue details // issue actions
fetchIssueDetails: (workspaceId: string, projectId: string, issueId: string) => void; fetchIssueDetails: (anchor: string, issueID: string) => void;
// issue comments // comment actions
addIssueComment: (workspaceId: string, projectId: string, issueId: string, data: any) => Promise<void>; addIssueComment: (anchor: string, issueID: string, data: any) => Promise<void>;
updateIssueComment: ( updateIssueComment: (anchor: string, issueID: string, commentID: string, data: any) => Promise<any>;
workspaceId: string, deleteIssueComment: (anchor: string, issueID: string, commentID: string) => void;
projectId: string, addCommentReaction: (anchor: string, issueID: string, commentID: string, reactionHex: string) => void;
issueId: string, removeCommentReaction: (anchor: string, issueID: string, commentID: string, reactionHex: string) => void;
comment_id: string, // reaction actions
data: any addIssueReaction: (anchor: string, issueID: string, reactionHex: string) => void;
) => Promise<any>; removeIssueReaction: (anchor: string, issueID: string, reactionHex: string) => void;
deleteIssueComment: (workspaceId: string, projectId: string, issueId: string, comment_id: string) => void; // vote actions
addCommentReaction: ( addIssueVote: (anchor: string, issueID: string, data: { vote: 1 | -1 }) => Promise<void>;
workspaceId: string, removeIssueVote: (anchor: string, issueID: string) => Promise<void>;
projectId: string,
issueId: string,
commentId: string,
reactionHex: string
) => void;
removeCommentReaction: (
workspaceId: string,
projectId: string,
issueId: string,
commentId: string,
reactionHex: string
) => void;
// issue reactions
addIssueReaction: (workspaceId: string, projectId: string, issueId: string, reactionHex: string) => void;
removeIssueReaction: (workspaceId: string, projectId: string, issueId: string, reactionHex: string) => void;
// issue votes
addIssueVote: (workspaceId: string, projectId: string, issueId: string, data: { vote: 1 | -1 }) => Promise<void>;
removeIssueVote: (workspaceId: string, projectId: string, issueId: string) => Promise<void>;
} }
export class IssueDetailStore implements IIssueDetailStore { export class IssueDetailStore implements IIssueDetailStore {
loader: boolean = false; loader: boolean = false;
error: any = null; error: any = null;
// observables
peekId: string | null = null; peekId: string | null = null;
peekMode: IPeekMode = "side"; peekMode: IPeekMode = "side";
details: { details: {
[key: string]: IIssue; [key: string]: IIssue;
} = {}; } = {};
issueService; // root store
rootStore: RootStore; rootStore: RootStore;
// services
issueService: IssueService;
constructor(_rootStore: RootStore) { constructor(_rootStore: RootStore) {
makeObservable(this, { makeObservable(this, {
loader: observable.ref, loader: observable.ref,
error: observable.ref, error: observable.ref,
// peek // observables
peekId: observable.ref, peekId: observable.ref,
peekMode: observable.ref, peekMode: observable.ref,
details: observable.ref, details: observable,
// actions // actions
setPeekId: action, setPeekId: action,
setPeekMode: action, setPeekMode: action,
// issue actions
fetchIssueDetails: action, fetchIssueDetails: action,
// comment actions
addIssueComment: action, addIssueComment: action,
updateIssueComment: action, updateIssueComment: action,
deleteIssueComment: action, deleteIssueComment: action,
addCommentReaction: action, addCommentReaction: action,
removeCommentReaction: action, removeCommentReaction: action,
// reaction actions
addIssueReaction: action, addIssueReaction: action,
removeIssueReaction: action, removeIssueReaction: action,
// vote actions
addIssueVote: action, addIssueVote: action,
removeIssueVote: action, removeIssueVote: action,
}); });
this.issueService = new IssueService();
this.rootStore = _rootStore; this.rootStore = _rootStore;
this.issueService = new IssueService();
} }
setPeekId = (issueId: string | null) => { setPeekId = (issueID: string | null) => {
this.peekId = issueId; this.peekId = issueID;
}; };
setPeekMode = (mode: IPeekMode) => { setPeekMode = (mode: IPeekMode) => {
this.peekMode = mode; this.peekMode = mode;
}; };
fetchIssueDetails = async (workspaceSlug: string, projectId: string, issueId: string) => { /**
* @description fetc
* @param {string} anchor
* @param {string} issueID
*/
fetchIssueDetails = async (anchor: string, issueID: string) => {
try { try {
this.loader = true; this.loader = true;
this.error = null; this.error = null;
const issueDetails = this.rootStore.issue.issues?.find((i) => i.id === issueId); const issueDetails = this.rootStore.issue.issues?.find((i) => i.id === issueID);
const commentsResponse = await this.issueService.getIssueComments(workspaceSlug, projectId, issueId); const commentsResponse = await this.issueService.getIssueComments(anchor, issueID);
if (issueDetails) { if (issueDetails) {
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueID]: {
...(this.details[issueId] ?? issueDetails), ...(this.details[issueID] ?? issueDetails),
comments: commentsResponse, comments: commentsResponse,
}, },
}; };
@ -123,17 +117,17 @@ export class IssueDetailStore implements IIssueDetailStore {
} }
}; };
addIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, data: any) => { addIssueComment = async (anchor: string, issueID: string, data: any) => {
try { try {
const issueDetails = this.rootStore.issue.issues?.find((i) => i.id === issueId); const issueDetails = this.rootStore.issue.issues?.find((i) => i.id === issueID);
const issueCommentResponse = await this.issueService.createIssueComment(workspaceSlug, projectId, issueId, data); const issueCommentResponse = await this.issueService.createIssueComment(anchor, issueID, data);
if (issueDetails) { if (issueDetails) {
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueID]: {
...issueDetails, ...issueDetails,
comments: [...this.details[issueId].comments, issueCommentResponse], comments: [...this.details[issueID].comments, issueCommentResponse],
}, },
}; };
}); });
@ -145,36 +139,30 @@ export class IssueDetailStore implements IIssueDetailStore {
} }
}; };
updateIssueComment = async ( updateIssueComment = async (anchor: string, issueID: string, commentID: string, data: any) => {
workspaceSlug: string,
projectId: string,
issueId: string,
commentId: string,
data: any
) => {
try { try {
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueID]: {
...this.details[issueId], ...this.details[issueID],
comments: this.details[issueId].comments.map((c) => ({ comments: this.details[issueID].comments.map((c) => ({
...c, ...c,
...(c.id === commentId ? data : {}), ...(c.id === commentID ? data : {}),
})), })),
}, },
}; };
}); });
await this.issueService.updateIssueComment(workspaceSlug, projectId, issueId, commentId, data); await this.issueService.updateIssueComment(anchor, issueID, commentID, data);
} catch (error) { } catch (error) {
const issueComments = await this.issueService.getIssueComments(workspaceSlug, projectId, issueId); const issueComments = await this.issueService.getIssueComments(anchor, issueID);
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueID]: {
...this.details[issueId], ...this.details[issueID],
comments: issueComments, comments: issueComments,
}, },
}; };
@ -182,15 +170,15 @@ export class IssueDetailStore implements IIssueDetailStore {
} }
}; };
deleteIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, comment_id: string) => { deleteIssueComment = async (anchor: string, issueID: string, commentID: string) => {
try { try {
await this.issueService.deleteIssueComment(workspaceSlug, projectId, issueId, comment_id); await this.issueService.deleteIssueComment(anchor, issueID, commentID);
const remainingComments = this.details[issueId].comments.filter((c) => c.id != comment_id); const remainingComments = this.details[issueID].comments.filter((c) => c.id != commentID);
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueID]: {
...this.details[issueId], ...this.details[issueID],
comments: remainingComments, comments: remainingComments,
}, },
}; };
@ -200,47 +188,41 @@ export class IssueDetailStore implements IIssueDetailStore {
} }
}; };
addCommentReaction = async ( addCommentReaction = async (anchor: string, issueID: string, commentID: string, reactionHex: string) => {
workspaceSlug: string,
projectId: string,
issueId: string,
commentId: string,
reactionHex: string
) => {
const newReaction = { const newReaction = {
id: uuidv4(), id: uuidv4(),
comment: commentId, comment: commentID,
reaction: reactionHex, reaction: reactionHex,
actor_detail: this.rootStore.user.currentActor, actor_detail: this.rootStore.user.currentActor,
}; };
const newComments = this.details[issueId].comments.map((comment) => ({ const newComments = this.details[issueID].comments.map((comment) => ({
...comment, ...comment,
comment_reactions: comment_reactions:
comment.id === commentId ? [...comment.comment_reactions, newReaction] : comment.comment_reactions, comment.id === commentID ? [...comment.comment_reactions, newReaction] : comment.comment_reactions,
})); }));
try { try {
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueID]: {
...this.details[issueId], ...this.details[issueID],
comments: [...newComments], comments: [...newComments],
}, },
}; };
}); });
await this.issueService.createCommentReaction(workspaceSlug, projectId, commentId, { await this.issueService.createCommentReaction(anchor, commentID, {
reaction: reactionHex, reaction: reactionHex,
}); });
} catch (error) { } catch (error) {
const issueComments = await this.issueService.getIssueComments(workspaceSlug, projectId, issueId); const issueComments = await this.issueService.getIssueComments(anchor, issueID);
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueID]: {
...this.details[issueId], ...this.details[issueID],
comments: issueComments, comments: issueComments,
}, },
}; };
@ -248,39 +230,33 @@ export class IssueDetailStore implements IIssueDetailStore {
} }
}; };
removeCommentReaction = async ( removeCommentReaction = async (anchor: string, issueID: string, commentID: string, reactionHex: string) => {
workspaceSlug: string,
projectId: string,
issueId: string,
commentId: string,
reactionHex: string
) => {
try { try {
const comment = this.details[issueId].comments.find((c) => c.id === commentId); const comment = this.details[issueID].comments.find((c) => c.id === commentID);
const newCommentReactions = comment?.comment_reactions.filter((r) => r.reaction !== reactionHex) ?? []; const newCommentReactions = comment?.comment_reactions.filter((r) => r.reaction !== reactionHex) ?? [];
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueID]: {
...this.details[issueId], ...this.details[issueID],
comments: this.details[issueId].comments.map((c) => ({ comments: this.details[issueID].comments.map((c) => ({
...c, ...c,
comment_reactions: c.id === commentId ? newCommentReactions : c.comment_reactions, comment_reactions: c.id === commentID ? newCommentReactions : c.comment_reactions,
})), })),
}, },
}; };
}); });
await this.issueService.deleteCommentReaction(workspaceSlug, projectId, commentId, reactionHex); await this.issueService.deleteCommentReaction(anchor, commentID, reactionHex);
} catch (error) { } catch (error) {
const issueComments = await this.issueService.getIssueComments(workspaceSlug, projectId, issueId); const issueComments = await this.issueService.getIssueComments(anchor, issueID);
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueID]: {
...this.details[issueId], ...this.details[issueID],
comments: issueComments, comments: issueComments,
}, },
}; };
@ -288,18 +264,18 @@ export class IssueDetailStore implements IIssueDetailStore {
} }
}; };
addIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, reactionHex: string) => { addIssueReaction = async (anchor: string, issueID: string, reactionHex: string) => {
try { try {
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueID]: {
...this.details[issueId], ...this.details[issueID],
reactions: [ reactions: [
...this.details[issueId].reactions, ...this.details[issueID].reactions,
{ {
id: uuidv4(), id: uuidv4(),
issue: issueId, issue: issueID,
reaction: reactionHex, reaction: reactionHex,
actor_detail: this.rootStore.user.currentActor, actor_detail: this.rootStore.user.currentActor,
}, },
@ -308,17 +284,17 @@ export class IssueDetailStore implements IIssueDetailStore {
}; };
}); });
await this.issueService.createIssueReaction(workspaceSlug, projectId, issueId, { await this.issueService.createIssueReaction(anchor, issueID, {
reaction: reactionHex, reaction: reactionHex,
}); });
} catch (error) { } catch (error) {
console.log("Failed to add issue vote"); console.log("Failed to add issue vote");
const issueReactions = await this.issueService.getIssueReactions(workspaceSlug, projectId, issueId); const issueReactions = await this.issueService.getIssueReactions(anchor, issueID);
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueID]: {
...this.details[issueId], ...this.details[issueID],
reactions: issueReactions, reactions: issueReactions,
}, },
}; };
@ -326,31 +302,31 @@ export class IssueDetailStore implements IIssueDetailStore {
} }
}; };
removeIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, reactionHex: string) => { removeIssueReaction = async (anchor: string, issueID: string, reactionHex: string) => {
try { try {
const newReactions = this.details[issueId].reactions.filter( const newReactions = this.details[issueID].reactions.filter(
(_r) => !(_r.reaction === reactionHex && _r.actor_detail.id === this.rootStore.user.data?.id) (_r) => !(_r.reaction === reactionHex && _r.actor_detail.id === this.rootStore.user.data?.id)
); );
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueID]: {
...this.details[issueId], ...this.details[issueID],
reactions: newReactions, reactions: newReactions,
}, },
}; };
}); });
await this.issueService.deleteIssueReaction(workspaceSlug, projectId, issueId, reactionHex); await this.issueService.deleteIssueReaction(anchor, issueID, reactionHex);
} catch (error) { } catch (error) {
console.log("Failed to remove issue reaction"); console.log("Failed to remove issue reaction");
const reactions = await this.issueService.getIssueReactions(workspaceSlug, projectId, issueId); const reactions = await this.issueService.getIssueReactions(anchor, issueID);
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueID]: {
...this.details[issueId], ...this.details[issueID],
reactions: reactions, reactions: reactions,
}, },
}; };
@ -358,39 +334,44 @@ export class IssueDetailStore implements IIssueDetailStore {
} }
}; };
addIssueVote = async (workspaceSlug: string, projectId: string, issueId: string, data: { vote: 1 | -1 }) => { addIssueVote = async (anchor: string, issueID: string, data: { vote: 1 | -1 }) => {
const publishSettings = this.rootStore.publishList?.publishMap?.[anchor];
const projectID = publishSettings?.project;
const workspaceSlug = publishSettings?.workspace_detail?.slug;
if (!projectID || !workspaceSlug) throw new Error("Publish settings not found");
const newVote: IVote = { const newVote: IVote = {
actor: this.rootStore.user.data?.id ?? "", actor: this.rootStore.user.data?.id ?? "",
actor_detail: this.rootStore.user.currentActor, actor_detail: this.rootStore.user.currentActor,
issue: issueId, issue: issueID,
project: projectId, project: projectID,
workspace: workspaceSlug, workspace: workspaceSlug,
vote: data.vote, vote: data.vote,
}; };
const filteredVotes = this.details[issueId].votes.filter((v) => v.actor !== this.rootStore.user.data?.id); const filteredVotes = this.details[issueID].votes.filter((v) => v.actor !== this.rootStore.user.data?.id);
try { try {
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueID]: {
...this.details[issueId], ...this.details[issueID],
votes: [...filteredVotes, newVote], votes: [...filteredVotes, newVote],
}, },
}; };
}); });
await this.issueService.createIssueVote(workspaceSlug, projectId, issueId, data); await this.issueService.createIssueVote(anchor, issueID, data);
} catch (error) { } catch (error) {
console.log("Failed to add issue vote"); console.log("Failed to add issue vote");
const issueVotes = await this.issueService.getIssueVotes(workspaceSlug, projectId, issueId); const issueVotes = await this.issueService.getIssueVotes(anchor, issueID);
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueID]: {
...this.details[issueId], ...this.details[issueID],
votes: issueVotes, votes: issueVotes,
}, },
}; };
@ -398,30 +379,30 @@ export class IssueDetailStore implements IIssueDetailStore {
} }
}; };
removeIssueVote = async (workspaceSlug: string, projectId: string, issueId: string) => { removeIssueVote = async (anchor: string, issueID: string) => {
const newVotes = this.details[issueId].votes.filter((v) => v.actor !== this.rootStore.user.data?.id); const newVotes = this.details[issueID].votes.filter((v) => v.actor !== this.rootStore.user.data?.id);
try { try {
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueID]: {
...this.details[issueId], ...this.details[issueID],
votes: newVotes, votes: newVotes,
}, },
}; };
}); });
await this.issueService.deleteIssueVote(workspaceSlug, projectId, issueId); await this.issueService.deleteIssueVote(anchor, issueID);
} catch (error) { } catch (error) {
console.log("Failed to remove issue vote"); console.log("Failed to remove issue vote");
const issueVotes = await this.issueService.getIssueVotes(workspaceSlug, projectId, issueId); const issueVotes = await this.issueService.getIssueVotes(anchor, issueID);
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueID]: {
...this.details[issueId], ...this.details[issueID],
votes: issueVotes, votes: issueVotes,
}, },
}; };

View File

@ -1,87 +1,85 @@
import { observable, action, makeObservable, runInAction } from "mobx"; import { observable, action, makeObservable, runInAction } from "mobx";
import { computedFn } from "mobx-utils";
// services // services
import IssueService from "@/services/issue.service"; import IssueService from "@/services/issue.service";
// types // types
import { IIssue, IIssueState, IIssueLabel } from "@/types/issue"; import { IIssue, IIssueState, IIssueLabel } from "@/types/issue";
// store // store
import { RootStore } from "./root.store"; import { RootStore } from "./root.store";
// import { IssueDetailType, TIssueBoardKeys } from "types/issue";
export interface IIssueStore { export interface IIssueStore {
loader: boolean; loader: boolean;
error: any; error: any;
// issue options // observables
issues: IIssue[] | null; issues: IIssue[];
states: IIssueState[] | null; states: IIssueState[];
labels: IIssueLabel[] | null; labels: IIssueLabel[];
// filtering // filter observables
filteredStates: string[]; filteredStates: string[];
filteredLabels: string[]; filteredLabels: string[];
filteredPriorities: string[]; filteredPriorities: string[];
// service
issueService: any;
// actions // actions
fetchPublicIssues: (workspace_slug: string, project_slug: string, params: any) => Promise<void>; fetchPublicIssues: (anchor: string, params: any) => Promise<void>;
getCountOfIssuesByState: (state: string) => number; // helpers
getFilteredIssuesByState: (state: string) => IIssue[]; getCountOfIssuesByState: (stateID: string) => number;
getFilteredIssuesByState: (stateID: string) => IIssue[];
} }
export class IssueStore implements IIssueStore { export class IssueStore implements IIssueStore {
loader: boolean = false; loader: boolean = false;
error: any | null = null; error: any | null = null;
// observables
states: IIssueState[] | null = []; states: IIssueState[] = [];
labels: IIssueLabel[] | null = []; labels: IIssueLabel[] = [];
issues: IIssue[] = [];
// filter observables
filteredStates: string[] = []; filteredStates: string[] = [];
filteredLabels: string[] = []; filteredLabels: string[] = [];
filteredPriorities: string[] = []; filteredPriorities: string[] = [];
// root store
issues: IIssue[] | null = [];
issue_detail: any = {};
rootStore: RootStore; rootStore: RootStore;
issueService: any; // services
issueService: IssueService;
constructor(_rootStore: any) { constructor(_rootStore: RootStore) {
makeObservable(this, { makeObservable(this, {
// observable loader: observable.ref,
loader: observable,
error: observable, error: observable,
// issue options // observables
states: observable.ref, states: observable,
labels: observable.ref, labels: observable,
// filtering issues: observable,
filteredStates: observable.ref, // filter observables
filteredLabels: observable.ref, filteredStates: observable,
filteredPriorities: observable.ref, filteredLabels: observable,
// issues filteredPriorities: observable,
issues: observable.ref,
issue_detail: observable.ref,
// actions // actions
fetchPublicIssues: action, fetchPublicIssues: action,
getFilteredIssuesByState: action,
}); });
this.rootStore = _rootStore; this.rootStore = _rootStore;
this.issueService = new IssueService(); this.issueService = new IssueService();
} }
fetchPublicIssues = async (workspaceSlug: string, projectId: string, params: any) => { /**
* @description fetch issues, states and labels
* @param {string} anchor
* @param params
*/
fetchPublicIssues = async (anchor: string, params: any) => {
try { try {
this.loader = true; runInAction(() => {
this.error = null; this.loader = true;
this.error = null;
});
const response = await this.issueService.getPublicIssues(workspaceSlug, projectId, params); const response = await this.issueService.fetchPublicIssues(anchor, params);
if (response) { if (response) {
const states: IIssueState[] = [...response?.states];
const labels: IIssueLabel[] = [...response?.labels];
const issues: IIssue[] = [...response?.issues];
runInAction(() => { runInAction(() => {
this.states = states; this.states = response.states;
this.labels = labels; this.labels = response.labels;
this.issues = issues; this.issues = response.issues;
this.loader = false; this.loader = false;
}); });
} }
@ -91,11 +89,21 @@ export class IssueStore implements IIssueStore {
} }
}; };
// computed /**
getCountOfIssuesByState(state_id: string): number { * @description get total count of issues under a particular state
return this.issues?.filter((issue) => issue.state == state_id).length || 0; * @param {string} stateID
} * @returns {number}
*/
getCountOfIssuesByState = computedFn(
(stateID: string) => this.issues?.filter((issue) => issue.state == stateID).length || 0
);
getFilteredIssuesByState = (state_id: string): IIssue[] | [] => /**
this.issues?.filter((issue) => issue.state == state_id) || []; * @description get array of issues under a particular state
* @param {string} stateID
* @returns {IIssue[]}
*/
getFilteredIssuesByState = computedFn(
(stateID: string) => this.issues?.filter((issue) => issue.state == stateID) || []
);
} }

View File

@ -18,7 +18,7 @@ export interface IProjectStore {
canComment: boolean; canComment: boolean;
canVote: boolean; canVote: boolean;
// actions // actions
fetchProjectSettings: (workspace_slug: string, project_slug: string) => Promise<void>; fetchProjectSettings: (workspaceSlug: string, project_slug: string) => Promise<void>;
hydrate: (projectSettings: any) => void; hydrate: (projectSettings: any) => void;
} }
@ -64,12 +64,12 @@ export class ProjectStore implements IProjectStore {
return this.settings?.votes ?? false; return this.settings?.votes ?? false;
} }
fetchProjectSettings = async (workspace_slug: string, project_slug: string) => { fetchProjectSettings = async (workspaceSlug: string, project_slug: string) => {
try { try {
this.loader = true; this.loader = true;
this.error = null; this.error = null;
const response = await this.projectService.getProjectSettings(workspace_slug, project_slug); const response = await this.projectService.getProjectSettings(workspaceSlug, project_slug);
if (response) { if (response) {
this.store.issueFilter.updateLayoutOptions(response?.views); this.store.issueFilter.updateLayoutOptions(response?.views);

View File

@ -1,11 +1,14 @@
import { observable, makeObservable } from "mobx"; import { observable, makeObservable, computed } from "mobx";
// store types // store types
import { RootStore } from "@/store/root.store"; import { RootStore } from "@/store/root.store";
// types // types
import { TProjectDetails, TViewDetails, TWorkspaceDetails } from "@/types/project"; import { TProjectDetails, TViewDetails, TWorkspaceDetails } from "@/types/project";
import { TPublishSettings } from "@/types/publish"; import { TPublishSettings } from "@/types/publish";
export interface IPublishStore extends TPublishSettings {} export interface IPublishStore extends TPublishSettings {
// computed
workspaceSlug: string | undefined;
}
export class PublishStore implements IPublishStore { export class PublishStore implements IPublishStore {
// observables // observables
@ -62,6 +65,12 @@ export class PublishStore implements IPublishStore {
votes: observable.ref, votes: observable.ref,
workspace: observable.ref, workspace: observable.ref,
workspace_detail: observable, workspace_detail: observable,
// computed
workspaceSlug: computed,
}); });
} }
get workspaceSlug() {
return this?.workspace_detail?.slug ?? undefined;
}
} }

View File

@ -43,6 +43,12 @@ export type TIssueQueryFilters = Partial<TFilters>;
export type TIssueQueryFiltersParams = Partial<Record<keyof TFilters, string>>; export type TIssueQueryFiltersParams = Partial<Record<keyof TFilters, string>>;
export type TIssuesResponse = {
states: IIssueState[];
labels: IIssueLabel[];
issues: IIssue[];
};
export interface IIssue { export interface IIssue {
id: string; id: string;
comments: Comment[]; comments: Comment[];
@ -79,6 +85,7 @@ export interface IIssueLabel {
id: string; id: string;
name: string; name: string;
color: string; color: string;
parent: string | null;
} }
export interface IVote { export interface IVote {