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 (
+
+
+
+
+ {projectStore?.loader ? (
+
Loading...
+ ) : (
+ <>
+ {projectStore?.error ? (
+
Something went wrong.
+ ) : (
+
+
+ {projectStore?.projectsList && projectStore?.projectsList.length > 0 ? (
+
+
{workspace_slug} Projects
+
+ {projectStore?.projectsList.map((_project) => (
+
+ ))}
+
+
+ ) : (
+
+ )}
+
+
+ )}
+ >
+ )}
+
+
+
+
+
+
+
+
+
+ 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)
+ ) : (
+
+ )}
+
+
{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;
}