diff --git a/space/app/[workspace_slug]/[project_id]/layout.tsx b/space/app/[workspace_slug]/[project_id]/layout.tsx index b1e134ea6..c3d4979d1 100644 --- a/space/app/[workspace_slug]/[project_id]/layout.tsx +++ b/space/app/[workspace_slug]/[project_id]/layout.tsx @@ -1,40 +1,15 @@ -import Image from "next/image"; -import { notFound } from "next/navigation"; -// components -import IssueNavbar from "@/components/issues/navbar"; -// assets -import planeLogo from "public/plane-logo.svg"; - -export default async function ProjectLayout({ - children, - params, -}: { +type Props = { children: React.ReactNode; - params: { workspace_slug: string; project_id: string }; -}) { - const { workspace_slug, project_id } = params; + params: { + workspace_slug: string; + project_id: string; + }; +}; - if (!workspace_slug || !project_id) notFound(); +const ProjectIssuesLayout = async (props: Props) => { + const { children } = props; - return ( -
-
- -
-
{children}
- -
- Plane logo -
-
- Powered by Plane Deploy -
-
-
- ); -} + return <>{children}; +}; + +export default ProjectIssuesLayout; diff --git a/space/app/[workspace_slug]/[project_id]/page.tsx b/space/app/[workspace_slug]/[project_id]/page.tsx index 0d08ae7eb..e9d6b9123 100644 --- a/space/app/[workspace_slug]/[project_id]/page.tsx +++ b/space/app/[workspace_slug]/[project_id]/page.tsx @@ -1,16 +1,50 @@ "use client"; -import { useSearchParams } from "next/navigation"; +import { useEffect, useState } from "react"; +import { notFound, useSearchParams } from "next/navigation"; // components -import { ProjectDetailsView } from "@/components/views"; +import { LogoSpinner } from "@/components/common"; +// helpers +import { navigate } from "@/helpers/actions"; +// services +import PublishService from "@/services/publish.service"; +const publishService = new PublishService(); -export default function WorkspaceProjectPage({ params }: { params: { workspace_slug: any; project_id: any } }) { +type Props = { + params: { + workspace_slug: string; + project_id: string; + }; +}; + +const ProjectIssuesPage = (props: Props) => { + const { params } = props; const { workspace_slug, project_id } = params; - + // states + const [error, setError] = useState(false); + // params const searchParams = useSearchParams(); + const board = searchParams.get("board") || undefined; const peekId = searchParams.get("peekId") || undefined; - if (!workspace_slug || !project_id) return <>; + useEffect(() => { + if (!workspace_slug || !project_id) return; + publishService + .fetchAnchorFromOldDetails(workspace_slug, project_id) + .then((res) => { + let url = `/${res.anchor}`; + const params = new URLSearchParams(); + if (board) params.append("board", board); + if (peekId) params.append("peekId", peekId); + if (params.toString()) url += `?${params.toString()}`; + navigate(url); + }) + .catch(() => setError(true)); + }, [board, peekId, project_id, workspace_slug]); - return ; -} + if (error) notFound(); + + return ; +}; + +export default ProjectIssuesPage; diff --git a/space/app/error.tsx b/space/app/error.tsx index 2d6f22e90..8ef727655 100644 --- a/space/app/error.tsx +++ b/space/app/error.tsx @@ -21,7 +21,7 @@ export default function InstanceError() {
Plane instance failure image -

Unable to fetch instance details.

+

Unable to fetch instance details.

We were unable to fetch the details of the instance.
Fret not, it might just be a connectivity issue. diff --git a/space/app/not-found.tsx b/space/app/not-found.tsx index cae576319..c5320b2dc 100644 --- a/space/app/not-found.tsx +++ b/space/app/not-found.tsx @@ -4,20 +4,18 @@ import Image from "next/image"; // assets import UserLoggedInImage from "public/user-logged-in.svg"; -export default function NotFound() { - return ( -

