diff --git a/apps/space/app/[workspace_slug]/layout.tsx b/apps/space/app/[workspace_slug]/layout.tsx new file mode 100644 index 00000000000..d2a194b17c4 --- /dev/null +++ b/apps/space/app/[workspace_slug]/layout.tsx @@ -0,0 +1,21 @@ +// next imports +import { Metadata } from "next"; + +type LayoutProps = { + params: { workspace_slug: string }; +}; + +export async function generateMetadata({ params }: LayoutProps): Promise { + // read route params + const { workspace_slug } = params; + + return { + title: `${workspace_slug} | Plane`, + description: `${workspace_slug} | Plane`, + icons: `/plane-logo.webp`, + }; +} + +const Layout = ({ children }: { children: React.ReactNode }) => <>{children}; + +export default Layout; diff --git a/apps/space/app/[workspace_slug]/page.tsx b/apps/space/app/[workspace_slug]/page.tsx index c35662f5a62..7cb9526ef38 100644 --- a/apps/space/app/[workspace_slug]/page.tsx +++ b/apps/space/app/[workspace_slug]/page.tsx @@ -1,7 +1,95 @@ "use client"; -const WorkspaceProjectPage = () => ( -
Plane Workspace Space
-); +import { useEffect } from "react"; +// next imports +import { useParams } from "next/navigation"; +import Image from "next/image"; +import Link from "next/link"; +// components +import { ProjectCard } from "components/project-card"; +// mobx store +import { observer } from "mobx-react-lite"; +import { RootStore } from "store/root"; +import { useMobxStore } from "lib/mobx/store-provider"; + +const WorkspaceProjectPage = observer(() => { + const store: RootStore = useMobxStore(); + const { project: projectStore } = store; + + const routerParams = useParams(); + const { workspace_slug } = routerParams as { workspace_slug: string }; + + useEffect(() => { + if (workspace_slug) { + store?.project?.getWorkspaceProjectsListAsync(workspace_slug); + } + }, [workspace_slug, store?.project, store?.issue]); + + return ( +
+
+
+
+ plane logo +
+
Plane Deploy
+
+
+
+
+ {projectStore?.loader ? ( +
Loading...
+ ) : ( + <> + {projectStore?.error ? ( +
Something went wrong.
+ ) : ( +
+
+ {projectStore?.projectsList && projectStore?.projectsList.length > 0 ? ( +
+
{workspace_slug} Projects
+
+ {projectStore?.projectsList.map((_project) => ( +
+ +
+ ))} +
+
+ ) : ( +
+
+ No projects are published under this workspace. +
+ + Go to your Workspace + +
+ )} +
+
+ )} + + )} +
+
+ +
+ +
+ plane logo +
+
+ Powered by Plane Deploy +
+ +
+
+ ); +}); export default WorkspaceProjectPage; diff --git a/apps/space/pages/404.tsx b/apps/space/app/not-found.tsx similarity index 100% rename from apps/space/pages/404.tsx rename to apps/space/app/not-found.tsx diff --git a/apps/space/components/project-card.tsx b/apps/space/components/project-card.tsx new file mode 100644 index 00000000000..bf2b3699a93 --- /dev/null +++ b/apps/space/components/project-card.tsx @@ -0,0 +1,39 @@ +"use client"; + +// next imports +import Link from "next/link"; +import Image from "next/image"; +// types +import { IProject } from "store/types"; + +interface ProjectCardProps { + project: IProject; + workspace_slug: string; +} + +const renderEmoji = (emoji: string | { name: string; color: string }) => { + if (!emoji) return; + + if (typeof emoji === "object") + return ( + + {emoji.name} + + ); + else return isNaN(parseInt(emoji)) ? emoji : String.fromCodePoint(parseInt(emoji)); +}; + +export const ProjectCard = ({ project, workspace_slug }: ProjectCardProps) => ( + +
+
+ {project?.emoji ? ( + renderEmoji(project?.emoji) + ) : ( + plane logo + )} +
+
{project?.name || ""}
+
+ +); diff --git a/apps/space/next.config.js b/apps/space/next.config.js index 4128b636a7d..abb92ebb6a1 100644 --- a/apps/space/next.config.js +++ b/apps/space/next.config.js @@ -9,6 +9,15 @@ const nextConfig = { appDir: true, }, output: "standalone", + async redirects() { + return [ + { + source: "/", + destination: "https://plane.so", + permanent: true, + }, + ]; + }, }; module.exports = nextConfig; diff --git a/apps/space/pages/_app.tsx b/apps/space/pages/_app.tsx deleted file mode 100644 index 8681006e165..00000000000 --- a/apps/space/pages/_app.tsx +++ /dev/null @@ -1,10 +0,0 @@ -// styles -import "styles/globals.css"; -// types -import type { AppProps } from "next/app"; - -function MyApp({ Component, pageProps }: AppProps) { - return ; -} - -export default MyApp; diff --git a/apps/space/pages/_document.tsx b/apps/space/pages/_document.tsx deleted file mode 100644 index 8b41d05fce8..00000000000 --- a/apps/space/pages/_document.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import Document, { Html, Head, Main, NextScript } from "next/document"; - -class MyDocument extends Document { - render() { - return ( - - - -
- - - - ); - } -} - -export default MyDocument; diff --git a/apps/space/services/project.service.ts b/apps/space/services/project.service.ts index 6f0275877f6..d9940e474ad 100644 --- a/apps/space/services/project.service.ts +++ b/apps/space/services/project.service.ts @@ -6,6 +6,14 @@ class ProjectService extends APIService { super(process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); } + async getWorkspaceProjectsListAsync(workspace_slug: string): Promise { + return this.get(`/api/public/workspaces/${workspace_slug}/project-boards/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + 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) diff --git a/apps/space/store/project.ts b/apps/space/store/project.ts index e5ac58261f3..a690cbb956c 100644 --- a/apps/space/store/project.ts +++ b/apps/space/store/project.ts @@ -9,6 +9,7 @@ class ProjectStore implements IProjectStore { loader: boolean = false; error: any | null = null; + projectsList: IProject[] | null = null; workspace: IWorkspace | null = null; project: IProject | null = null; workspaceProjectSettings: IProjectSettings | null = null; @@ -20,12 +21,14 @@ class ProjectStore implements IProjectStore { constructor(_rootStore: any | null = null) { makeObservable(this, { // observable + projectsList: observable.ref, workspace: observable.ref, project: observable.ref, workspaceProjectSettings: observable.ref, loader: observable, error: observable.ref, // action + getWorkspaceProjectsListAsync: action, getProjectSettingsAsync: action, // computed }); @@ -34,6 +37,43 @@ class ProjectStore implements IProjectStore { this.projectService = new ProjectService(); } + getWorkspaceProjectsListAsync = async (workspace_slug: string) => { + try { + this.loader = true; + this.error = null; + + const response = await this.projectService.getWorkspaceProjectsListAsync(workspace_slug); + + if (response && response.length > 0) { + const _projectsList: IProject[] = response.map((_project: IProject) => { + const _p = { ..._project }; + return { + id: _p?.id, + identifier: _p?.identifier, + name: _p?.name, + description: _p?.description, + cover_image: _p?.cover_image, + icon_prop: _p?.icon_prop, + emoji: _p?.emoji, + }; + }); + + runInAction(() => { + this.projectsList = [..._projectsList]; + this.loader = false; + }); + } else { + this.loader = false; + } + + return response; + } catch (error) { + this.loader = false; + this.error = error; + throw error; + } + }; + getProjectSettingsAsync = async (workspace_slug: string, project_slug: string) => { try { this.loader = true; @@ -61,7 +101,7 @@ class ProjectStore implements IProjectStore { } catch (error) { this.loader = false; this.error = error; - return error; + throw error; } }; } diff --git a/apps/space/store/types/project.ts b/apps/space/store/types/project.ts index 41f4c2f44d1..7924d5386eb 100644 --- a/apps/space/store/types/project.ts +++ b/apps/space/store/types/project.ts @@ -9,7 +9,6 @@ export interface IProject { identifier: string; name: string; description: string; - icon: string; cover_image: string | null; icon_prop: string | null; emoji: string | null; @@ -32,9 +31,11 @@ export interface IProjectStore { loader: boolean; error: any | null; + projectsList: IProject[] | null; workspace: IWorkspace | null; project: IProject | null; workspaceProjectSettings: IProjectSettings | null; + getWorkspaceProjectsListAsync: (workspace_slug: string) => Promise; getProjectSettingsAsync: (workspace_slug: string, project_slug: string) => Promise; }