refactor: publish boards

This commit is contained in:
sriram veeraghanta 2024-05-15 02:25:38 +05:30
parent 2b196ba1f1
commit a2fbd6132b
13 changed files with 187 additions and 177 deletions

View File

@ -1,5 +0,0 @@
"use client";
export default function ProjectError() {
return <>Project Error</>;
}

View File

@ -1,4 +1,5 @@
import Image from "next/image"; import Image from "next/image";
import { notFound } from "next/navigation";
// components // components
import IssueNavbar from "@/components/issues/navbar"; import IssueNavbar from "@/components/issues/navbar";
// services // services
@ -10,7 +11,11 @@ const projectService = new ProjectService();
export default async function ProjectLayout({ children, params }: { children: React.ReactNode; params: any }) { export default async function ProjectLayout({ children, params }: { children: React.ReactNode; params: any }) {
const { workspace_slug, project_id } = params; const { workspace_slug, project_id } = params;
const projectSettings = await projectService.getProjectSettings(workspace_slug, project_id); const projectSettings = await projectService.getProjectSettings(workspace_slug, project_id).catch(() => null);
if (!projectSettings) {
notFound();
}
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">

View File

@ -1,3 +0,0 @@
export default function ProjectSettingsNotFound() {
return <>Project Settings not found</>;
}

View File

@ -1,7 +1,8 @@
"use client";
// components // components
import { ProjectDetailsView } from "@/components/views"; import { ProjectDetailsView } from "@/components/views";
export default async function WorkspaceProjectPage({ params }: { params: any }) { export default function WorkspaceProjectPage({ params }: { params: any }) {
const { workspace_slug, project_id, peekId } = params; const { workspace_slug, project_id, peekId } = params;
return <ProjectDetailsView workspaceSlug={workspace_slug} projectId={project_id} peekId={peekId} />; return <ProjectDetailsView workspaceSlug={workspace_slug} projectId={project_id} peekId={peekId} />;

View File

@ -1,8 +1,16 @@
import { Metadata } from "next"; import { Metadata } from "next";
// styles // styles
import "@/styles/globals.css"; import "@/styles/globals.css";
// components
import { InstanceNotReady, InstanceFailureView } from "@/components/instance";
// helpers // helpers
import { ASSET_PREFIX } from "@/helpers/common.helper"; import { ASSET_PREFIX } from "@/helpers/common.helper";
// lib
import { AppProvider } from "@/lib/app-providers";
// services
import { InstanceService } from "@/services/instance.service";
const instanceService = new InstanceService();
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Plane Deploy | Make your Plane boards public with one-click", title: "Plane Deploy | Make your Plane boards public with one-click",
@ -20,6 +28,8 @@ export const metadata: Metadata = {
}; };
export default async function RootLayout({ children }: { children: React.ReactNode }) { export default async function RootLayout({ children }: { children: React.ReactNode }) {
const instanceDetails = await instanceService.getInstanceInfo().catch(() => undefined);
return ( return (
<html lang="en"> <html lang="en">
<head> <head>
@ -29,7 +39,17 @@ export default async function RootLayout({ children }: { children: React.ReactNo
<link rel="manifest" href={`${ASSET_PREFIX}/site.webmanifest.json`} /> <link rel="manifest" href={`${ASSET_PREFIX}/site.webmanifest.json`} />
<link rel="shortcut icon" href={`${ASSET_PREFIX}/favicon/favicon.ico`} /> <link rel="shortcut icon" href={`${ASSET_PREFIX}/favicon/favicon.ico`} />
</head> </head>
<body>{children}</body> <body>
<AppProvider initialState={{ instance: instanceDetails?.instance }}>
{!instanceDetails ? (
<InstanceFailureView />
) : (
<>{instanceDetails.instance.is_setup_done ? <>{children}</> : <InstanceNotReady />}</>
)}
{children}
</AppProvider>
</body>
</html> </html>
); );
} }

View File

@ -1,36 +1,21 @@
"use client"; "use client";
import Image from "next/image"; import Image from "next/image";
import { useTheme } from "next-themes";
import { Button } from "@plane/ui";
// assets // assets
import InstanceFailureDarkImage from "@/public/instance/instance-failure-dark.svg"; import UserLoggedInImage from "public/user-logged-in.svg";
import InstanceFailureImage from "@/public/instance/instance-failure.svg";
export default function InstanceNotFound() { export default function InstanceNotFound() {
const { resolvedTheme } = useTheme();
const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage;
const handleRetry = () => {
window.location.reload();
};
return ( return (
<div> <div>
<div className="h-full w-full relative container px-5 mx-auto flex justify-center items-center mt-10"> <div className="flex h-screen w-screen flex-col">
<div className="w-auto max-w-2xl relative space-y-8 py-10"> <div className="grid h-full w-full place-items-center p-6">
<div className="relative flex flex-col justify-center items-center space-y-4"> <div className="text-center">
<Image src={instanceImage} alt="Plane Logo" /> <div className="mx-auto grid h-52 w-52 place-items-center rounded-full bg-custom-background-80">
<h3 className="font-medium text-2xl text-white ">Unable to fetch instance details.</h3> <div className="h-32 w-32">
<p className="font-medium text-base text-center"> <Image src={UserLoggedInImage} alt="User already logged in" />
We were unable to fetch the details of the instance. <br /> </div>
Fret not, it might just be a connectivity issue. </div>
</p> <h1 className="mt-12 text-3xl font-semibold">Not Found</h1>
</div> <p className="mt-4">Please enter the appropriate project URL to view the issue board.</p>
<div className="flex justify-center">
<Button size="md" onClick={handleRetry}>
Retry
</Button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,43 +1,20 @@
// components // components
import { UserLoggedIn } from "@/components/accounts"; import { UserLoggedIn } from "@/components/accounts";
import { InstanceNotReady, InstanceFailureView } from "@/components/instance";
import { AuthView } from "@/components/views"; import { AuthView } from "@/components/views";
// helpers
// import { EPageTypes } from "@/helpers/authentication.helper";
// import { useInstance, useUser } from "@/hooks/store";
// wrapper
// import { AuthWrapper } from "@/lib/wrappers";
// lib
import { AppProvider } from "@/lib/app-providers";
// services // services
import { InstanceService } from "@/services/instance.service";
import { UserService } from "@/services/user.service"; import { UserService } from "@/services/user.service";
const userServices = new UserService(); const userServices = new UserService();
const instanceService = new InstanceService();
export default async function HomePage() { export default async function HomePage() {
const instanceDetails = await instanceService.getInstanceInfo().catch(() => undefined);
const user = await userServices const user = await userServices
.currentUser() .currentUser()
.then((user) => ({ ...user, isAuthenticated: true })) .then((user) => ({ ...user, isAuthenticated: true }))
.catch(() => ({ isAuthenticated: false })); .catch(() => ({ isAuthenticated: false }));
if (!instanceDetails) {
return <InstanceFailureView />;
}
if (!instanceDetails?.instance?.is_setup_done) {
<InstanceNotReady />;
}
if (user.isAuthenticated) { if (user.isAuthenticated) {
return <UserLoggedIn />; return <UserLoggedIn />;
} }
return ( return <AuthView />;
<AppProvider initialState={{ instance: instanceDetails.instance }}>
<AuthView />
</AppProvider>
);
} }

View File

@ -1,3 +1,4 @@
"use client";
// mobx react lite // mobx react lite
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// ui // ui
@ -5,12 +6,12 @@ import { StateGroupIcon } from "@plane/ui";
// constants // constants
import { issueGroupFilter } from "@/constants/data"; import { issueGroupFilter } from "@/constants/data";
// mobx hook // mobx hook
import { useIssue } from "@/hooks/store"; // import { useIssue } from "@/hooks/store";
// interfaces // interfaces
import { IIssueState } from "@/types/issue"; import { IIssueState } from "@/types/issue";
export const IssueKanBanHeader = observer(({ state }: { state: IIssueState }) => { export const IssueKanBanHeader = observer(({ state }: { state: IIssueState }) => {
const { getCountOfIssuesByState } = useIssue(); // const { getCountOfIssuesByState } = useIssue();
const stateGroup = issueGroupFilter(state.group); const stateGroup = issueGroupFilter(state.group);
if (stateGroup === null) return <></>; if (stateGroup === null) return <></>;
@ -21,7 +22,7 @@ export const IssueKanBanHeader = observer(({ state }: { state: IIssueState }) =>
<StateGroupIcon stateGroup={state.group} color={state.color} height="14" width="14" /> <StateGroupIcon stateGroup={state.group} color={state.color} height="14" width="14" />
</div> </div>
<div className="mr-1 truncate font-semibold capitalize text-custom-text-200">{state?.name}</div> <div className="mr-1 truncate font-semibold capitalize text-custom-text-200">{state?.name}</div>
<span className="flex-shrink-0 rounded-full text-custom-text-300">{getCountOfIssuesByState(state.id)}</span> {/* <span className="flex-shrink-0 rounded-full text-custom-text-300">{getCountOfIssuesByState(state.id)}</span> */}
</div> </div>
); );
}); });

View File

@ -1,3 +1,4 @@
"use client";
import { FC } from "react"; import { FC } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useParams, useRouter, useSearchParams } from "next/navigation"; import { useParams, useRouter, useSearchParams } from "next/navigation";

View File

@ -1,16 +1,18 @@
"use client";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// ui // ui
import { StateGroupIcon } from "@plane/ui"; import { StateGroupIcon } from "@plane/ui";
// constants // constants
import { issueGroupFilter } from "@/constants/data"; import { issueGroupFilter } from "@/constants/data";
// mobx hook // mobx hook
import { useIssue } from "@/hooks/store"; // import { useIssue } from "@/hooks/store";
// types // types
import { IIssueState } from "@/types/issue"; import { IIssueState } from "@/types/issue";
export const IssueListHeader = observer(({ state }: { state: IIssueState }) => { export const IssueListHeader = observer(({ state }: { state: IIssueState }) => {
const { getCountOfIssuesByState } = useIssue(); // const { getCountOfIssuesByState } = useIssue();
const stateGroup = issueGroupFilter(state.group); const stateGroup = issueGroupFilter(state.group);
// const count = getCountOfIssuesByState(state.id);
if (stateGroup === null) return <></>; if (stateGroup === null) return <></>;
@ -20,7 +22,7 @@ export const IssueListHeader = observer(({ state }: { state: IIssueState }) => {
<StateGroupIcon stateGroup={state.group} color={state.color} height="14" width="14" /> <StateGroupIcon stateGroup={state.group} color={state.color} height="14" width="14" />
</div> </div>
<div className="mr-1 font-medium capitalize">{state?.name}</div> <div className="mr-1 font-medium capitalize">{state?.name}</div>
<div className="text-sm font-medium text-custom-text-200">{getCountOfIssuesByState(state.id)}</div> {/* <div className="text-sm font-medium text-custom-text-200">{count}</div> */}
</div> </div>
); );
}); });

View File

@ -1,3 +1,4 @@
"use client";
import { FC } from "react"; import { FC } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// components // components

View File

@ -0,0 +1,129 @@
"use client";
import { useEffect, FC } from "react";
import Link from "next/link";
import { useRouter, useParams, useSearchParams, usePathname } from "next/navigation";
// ui
import { Avatar, Button } from "@plane/ui";
// components
import { IssueFiltersDropdown } from "@/components/issues/filters";
// hooks
import { useProject, useUser, useIssueFilter } from "@/hooks/store";
// types
import { TIssueBoardKeys } from "@/types/issue";
// components
import { NavbarIssueBoardView } from "./issue-board-view";
import { NavbarTheme } from "./theme";
export type NavbarControlsProps = {
workspaceSlug: string;
projectId: string;
projectSettings: any;
};
export const NavbarControls: FC<NavbarControlsProps> = (props) => {
const { workspaceSlug, projectId, projectSettings } = props;
const { views } = projectSettings;
// router
const router = useRouter();
const { board, labels, states, priorities, peekId } = useParams<any>();
const searchParams = useSearchParams();
const pathName = usePathname();
// store
const { updateFilters } = useIssueFilter();
const { settings, activeLayout, hydrate, setActiveLayout } = useProject();
hydrate(projectSettings);
const { data: user } = useUser();
useEffect(() => {
if (workspaceSlug && projectId && settings) {
const viewsAcceptable: string[] = [];
const currentBoard: TIssueBoardKeys | 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 (board) {
// if (viewsAcceptable.includes(board.toString())) {
// currentBoard = board.toString() as TIssueBoardKeys;
// } else {
// if (viewsAcceptable && viewsAcceptable.length > 0) {
// currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
// }
// }
// } else {
// if (viewsAcceptable && viewsAcceptable.length > 0) {
// currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
// }
// }
if (currentBoard) {
if (activeLayout === null || activeLayout !== currentBoard) {
let params: any = { board: currentBoard };
if (peekId && peekId.length > 0) params = { ...params, peekId: peekId };
if (priorities && priorities.length > 0) params = { ...params, priorities: priorities };
if (states && states.length > 0) params = { ...params, states: states };
if (labels && labels.length > 0) params = { ...params, labels: labels };
console.log("params", params);
let storeParams: any = {};
if (priorities && priorities.length > 0) storeParams = { ...storeParams, priority: priorities.split(",") };
if (states && states.length > 0) storeParams = { ...storeParams, state: states.split(",") };
if (labels && labels.length > 0) storeParams = { ...storeParams, labels: labels.split(",") };
if (storeParams) updateFilters(projectId, storeParams);
setActiveLayout(currentBoard);
router.push(`/${workspaceSlug}/${projectId}?${searchParams}`);
}
}
}
}, [
board,
workspaceSlug,
projectId,
router,
updateFilters,
labels,
states,
priorities,
peekId,
settings,
activeLayout,
setActiveLayout,
searchParams,
]);
return (
<>
{/* issue views */}
<div className="relative flex flex-shrink-0 items-center gap-1 transition-all delay-150 ease-in-out">
<NavbarIssueBoardView layouts={views} />
</div>
{/* issue filters */}
<div className="relative flex flex-shrink-0 items-center gap-1 transition-all delay-150 ease-in-out">
<IssueFiltersDropdown workspaceSlug={workspaceSlug} projectId={projectId} />
</div>
{/* theming */}
<div className="relative flex-shrink-0">
<NavbarTheme />
</div>
{user?.id ? (
<div className="flex items-center gap-2 rounded border border-custom-border-200 p-2">
<Avatar name={user?.display_name} src={user?.avatar ?? undefined} shape="square" size="sm" />
<h6 className="text-xs font-medium">{user.display_name}</h6>
</div>
) : (
<div className="flex-shrink-0">
<Link href={`/?next_path=${pathName}`}>
<Button variant="outline-primary">Sign in</Button>
</Link>
</div>
)}
</>
);
};

View File

@ -1,21 +1,11 @@
"use client"; "use client";
import { useEffect, FC } from "react"; import { FC } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import Link from "next/link";
import { useRouter, useParams, useSearchParams, usePathname } from "next/navigation";
import { Briefcase } from "lucide-react"; import { Briefcase } from "lucide-react";
import { Avatar, Button } from "@plane/ui";
// components // components
import { ProjectLogo } from "@/components/common"; import { ProjectLogo } from "@/components/common";
import { IssueFiltersDropdown } from "@/components/issues/filters"; import { NavbarControls } from "./controls";
// hooks
import { useProject, useUser, useIssueFilter } from "@/hooks/store";
// types
import { TIssueBoardKeys } from "@/types/issue";
// components
import { NavbarIssueBoardView } from "./issue-board-view";
import { NavbarTheme } from "./theme";
type IssueNavbarProps = { type IssueNavbarProps = {
projectSettings: any; projectSettings: any;
@ -25,80 +15,10 @@ type IssueNavbarProps = {
const IssueNavbar: FC<IssueNavbarProps> = observer((props) => { const IssueNavbar: FC<IssueNavbarProps> = observer((props) => {
const { projectSettings, workspaceSlug, projectId } = props; const { projectSettings, workspaceSlug, projectId } = props;
const { project_details, views } = projectSettings; const { project_details } = projectSettings;
const { board, labels, states, priorities, peekId } = useParams<any>();
const searchParams = useSearchParams();
const pathName = usePathname();
// hooks
const router = useRouter();
// store
const { settings, activeLayout, hydrate, setActiveLayout } = useProject();
const { data: user } = useUser();
const { updateFilters } = useIssueFilter();
hydrate(projectSettings);
useEffect(() => {
if (workspaceSlug && projectId && settings) {
const viewsAcceptable: string[] = [];
const currentBoard: TIssueBoardKeys | 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 (board) {
// if (viewsAcceptable.includes(board.toString())) {
// currentBoard = board.toString() as TIssueBoardKeys;
// } else {
// if (viewsAcceptable && viewsAcceptable.length > 0) {
// currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
// }
// }
// } else {
// if (viewsAcceptable && viewsAcceptable.length > 0) {
// currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
// }
// }
if (currentBoard) {
if (activeLayout === null || activeLayout !== currentBoard) {
let params: any = { board: currentBoard };
if (peekId && peekId.length > 0) params = { ...params, peekId: peekId };
if (priorities && priorities.length > 0) params = { ...params, priorities: priorities };
if (states && states.length > 0) params = { ...params, states: states };
if (labels && labels.length > 0) params = { ...params, labels: labels };
console.log("params", params);
let storeParams: any = {};
if (priorities && priorities.length > 0) storeParams = { ...storeParams, priority: priorities.split(",") };
if (states && states.length > 0) storeParams = { ...storeParams, state: states.split(",") };
if (labels && labels.length > 0) storeParams = { ...storeParams, labels: labels.split(",") };
if (storeParams) updateFilters(projectId, storeParams);
setActiveLayout(currentBoard);
router.push(`/${workspaceSlug}/${projectId}?${searchParams}`);
}
}
}
}, [
board,
workspaceSlug,
projectId,
router,
updateFilters,
labels,
states,
priorities,
peekId,
settings,
activeLayout,
setActiveLayout,
searchParams,
]);
return ( return (
<div className="relative flex w-full items-center gap-4 px-5"> <div className="relative flex justify-between w-full gap-4 px-5">
{/* project detail */} {/* project detail */}
<div className="flex flex-shrink-0 items-center gap-2"> <div className="flex flex-shrink-0 items-center gap-2">
{project_details ? ( {project_details ? (
@ -115,33 +35,9 @@ const IssueNavbar: FC<IssueNavbarProps> = observer((props) => {
</div> </div>
</div> </div>
{/* issue views */} <div className="flex flex-shrink-0 items-center gap-2">
<div className="relative flex flex-shrink-0 items-center gap-1 transition-all delay-150 ease-in-out"> <NavbarControls workspaceSlug={workspaceSlug} projectId={projectId} projectSettings={projectSettings} />
<NavbarIssueBoardView layouts={views} />
</div> </div>
{/* issue filters */}
<div className="relative flex flex-shrink-0 items-center gap-1 transition-all delay-150 ease-in-out">
<IssueFiltersDropdown workspaceSlug={workspaceSlug} projectId={projectId} />
</div>
{/* theming */}
<div className="relative flex-shrink-0">
<NavbarTheme />
</div>
{user?.id ? (
<div className="flex items-center gap-2 rounded border border-custom-border-200 p-2">
<Avatar name={user?.display_name} src={user?.avatar ?? undefined} shape="square" size="sm" />
<h6 className="text-xs font-medium">{user.display_name}</h6>
</div>
) : (
<div className="flex-shrink-0">
<Link href={`/?next_path=${pathName}`}>
<Button variant="outline-primary">Sign in</Button>
</Link>
</div>
)}
</div> </div>
); );
}); });