-
-
-
-
- User already logged in -
-
-

Not Found

-

Please enter the appropriate project URL to view the issue board.

+const NotFound = () => ( +
+
+
+
+ User already logged in
+

Not Found

+

Please enter the appropriate project URL to view the issue board.

- ); -} +
+); + +export default NotFound; diff --git a/space/app/page/layout.tsx b/space/app/page/layout.tsx new file mode 100644 index 000000000..5527c31c1 --- /dev/null +++ b/space/app/page/layout.tsx @@ -0,0 +1,48 @@ +import Image from "next/image"; +import { notFound } from "next/navigation"; +import useSWR from "swr"; +// components +import IssueNavbar from "@/components/issues/navbar"; +// hooks +import { usePublish, usePublishList } from "@/hooks/store"; +// assets +import planeLogo from "@/public/plane-logo.svg"; + +type Props = { children: React.ReactNode; params: { anchor: string } }; + +const PageDetailsLayout = (props: Props) => { + const { children, params } = props; + // params + const { anchor } = params; + // store hooks + const { fetchPublishSettings } = usePublishList(); + const { id, workspace_detail, project } = usePublish(anchor); + + useSWR(anchor ? `PUBLISH_SETTINGS_${anchor}` : null, anchor ? () => fetchPublishSettings(anchor) : null); + + if (!workspace_detail || !project || !id) notFound(); + + return ( + + ); +}; + +export default PageDetailsLayout; diff --git a/space/app/page/page.tsx b/space/app/page/page.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/space/app/project-issues/[anchor]/layout.tsx b/space/app/project-issues/[anchor]/layout.tsx new file mode 100644 index 000000000..be6d3f516 --- /dev/null +++ b/space/app/project-issues/[anchor]/layout.tsx @@ -0,0 +1,46 @@ +import { observer } from "mobx-react"; +import Image from "next/image"; +import { notFound } from "next/navigation"; +// components +import IssueNavbar from "@/components/issues/navbar"; +// hooks +import { usePublish } from "@/hooks/store"; +// assets +import planeLogo from "@/public/plane-logo.svg"; + +type Props = { children: React.ReactNode; params: { anchor: string } }; + +const ProjectIssuesLayout = (props: Props) => { + const { children, params } = props; + // params + const { anchor } = params; + // store hooks + const publishSettings = usePublish(anchor); + const { id, workspace_detail, project } = publishSettings; + + if (!workspace_detail || !project || !id) notFound(); + + return ( + + ); +}; + +export default ProjectIssuesLayout; diff --git a/space/app/project-issues/[anchor]/page.tsx b/space/app/project-issues/[anchor]/page.tsx new file mode 100644 index 000000000..5d138ff2d --- /dev/null +++ b/space/app/project-issues/[anchor]/page.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { useSearchParams } from "next/navigation"; +import useSWR from "swr"; +// components +import { ProjectDetailsView } from "@/components/views"; +// hooks +import { usePublish, usePublishList } from "@/hooks/store"; + +type Props = { + params: { + anchor: string; + }; +}; + +const ProjectIssuesPage = (props: Props) => { + const { params } = props; + const { anchor } = params; + // params + const searchParams = useSearchParams(); + const peekId = searchParams.get("peekId") || undefined; + // store hooks + const { fetchPublishSettings } = usePublishList(); + const publishSettings = usePublish(anchor); + + useSWR(anchor ? `PUBLISH_SETTINGS_${anchor}` : null, anchor ? () => fetchPublishSettings(anchor) : null); + + if (!publishSettings) return null; + + return ; +}; + +export default ProjectIssuesPage; diff --git a/space/components/account/user-logged-in.tsx b/space/components/account/user-logged-in.tsx index 33be330fa..5a44cb55f 100644 --- a/space/components/account/user-logged-in.tsx +++ b/space/components/account/user-logged-in.tsx @@ -1,36 +1,44 @@ "use client"; +import { observer } from "mobx-react-lite"; import Image from "next/image"; +import { useTheme } from "next-themes"; // components import { UserAvatar } from "@/components/issues/navbar/user-avatar"; // hooks import { useUser } from "@/hooks/store"; // assets -import PlaneLogo from "@/public/plane-logos/black-horizontal-with-blue-logo.png"; +import PlaneBlackLogo from "@/public/plane-logos/black-horizontal-with-blue-logo.png"; +import PlaneWhiteLogo from "@/public/plane-logos/white-horizontal-with-blue-logo.png"; import UserLoggedInImage from "@/public/user-logged-in.svg"; -export const UserLoggedIn = () => { +export const UserLoggedIn = observer(() => { + // store hooks const { data: user } = useUser(); + // next-themes + const { resolvedTheme } = useTheme(); + + const logo = resolvedTheme === "dark" ? PlaneWhiteLogo : PlaneBlackLogo; if (!user) return null; return ( -
+
- User already logged in + Plane logo
-
+
-
-
+
+
User already logged in
-

