diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/archives/layout.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/archives/layout.tsx
new file mode 100644
index 00000000000..a308e197897
--- /dev/null
+++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/archives/layout.tsx
@@ -0,0 +1,16 @@
+"use client";
+
+import { ReactNode } from "react";
+// components
+import { AppHeader, ContentWrapper } from "@/components/core";
+// local components
+import { ProjectsListHeader } from "@/plane-web/components/projects/header";
+import { ProjectsListMobileHeader } from "@/plane-web/components/projects/mobile-header";
+export default function ProjectListLayout({ children }: { children: ReactNode }) {
+ return (
+ <>
+ } mobileHeader={} />
+ {children}
+ >
+ );
+}
diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/archives/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/archives/page.tsx
new file mode 100644
index 00000000000..b9b78bd5f46
--- /dev/null
+++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/archives/page.tsx
@@ -0,0 +1,4 @@
+import ProjectPageRoot from "@/plane-web/components/projects/page";
+
+const ProjectsPage = () => ;
+export default ProjectsPage;
diff --git a/web/app/[workspaceSlug]/(projects)/projects/(list)/layout.tsx b/web/app/[workspaceSlug]/(projects)/projects/(list)/layout.tsx
index 259c412dcb7..a308e197897 100644
--- a/web/app/[workspaceSlug]/(projects)/projects/(list)/layout.tsx
+++ b/web/app/[workspaceSlug]/(projects)/projects/(list)/layout.tsx
@@ -4,9 +4,8 @@ import { ReactNode } from "react";
// components
import { AppHeader, ContentWrapper } from "@/components/core";
// local components
-import { ProjectsListHeader } from "./header";
-import { ProjectsListMobileHeader } from "./mobile-header";
-
+import { ProjectsListHeader } from "@/plane-web/components/projects/header";
+import { ProjectsListMobileHeader } from "@/plane-web/components/projects/mobile-header";
export default function ProjectListLayout({ children }: { children: ReactNode }) {
return (
<>
diff --git a/web/app/[workspaceSlug]/(projects)/projects/(list)/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(list)/page.tsx
index 40e7f30a2a1..b9b78bd5f46 100644
--- a/web/app/[workspaceSlug]/(projects)/projects/(list)/page.tsx
+++ b/web/app/[workspaceSlug]/(projects)/projects/(list)/page.tsx
@@ -1,84 +1,4 @@
-"use client";
-
-import { useCallback } from "react";
-import { observer } from "mobx-react";
-import { useParams } from "next/navigation";
-// types
-import { TProjectAppliedDisplayFilterKeys, TProjectFilters } from "@plane/types";
-// components
-import { PageHead } from "@/components/core";
-import { ProjectAppliedFiltersList, ProjectCardList } from "@/components/project";
-// helpers
-import { calculateTotalFilters } from "@/helpers/filter.helper";
-// hooks
-import { useProject, useProjectFilter, useWorkspace } from "@/hooks/store";
-
-const ProjectsPage = observer(() => {
- // store
- const { workspaceSlug } = useParams();
- const { currentWorkspace } = useWorkspace();
- const { totalProjectIds, filteredProjectIds } = useProject();
- const {
- currentWorkspaceFilters,
- currentWorkspaceAppliedDisplayFilters,
- clearAllFilters,
- clearAllAppliedDisplayFilters,
- updateFilters,
- updateDisplayFilters,
- } = useProjectFilter();
- // derived values
- const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Projects` : undefined;
-
- const handleRemoveFilter = useCallback(
- (key: keyof TProjectFilters, value: string | null) => {
- if (!workspaceSlug) return;
- let newValues = currentWorkspaceFilters?.[key] ?? [];
-
- if (!value) newValues = [];
- else newValues = newValues.filter((val) => val !== value);
-
- updateFilters(workspaceSlug.toString(), { [key]: newValues });
- },
- [currentWorkspaceFilters, updateFilters, workspaceSlug]
- );
-
- const handleRemoveDisplayFilter = useCallback(
- (key: TProjectAppliedDisplayFilterKeys) => {
- if (!workspaceSlug) return;
- updateDisplayFilters(workspaceSlug.toString(), { [key]: false });
- },
- [updateDisplayFilters, workspaceSlug]
- );
-
- const handleClearAllFilters = useCallback(() => {
- if (!workspaceSlug) return;
- clearAllFilters(workspaceSlug.toString());
- clearAllAppliedDisplayFilters(workspaceSlug.toString());
- }, [clearAllFilters, clearAllAppliedDisplayFilters, workspaceSlug]);
-
- return (
- <>
-
-
- {(calculateTotalFilters(currentWorkspaceFilters ?? {}) !== 0 ||
- currentWorkspaceAppliedDisplayFilters?.length !== 0) && (
-
- )}
-
-
- >
- );
-});
+import ProjectPageRoot from "@/plane-web/components/projects/page";
+const ProjectsPage = () => ;
export default ProjectsPage;
diff --git a/web/ce/components/projects/create/attributes.tsx b/web/ce/components/projects/create/attributes.tsx
new file mode 100644
index 00000000000..ead92208f44
--- /dev/null
+++ b/web/ce/components/projects/create/attributes.tsx
@@ -0,0 +1,80 @@
+import { Controller, useFormContext } from "react-hook-form";
+import { IProject } from "@plane/types";
+import { CustomSelect } from "@plane/ui";
+import { MemberDropdown } from "@/components/dropdowns";
+import { NETWORK_CHOICES } from "@/constants/project";
+
+const ProjectAttributes = () => {
+ const { control } = useFormContext();
+ return (
+
+
{
+ const currentNetwork = NETWORK_CHOICES.find((n) => n.key === value);
+
+ return (
+
+
+ {currentNetwork ? (
+ <>
+
+ {currentNetwork.label}
+ >
+ ) : (
+ Select network
+ )}
+
+ }
+ placement="bottom-start"
+ className="h-full"
+ buttonClassName="h-full"
+ noChevron
+ tabIndex={4}
+ >
+ {NETWORK_CHOICES.map((network) => (
+
+
+
+
+
{network.label}
+
{network.description}
+
+
+
+ ))}
+
+
+ );
+ }}
+ />
+ {
+ if (value === undefined || value === null || typeof value === "string")
+ return (
+
+ onChange(lead === value ? null : lead)}
+ placeholder="Lead"
+ multiple={false}
+ buttonVariant="border-with-text"
+ tabIndex={5}
+ />
+
+ );
+ else return <>>;
+ }}
+ />
+
+ );
+};
+
+export default ProjectAttributes;
diff --git a/web/ce/components/projects/create/root.tsx b/web/ce/components/projects/create/root.tsx
new file mode 100644
index 00000000000..76fea48f798
--- /dev/null
+++ b/web/ce/components/projects/create/root.tsx
@@ -0,0 +1,139 @@
+"use client";
+
+import { useState, FC } from "react";
+import { observer } from "mobx-react";
+import { FormProvider, useForm } from "react-hook-form";
+// ui
+import { setToast, TOAST_TYPE } from "@plane/ui";
+// constants
+import ProjectCommonAttributes from "@/components/project/create/common-attributes";
+import ProjectCreateHeader from "@/components/project/create/header";
+import ProjectCreateButtons from "@/components/project/create/project-create-buttons";
+import { PROJECT_CREATED } from "@/constants/event-tracker";
+import { PROJECT_UNSPLASH_COVERS } from "@/constants/project";
+// helpers
+import { getRandomEmoji } from "@/helpers/emoji.helper";
+// hooks
+import { useEventTracker, useProject } from "@/hooks/store";
+import { usePlatformOS } from "@/hooks/use-platform-os";
+import { TProject } from "@/plane-web/types/projects";
+import ProjectAttributes from "./attributes";
+
+type Props = {
+ setToFavorite?: boolean;
+ workspaceSlug: string;
+ onClose: () => void;
+ handleNextStep: (projectId: string) => void;
+ data?: Partial;
+};
+
+const defaultValues: Partial = {
+ cover_image: PROJECT_UNSPLASH_COVERS[Math.floor(Math.random() * PROJECT_UNSPLASH_COVERS.length)],
+ description: "",
+ logo_props: {
+ in_use: "emoji",
+ emoji: {
+ value: getRandomEmoji(),
+ },
+ },
+ identifier: "",
+ name: "",
+ network: 2,
+ project_lead: null,
+};
+
+export const CreateProjectForm: FC = observer((props) => {
+ const { setToFavorite, workspaceSlug, onClose, handleNextStep } = props;
+ // store
+ const { captureProjectEvent } = useEventTracker();
+ const { addProjectToFavorites, createProject } = useProject();
+ // states
+ const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true);
+ // form info
+ const methods = useForm({
+ defaultValues,
+ reValidateMode: "onChange",
+ });
+ const { handleSubmit, reset, setValue } = methods;
+ const { isMobile } = usePlatformOS();
+ const handleAddToFavorites = (projectId: string) => {
+ if (!workspaceSlug) return;
+
+ addProjectToFavorites(workspaceSlug.toString(), projectId).catch(() => {
+ setToast({
+ type: TOAST_TYPE.ERROR,
+ title: "Error!",
+ message: "Couldn't remove the project from favorites. Please try again.",
+ });
+ });
+ };
+
+ const onSubmit = async (formData: Partial) => {
+ // Upper case identifier
+ formData.identifier = formData.identifier?.toUpperCase();
+
+ return createProject(workspaceSlug.toString(), formData)
+ .then((res) => {
+ const newPayload = {
+ ...res,
+ state: "SUCCESS",
+ };
+ captureProjectEvent({
+ eventName: PROJECT_CREATED,
+ payload: newPayload,
+ });
+ setToast({
+ type: TOAST_TYPE.SUCCESS,
+ title: "Success!",
+ message: "Project created successfully.",
+ });
+ if (setToFavorite) {
+ handleAddToFavorites(res.id);
+ }
+ handleNextStep(res.id);
+ })
+ .catch((err) => {
+ Object.keys(err.data).map((key) => {
+ setToast({
+ type: TOAST_TYPE.ERROR,
+ title: "Error!",
+ message: err.data[key],
+ });
+ captureProjectEvent({
+ eventName: PROJECT_CREATED,
+ payload: {
+ ...formData,
+ state: "FAILED",
+ },
+ });
+ });
+ });
+ };
+
+ const handleClose = () => {
+ onClose();
+ setIsChangeInIdentifierRequired(true);
+ setTimeout(() => {
+ reset();
+ }, 300);
+ };
+
+ return (
+
+
+
+
+
+ );
+});
diff --git a/web/ce/components/projects/header.tsx b/web/ce/components/projects/header.tsx
new file mode 100644
index 00000000000..08871ec9b6c
--- /dev/null
+++ b/web/ce/components/projects/header.tsx
@@ -0,0 +1,5 @@
+"use client";
+
+import { ProjectsBaseHeader } from "@/components/project/header";
+
+export const ProjectsListHeader = () => ;
diff --git a/web/app/[workspaceSlug]/(projects)/projects/(list)/mobile-header.tsx b/web/ce/components/projects/mobile-header.tsx
similarity index 99%
rename from web/app/[workspaceSlug]/(projects)/projects/(list)/mobile-header.tsx
rename to web/ce/components/projects/mobile-header.tsx
index cd8eb9dfe08..3804721600e 100644
--- a/web/app/[workspaceSlug]/(projects)/projects/(list)/mobile-header.tsx
+++ b/web/ce/components/projects/mobile-header.tsx
@@ -1,3 +1,4 @@
+"use client";
import { useCallback } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
@@ -23,7 +24,6 @@ export const ProjectsListMobileHeader = observer(() => {
updateFilters,
} = useProjectFilter();
-
const {
workspace: { workspaceMemberIds },
} = useMember();
diff --git a/web/ce/components/projects/page.tsx b/web/ce/components/projects/page.tsx
new file mode 100644
index 00000000000..960c5973000
--- /dev/null
+++ b/web/ce/components/projects/page.tsx
@@ -0,0 +1,5 @@
+import Root from "@/components/project/root";
+
+const ProjectPageRoot = () => ;
+
+export default ProjectPageRoot;
diff --git a/web/ce/types/projects/index.ts b/web/ce/types/projects/index.ts
new file mode 100644
index 00000000000..244d8c4df33
--- /dev/null
+++ b/web/ce/types/projects/index.ts
@@ -0,0 +1 @@
+export * from "./projects";
diff --git a/web/ce/types/projects/projects.ts b/web/ce/types/projects/projects.ts
new file mode 100644
index 00000000000..567c9488db7
--- /dev/null
+++ b/web/ce/types/projects/projects.ts
@@ -0,0 +1,3 @@
+import { IProject } from "@plane/types";
+
+export type TProject = IProject;
diff --git a/web/core/components/gantt-chart/blocks/blocks-list.tsx b/web/core/components/gantt-chart/blocks/blocks-list.tsx
index 8c94b07d036..c4ffae1387e 100644
--- a/web/core/components/gantt-chart/blocks/blocks-list.tsx
+++ b/web/core/components/gantt-chart/blocks/blocks-list.tsx
@@ -14,10 +14,10 @@ export type GanttChartBlocksProps = {
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
blockToRender: (data: any) => React.ReactNode;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
- enableBlockLeftResize: boolean;
- enableBlockRightResize: boolean;
- enableBlockMove: boolean;
- enableAddBlock: boolean;
+ enableBlockLeftResize: boolean | ((blockId: string) => boolean);
+ enableBlockRightResize: boolean | ((blockId: string) => boolean);
+ enableBlockMove: boolean | ((blockId: string) => boolean);
+ enableAddBlock: boolean | ((blockId: string) => boolean);
ganttContainerRef: React.RefObject;
showAllBlocks: boolean;
selectionHelpers: TSelectionHelper;
@@ -55,10 +55,14 @@ export const GanttChartBlocksList: FC = (props) => {
showAllBlocks={showAllBlocks}
blockToRender={blockToRender}
blockUpdateHandler={blockUpdateHandler}
- enableBlockLeftResize={enableBlockLeftResize}
- enableBlockRightResize={enableBlockRightResize}
- enableBlockMove={enableBlockMove}
- enableAddBlock={enableAddBlock}
+ enableBlockLeftResize={
+ typeof enableBlockLeftResize === "function" ? enableBlockLeftResize(blockId) : enableBlockLeftResize
+ }
+ enableBlockRightResize={
+ typeof enableBlockRightResize === "function" ? enableBlockRightResize(blockId) : enableBlockRightResize
+ }
+ enableBlockMove={typeof enableBlockMove === "function" ? enableBlockMove(blockId) : enableBlockMove}
+ enableAddBlock={typeof enableAddBlock === "function" ? enableAddBlock(blockId) : enableAddBlock}
ganttContainerRef={ganttContainerRef}
selectionHelpers={selectionHelpers}
/>
diff --git a/web/core/components/gantt-chart/chart/header.tsx b/web/core/components/gantt-chart/chart/header.tsx
index 8756e200fd8..4e16436df40 100644
--- a/web/core/components/gantt-chart/chart/header.tsx
+++ b/web/core/components/gantt-chart/chart/header.tsx
@@ -16,10 +16,12 @@ type Props = {
handleToday: () => void;
loaderTitle: string;
toggleFullScreenMode: () => void;
+ showToday: boolean;
};
export const GanttChartHeader: React.FC = observer((props) => {
- const { blockIds, fullScreenMode, handleChartView, handleToday, loaderTitle, toggleFullScreenMode } = props;
+ const { blockIds, fullScreenMode, handleChartView, handleToday, loaderTitle, toggleFullScreenMode, showToday } =
+ props;
// chart hook
const { currentView } = useGanttChart();
@@ -46,9 +48,15 @@ export const GanttChartHeader: React.FC = observer((props) => {
))}
-
+ {showToday && (
+
+ )}