= ({
+ width = "14",
+ height = "14",
+ className,
+ color = issueGroupColors["unstarted"],
+}) => (
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/apps/space/components/icons/types.d.ts b/apps/space/components/icons/types.d.ts
new file mode 100644
index 000000000..f82a18147
--- /dev/null
+++ b/apps/space/components/icons/types.d.ts
@@ -0,0 +1,6 @@
+export type Props = {
+ width?: string | number;
+ height?: string | number;
+ color?: string;
+ className?: string;
+};
diff --git a/apps/space/components/issues/board-views/block-due-date.tsx b/apps/space/components/issues/board-views/block-due-date.tsx
new file mode 100644
index 000000000..6d3cc3cc0
--- /dev/null
+++ b/apps/space/components/issues/board-views/block-due-date.tsx
@@ -0,0 +1,32 @@
+"use client";
+
+// helpers
+import { renderDateFormat } from "constants/helpers";
+
+export const findHowManyDaysLeft = (date: string | Date) => {
+ const today = new Date();
+ const eventDate = new Date(date);
+ const timeDiff = Math.abs(eventDate.getTime() - today.getTime());
+ return Math.ceil(timeDiff / (1000 * 3600 * 24));
+};
+
+const validDate = (date: any, state: string): string => {
+ if (date === null || ["backlog", "unstarted", "cancelled"].includes(state))
+ return `bg-gray-500/10 text-gray-500 border-gray-500/50`;
+ else {
+ const today = new Date();
+ const dueDate = new Date(date);
+
+ if (dueDate < today) return `bg-red-500/10 text-red-500 border-red-500/50`;
+ else return `bg-green-500/10 text-green-500 border-green-500/50`;
+ }
+};
+
+export const IssueBlockDueDate = ({ due_date, state }: any) => (
+
+ {renderDateFormat(due_date)}
+
+);
diff --git a/apps/space/components/issues/board-views/block-labels.tsx b/apps/space/components/issues/board-views/block-labels.tsx
new file mode 100644
index 000000000..90cc1629c
--- /dev/null
+++ b/apps/space/components/issues/board-views/block-labels.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+export const IssueBlockLabels = ({ labels }: any) => (
+
+ {labels &&
+ labels.length > 0 &&
+ labels.map((_label: any) => (
+
+ ))}
+
+);
diff --git a/apps/space/components/issues/board-views/block-priority.tsx b/apps/space/components/issues/board-views/block-priority.tsx
new file mode 100644
index 000000000..61ca50765
--- /dev/null
+++ b/apps/space/components/issues/board-views/block-priority.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+// types
+import { TIssuePriorityKey } from "store/types/issue";
+// constants
+import { issuePriorityFilter } from "constants/data";
+
+export const IssueBlockPriority = ({ priority }: { priority: TIssuePriorityKey | null }) => {
+ const priority_detail = priority != null ? issuePriorityFilter(priority) : null;
+
+ if (priority_detail === null) return <>>;
+ return (
+
+ {priority_detail?.icon}
+
+ );
+};
diff --git a/apps/space/components/issues/board-views/block-state.tsx b/apps/space/components/issues/board-views/block-state.tsx
new file mode 100644
index 000000000..87cd65938
--- /dev/null
+++ b/apps/space/components/issues/board-views/block-state.tsx
@@ -0,0 +1,18 @@
+"use client";
+
+// constants
+import { issueGroupFilter } from "constants/data";
+
+export const IssueBlockState = ({ state }: any) => {
+ const stateGroup = issueGroupFilter(state.group);
+
+ if (stateGroup === null) return <>>;
+ return (
+
+ );
+};
diff --git a/apps/space/components/issues/board-views/calendar/index.tsx b/apps/space/components/issues/board-views/calendar/index.tsx
new file mode 100644
index 000000000..0edeca96c
--- /dev/null
+++ b/apps/space/components/issues/board-views/calendar/index.tsx
@@ -0,0 +1 @@
+export const IssueCalendarView = () =>
;
diff --git a/apps/space/components/issues/board-views/gantt/index.tsx b/apps/space/components/issues/board-views/gantt/index.tsx
new file mode 100644
index 000000000..5da924b2c
--- /dev/null
+++ b/apps/space/components/issues/board-views/gantt/index.tsx
@@ -0,0 +1 @@
+export const IssueGanttView = () =>
;
diff --git a/apps/space/components/issues/board-views/kanban/block.tsx b/apps/space/components/issues/board-views/kanban/block.tsx
new file mode 100644
index 000000000..304e05612
--- /dev/null
+++ b/apps/space/components/issues/board-views/kanban/block.tsx
@@ -0,0 +1,57 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// components
+import { IssueBlockPriority } from "components/issues/board-views/block-priority";
+import { IssueBlockState } from "components/issues/board-views/block-state";
+import { IssueBlockLabels } from "components/issues/board-views/block-labels";
+import { IssueBlockDueDate } from "components/issues/board-views/block-due-date";
+// mobx hook
+import { useMobxStore } from "lib/mobx/store-provider";
+// interfaces
+import { IIssue } from "store/types/issue";
+import { RootStore } from "store/root";
+
+export const IssueListBlock = ({ issue }: { issue: IIssue }) => {
+ const store: RootStore = useMobxStore();
+
+ return (
+
+ {/* id */}
+
+ {store?.project?.project?.identifier}-{issue?.sequence_id}
+
+
+ {/* name */}
+
{issue.name}
+
+ {/* priority */}
+
+ {issue?.priority && (
+
+
+
+ )}
+ {/* state */}
+ {issue?.state_detail && (
+
+
+
+ )}
+ {/* labels */}
+ {issue?.label_details && issue?.label_details.length > 0 && (
+
+
+
+ )}
+ {/* due date */}
+ {issue?.target_date && (
+
+
+
+ )}
+
+
+ );
+};
diff --git a/apps/space/components/issues/board-views/kanban/header.tsx b/apps/space/components/issues/board-views/kanban/header.tsx
new file mode 100644
index 000000000..43c19f5f5
--- /dev/null
+++ b/apps/space/components/issues/board-views/kanban/header.tsx
@@ -0,0 +1,31 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// interfaces
+import { IIssueState } from "store/types/issue";
+// constants
+import { issueGroupFilter } from "constants/data";
+// mobx hook
+import { useMobxStore } from "lib/mobx/store-provider";
+import { RootStore } from "store/root";
+
+export const IssueListHeader = observer(({ state }: { state: IIssueState }) => {
+ const store: RootStore = useMobxStore();
+
+ const stateGroup = issueGroupFilter(state.group);
+
+ if (stateGroup === null) return <>>;
+
+ return (
+
+
+
+
+
{state?.name}
+
+ {store.issue.getCountOfIssuesByState(state.id)}
+
+
+ );
+});
diff --git a/apps/space/components/issues/board-views/kanban/index.tsx b/apps/space/components/issues/board-views/kanban/index.tsx
new file mode 100644
index 000000000..d716356ff
--- /dev/null
+++ b/apps/space/components/issues/board-views/kanban/index.tsx
@@ -0,0 +1,44 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// components
+import { IssueListHeader } from "components/issues/board-views/kanban/header";
+import { IssueListBlock } from "components/issues/board-views/kanban/block";
+// interfaces
+import { IIssueState, IIssue } from "store/types/issue";
+// mobx hook
+import { useMobxStore } from "lib/mobx/store-provider";
+import { RootStore } from "store/root";
+
+export const IssueKanbanView = observer(() => {
+ const store: RootStore = useMobxStore();
+
+ return (
+
+ {store?.issue?.states &&
+ store?.issue?.states.length > 0 &&
+ store?.issue?.states.map((_state: IIssueState) => (
+
+
+
+
+
+ {store.issue.getFilteredIssuesByState(_state.id) &&
+ store.issue.getFilteredIssuesByState(_state.id).length > 0 ? (
+
+ {store.issue.getFilteredIssuesByState(_state.id).map((_issue: IIssue) => (
+
+ ))}
+
+ ) : (
+
+ No Issues are available.
+
+ )}
+
+
+ ))}
+
+ );
+});
diff --git a/apps/space/components/issues/board-views/list/block.tsx b/apps/space/components/issues/board-views/list/block.tsx
new file mode 100644
index 000000000..b9dfcc6ab
--- /dev/null
+++ b/apps/space/components/issues/board-views/list/block.tsx
@@ -0,0 +1,59 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// components
+import { IssueBlockPriority } from "components/issues/board-views/block-priority";
+import { IssueBlockState } from "components/issues/board-views/block-state";
+import { IssueBlockLabels } from "components/issues/board-views/block-labels";
+import { IssueBlockDueDate } from "components/issues/board-views/block-due-date";
+// mobx hook
+import { useMobxStore } from "lib/mobx/store-provider";
+// interfaces
+import { IIssue } from "store/types/issue";
+import { RootStore } from "store/root";
+
+export const IssueListBlock = ({ issue }: { issue: IIssue }) => {
+ const store: RootStore = useMobxStore();
+
+ return (
+
+
+ {/* id */}
+
+ {store?.project?.project?.identifier}-{issue?.sequence_id}
+
+ {/* name */}
+
{issue.name}
+
+
+ {/* priority */}
+ {issue?.priority && (
+
+
+
+ )}
+
+ {/* state */}
+ {issue?.state_detail && (
+
+
+
+ )}
+
+ {/* labels */}
+ {issue?.label_details && issue?.label_details.length > 0 && (
+
+
+
+ )}
+
+ {/* due date */}
+ {issue?.target_date && (
+
+
+
+ )}
+
+ );
+};
diff --git a/apps/space/components/issues/board-views/list/header.tsx b/apps/space/components/issues/board-views/list/header.tsx
new file mode 100644
index 000000000..e87cac6f7
--- /dev/null
+++ b/apps/space/components/issues/board-views/list/header.tsx
@@ -0,0 +1,31 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// interfaces
+import { IIssueState } from "store/types/issue";
+// constants
+import { issueGroupFilter } from "constants/data";
+// mobx hook
+import { useMobxStore } from "lib/mobx/store-provider";
+import { RootStore } from "store/root";
+
+export const IssueListHeader = observer(({ state }: { state: IIssueState }) => {
+ const store: RootStore = useMobxStore();
+
+ const stateGroup = issueGroupFilter(state.group);
+
+ if (stateGroup === null) return <>>;
+
+ return (
+
+
+
+
+
{state?.name}
+
+ {store.issue.getCountOfIssuesByState(state.id)}
+
+
+ );
+});
diff --git a/apps/space/components/issues/board-views/list/index.tsx b/apps/space/components/issues/board-views/list/index.tsx
new file mode 100644
index 000000000..7a7ec0de1
--- /dev/null
+++ b/apps/space/components/issues/board-views/list/index.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// components
+import { IssueListHeader } from "components/issues/board-views/list/header";
+import { IssueListBlock } from "components/issues/board-views/list/block";
+// interfaces
+import { IIssueState, IIssue } from "store/types/issue";
+// mobx hook
+import { useMobxStore } from "lib/mobx/store-provider";
+import { RootStore } from "store/root";
+
+export const IssueListView = observer(() => {
+ const store: RootStore = useMobxStore();
+
+ return (
+ <>
+ {store?.issue?.states &&
+ store?.issue?.states.length > 0 &&
+ store?.issue?.states.map((_state: IIssueState) => (
+
+
+ {store.issue.getFilteredIssuesByState(_state.id) &&
+ store.issue.getFilteredIssuesByState(_state.id).length > 0 ? (
+
+ {store.issue.getFilteredIssuesByState(_state.id).map((_issue: IIssue) => (
+
+ ))}
+
+ ) : (
+
No Issues are available.
+ )}
+
+ ))}
+ >
+ );
+});
diff --git a/apps/space/components/issues/board-views/spreadsheet/index.tsx b/apps/space/components/issues/board-views/spreadsheet/index.tsx
new file mode 100644
index 000000000..45ebf2792
--- /dev/null
+++ b/apps/space/components/issues/board-views/spreadsheet/index.tsx
@@ -0,0 +1 @@
+export const IssueSpreadsheetView = () =>
;
diff --git a/apps/space/components/issues/filters-render/date.tsx b/apps/space/components/issues/filters-render/date.tsx
new file mode 100644
index 000000000..e01d0ae58
--- /dev/null
+++ b/apps/space/components/issues/filters-render/date.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// mobx hook
+import { useMobxStore } from "lib/mobx/store-provider";
+
+const IssueDateFilter = observer(() => {
+ const store = useMobxStore();
+
+ return (
+ <>
+
+
Due Date
+
+ {/*
+
+ close
+
+
Backlog
+
+ close
+
+
*/}
+
+
+ close
+
+
+ >
+ );
+});
+
+export default IssueDateFilter;
diff --git a/apps/space/components/issues/filters-render/index.tsx b/apps/space/components/issues/filters-render/index.tsx
new file mode 100644
index 000000000..366ae1030
--- /dev/null
+++ b/apps/space/components/issues/filters-render/index.tsx
@@ -0,0 +1,40 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// components
+import IssueStateFilter from "./state";
+import IssueLabelFilter from "./label";
+import IssuePriorityFilter from "./priority";
+import IssueDateFilter from "./date";
+// mobx hook
+import { useMobxStore } from "lib/mobx/store-provider";
+import { RootStore } from "store/root";
+
+const IssueFilter = observer(() => {
+ const store: RootStore = useMobxStore();
+
+ const clearAllFilters = () => {};
+
+ return (
+
+ {/* state */}
+ {store?.issue?.states &&
}
+ {/* labels */}
+ {store?.issue?.labels &&
}
+ {/* priority */}
+
+ {/* due date */}
+
+ {/* clear all filters */}
+
+
+ );
+});
+
+export default IssueFilter;
diff --git a/apps/space/components/issues/filters-render/label/filter-label-block.tsx b/apps/space/components/issues/filters-render/label/filter-label-block.tsx
new file mode 100644
index 000000000..0606bfc95
--- /dev/null
+++ b/apps/space/components/issues/filters-render/label/filter-label-block.tsx
@@ -0,0 +1,34 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// mobx hook
+import { useMobxStore } from "lib/mobx/store-provider";
+// interfaces
+import { IIssueLabel } from "store/types/issue";
+// constants
+import { issueGroupFilter } from "constants/data";
+
+export const RenderIssueLabel = observer(({ label }: { label: IIssueLabel }) => {
+ const store = useMobxStore();
+
+ const removeLabelFromFilter = () => {};
+
+ return (
+
+
+
{label?.name}
+
+ close
+
+
+ );
+});
diff --git a/apps/space/components/issues/filters-render/label/index.tsx b/apps/space/components/issues/filters-render/label/index.tsx
new file mode 100644
index 000000000..7d313153a
--- /dev/null
+++ b/apps/space/components/issues/filters-render/label/index.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// components
+import { RenderIssueLabel } from "./filter-label-block";
+// interfaces
+import { IIssueLabel } from "store/types/issue";
+// mobx hook
+import { useMobxStore } from "lib/mobx/store-provider";
+import { RootStore } from "store/root";
+
+const IssueLabelFilter = observer(() => {
+ const store: RootStore = useMobxStore();
+
+ const clearLabelFilters = () => {};
+
+ return (
+ <>
+
+
Labels
+
+ {store?.issue?.labels &&
+ store?.issue?.labels.map((_label: IIssueLabel, _index: number) => )}
+
+
+ close
+
+
+ >
+ );
+});
+
+export default IssueLabelFilter;
diff --git a/apps/space/components/issues/filters-render/priority/filter-priority-block.tsx b/apps/space/components/issues/filters-render/priority/filter-priority-block.tsx
new file mode 100644
index 000000000..98173fd66
--- /dev/null
+++ b/apps/space/components/issues/filters-render/priority/filter-priority-block.tsx
@@ -0,0 +1,33 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// mobx hook
+import { useMobxStore } from "lib/mobx/store-provider";
+// interfaces
+import { IIssuePriorityFilters } from "store/types/issue";
+
+export const RenderIssuePriority = observer(({ priority }: { priority: IIssuePriorityFilters }) => {
+ const store = useMobxStore();
+
+ const removePriorityFromFilter = () => {};
+
+ return (
+
+
+ {priority?.icon}
+
+
{priority?.title}
+
+ close
+
+
+ );
+});
diff --git a/apps/space/components/issues/filters-render/priority/index.tsx b/apps/space/components/issues/filters-render/priority/index.tsx
new file mode 100644
index 000000000..2253a0be2
--- /dev/null
+++ b/apps/space/components/issues/filters-render/priority/index.tsx
@@ -0,0 +1,36 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// mobx hook
+import { useMobxStore } from "lib/mobx/store-provider";
+// components
+import { RenderIssuePriority } from "./filter-priority-block";
+// interfaces
+import { IIssuePriorityFilters } from "store/types/issue";
+// constants
+import { issuePriorityFilters } from "constants/data";
+
+const IssuePriorityFilter = observer(() => {
+ const store = useMobxStore();
+
+ return (
+ <>
+
+
Priority
+
+ {issuePriorityFilters.map((_priority: IIssuePriorityFilters, _index: number) => (
+
+ ))}
+
+
+ close
+
+
{" "}
+ >
+ );
+});
+
+export default IssuePriorityFilter;
diff --git a/apps/space/components/issues/filters-render/state/filter-state-block.tsx b/apps/space/components/issues/filters-render/state/filter-state-block.tsx
new file mode 100644
index 000000000..95a4f4c70
--- /dev/null
+++ b/apps/space/components/issues/filters-render/state/filter-state-block.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// mobx hook
+import { useMobxStore } from "lib/mobx/store-provider";
+// interfaces
+import { IIssueState } from "store/types/issue";
+// constants
+import { issueGroupFilter } from "constants/data";
+
+export const RenderIssueState = observer(({ state }: { state: IIssueState }) => {
+ const store = useMobxStore();
+
+ const stateGroup = issueGroupFilter(state.group);
+
+ const removeStateFromFilter = () => {};
+
+ if (stateGroup === null) return <>>;
+ return (
+
+
+
+
+
{state?.name}
+
+ close
+
+
+ );
+});
diff --git a/apps/space/components/issues/filters-render/state/index.tsx b/apps/space/components/issues/filters-render/state/index.tsx
new file mode 100644
index 000000000..fc73af381
--- /dev/null
+++ b/apps/space/components/issues/filters-render/state/index.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// components
+import { RenderIssueState } from "./filter-state-block";
+// interfaces
+import { IIssueState } from "store/types/issue";
+// mobx hook
+import { useMobxStore } from "lib/mobx/store-provider";
+import { RootStore } from "store/root";
+
+const IssueStateFilter = observer(() => {
+ const store: RootStore = useMobxStore();
+
+ const clearStateFilters = () => {};
+
+ return (
+ <>
+
+
State
+
+ {store?.issue?.states &&
+ store?.issue?.states.map((_state: IIssueState, _index: number) => )}
+
+
+ close
+
+
+ >
+ );
+});
+
+export default IssueStateFilter;
diff --git a/apps/space/components/issues/navbar/index.tsx b/apps/space/components/issues/navbar/index.tsx
new file mode 100644
index 000000000..0207aaee2
--- /dev/null
+++ b/apps/space/components/issues/navbar/index.tsx
@@ -0,0 +1,54 @@
+"use client";
+
+// components
+import { NavbarSearch } from "./search";
+import { NavbarIssueBoardView } from "./issue-board-view";
+import { NavbarIssueFilter } from "./issue-filter";
+import { NavbarIssueView } from "./issue-view";
+import { NavbarTheme } from "./theme";
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// mobx
+import { useMobxStore } from "lib/mobx/store-provider";
+import { RootStore } from "store/root";
+
+const IssueNavbar = observer(() => {
+ const store: RootStore = useMobxStore();
+
+ return (
+
+ {/* project detail */}
+
+
+ {store?.project?.project && store?.project?.project?.icon ? store?.project?.project?.icon : "😊"}
+
+
+ {store?.project?.project?.name || `...`}
+
+
+
+ {/* issue search bar */}
+
+
+
+
+ {/* issue views */}
+
+
+
+
+ {/* issue filters */}
+ {/*
+
+
+
*/}
+
+ {/* theming */}
+ {/*
+
+
*/}
+
+ );
+});
+
+export default IssueNavbar;
diff --git a/apps/space/components/issues/navbar/issue-board-view.tsx b/apps/space/components/issues/navbar/issue-board-view.tsx
new file mode 100644
index 000000000..57c8b27c1
--- /dev/null
+++ b/apps/space/components/issues/navbar/issue-board-view.tsx
@@ -0,0 +1,54 @@
+"use client";
+
+// next imports
+import { useRouter, useParams } from "next/navigation";
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// constants
+import { issueViews } from "constants/data";
+// interfaces
+import { TIssueBoardKeys } from "store/types";
+// mobx
+import { useMobxStore } from "lib/mobx/store-provider";
+import { RootStore } from "store/root";
+
+export const NavbarIssueBoardView = observer(() => {
+ const store: RootStore = useMobxStore();
+
+ const router = useRouter();
+ const routerParams = useParams();
+
+ const { workspace_slug, project_slug } = routerParams as { workspace_slug: string; project_slug: string };
+
+ const handleCurrentBoardView = (boardView: TIssueBoardKeys) => {
+ store?.issue?.setCurrentIssueBoardView(boardView);
+ router.replace(`/${workspace_slug}/${project_slug}?board=${boardView}`);
+ };
+
+ return (
+ <>
+ {store?.project?.workspaceProjectSettings &&
+ issueViews &&
+ issueViews.length > 0 &&
+ issueViews.map(
+ (_view) =>
+ store?.project?.workspaceProjectSettings?.views[_view?.key] && (
+ handleCurrentBoardView(_view?.key)}
+ title={_view?.title}
+ >
+
+ {_view?.icon}
+
+
+ )
+ )}
+ >
+ );
+});
diff --git a/apps/space/components/issues/navbar/issue-filter.tsx b/apps/space/components/issues/navbar/issue-filter.tsx
new file mode 100644
index 000000000..10255882d
--- /dev/null
+++ b/apps/space/components/issues/navbar/issue-filter.tsx
@@ -0,0 +1,13 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// mobx
+import { useMobxStore } from "lib/mobx/store-provider";
+import { RootStore } from "store/root";
+
+export const NavbarIssueFilter = observer(() => {
+ const store: RootStore = useMobxStore();
+
+ return Filter
;
+});
diff --git a/apps/space/components/issues/navbar/issue-view.tsx b/apps/space/components/issues/navbar/issue-view.tsx
new file mode 100644
index 000000000..0a8f5c860
--- /dev/null
+++ b/apps/space/components/issues/navbar/issue-view.tsx
@@ -0,0 +1,13 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// mobx
+import { useMobxStore } from "lib/mobx/store-provider";
+import { RootStore } from "store/root";
+
+export const NavbarIssueView = observer(() => {
+ const store: RootStore = useMobxStore();
+
+ return View
;
+});
diff --git a/apps/space/components/issues/navbar/search.tsx b/apps/space/components/issues/navbar/search.tsx
new file mode 100644
index 000000000..d1cafea6a
--- /dev/null
+++ b/apps/space/components/issues/navbar/search.tsx
@@ -0,0 +1,13 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// mobx
+import { useMobxStore } from "lib/mobx/store-provider";
+import { RootStore } from "store/root";
+
+export const NavbarSearch = observer(() => {
+ const store: RootStore = useMobxStore();
+
+ return
;
+});
diff --git a/apps/space/components/issues/navbar/theme.tsx b/apps/space/components/issues/navbar/theme.tsx
new file mode 100644
index 000000000..c122f8478
--- /dev/null
+++ b/apps/space/components/issues/navbar/theme.tsx
@@ -0,0 +1,28 @@
+"use client";
+
+// mobx react lite
+import { observer } from "mobx-react-lite";
+// mobx
+import { useMobxStore } from "lib/mobx/store-provider";
+import { RootStore } from "store/root";
+
+export const NavbarTheme = observer(() => {
+ const store: RootStore = useMobxStore();
+
+ const handleTheme = () => {
+ store?.theme?.setTheme(store?.theme?.theme === "light" ? "dark" : "light");
+ };
+
+ return (
+
+ {store?.theme?.theme === "light" ? (
+ dark_mode
+ ) : (
+ light_mode
+ )}
+
+ );
+});
diff --git a/apps/space/constants/data.ts b/apps/space/constants/data.ts
new file mode 100644
index 000000000..81ccae116
--- /dev/null
+++ b/apps/space/constants/data.ts
@@ -0,0 +1,153 @@
+// interfaces
+import {
+ IIssueBoardViews,
+ // priority
+ TIssuePriorityKey,
+ // state groups
+ TIssueGroupKey,
+ IIssuePriorityFilters,
+ IIssueGroup,
+} from "store/types/issue";
+// icons
+import {
+ BacklogStateIcon,
+ UnstartedStateIcon,
+ StartedStateIcon,
+ CompletedStateIcon,
+ CancelledStateIcon,
+} from "components/icons";
+
+// all issue views
+export const issueViews: IIssueBoardViews[] = [
+ {
+ key: "list",
+ title: "List View",
+ icon: "format_list_bulleted",
+ className: "",
+ },
+ {
+ key: "kanban",
+ title: "Board View",
+ icon: "grid_view",
+ className: "",
+ },
+ // {
+ // key: "calendar",
+ // title: "Calendar View",
+ // icon: "calendar_month",
+ // className: "",
+ // },
+ // {
+ // key: "spreadsheet",
+ // title: "Spreadsheet View",
+ // icon: "table_chart",
+ // className: "",
+ // },
+ // {
+ // key: "gantt",
+ // title: "Gantt Chart View",
+ // icon: "waterfall_chart",
+ // className: "rotate-90",
+ // },
+];
+
+// issue priority filters
+export const issuePriorityFilters: IIssuePriorityFilters[] = [
+ {
+ key: "urgent",
+ title: "Urgent",
+ className: "border border-red-500/50 bg-red-500/20 text-red-500",
+ icon: "error",
+ },
+ {
+ key: "high",
+ title: "High",
+ className: "border border-orange-500/50 bg-orange-500/20 text-orange-500",
+ icon: "signal_cellular_alt",
+ },
+ {
+ key: "medium",
+ title: "Medium",
+ className: "border border-yellow-500/50 bg-yellow-500/20 text-yellow-500",
+ icon: "signal_cellular_alt_2_bar",
+ },
+ {
+ key: "low",
+ title: "Low",
+ className: "border border-green-500/50 bg-green-500/20 text-green-500",
+ icon: "signal_cellular_alt_1_bar",
+ },
+ {
+ key: "none",
+ title: "None",
+ className: "border border-gray-500/50 bg-gray-500/20 text-gray-500",
+ icon: "block",
+ },
+];
+
+export const issuePriorityFilter = (priorityKey: TIssuePriorityKey): IIssuePriorityFilters | null => {
+ const currentIssuePriority: IIssuePriorityFilters | undefined | null =
+ issuePriorityFilters && issuePriorityFilters.length > 0
+ ? issuePriorityFilters.find((_priority) => _priority.key === priorityKey)
+ : null;
+
+ if (currentIssuePriority === undefined || currentIssuePriority === null) return null;
+ return { ...currentIssuePriority };
+};
+
+// issue group filters
+export const issueGroupColors: {
+ [key: string]: string;
+} = {
+ backlog: "#d9d9d9",
+ unstarted: "#3f76ff",
+ started: "#f59e0b",
+ completed: "#16a34a",
+ cancelled: "#dc2626",
+};
+
+export const issueGroups: IIssueGroup[] = [
+ {
+ key: "backlog",
+ title: "Backlog",
+ color: "#d9d9d9",
+ className: `border-[#d9d9d9]/50 text-[#d9d9d9] bg-[#d9d9d9]/10`,
+ icon: BacklogStateIcon,
+ },
+ {
+ key: "unstarted",
+ title: "Unstarted",
+ color: "#3f76ff",
+ className: `border-[#3f76ff]/50 text-[#3f76ff] bg-[#3f76ff]/10`,
+ icon: UnstartedStateIcon,
+ },
+ {
+ key: "started",
+ title: "Started",
+ color: "#f59e0b",
+ className: `border-[#f59e0b]/50 text-[#f59e0b] bg-[#f59e0b]/10`,
+ icon: StartedStateIcon,
+ },
+ {
+ key: "completed",
+ title: "Completed",
+ color: "#16a34a",
+ className: `border-[#16a34a]/50 text-[#16a34a] bg-[#16a34a]/10`,
+ icon: CompletedStateIcon,
+ },
+ {
+ key: "cancelled",
+ title: "Cancelled",
+ color: "#dc2626",
+ className: `border-[#dc2626]/50 text-[#dc2626] bg-[#dc2626]/10`,
+ icon: CancelledStateIcon,
+ },
+];
+
+export const issueGroupFilter = (issueKey: TIssueGroupKey): IIssueGroup | null => {
+ const currentIssueStateGroup: IIssueGroup | undefined | null =
+ issueGroups && issueGroups.length > 0 ? issueGroups.find((group) => group.key === issueKey) : null;
+
+ if (currentIssueStateGroup === undefined || currentIssueStateGroup === null) return null;
+ return { ...currentIssueStateGroup };
+};
diff --git a/apps/space/constants/helpers.ts b/apps/space/constants/helpers.ts
new file mode 100644
index 000000000..fd4dba217
--- /dev/null
+++ b/apps/space/constants/helpers.ts
@@ -0,0 +1,13 @@
+export const renderDateFormat = (date: string | Date | null) => {
+ if (!date) return "N/A";
+
+ var d = new Date(date),
+ month = "" + (d.getMonth() + 1),
+ day = "" + d.getDate(),
+ year = d.getFullYear();
+
+ if (month.length < 2) month = "0" + month;
+ if (day.length < 2) day = "0" + day;
+
+ return [year, month, day].join("-");
+};
diff --git a/apps/space/lib/mobx-store/root.ts b/apps/space/lib/index.ts
similarity index 100%
rename from apps/space/lib/mobx-store/root.ts
rename to apps/space/lib/index.ts
diff --git a/apps/space/lib/mobx/store-init.tsx b/apps/space/lib/mobx/store-init.tsx
new file mode 100644
index 000000000..2ba2f9024
--- /dev/null
+++ b/apps/space/lib/mobx/store-init.tsx
@@ -0,0 +1,35 @@
+"use client";
+
+import { useEffect } from "react";
+// next imports
+import { useSearchParams } from "next/navigation";
+// interface
+import { TIssueBoardKeys } from "store/types";
+// mobx store
+import { useMobxStore } from "lib/mobx/store-provider";
+import { RootStore } from "store/root";
+
+const MobxStoreInit = () => {
+ const store: RootStore = useMobxStore();
+
+ // search params
+ const routerSearchparams = useSearchParams();
+
+ const board = routerSearchparams.get("board") as TIssueBoardKeys;
+
+ useEffect(() => {
+ // theme
+ const _theme = localStorage && localStorage.getItem("app_theme") ? localStorage.getItem("app_theme") : "light";
+ if (_theme && store?.theme?.theme != _theme) store.theme.setTheme(_theme);
+ else localStorage.setItem("app_theme", _theme && _theme != "light" ? "dark" : "light");
+ }, [store?.theme]);
+
+ // updating default board view when we are in the issues page
+ useEffect(() => {
+ if (board && board != store?.issue?.currentIssueBoardView) store.issue.setCurrentIssueBoardView(board);
+ }, [board, store?.issue]);
+
+ return <>>;
+};
+
+export default MobxStoreInit;
diff --git a/apps/space/lib/mobx/store-provider.tsx b/apps/space/lib/mobx/store-provider.tsx
new file mode 100644
index 000000000..c6fde14ae
--- /dev/null
+++ b/apps/space/lib/mobx/store-provider.tsx
@@ -0,0 +1,28 @@
+"use client";
+
+import { createContext, useContext } from "react";
+// mobx store
+import { RootStore } from "store/root";
+
+let rootStore: RootStore = new RootStore();
+
+export const MobxStoreContext = createContext(rootStore);
+
+const initializeStore = () => {
+ const _rootStore: RootStore = rootStore ?? new RootStore();
+ if (typeof window === "undefined") return _rootStore;
+ if (!rootStore) rootStore = _rootStore;
+ return _rootStore;
+};
+
+export const MobxStoreProvider = ({ children }: any) => {
+ const store: RootStore = initializeStore();
+ return {children} ;
+};
+
+// hook
+export const useMobxStore = () => {
+ const context = useContext(MobxStoreContext);
+ if (context === undefined) throw new Error("useMobxStore must be used within MobxStoreProvider");
+ return context;
+};
diff --git a/apps/space/package.json b/apps/space/package.json
index 7ace168aa..4af31d312 100644
--- a/apps/space/package.json
+++ b/apps/space/package.json
@@ -18,6 +18,8 @@
"eslint": "8.34.0",
"eslint-config-next": "13.2.1",
"js-cookie": "^3.0.1",
+ "mobx": "^6.10.0",
+ "mobx-react-lite": "^4.0.3",
"next": "^13.4.13",
"nprogress": "^0.2.0",
"react": "^18.2.0",
diff --git a/apps/space/public/plane-logo.webp b/apps/space/public/plane-logo.webp
new file mode 100644
index 000000000..52e7c98da
Binary files /dev/null and b/apps/space/public/plane-logo.webp differ
diff --git a/apps/space/services/api.service.ts b/apps/space/services/api.service.ts
new file mode 100644
index 000000000..900d5d15f
--- /dev/null
+++ b/apps/space/services/api.service.ts
@@ -0,0 +1,100 @@
+// axios
+import axios from "axios";
+// js cookie
+import Cookies from "js-cookie";
+
+const base_url: string | null = "https://boarding.plane.so";
+
+abstract class APIService {
+ protected baseURL: string;
+ protected headers: any = {};
+
+ constructor(baseURL: string) {
+ this.baseURL = base_url ? base_url : baseURL;
+ }
+
+ setRefreshToken(token: string) {
+ Cookies.set("refreshToken", token);
+ }
+
+ getRefreshToken() {
+ return Cookies.get("refreshToken");
+ }
+
+ purgeRefreshToken() {
+ Cookies.remove("refreshToken", { path: "/" });
+ }
+
+ setAccessToken(token: string) {
+ Cookies.set("accessToken", token);
+ }
+
+ getAccessToken() {
+ return Cookies.get("accessToken");
+ }
+
+ purgeAccessToken() {
+ Cookies.remove("accessToken", { path: "/" });
+ }
+
+ getHeaders() {
+ return {
+ Authorization: `Bearer ${this.getAccessToken()}`,
+ };
+ }
+
+ get(url: string, config = {}): Promise {
+ return axios({
+ method: "get",
+ url: this.baseURL + url,
+ headers: this.getAccessToken() ? this.getHeaders() : {},
+ ...config,
+ });
+ }
+
+ post(url: string, data = {}, config = {}): Promise {
+ return axios({
+ method: "post",
+ url: this.baseURL + url,
+ data,
+ headers: this.getAccessToken() ? this.getHeaders() : {},
+ ...config,
+ });
+ }
+
+ put(url: string, data = {}, config = {}): Promise {
+ return axios({
+ method: "put",
+ url: this.baseURL + url,
+ data,
+ headers: this.getAccessToken() ? this.getHeaders() : {},
+ ...config,
+ });
+ }
+
+ patch(url: string, data = {}, config = {}): Promise {
+ return axios({
+ method: "patch",
+ url: this.baseURL + url,
+ data,
+ headers: this.getAccessToken() ? this.getHeaders() : {},
+ ...config,
+ });
+ }
+
+ delete(url: string, data?: any, config = {}): Promise {
+ return axios({
+ method: "delete",
+ url: this.baseURL + url,
+ data: data,
+ headers: this.getAccessToken() ? this.getHeaders() : {},
+ ...config,
+ });
+ }
+
+ request(config = {}) {
+ return axios(config);
+ }
+}
+
+export default APIService;
diff --git a/apps/space/services/issue.service.ts b/apps/space/services/issue.service.ts
new file mode 100644
index 000000000..4b40bdf5c
--- /dev/null
+++ b/apps/space/services/issue.service.ts
@@ -0,0 +1,20 @@
+// services
+import APIService from "services/api.service";
+
+const { NEXT_PUBLIC_API_BASE_URL } = process.env;
+
+class IssueService extends APIService {
+ constructor() {
+ super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
+ }
+
+ async getPublicIssues(workspace_slug: string, project_slug: string): Promise {
+ return this.get(`/api/public/workspaces/${workspace_slug}/project-boards/${project_slug}/issues/`)
+ .then((response) => response?.data)
+ .catch((error) => {
+ throw error?.response;
+ });
+ }
+}
+
+export default IssueService;
diff --git a/apps/space/services/project.service.ts b/apps/space/services/project.service.ts
new file mode 100644
index 000000000..4d973051f
--- /dev/null
+++ b/apps/space/services/project.service.ts
@@ -0,0 +1,20 @@
+// services
+import APIService from "services/api.service";
+
+const { NEXT_PUBLIC_API_BASE_URL } = process.env;
+
+class ProjectService extends APIService {
+ constructor() {
+ super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
+ }
+
+ async getProjectSettingsAsync(workspace_slug: string, project_slug: string): Promise {
+ return this.get(`/api/public/workspaces/${workspace_slug}/project-boards/${project_slug}/settings/`)
+ .then((response) => response?.data)
+ .catch((error) => {
+ throw error?.response;
+ });
+ }
+}
+
+export default ProjectService;
diff --git a/apps/space/services/user.service.ts b/apps/space/services/user.service.ts
new file mode 100644
index 000000000..d724374b6
--- /dev/null
+++ b/apps/space/services/user.service.ts
@@ -0,0 +1,20 @@
+// services
+import APIService from "services/api.service";
+
+const { NEXT_PUBLIC_API_BASE_URL } = process.env;
+
+class UserService extends APIService {
+ constructor() {
+ super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
+ }
+
+ async currentUser(): Promise {
+ return this.get("/api/users/me/")
+ .then((response) => response?.data)
+ .catch((error) => {
+ throw error?.response;
+ });
+ }
+}
+
+export default UserService;
diff --git a/apps/space/store/issue.ts b/apps/space/store/issue.ts
new file mode 100644
index 000000000..79ad4b910
--- /dev/null
+++ b/apps/space/store/issue.ts
@@ -0,0 +1,91 @@
+// mobx
+import { observable, action, computed, makeObservable, runInAction } from "mobx";
+// service
+import IssueService from "services/issue.service";
+// types
+import { TIssueBoardKeys } from "store/types/issue";
+import { IIssueStore, IIssue, IIssueState, IIssueLabel } from "./types";
+
+class IssueStore implements IIssueStore {
+ currentIssueBoardView: TIssueBoardKeys | null = null;
+
+ loader: boolean = false;
+ error: any | null = null;
+
+ states: IIssueState[] | null = null;
+ labels: IIssueLabel[] | null = null;
+ issues: IIssue[] | null = null;
+
+ userSelectedStates: string[] = [];
+ userSelectedLabels: string[] = [];
+ // root store
+ rootStore;
+ // service
+ issueService;
+
+ constructor(_rootStore: any) {
+ makeObservable(this, {
+ // observable
+ currentIssueBoardView: observable,
+
+ loader: observable,
+ error: observable,
+
+ states: observable.ref,
+ labels: observable.ref,
+ issues: observable.ref,
+
+ userSelectedStates: observable,
+ userSelectedLabels: observable,
+ // action
+ setCurrentIssueBoardView: action,
+ getIssuesAsync: action,
+ // computed
+ });
+
+ this.rootStore = _rootStore;
+ this.issueService = new IssueService();
+ }
+
+ // computed
+ getCountOfIssuesByState(state_id: string): number {
+ return this.issues?.filter((issue) => issue.state == state_id).length || 0;
+ }
+
+ getFilteredIssuesByState(state_id: string): IIssue[] | [] {
+ return this.issues?.filter((issue) => issue.state == state_id) || [];
+ }
+
+ // action
+ setCurrentIssueBoardView = async (view: TIssueBoardKeys) => {
+ this.currentIssueBoardView = view;
+ };
+
+ getIssuesAsync = async (workspace_slug: string, project_slug: string) => {
+ try {
+ this.loader = true;
+ this.error = null;
+
+ const response = await this.issueService.getPublicIssues(workspace_slug, project_slug);
+
+ if (response) {
+ const _states: IIssueState[] = [...response?.states];
+ const _labels: IIssueLabel[] = [...response?.labels];
+ const _issues: IIssue[] = [...response?.issues];
+ runInAction(() => {
+ this.states = _states;
+ this.labels = _labels;
+ this.issues = _issues;
+ this.loader = false;
+ });
+ return response;
+ }
+ } catch (error) {
+ this.loader = false;
+ this.error = error;
+ return error;
+ }
+ };
+}
+
+export default IssueStore;
diff --git a/apps/space/store/project.ts b/apps/space/store/project.ts
new file mode 100644
index 000000000..e5ac58261
--- /dev/null
+++ b/apps/space/store/project.ts
@@ -0,0 +1,69 @@
+// mobx
+import { observable, action, makeObservable, runInAction } from "mobx";
+// service
+import ProjectService from "services/project.service";
+// types
+import { IProjectStore, IWorkspace, IProject, IProjectSettings } from "./types";
+
+class ProjectStore implements IProjectStore {
+ loader: boolean = false;
+ error: any | null = null;
+
+ workspace: IWorkspace | null = null;
+ project: IProject | null = null;
+ workspaceProjectSettings: IProjectSettings | null = null;
+ // root store
+ rootStore;
+ // service
+ projectService;
+
+ constructor(_rootStore: any | null = null) {
+ makeObservable(this, {
+ // observable
+ workspace: observable.ref,
+ project: observable.ref,
+ workspaceProjectSettings: observable.ref,
+ loader: observable,
+ error: observable.ref,
+ // action
+ getProjectSettingsAsync: action,
+ // computed
+ });
+
+ this.rootStore = _rootStore;
+ this.projectService = new ProjectService();
+ }
+
+ getProjectSettingsAsync = async (workspace_slug: string, project_slug: string) => {
+ try {
+ this.loader = true;
+ this.error = null;
+
+ const response = await this.projectService.getProjectSettingsAsync(workspace_slug, project_slug);
+
+ if (response) {
+ const _project: IProject = { ...response?.project_details };
+ const _workspace: IWorkspace = { ...response?.workspace_detail };
+ const _workspaceProjectSettings: IProjectSettings = {
+ comments: response?.comments,
+ reactions: response?.reactions,
+ votes: response?.votes,
+ views: { ...response?.views },
+ };
+ runInAction(() => {
+ this.project = _project;
+ this.workspace = _workspace;
+ this.workspaceProjectSettings = _workspaceProjectSettings;
+ this.loader = false;
+ });
+ }
+ return response;
+ } catch (error) {
+ this.loader = false;
+ this.error = error;
+ return error;
+ }
+ };
+}
+
+export default ProjectStore;
diff --git a/apps/space/store/root.ts b/apps/space/store/root.ts
index a10356821..dd6d620c0 100644
--- a/apps/space/store/root.ts
+++ b/apps/space/store/root.ts
@@ -1 +1,25 @@
-export const init = {};
+// mobx lite
+import { enableStaticRendering } from "mobx-react-lite";
+// store imports
+import UserStore from "./user";
+import ThemeStore from "./theme";
+import IssueStore from "./issue";
+import ProjectStore from "./project";
+// types
+import { IIssueStore, IProjectStore, IThemeStore, IUserStore } from "./types";
+
+enableStaticRendering(typeof window === "undefined");
+
+export class RootStore {
+ user: IUserStore;
+ theme: IThemeStore;
+ issue: IIssueStore;
+ project: IProjectStore;
+
+ constructor() {
+ this.user = new UserStore(this);
+ this.theme = new ThemeStore(this);
+ this.issue = new IssueStore(this);
+ this.project = new ProjectStore(this);
+ }
+}
diff --git a/apps/space/store/theme.ts b/apps/space/store/theme.ts
new file mode 100644
index 000000000..809d56b97
--- /dev/null
+++ b/apps/space/store/theme.ts
@@ -0,0 +1,33 @@
+// mobx
+import { observable, action, computed, makeObservable, runInAction } from "mobx";
+// types
+import { IThemeStore } from "./types";
+
+class ThemeStore implements IThemeStore {
+ theme: "light" | "dark" = "light";
+ // root store
+ rootStore;
+
+ constructor(_rootStore: any | null = null) {
+ makeObservable(this, {
+ // observable
+ theme: observable,
+ // action
+ setTheme: action,
+ // computed
+ });
+
+ this.rootStore = _rootStore;
+ }
+
+ setTheme = async (_theme: "light" | "dark" | string) => {
+ try {
+ localStorage.setItem("app_theme", _theme);
+ this.theme = _theme === "light" ? "light" : "dark";
+ } catch (error) {
+ console.error("setting user theme error", error);
+ }
+ };
+}
+
+export default ThemeStore;
diff --git a/apps/space/store/types/index.ts b/apps/space/store/types/index.ts
new file mode 100644
index 000000000..5a0a51eda
--- /dev/null
+++ b/apps/space/store/types/index.ts
@@ -0,0 +1,4 @@
+export * from "./user";
+export * from "./theme";
+export * from "./project";
+export * from "./issue";
diff --git a/apps/space/store/types/issue.ts b/apps/space/store/types/issue.ts
new file mode 100644
index 000000000..5feeba7bd
--- /dev/null
+++ b/apps/space/store/types/issue.ts
@@ -0,0 +1,72 @@
+export type TIssueBoardKeys = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt";
+
+export interface IIssueBoardViews {
+ key: TIssueBoardKeys;
+ title: string;
+ icon: string;
+ className: string;
+}
+
+export type TIssuePriorityKey = "urgent" | "high" | "medium" | "low" | "none";
+export type TIssuePriorityTitle = "Urgent" | "High" | "Medium" | "Low" | "None";
+export interface IIssuePriorityFilters {
+ key: TIssuePriorityKey;
+ title: TIssuePriorityTitle;
+ className: string;
+ icon: string;
+}
+
+export type TIssueGroupKey = "backlog" | "unstarted" | "started" | "completed" | "cancelled";
+export type TIssueGroupTitle = "Backlog" | "Unstarted" | "Started" | "Completed" | "Cancelled";
+
+export interface IIssueGroup {
+ key: TIssueGroupKey;
+ title: TIssueGroupTitle;
+ color: string;
+ className: string;
+ icon: React.FC;
+}
+
+export interface IIssue {
+ id: string;
+ sequence_id: number;
+ name: string;
+ description_html: string;
+ priority: TIssuePriorityKey | null;
+ state: string;
+ state_detail: any;
+ label_details: any;
+ target_date: any;
+}
+
+export interface IIssueState {
+ id: string;
+ name: string;
+ group: TIssueGroupKey;
+ color: string;
+}
+
+export interface IIssueLabel {
+ id: string;
+ name: string;
+ color: string;
+}
+
+export interface IIssueStore {
+ currentIssueBoardView: TIssueBoardKeys | null;
+ loader: boolean;
+ error: any | null;
+
+ states: IIssueState[] | null;
+ labels: IIssueLabel[] | null;
+ issues: IIssue[] | null;
+
+ userSelectedStates: string[];
+ userSelectedLabels: string[];
+
+ getCountOfIssuesByState: (state: string) => number;
+ getFilteredIssuesByState: (state: string) => IIssue[];
+
+ setCurrentIssueBoardView: (view: TIssueBoardKeys) => void;
+ getIssuesAsync: (workspace_slug: string, project_slug: string) => Promise;
+}
diff --git a/apps/space/store/types/project.ts b/apps/space/store/types/project.ts
new file mode 100644
index 000000000..a55f30be0
--- /dev/null
+++ b/apps/space/store/types/project.ts
@@ -0,0 +1,39 @@
+export interface IWorkspace {
+ id: string;
+ name: string;
+ slug: string;
+}
+
+export interface IProject {
+ id: string;
+ identifier: string;
+ name: string;
+ icon: string;
+ cover_image: string | null;
+ icon_prop: string | null;
+ emoji: string | null;
+}
+
+export interface IProjectSettings {
+ comments: boolean;
+ reactions: boolean;
+ votes: boolean;
+ views: {
+ list: boolean;
+ gantt: boolean;
+ kanban: boolean;
+ calendar: boolean;
+ spreadsheet: boolean;
+ };
+}
+
+export interface IProjectStore {
+ loader: boolean;
+ error: any | null;
+
+ workspace: IWorkspace | null;
+ project: IProject | null;
+ workspaceProjectSettings: IProjectSettings | null;
+
+ getProjectSettingsAsync: (workspace_slug: string, project_slug: string) => Promise;
+}
diff --git a/apps/space/store/types/theme.ts b/apps/space/store/types/theme.ts
new file mode 100644
index 000000000..ca306be51
--- /dev/null
+++ b/apps/space/store/types/theme.ts
@@ -0,0 +1,4 @@
+export interface IThemeStore {
+ theme: string;
+ setTheme: (theme: "light" | "dark" | string) => void;
+}
diff --git a/apps/space/store/types/user.ts b/apps/space/store/types/user.ts
new file mode 100644
index 000000000..0293c5381
--- /dev/null
+++ b/apps/space/store/types/user.ts
@@ -0,0 +1,4 @@
+export interface IUserStore {
+ currentUser: any | null;
+ getUserAsync: () => void;
+}
diff --git a/apps/space/store/user.ts b/apps/space/store/user.ts
new file mode 100644
index 000000000..2f4782236
--- /dev/null
+++ b/apps/space/store/user.ts
@@ -0,0 +1,43 @@
+// mobx
+import { observable, action, computed, makeObservable, runInAction } from "mobx";
+// service
+import UserService from "services/user.service";
+// types
+import { IUserStore } from "./types";
+
+class UserStore implements IUserStore {
+ currentUser: any | null = null;
+ // root store
+ rootStore;
+ // service
+ userService;
+
+ constructor(_rootStore: any) {
+ makeObservable(this, {
+ // observable
+ currentUser: observable,
+ // actions
+ // computed
+ });
+ this.rootStore = _rootStore;
+ this.userService = new UserService();
+ }
+
+ getUserAsync = async () => {
+ try {
+ const response = this.userService.currentUser();
+ if (response) {
+ runInAction(() => {
+ this.currentUser = response;
+ });
+ }
+ } catch (error) {
+ console.error("error", error);
+ runInAction(() => {
+ // render error actions
+ });
+ }
+ };
+}
+
+export default UserStore;
diff --git a/apps/space/tailwind.config.js b/apps/space/tailwind.config.js
index 145c65b5a..55aaa9a31 100644
--- a/apps/space/tailwind.config.js
+++ b/apps/space/tailwind.config.js
@@ -6,6 +6,7 @@ module.exports = {
"./pages/**/*.{js,ts,jsx,tsx}",
"./layouts/**/*.tsx",
"./components/**/*.{js,ts,jsx,tsx}",
+ "./constants/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
diff --git a/yarn.lock b/yarn.lock
index 01e9d2a84..578f10026 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3617,6 +3617,14 @@
"@typescript-eslint/types" "5.62.0"
eslint-visitor-keys "^3.3.0"
+"@typescript-eslint/visitor-keys@5.62.0":
+ version "5.62.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e"
+ integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==
+ dependencies:
+ "@typescript-eslint/types" "5.62.0"
+ eslint-visitor-keys "^3.3.0"
+
a11y-status@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/a11y-status/-/a11y-status-2.0.1.tgz#a7883105910b9e3cd09ea90e5acf8404dc01b47e"
@@ -3641,6 +3649,11 @@ acorn@^8.8.2, acorn@^8.9.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
+acorn@^8.9.0:
+ version "8.10.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
+ integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
+
agent-base@6:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
@@ -5180,6 +5193,56 @@ eslint@8.34.0:
strip-json-comments "^3.1.0"
text-table "^0.2.0"
+eslint-visitor-keys@^3.4.1:
+ version "3.4.2"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz#8c2095440eca8c933bedcadf16fefa44dbe9ba5f"
+ integrity sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==
+
+eslint@8.34.0:
+ version "8.34.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.34.0.tgz#fe0ab0ef478104c1f9ebc5537e303d25a8fb22d6"
+ integrity sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==
+ dependencies:
+ "@eslint/eslintrc" "^1.4.1"
+ "@humanwhocodes/config-array" "^0.11.8"
+ "@humanwhocodes/module-importer" "^1.0.1"
+ "@nodelib/fs.walk" "^1.2.8"
+ ajv "^6.10.0"
+ chalk "^4.0.0"
+ cross-spawn "^7.0.2"
+ debug "^4.3.2"
+ doctrine "^3.0.0"
+ escape-string-regexp "^4.0.0"
+ eslint-scope "^7.1.1"
+ eslint-utils "^3.0.0"
+ eslint-visitor-keys "^3.3.0"
+ espree "^9.4.0"
+ esquery "^1.4.0"
+ esutils "^2.0.2"
+ fast-deep-equal "^3.1.3"
+ file-entry-cache "^6.0.1"
+ find-up "^5.0.0"
+ glob-parent "^6.0.2"
+ globals "^13.19.0"
+ grapheme-splitter "^1.0.4"
+ ignore "^5.2.0"
+ import-fresh "^3.0.0"
+ imurmurhash "^0.1.4"
+ is-glob "^4.0.0"
+ is-path-inside "^3.0.3"
+ js-sdsl "^4.1.4"
+ js-yaml "^4.1.0"
+ json-stable-stringify-without-jsonify "^1.0.1"
+ levn "^0.4.1"
+ lodash.merge "^4.6.2"
+ minimatch "^3.1.2"
+ natural-compare "^1.4.0"
+ optionator "^0.9.1"
+ regexpp "^3.2.0"
+ strip-ansi "^6.0.1"
+ strip-json-comments "^3.1.0"
+ text-table "^0.2.0"
+
eslint@^7.23.0, eslint@^7.32.0:
version "7.32.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
@@ -5397,6 +5460,17 @@ fast-glob@^3.2.12, fast-glob@^3.2.9, fast-glob@^3.3.0:
merge2 "^1.3.0"
micromatch "^4.0.4"
+fast-glob@^3.3.0:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4"
+ integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==
+ dependencies:
+ "@nodelib/fs.stat" "^2.0.2"
+ "@nodelib/fs.walk" "^1.2.3"
+ glob-parent "^5.1.2"
+ merge2 "^1.3.0"
+ micromatch "^4.0.4"
+
fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
@@ -6247,6 +6321,13 @@ is-wsl@^2.2.0:
dependencies:
is-docker "^2.0.0"
+is-wsl@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
+ integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
+ dependencies:
+ is-docker "^2.0.0"
+
isarray@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
@@ -7569,6 +7650,15 @@ postcss@^8.4.14, postcss@^8.4.21, postcss@^8.4.23:
picocolors "^1.0.0"
source-map-js "^1.0.2"
+postcss@^8.4.21:
+ version "8.4.27"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.27.tgz#234d7e4b72e34ba5a92c29636734349e0d9c3057"
+ integrity sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==
+ dependencies:
+ nanoid "^3.3.6"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
+
prebuild-install@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45"
@@ -8619,6 +8709,11 @@ streamx@^2.15.0:
fast-fifo "^1.1.0"
queue-tick "^1.0.1"
+streamsearch@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
+ integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
+
string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
@@ -9049,6 +9144,11 @@ tslib@~2.5.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913"
integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==
+tslib@^2.5.0, tslib@^2.6.0:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410"
+ integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==
+
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"