refactor: Clean up DashboardMain, SidePannel, and ProjectSocketRes components by removing unused imports and optimizing code structure

This commit is contained in:
2025-09-08 11:59:16 +05:30
parent 80a672adf0
commit 90fb7fbc06
3 changed files with 275 additions and 326 deletions

View File

@@ -15,258 +15,220 @@ import { generateUniqueId } from "../../functions/generateUniqueId";
import ProjectSocketRes from "./socket/projectSocketRes"; import ProjectSocketRes from "./socket/projectSocketRes";
interface Project { interface Project {
_id: string; _id: string;
projectName: string; projectName: string;
thumbnail: string; thumbnail: string;
createdBy: { _id: string; userName: string }; createdBy: { _id: string; userName: string };
projectUuid?: string; projectUuid?: string;
createdAt?: string; createdAt?: string;
DeletedAt?: string; DeletedAt?: string;
} }
interface ProjectCollection { interface ProjectCollection {
[key: string]: Project[]; [key: string]: Project[];
} }
type Folder = "home" | "projects" | "shared" | "trash"; type Folder = "home" | "projects" | "shared" | "trash";
interface DashboardMainProps { interface DashboardMainProps {
activeFolder: Folder; activeFolder: Folder;
} }
const DashboardMain: React.FC<DashboardMainProps> = ({ activeFolder }) => { const DashboardMain: React.FC<DashboardMainProps> = ({ activeFolder }) => {
const [activeSubFolder, setActiveSubFolder] = useState("myProjects"); const [activeSubFolder, setActiveSubFolder] = useState("myProjects");
const [projectsData, setProjectsData] = useState<ProjectCollection>({}); const [projectsData, setProjectsData] = useState<ProjectCollection>({});
const [isSearchActive, setIsSearchActive] = useState<boolean>(false); const [isSearchActive, setIsSearchActive] = useState<boolean>(false);
const [duplicateData, setDuplicateData] = useState<Object>({}); const [duplicateData, setDuplicateData] = useState<Object>({});
const [openKebabProjectId, setOpenKebabProjectId] = useState<string | null>( const [openKebabProjectId, setOpenKebabProjectId] = useState<string | null>(null);
null const [projectsCache, setProjectsCache] = useState<{
); [key: string]: ProjectCollection;
const [projectsCache, setProjectsCache] = useState<{ }>({});
[key: string]: ProjectCollection;
}>({});
const { userId, organization } = getUserData(); const { userId, organization } = getUserData();
const { projectSocket } = useSocketStore(); const { projectSocket } = useSocketStore();
// #region API Fetchers // #region API Fetchers
const fetchData = async () => { const fetchData = async () => {
const cacheKey = const cacheKey = activeFolder + (activeFolder === "projects" ? `-${activeSubFolder}` : "");
activeFolder + (activeFolder === "projects" ? `-${activeSubFolder}` : "");
if (projectsCache[cacheKey] && !isSearchActive) { if (projectsCache[cacheKey] && !isSearchActive) {
setProjectsData(projectsCache[cacheKey]); setProjectsData(projectsCache[cacheKey]);
return; return;
}
try {
let projects: ProjectCollection = {}; // initialize as empty object
switch (activeFolder) {
case "home":
projects = await recentlyViewed(organization, userId);
break;
case "projects":
if (activeSubFolder === "myProjects") {
projects = await getAllProjects(userId, organization);
} else {
projects = await sharedWithMeProjects();
}
break;
case "trash":
projects = await getTrash(organization);
break;
}
// Only update cache if projects is not empty
if (
projects &&
JSON.stringify(projects) !== JSON.stringify(projectsData)
) {
setProjectsCache((prev) => ({ ...prev, [cacheKey]: projects }));
setProjectsData(projects);
}
} catch (error) {
console.error(error);
}
};
// #region Search Handlers
const handleSearch = async (inputValue: string) => {
if (!inputValue.trim()) {
setIsSearchActive(false);
return;
}
let results;
if (activeFolder === "trash") {
results = await trashSearchProject(organization, userId, inputValue);
} else {
results = await searchProject(organization, userId, inputValue);
}
setIsSearchActive(true);
setProjectsData(results?.message ? {} : results);
};
// #region Socket Actions
const handleDelete = async (projectId: string): Promise<void> => {
if (projectSocket) {
projectSocket.emit("v1:project:delete", {
projectId,
organization,
userId,
});
}
updateStateAfterRemove(projectId);
};
const handlePermanentDelete = async (projectId: string): Promise<void> => {
if (projectSocket) {
projectSocket.emit("v1:trash:delete", {
projectId,
organization,
userId,
});
}
updateStateAfterRemove(projectId);
};
const handleRestore = async (projectId: string): Promise<void> => {
await restoreTrash(organization, projectId);
updateStateAfterRemove(projectId);
};
const handleDuplicate = async (
projectId: string,
projectName: string,
thumbnail: string
): Promise<void> => {
if (projectSocket) {
projectSocket.emit("v1:project:Duplicate", {
userId,
thumbnail,
organization,
projectUuid: generateUniqueId(),
refProjectID: projectId,
projectName,
});
}
};
// #region Project Map
const updateStateAfterRemove = (projectId: string) => {
setProjectsData((prev: ProjectCollection) => {
const key = Object.keys(prev)[0];
const updatedList = prev[key]?.filter((p) => p._id !== projectId) || [];
return { ...prev, [key]: updatedList };
});
setIsSearchActive(false);
};
const renderProjects = () => {
const key = Object.keys(projectsData)[0];
const projectList = projectsData[key];
if (!projectList?.length) {
return <div className="empty-state">No projects found</div>;
}
return projectList.map((project) => (
<DashboardCard
key={project._id}
projectName={project.projectName}
thumbnail={project.thumbnail}
projectId={project._id}
createdBy={project.createdBy}
createdAt={
activeFolder === "trash" ? project.DeletedAt : project.createdAt
} }
{...(activeFolder === "home" && {
handleDeleteProject: handleDelete,
handleDuplicateRecentProject: handleDuplicate,
setRecentDuplicateData: setDuplicateData,
})}
{...(activeSubFolder === "myProjects" && {
handleDeleteProject: handleDelete,
handleDuplicateWorkspaceProject: handleDuplicate,
setProjectDuplicateData: setDuplicateData,
})}
{...(activeSubFolder === "shared" && {
handleDuplicateWorkspaceProject: handleDuplicate,
setProjectDuplicateData: setDuplicateData,
active: "shared",
})}
{...(activeFolder === "trash" && {
handleRestoreProject: handleRestore,
handleTrashDeleteProject: handlePermanentDelete,
active: "trash",
})}
openKebabProjectId={openKebabProjectId}
setOpenKebabProjectId={setOpenKebabProjectId}
/>
));
};
// #region Use Effects try {
useEffect(() => { let projects: ProjectCollection = {}; // initialize as empty object
if (!isSearchActive) fetchData();
// eslint-disable-next-line
}, [activeFolder, isSearchActive, activeSubFolder]);
return ( switch (activeFolder) {
<div className="dashboard-home-container"> case "home":
<DashboardNavBar projects = await recentlyViewed(organization, userId);
page={activeFolder} break;
{...(activeFolder === "trash" case "projects":
? { handleTrashSearch: handleSearch } if (activeSubFolder === "myProjects") {
: { projects = await getAllProjects(userId, organization);
handleProjectsSearch: handleSearch, } else {
handleRecentProjectSearch: handleSearch, projects = await sharedWithMeProjects();
})} }
/> break;
case "trash":
projects = await getTrash(organization);
break;
}
{activeFolder === "home" && <MarketPlaceBanner />} // Only update cache if projects is not empty
if (projects && JSON.stringify(projects) !== JSON.stringify(projectsData)) {
setProjectsCache((prev) => ({ ...prev, [cacheKey]: projects }));
setProjectsData(projects);
}
} catch (error) {
console.error(error);
}
};
<div // #region Search Handlers
className="dashboard-container" const handleSearch = async (inputValue: string) => {
style={{ height: "calc(100% - 87px)" }} if (!inputValue.trim()) {
> setIsSearchActive(false);
{activeFolder === "projects" && ( return;
<div }
className="header-wrapper"
style={{ display: "flex", gap: "7px" }}
>
<button
className={`header ${
activeSubFolder === "myProjects" && "active"
}`}
onClick={() => setActiveSubFolder("myProjects")}
>
My Projects
</button>
<button
className={`header ${activeSubFolder === "shared" && "active"}`}
onClick={() => setActiveSubFolder("shared")}
>
Shared with me
</button>
</div>
)}
<div className="cards-container">{renderProjects()}</div> let results;
if (activeFolder === "trash") {
results = await trashSearchProject(organization, userId, inputValue);
} else {
results = await searchProject(organization, userId, inputValue);
}
{duplicateData && Object.keys(duplicateData).length > 0 && ( setIsSearchActive(true);
<ProjectSocketRes setProjectsData(results?.message ? {} : results);
setIsSearchActive={setIsSearchActive} };
{...(activeFolder === "home"
? { setRecentProjects: setProjectsData } // #region Socket Actions
: { setWorkspaceProjects: setProjectsData })} const handleDelete = async (projectId: string): Promise<void> => {
/> if (projectSocket) {
)} projectSocket.emit("v1:project:delete", {
</div> projectId,
</div> organization,
); userId,
});
}
updateStateAfterRemove(projectId);
};
const handlePermanentDelete = async (projectId: string): Promise<void> => {
if (projectSocket) {
projectSocket.emit("v1:trash:delete", {
projectId,
organization,
userId,
});
}
updateStateAfterRemove(projectId);
};
const handleRestore = async (projectId: string): Promise<void> => {
await restoreTrash(organization, projectId);
updateStateAfterRemove(projectId);
};
const handleDuplicate = async (projectId: string, projectName: string, thumbnail: string): Promise<void> => {
if (projectSocket) {
projectSocket.emit("v1:project:Duplicate", {
userId,
thumbnail,
organization,
projectUuid: generateUniqueId(),
refProjectID: projectId,
projectName,
});
}
};
// #region Project Map
const updateStateAfterRemove = (projectId: string) => {
setProjectsData((prev: ProjectCollection) => {
const key = Object.keys(prev)[0];
const updatedList = prev[key]?.filter((p) => p._id !== projectId) || [];
return { ...prev, [key]: updatedList };
});
setIsSearchActive(false);
};
const renderProjects = () => {
const key = Object.keys(projectsData)[0];
const projectList = projectsData[key];
if (!projectList?.length) {
return <div className="empty-state">No projects found</div>;
}
return projectList.map((project) => (
<DashboardCard
key={project._id}
projectName={project.projectName}
thumbnail={project.thumbnail}
projectId={project._id}
createdBy={project.createdBy}
createdAt={activeFolder === "trash" ? project.DeletedAt : project.createdAt}
{...(activeFolder === "home" && {
handleDeleteProject: handleDelete,
handleDuplicateRecentProject: handleDuplicate,
setRecentDuplicateData: setDuplicateData,
})}
{...(activeSubFolder === "myProjects" && {
handleDeleteProject: handleDelete,
handleDuplicateWorkspaceProject: handleDuplicate,
setProjectDuplicateData: setDuplicateData,
})}
{...(activeSubFolder === "shared" && {
handleDuplicateWorkspaceProject: handleDuplicate,
setProjectDuplicateData: setDuplicateData,
active: "shared",
})}
{...(activeFolder === "trash" && {
handleRestoreProject: handleRestore,
handleTrashDeleteProject: handlePermanentDelete,
active: "trash",
})}
openKebabProjectId={openKebabProjectId}
setOpenKebabProjectId={setOpenKebabProjectId}
/>
));
};
// #region Use Effects
useEffect(() => {
if (!isSearchActive) fetchData();
// eslint-disable-next-line
}, [activeFolder, isSearchActive, activeSubFolder]);
return (
<div className="dashboard-home-container">
<DashboardNavBar
page={activeFolder}
{...(activeFolder === "trash" ? { handleTrashSearch: handleSearch } : { handleProjectsSearch: handleSearch, handleRecentProjectSearch: handleSearch })}
/>
{activeFolder === "home" && <MarketPlaceBanner />}
<div className="dashboard-container" style={{ height: "calc(100% - 87px)" }}>
{activeFolder === "projects" && (
<div className="header-wrapper" style={{ display: "flex", gap: "7px" }}>
<button className={`header ${activeSubFolder === "myProjects" && "active"}`} onClick={() => setActiveSubFolder("myProjects")}>
My Projects
</button>
<button className={`header ${activeSubFolder === "shared" && "active"}`} onClick={() => setActiveSubFolder("shared")}>
Shared with me
</button>
</div>
)}
<div className="cards-container">{renderProjects()}</div>
<ProjectSocketRes setIsSearchActive={setIsSearchActive} {...(activeFolder === "home" ? { setRecentProjects: setProjectsData } : { setWorkspaceProjects: setProjectsData })} />
</div>
</div>
);
}; };
export default DashboardMain; export default DashboardMain;