Logged in Successfully!

+

Logged in successfully!

You{"'"}ve successfully logged in. Please enter the appropriate project URL to view the issue board.

@@ -38,4 +46,4 @@ export const UserLoggedIn = () => {
); -}; +}); diff --git a/space/components/issues/navbar/controls.tsx b/space/components/issues/navbar/controls.tsx index 20c0ca408..b6302e2fc 100644 --- a/space/components/issues/navbar/controls.tsx +++ b/space/components/issues/navbar/controls.tsx @@ -11,19 +11,20 @@ import { UserAvatar } from "@/components/issues/navbar/user-avatar"; // helpers import { queryParamGenerator } from "@/helpers/query-param-generator"; // hooks -import { useProject, useIssueFilter, useIssueDetails } from "@/hooks/store"; +import { useIssueFilter, useIssueDetails } from "@/hooks/store"; import useIsInIframe from "@/hooks/use-is-in-iframe"; +// store +import { PublishStore } from "@/store/publish/publish.store"; // types import { TIssueLayout } from "@/types/issue"; export type NavbarControlsProps = { - workspaceSlug: string; - projectId: string; + publishSettings: PublishStore; }; export const NavbarControls: FC = observer((props) => { // props - const { workspaceSlug, projectId } = props; + const { publishSettings } = props; // router const router = useRouter(); const searchParams = useSearchParams(); @@ -35,23 +36,23 @@ export const NavbarControls: FC = observer((props) => { const peekId = searchParams.get("peekId") || undefined; // hooks const { issueFilters, isIssueFiltersUpdated, initIssueFilters } = useIssueFilter(); - const { settings } = useProject(); const { setPeekId } = useIssueDetails(); // derived values const activeLayout = issueFilters?.display_filters?.layout || undefined; + const { project, views, workspace_detail } = publishSettings; const isInIframe = useIsInIframe(); useEffect(() => { - if (workspaceSlug && projectId && settings) { + if (project && workspace_detail) { const viewsAcceptable: string[] = []; let currentBoard: TIssueLayout | null = null; - if (settings?.views?.list) viewsAcceptable.push("list"); - if (settings?.views?.kanban) viewsAcceptable.push("kanban"); - if (settings?.views?.calendar) viewsAcceptable.push("calendar"); - if (settings?.views?.gantt) viewsAcceptable.push("gantt"); - if (settings?.views?.spreadsheet) viewsAcceptable.push("spreadsheet"); + if (views?.list) viewsAcceptable.push("list"); + if (views?.kanban) viewsAcceptable.push("kanban"); + if (views?.calendar) viewsAcceptable.push("calendar"); + if (views?.gantt) viewsAcceptable.push("gantt"); + if (views?.spreadsheet) viewsAcceptable.push("spreadsheet"); if (board) { if (viewsAcceptable.includes(board.toString())) currentBoard = board.toString() as TIssueLayout; @@ -75,38 +76,40 @@ export const NavbarControls: FC = observer((props) => { }; if (!isIssueFiltersUpdated(params)) { - initIssueFilters(projectId, params); - router.push(`/${workspaceSlug}/${projectId}?${queryParam}`); + initIssueFilters(project, params); + router.push(`/${workspace_detail.slug}/${project}?${queryParam}`); } } } } }, [ - workspaceSlug, - projectId, board, labels, state, priority, peekId, - settings, activeLayout, router, initIssueFilters, setPeekId, isIssueFiltersUpdated, + views, + project, + workspace_detail, ]); + if (!workspace_detail || !project) return; + return ( <> {/* issue views */}
- +
{/* issue filters */}
- +
{/* theming */} diff --git a/space/components/issues/navbar/index.tsx b/space/components/issues/navbar/index.tsx index f5d60b8b0..048e169e0 100644 --- a/space/components/issues/navbar/index.tsx +++ b/space/components/issues/navbar/index.tsx @@ -5,37 +5,38 @@ import { Briefcase } from "lucide-react"; // components import { ProjectLogo } from "@/components/common"; import { NavbarControls } from "@/components/issues/navbar/controls"; -// hooks -import { useProject } from "@/hooks/store"; +// store +import { PublishStore } from "@/store/publish/publish.store"; type IssueNavbarProps = { - workspaceSlug: string; - projectId: string; + publishSettings: PublishStore; }; const IssueNavbar: FC = observer((props) => { - const { workspaceSlug, projectId } = props; + const { publishSettings } = props; // hooks - const { project } = useProject(); + const { project_details } = publishSettings; return (
{/* project detail */}
- {project ? ( + {project_details ? ( - + ) : ( )} -
{project?.name || `...`}
+
+ {project_details?.name || `...`} +
- +
); diff --git a/space/components/issues/peek-overview/issue-emoji-reactions.tsx b/space/components/issues/peek-overview/issue-emoji-reactions.tsx index 4a0e61554..5de3beb25 100644 --- a/space/components/issues/peek-overview/issue-emoji-reactions.tsx +++ b/space/components/issues/peek-overview/issue-emoji-reactions.tsx @@ -1,4 +1,3 @@ -import { useEffect } from "react"; import { observer } from "mobx-react-lite"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; // lib @@ -29,7 +28,7 @@ export const IssueEmojiReactions: React.FC = observer( const { workspaceSlug, projectId } = props; // store const issueDetailsStore = useIssueDetails(); - const { data: user, fetchCurrentUser } = useUser(); + const { data: user } = useUser(); const issueId = issueDetailsStore.peekId; const reactions = issueId ? issueDetailsStore.details[issueId]?.reactions || [] : []; @@ -53,11 +52,6 @@ export const IssueEmojiReactions: React.FC = observer( else handleAddReaction(reactionHex); }; - useEffect(() => { - if (user) return; - fetchCurrentUser(); - }, [user, fetchCurrentUser]); - // derived values const { queryParam } = queryParamGenerator({ peekId, board, state, priority, labels }); diff --git a/space/components/issues/peek-overview/issue-vote-reactions.tsx b/space/components/issues/peek-overview/issue-vote-reactions.tsx index 1e565e862..14e9ef30e 100644 --- a/space/components/issues/peek-overview/issue-vote-reactions.tsx +++ b/space/components/issues/peek-overview/issue-vote-reactions.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState } from "react"; import { observer } from "mobx-react-lite"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { Tooltip } from "@plane/ui"; @@ -32,7 +32,7 @@ export const IssueVotes: React.FC = observer((props) => { const [isSubmitting, setIsSubmitting] = useState(false); const issueDetailsStore = useIssueDetails(); - const { data: user, fetchCurrentUser } = useUser(); + const { data: user } = useUser(); const isInIframe = useIsInIframe(); @@ -63,12 +63,6 @@ export const IssueVotes: React.FC = observer((props) => { setIsSubmitting(false); }; - useEffect(() => { - if (user) return; - - fetchCurrentUser(); - }, [user, fetchCurrentUser]); - const VOTES_LIMIT = 1000; // derived values diff --git a/space/components/views/project-details.tsx b/space/components/views/project-details.tsx index 462c656f0..f5754d43a 100644 --- a/space/components/views/project-details.tsx +++ b/space/components/views/project-details.tsx @@ -13,62 +13,52 @@ import { IssueListView } from "@/components/issues/board-views/list"; import { IssueSpreadsheetView } from "@/components/issues/board-views/spreadsheet"; import { IssueAppliedFilters } from "@/components/issues/filters/applied-filters/root"; import { IssuePeekOverview } from "@/components/issues/peek-overview"; -// mobx store -import { useIssue, useUser, useIssueDetails, useIssueFilter, useProject } from "@/hooks/store"; +// hooks +import { useIssue, useIssueDetails, useIssueFilter } from "@/hooks/store"; +// store +import { PublishStore } from "@/store/publish/publish.store"; // assets import SomethingWentWrongImage from "public/something-went-wrong.svg"; type ProjectDetailsViewProps = { - workspaceSlug: string; - projectId: string; peekId: string | undefined; + publishSettings: PublishStore; }; export const ProjectDetailsView: FC = observer((props) => { - // router - const searchParams = useSearchParams(); + const { peekId, publishSettings } = props; // query params + const searchParams = useSearchParams(); const states = searchParams.get("states") || undefined; const priority = searchParams.get("priority") || undefined; const labels = searchParams.get("labels") || undefined; - - const { workspaceSlug, projectId, peekId } = props; - // hooks - const { fetchProjectSettings } = useProject(); + // store hooks const { issueFilters } = useIssueFilter(); const { loader, issues, error, fetchPublicIssues } = useIssue(); const issueDetailStore = useIssueDetails(); - const { data: currentUser, fetchCurrentUser } = useUser(); + // derived values + const { workspace_detail, project } = publishSettings; + const workspaceSlug = workspace_detail?.slug; useSWR( - workspaceSlug && projectId ? "WORKSPACE_PROJECT_SETTINGS" : null, - workspaceSlug && projectId ? () => fetchProjectSettings(workspaceSlug, projectId) : null - ); - useSWR( - (workspaceSlug && projectId) || states || priority || labels ? "WORKSPACE_PROJECT_PUBLIC_ISSUES" : null, - (workspaceSlug && projectId) || states || priority || labels - ? () => fetchPublicIssues(workspaceSlug, projectId, { states, priority, labels }) - : null - ); - useSWR( - workspaceSlug && projectId && !currentUser ? "WORKSPACE_PROJECT_CURRENT_USER" : null, - workspaceSlug && projectId && !currentUser ? () => fetchCurrentUser() : null + workspaceSlug && project ? `WORKSPACE_PROJECT_PUBLIC_ISSUES_${workspaceSlug}_${project}` : null, + workspaceSlug && project ? () => fetchPublicIssues(workspaceSlug, project, { states, priority, labels }) : null ); useEffect(() => { - if (peekId && workspaceSlug && projectId) { + if (peekId) { issueDetailStore.setPeekId(peekId.toString()); } - }, [peekId, issueDetailStore, projectId, workspaceSlug]); + }, [peekId, issueDetailStore]); // derived values const activeLayout = issueFilters?.display_filters?.layout || undefined; + if (!workspaceSlug || !project) return null; + return (
- {workspaceSlug && projectId && peekId && ( - - )} + {peekId && } {loader && !issues ? (
Loading...
@@ -90,16 +80,16 @@ export const ProjectDetailsView: FC = observer((props) activeLayout && (
{/* applied filters */} - + {activeLayout === "list" && (
- +
)} {activeLayout === "kanban" && (
- +
)} {activeLayout === "calendar" && } diff --git a/space/helpers/actions.ts b/space/helpers/actions.ts new file mode 100644 index 000000000..5e85029ca --- /dev/null +++ b/space/helpers/actions.ts @@ -0,0 +1,5 @@ +"use server"; + +import { redirect } from "next/navigation"; + +export const navigate = async (path: string) => redirect(path); diff --git a/space/hooks/store/index.ts b/space/hooks/store/index.ts index 76b6f9315..3f82613d5 100644 --- a/space/hooks/store/index.ts +++ b/space/hooks/store/index.ts @@ -1,5 +1,5 @@ +export * from "./publish"; export * from "./use-instance"; -export * from "./use-project"; export * from "./use-issue"; export * from "./use-user"; export * from "./use-user-profile"; diff --git a/space/hooks/store/publish/index.ts b/space/hooks/store/publish/index.ts new file mode 100644 index 000000000..a7b42ad5b --- /dev/null +++ b/space/hooks/store/publish/index.ts @@ -0,0 +1,2 @@ +export * from "./use-publish-list"; +export * from "./use-publish"; diff --git a/space/hooks/store/publish/use-publish-list.ts b/space/hooks/store/publish/use-publish-list.ts new file mode 100644 index 000000000..aa50c295a --- /dev/null +++ b/space/hooks/store/publish/use-publish-list.ts @@ -0,0 +1,11 @@ +import { useContext } from "react"; +// lib +import { StoreContext } from "@/lib/store-provider"; +// store +import { IPublishListStore } from "@/store/publish/publish_list.store"; + +export const usePublishList = (): IPublishListStore => { + const context = useContext(StoreContext); + if (context === undefined) throw new Error("usePublishList must be used within StoreProvider"); + return context.publishList; +}; diff --git a/space/hooks/store/publish/use-publish.ts b/space/hooks/store/publish/use-publish.ts new file mode 100644 index 000000000..3d920e8cb --- /dev/null +++ b/space/hooks/store/publish/use-publish.ts @@ -0,0 +1,11 @@ +import { useContext } from "react"; +// lib +import { StoreContext } from "@/lib/store-provider"; +// store +import { PublishStore } from "@/store/publish/publish.store"; + +export const usePublish = (anchor: string): PublishStore => { + const context = useContext(StoreContext); + if (context === undefined) throw new Error("usePublish must be used within StoreProvider"); + return context.publishList.publishMap?.[anchor] ?? {}; +}; diff --git a/space/lib/user-provider.tsx b/space/lib/user-provider.tsx deleted file mode 100644 index 1ac1c786c..000000000 --- a/space/lib/user-provider.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { ReactNode } from "react"; -import { observer } from "mobx-react-lite"; -import useSWR from "swr"; -import { useUser } from "@/hooks/store"; - -export const UserProvider = observer(({ children }: { children: ReactNode }) => { - const { fetchCurrentUser } = useUser(); - - useSWR("CURRENT_USER", () => fetchCurrentUser()); - - return <>{children}; -}); diff --git a/space/services/project.service.ts b/space/services/project.service.ts deleted file mode 100644 index 14ed7837b..000000000 --- a/space/services/project.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { API_BASE_URL } from "@/helpers/common.helper"; -// services -import { APIService } from "@/services/api.service"; - -class ProjectService extends APIService { - constructor() { - super(API_BASE_URL); - } - - async getProjectSettings(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/space/services/publish.service.ts b/space/services/publish.service.ts new file mode 100644 index 000000000..e44b8dede --- /dev/null +++ b/space/services/publish.service.ts @@ -0,0 +1,37 @@ +import { API_BASE_URL } from "@/helpers/common.helper"; +// services +import { APIService } from "@/services/api.service"; +// types +import { TPublishSettings } from "@/types/publish"; + +class PublishService extends APIService { + constructor() { + super(API_BASE_URL); + } + + async fetchPublishSettings(anchor: string): Promise { + return this.get(`/api/public/publish-settings/${anchor}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + + async fetchAnchorFromOldDetails( + workspaceSlug: string, + projectID: string + ): Promise<{ + anchor: string; + }> { + return this.post(`/api/public/publish-anchor/`, { + workspaceSlug, + projectID, + }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } +} + +export default PublishService; diff --git a/space/store/project.store.ts b/space/store/project.store.ts index 02f250323..0ddbcbabb 100644 --- a/space/store/project.store.ts +++ b/space/store/project.store.ts @@ -13,7 +13,7 @@ export interface IProjectStore { error: any | undefined; settings: TProjectSettings | undefined; workspace: TWorkspaceDetails | undefined; - project: TProjectDetails | undefined; + projectMap: Record; // { [projectID]: TProjectDetails } canReact: boolean; canComment: boolean; canVote: boolean; @@ -28,7 +28,7 @@ export class ProjectStore implements IProjectStore { error: any | undefined = undefined; settings: TProjectSettings | undefined = undefined; workspace: TWorkspaceDetails | undefined = undefined; - project: TProjectDetails | undefined = undefined; + projectMap: Record = {}; // service projectService; @@ -39,7 +39,7 @@ export class ProjectStore implements IProjectStore { error: observable.ref, // observable workspace: observable, - project: observable, + projectMap: observable, settings: observable, // computed canReact: computed, diff --git a/space/store/publish/publish.store.ts b/space/store/publish/publish.store.ts new file mode 100644 index 000000000..198ce0a3f --- /dev/null +++ b/space/store/publish/publish.store.ts @@ -0,0 +1,67 @@ +import { observable, makeObservable } from "mobx"; +// store types +import { RootStore } from "@/store/root.store"; +// types +import { TProjectDetails, TViewDetails, TWorkspaceDetails } from "@/types/project"; +import { TPublishSettings } from "@/types/publish"; + +export interface IPublishStore extends TPublishSettings {} + +export class PublishStore implements IPublishStore { + // observables + anchor: string | undefined; + comments: boolean; + created_at: string | undefined; + created_by: string | undefined; + id: string | undefined; + inbox: unknown; + project: string | undefined; + project_details: TProjectDetails | undefined; + reactions: boolean; + updated_at: string | undefined; + updated_by: string | undefined; + views: TViewDetails | undefined; + votes: boolean; + workspace: string | undefined; + workspace_detail: TWorkspaceDetails | undefined; + + constructor( + private store: RootStore, + publishSettings: TPublishSettings + ) { + this.anchor = publishSettings.anchor; + this.comments = publishSettings.comments; + this.created_at = publishSettings.created_at; + this.created_by = publishSettings.created_by; + this.id = publishSettings.id; + this.inbox = publishSettings.inbox; + this.project = publishSettings.project; + this.project_details = publishSettings.project_details; + this.reactions = publishSettings.reactions; + this.updated_at = publishSettings.updated_at; + this.updated_by = publishSettings.updated_by; + this.views = publishSettings.views; + this.votes = publishSettings.votes; + this.workspace = publishSettings.workspace; + this.workspace_detail = publishSettings.workspace_detail; + + makeObservable(this, { + // observables + anchor: observable.ref, + comments: observable.ref, + created_at: observable.ref, + created_by: observable.ref, + id: observable.ref, + inbox: observable, + project: observable.ref, + project_details: observable, + reactions: observable.ref, + updated_at: observable.ref, + updated_by: observable.ref, + views: observable, + votes: observable.ref, + workspace: observable.ref, + workspace_detail: observable, + }); + } +} diff --git a/space/store/publish/publish_list.store.ts b/space/store/publish/publish_list.store.ts new file mode 100644 index 000000000..680adb809 --- /dev/null +++ b/space/store/publish/publish_list.store.ts @@ -0,0 +1,52 @@ +import set from "lodash/set"; +import { makeObservable, observable, runInAction, action } from "mobx"; +// services +import PublishService from "@/services/publish.service"; +// store +import { PublishStore } from "@/store/publish/publish.store"; +// store +import { TPublishSettings } from "@/types/publish"; +import { RootStore } from "../root.store"; + +export interface IPublishListStore { + // observables + publishMap: Record; // anchor => PublishStore + // actions + fetchPublishSettings: (pageId: string) => Promise; +} + +export class PublishListStore implements IPublishListStore { + // observables + publishMap: Record = {}; // anchor => PublishStore + // service + publishService; + + constructor(private store: RootStore) { + makeObservable(this, { + // observables + publishMap: observable, + // actions + fetchPublishSettings: action, + }); + // services + this.publishService = new PublishService(); + } + + /** + * @description fetch publish settings + * @param {string} anchor + */ + fetchPublishSettings = async (anchor: string) => { + try { + const publishSettings = await this.publishService.fetchPublishSettings(anchor); + runInAction(() => { + if (publishSettings.anchor) + set(this.publishMap, [publishSettings.anchor], new PublishStore(this.store, publishSettings)); + }); + + return publishSettings; + } catch (error) { + throw error; + } + }; +} diff --git a/space/store/root.store.ts b/space/store/root.store.ts index 4a31840db..082220f5d 100644 --- a/space/store/root.store.ts +++ b/space/store/root.store.ts @@ -3,30 +3,30 @@ import { enableStaticRendering } from "mobx-react-lite"; import { IInstanceStore, InstanceStore } from "@/store/instance.store"; import { IssueDetailStore, IIssueDetailStore } from "@/store/issue-detail.store"; import { IssueStore, IIssueStore } from "@/store/issue.store"; -import { IProjectStore, ProjectStore } from "@/store/project.store"; import { IUserStore, UserStore } from "@/store/user.store"; import { IssueFilterStore, IIssueFilterStore } from "./issue-filters.store"; import { IMentionsStore, MentionsStore } from "./mentions.store"; +import { IPublishListStore, PublishListStore } from "./publish/publish_list.store"; enableStaticRendering(typeof window === "undefined"); export class RootStore { instance: IInstanceStore; user: IUserStore; - project: IProjectStore; issue: IIssueStore; issueDetail: IIssueDetailStore; mentionStore: IMentionsStore; issueFilter: IIssueFilterStore; + publishList: IPublishListStore; constructor() { this.instance = new InstanceStore(this); this.user = new UserStore(this); - this.project = new ProjectStore(this); this.issue = new IssueStore(this); this.issueDetail = new IssueDetailStore(this); this.mentionStore = new MentionsStore(this); this.issueFilter = new IssueFilterStore(this); + this.publishList = new PublishListStore(this); } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -40,10 +40,10 @@ export class RootStore { localStorage.setItem("theme", "system"); this.instance = new InstanceStore(this); this.user = new UserStore(this); - this.project = new ProjectStore(this); this.issue = new IssueStore(this); this.issueDetail = new IssueDetailStore(this); this.mentionStore = new MentionsStore(this); this.issueFilter = new IssueFilterStore(this); + this.publishList = new PublishListStore(this); }; } diff --git a/space/types/publish.d.ts b/space/types/publish.d.ts new file mode 100644 index 000000000..f0f23f09f --- /dev/null +++ b/space/types/publish.d.ts @@ -0,0 +1,19 @@ +import { TProjectDetails, TViewDetails, TWorkspaceDetails } from "./project"; + +export type TPublishSettings = { + anchor: string | undefined; + comments: boolean; + created_at: string | undefined; + created_by: string | undefined; + id: string | undefined; + inbox: unknown; + project: string | undefined; + project_details: TProjectDetails | undefined; + reactions: boolean; + updated_at: string | undefined; + updated_by: string | undefined; + views: TViewDetails | undefined; + votes: boolean; + workspace: string | undefined; + workspace_detail: TWorkspaceDetails | undefined; +};