View File

@@ -13,7 +13,7 @@ import darkThemeImage from "../../assets/image/darkThemeProject.png";
import lightThemeImage from "../../assets/image/lightThemeProject.png"; import lightThemeImage from "../../assets/image/lightThemeProject.png";
import { SettingsIcon, TrashIcon } from "../icons/ExportCommonIcons"; import { SettingsIcon, TrashIcon } from "../icons/ExportCommonIcons";
import { getUserData } from "../../functions/getUserData"; import { getUserData } from "../../functions/getUserData";
import { useLoadingProgress, useSocketStore } from "../../store/builder/store"; import { useSocketStore } from "../../store/builder/store";
// import { createProject } from "../../services/dashboard/createProject"; // import { createProject } from "../../services/dashboard/createProject";
@@ -25,23 +25,20 @@ interface SidePannelProps {
const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => { const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
const { email, userName, userId, organization } = getUserData(); const { email, userName, userId, organization } = getUserData();
const navigate = useNavigate(); const navigate = useNavigate();
const { setLoadingProgress } = useLoadingProgress();
const { projectSocket, initializeSocket } = useSocketStore(); const { projectSocket, initializeSocket } = useSocketStore();
const savedTheme = localStorage.getItem("theme") ?? "light"; const savedTheme = localStorage.getItem("theme") ?? "light";
function generateProjectId() { function generateProjectId() {
const randomBytes = new Uint8Array(12); const randomBytes = new Uint8Array(12);
crypto.getRandomValues(randomBytes); crypto.getRandomValues(randomBytes);
return Array.from(randomBytes, (byte) => return Array.from(randomBytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
byte.toString(16).padStart(2, "0")
).join("");
} }
const handleCreateNewProject = async () => { const handleCreateNewProject = async () => {
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
const refreshToken = localStorage.getItem("refreshToken"); const refreshToken = localStorage.getItem("refreshToken");
if (!token || !refreshToken) { if (!token || !refreshToken) {
console.error('token expired'); console.error("token expired");
return; return;
} }
@@ -56,15 +53,8 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
organization: organization, organization: organization,
projectUuid: projectId, projectUuid: projectId,
}; };
const handleResponse = (data: any) => {
if (data.message === "Project created successfully") {
setLoadingProgress(1);
navigate(`/projects/${data.data.projectId}`);
}
projectSocket.off("v1-project:response:add", handleResponse);
};
projectSocket.on("v1-project:response:add", handleResponse);
console.log('addProject: ', addProject);
projectSocket.emit("v1:project:add", addProject); projectSocket.emit("v1:project:add", addProject);
} else { } else {
// API // API
@@ -81,11 +71,11 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
<div className="side-pannel-container"> <div className="side-pannel-container">
<div className="side-pannel-header"> <div className="side-pannel-header">
<div className="user-container"> <div className="user-container">
<div className="user-profile"> <div className="user-profile">{userName?.charAt(0).toUpperCase()}</div>
{userName?.charAt(0).toUpperCase()}
</div>
<div className="user-name"> <div className="user-name">
{userName ? userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase() : "Anonymous"} {userName
? userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase()
: "Anonymous"}
</div> </div>
</div> </div>
<div className="notifications-container"> <div className="notifications-container">
@@ -98,18 +88,14 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
<div className="side-bar-content-container"> <div className="side-bar-content-container">
<div className="side-bar-options-container"> <div className="side-bar-options-container">
<button <button
className={ className={activeTab === "Home" ? "option-list active" : "option-list"}
activeTab === "Home" ? "option-list active" : "option-list"
}
onClick={() => setActiveTab("Home")} onClick={() => setActiveTab("Home")}
> >
<HomeIcon /> <HomeIcon />
Home Home
</button> </button>
<button <button
className={ className={activeTab === "Projects" ? "option-list active" : "option-list"}
activeTab === "Projects" ? "option-list active" : "option-list"
}
title="Projects" title="Projects"
onClick={() => setActiveTab("Projects")} onClick={() => setActiveTab("Projects")}
> >
@@ -117,9 +103,7 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
Projects Projects
</button> </button>
<button <button
className={ className={activeTab === "Trash" ? "option-list active" : "option-list"}
activeTab === "Trash" ? "option-list active" : "option-list"
}
title="Trash" title="Trash"
onClick={() => setActiveTab("Trash")} onClick={() => setActiveTab("Trash")}
> >
@@ -127,9 +111,7 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
Trash Trash
</button> </button>
<button <button
className={ className={activeTab === "Tutorials" ? "option-list active" : "option-list"}
activeTab === "Tutorials" ? "option-list active" : "option-list"
}
title="coming soon" title="coming soon"
disabled disabled
onClick={() => { onClick={() => {
@@ -142,9 +124,7 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
</button> </button>
<button <button
className={ className={
activeTab === "Documentation" activeTab === "Documentation" ? "option-list active" : "option-list"
? "option-list active"
: "option-list"
} }
title="coming soon" title="coming soon"
disabled disabled

View File

@@ -1,90 +1,97 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useSocketStore } from "../../../store/builder/store"; import { useLoadingProgress, useSocketStore } from "../../../store/builder/store";
import { getUserData } from "../../../functions/getUserData"; import { getUserData } from "../../../functions/getUserData";
import { getAllProjects } from "../../../services/dashboard/getAllProjects"; import { getAllProjects } from "../../../services/dashboard/getAllProjects";
import { recentlyViewed } from "../../../services/dashboard/recentlyViewed"; import { recentlyViewed } from "../../../services/dashboard/recentlyViewed";
import { useNavigate } from "react-router-dom";
interface Project { interface Project {
_id: string; _id: string;
projectName: string; projectName: string;
thumbnail: string; thumbnail: string;
createdBy: { _id: string; userName: string }; createdBy: { _id: string; userName: string };
projectUuid?: string; projectUuid?: string;
createdAt?: string; createdAt?: string;
DeletedAt?: string; DeletedAt?: string;
isViewed?: string; isViewed?: string;
} }
interface ProjectCollection { interface ProjectCollection {
[key: string]: Project[]; [key: string]: Project[];
} }
interface ProjectSocketResProps { interface ProjectSocketResProps {
setRecentProjects?: React.Dispatch<React.SetStateAction<ProjectCollection>>; setRecentProjects?: React.Dispatch<React.SetStateAction<ProjectCollection>>;
setWorkspaceProjects?: React.Dispatch<React.SetStateAction<ProjectCollection>>; setWorkspaceProjects?: React.Dispatch<React.SetStateAction<ProjectCollection>>;
setIsSearchActive?: React.Dispatch<React.SetStateAction<boolean>>; setIsSearchActive?: React.Dispatch<React.SetStateAction<boolean>>;
} }
const ProjectSocketRes = ({ const ProjectSocketRes = ({
setRecentProjects, setRecentProjects,
setWorkspaceProjects, setWorkspaceProjects,
setIsSearchActive, setIsSearchActive,
}: ProjectSocketResProps) => { }: ProjectSocketResProps) => {
const { projectSocket } = useSocketStore(); const navigate = useNavigate();
const { userId, organization } = getUserData(); const { projectSocket } = useSocketStore();
const { userId, organization } = getUserData();
const { setLoadingProgress } = useLoadingProgress();
useEffect(() => { useEffect(() => {
if (!projectSocket) return; console.log('projectSocket: ', projectSocket);
if (!projectSocket) return;
const handleAdd = (data: any) => { const handleAdd = (data: any) => {
// console.log("Add:", data); console.log('data: ', data);
}; if (data.message === "Project created successfully") {
setLoadingProgress(1);
navigate(`/projects/${data.data.projectId}`);
}
};
const handleDelete = (data: any) => { const handleDelete = (data: any) => {
// console.log("Delete:", data); // console.log("Delete:", data);
}; };
const handleUpdate = (data: any) => { const handleUpdate = (data: any) => {
// console.log("Update:", data); // console.log("Update:", data);
}; };
const handleTrashDelete = (data: any) => { const handleTrashDelete = (data: any) => {
// console.log("Trash Delete:", data); // console.log("Trash Delete:", data);
}; };
const handleDuplicate = async (data: any) => { const handleDuplicate = async (data: any) => {
console.log("Project duplicate response:", data); if (data?.message === "Project Duplicated successfully") {
if (data?.message === "Project Duplicated successfully") { if (setWorkspaceProjects) {
if (setWorkspaceProjects) { const allProjects = await getAllProjects(userId, organization);
const allProjects = await getAllProjects(userId, organization); setWorkspaceProjects(allProjects);
setWorkspaceProjects(allProjects); } else if (setRecentProjects) {
} else if (setRecentProjects) { const recentProjects = await recentlyViewed(organization, userId);
const recentProjects = await recentlyViewed(organization, userId); setRecentProjects(recentProjects);
setRecentProjects(recentProjects); }
} setIsSearchActive && setIsSearchActive(false);
setIsSearchActive && setIsSearchActive(false); } else {
} else { console.warn("Duplication failed or unexpected response.");
console.warn("Duplication failed or unexpected response."); }
} };
};
projectSocket.on("v1-project:response:add", handleAdd); projectSocket.on("v1-project:response:add", handleAdd);
projectSocket.on("v1-project:response:delete", handleDelete); projectSocket.on("v1-project:response:delete", handleDelete);
projectSocket.on("v1-project:response:update", handleUpdate); projectSocket.on("v1-project:response:update", handleUpdate);
projectSocket.on("v1-project:response:Duplicate", handleDuplicate); projectSocket.on("v1-project:response:Duplicate", handleDuplicate);
projectSocket.on("v1:trash:response:delete", handleTrashDelete); projectSocket.on("v1:trash:response:delete", handleTrashDelete);
return () => { return () => {
projectSocket.off("v1-project:response:add", handleAdd); projectSocket.off("v1-project:response:add", handleAdd);
projectSocket.off("v1-project:response:delete", handleDelete); projectSocket.off("v1-project:response:delete", handleDelete);
projectSocket.off("v1-project:response:update", handleUpdate); projectSocket.off("v1-project:response:update", handleUpdate);
projectSocket.off("v1-project:response:Duplicate", handleDuplicate); projectSocket.off("v1-project:response:Duplicate", handleDuplicate);
projectSocket.off("v1:trash:response:delete", handleTrashDelete); projectSocket.off("v1:trash:response:delete", handleTrashDelete);
}; };
// eslint-disable-next-line // eslint-disable-next-line
}, [projectSocket, userId, organization]); }, [projectSocket, userId, organization]);
return null; return null;
}; };
export default ProjectSocketRes; export default ProjectSocketRes;