first commit
This commit is contained in:
281
app/src/components/Dashboard/DashboardCard.tsx
Normal file
281
app/src/components/Dashboard/DashboardCard.tsx
Normal file
@@ -0,0 +1,281 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import img from "../../assets/image/image.png";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { getUserData } from "./functions/getUserData";
|
||||
import { useLoadingProgress, useProjectName, useSocketStore } from "../../store/builder/store";
|
||||
import { viewProject } from "../../services/dashboard/viewProject";
|
||||
import OuterClick from "../../utils/outerClick";
|
||||
import { KebabIcon } from "../icons/ExportCommonIcons";
|
||||
import { getAllProjects } from "../../services/dashboard/getAllProjects";
|
||||
|
||||
interface DashBoardCardProps {
|
||||
projectName: string;
|
||||
thumbnail: any;
|
||||
projectId: string;
|
||||
createdAt?: string;
|
||||
isViewed?: string;
|
||||
handleDeleteProject?: (projectId: string) => Promise<void>;
|
||||
handleTrashDeleteProject?: (projectId: string) => Promise<void>;
|
||||
handleRestoreProject?: (projectId: string) => Promise<void>;
|
||||
handleDuplicateWorkspaceProject?: (
|
||||
projectId: string,
|
||||
projectName: string,
|
||||
thumbnail: string
|
||||
) => Promise<void>;
|
||||
handleDuplicateRecentProject?: (
|
||||
projectId: string,
|
||||
projectName: string,
|
||||
thumbnail: string
|
||||
) => Promise<void>;
|
||||
active?: string;
|
||||
setIsSearchActive?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setRecentDuplicateData?: React.Dispatch<React.SetStateAction<Object>>;
|
||||
setProjectDuplicateData?: React.Dispatch<React.SetStateAction<Object>>;
|
||||
}
|
||||
type RelativeTimeFormatUnit = any;
|
||||
|
||||
const DashboardCard: React.FC<DashBoardCardProps> = ({
|
||||
projectName,
|
||||
thumbnail,
|
||||
projectId,
|
||||
active,
|
||||
handleDeleteProject,
|
||||
handleRestoreProject,
|
||||
handleTrashDeleteProject,
|
||||
handleDuplicateWorkspaceProject,
|
||||
handleDuplicateRecentProject,
|
||||
createdAt,
|
||||
setRecentDuplicateData,
|
||||
setProjectDuplicateData,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const { setProjectName } = useProjectName();
|
||||
const { userId, organization, userName } = getUserData();
|
||||
const [isKebabOpen, setIsKebabOpen] = useState(false);
|
||||
const [renameValue, setRenameValue] = useState(projectName);
|
||||
const [isRenaming, setIsRenaming] = useState(false);
|
||||
const { projectSocket } = useSocketStore();
|
||||
const { setLoadingProgress } = useLoadingProgress();
|
||||
const kebabRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const navigateToProject = async (e: any) => {
|
||||
if (active && active == "trash") return;
|
||||
try {
|
||||
const viewedProject = await viewProject(organization, projectId, userId);
|
||||
console.log("Viewed project:", viewedProject);
|
||||
} catch (error) {
|
||||
console.error("Error opening project:", error);
|
||||
}
|
||||
setLoadingProgress(1)
|
||||
setProjectName(projectName);
|
||||
navigate(`/${projectId}`);
|
||||
};
|
||||
|
||||
const handleOptionClick = async (option: string) => {
|
||||
switch (option) {
|
||||
case "delete":
|
||||
if (handleDeleteProject) {
|
||||
await handleDeleteProject(projectId);
|
||||
} else if (handleTrashDeleteProject) {
|
||||
await handleTrashDeleteProject(projectId);
|
||||
}
|
||||
break;
|
||||
case "restore":
|
||||
if (handleRestoreProject) {
|
||||
await handleRestoreProject(projectId);
|
||||
}
|
||||
break;
|
||||
case "open in new tab":
|
||||
try {
|
||||
await viewProject(organization, projectId, userId);
|
||||
setProjectName(projectName);
|
||||
setIsKebabOpen(false);
|
||||
} catch (error) {
|
||||
console.error("Error opening project in new tab:", error);
|
||||
}
|
||||
window.open(`/${projectId}`, "_blank");
|
||||
break;
|
||||
case "rename":
|
||||
setIsRenaming(true);
|
||||
break;
|
||||
case "duplicate":
|
||||
if (handleDuplicateWorkspaceProject) {
|
||||
setProjectDuplicateData &&
|
||||
setProjectDuplicateData({
|
||||
projectId,
|
||||
projectName,
|
||||
thumbnail,
|
||||
});
|
||||
await handleDuplicateWorkspaceProject(projectId, projectName, thumbnail);
|
||||
} else if (handleDuplicateRecentProject) {
|
||||
setRecentDuplicateData &&
|
||||
setRecentDuplicateData({
|
||||
projectId,
|
||||
projectName,
|
||||
thumbnail,
|
||||
});
|
||||
await handleDuplicateRecentProject(projectId, projectName, thumbnail);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
setIsKebabOpen(false);
|
||||
};
|
||||
|
||||
OuterClick({
|
||||
contextClassName: ["kebab-wrapper", "kebab-options-wrapper"],
|
||||
setMenuVisible: () => setIsKebabOpen(false),
|
||||
});
|
||||
|
||||
const handleProjectName = async (projectName: string) => {
|
||||
setRenameValue(projectName);
|
||||
if (!projectId) return;
|
||||
try {
|
||||
const projects = await getAllProjects(userId, organization);
|
||||
console.log("projects: ", projects);
|
||||
let projectUuid = projects.Projects.find(
|
||||
(val: any) => val.projectUuid === projectId || val._id === projectId
|
||||
);
|
||||
const updateProjects = {
|
||||
projectId: projectUuid,
|
||||
organization,
|
||||
userId,
|
||||
projectName,
|
||||
thumbnail: undefined,
|
||||
};
|
||||
|
||||
if (projectSocket) {
|
||||
projectSocket.emit("v1:project:update", updateProjects);
|
||||
}
|
||||
} catch (error) { }
|
||||
};
|
||||
|
||||
function getRelativeTime(dateString: string): string {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
||||
|
||||
const intervals: Record<RelativeTimeFormatUnit, number> = {
|
||||
year: 31536000,
|
||||
month: 2592000,
|
||||
week: 604800,
|
||||
day: 86400,
|
||||
hour: 3600,
|
||||
minute: 60,
|
||||
second: 1,
|
||||
};
|
||||
|
||||
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
||||
|
||||
for (const key in intervals) {
|
||||
const unit = key as RelativeTimeFormatUnit;
|
||||
const diff = Math.floor(diffInSeconds / intervals[unit]);
|
||||
if (diff >= 1) {
|
||||
return rtf.format(-diff, unit);
|
||||
}
|
||||
}
|
||||
return "just now";
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className="dashboard-card-container"
|
||||
onClick={navigateToProject}
|
||||
title={projectName}
|
||||
onMouseLeave={() => setIsKebabOpen(false)}
|
||||
>
|
||||
<div className="dashboard-card-wrapper">
|
||||
<div className="preview-container">
|
||||
{thumbnail ? <img src={thumbnail} alt="" /> : <img src={img} alt="" />}
|
||||
</div>
|
||||
<div className="project-details-container" onClick={(e) => { e.stopPropagation() }}>
|
||||
<div className="project-details">
|
||||
{isRenaming ? (
|
||||
<input
|
||||
value={renameValue}
|
||||
onChange={(e) => {
|
||||
e.stopPropagation();
|
||||
handleProjectName(e.target.value);
|
||||
}}
|
||||
onBlur={() => {
|
||||
setIsRenaming(false);
|
||||
setProjectName(renameValue);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
setProjectName(renameValue);
|
||||
setIsRenaming(false);
|
||||
}
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<span>{renameValue}</span>
|
||||
)}
|
||||
{createdAt && (
|
||||
<div className="project-data">
|
||||
{active && active == "trash" ? `Trashed by you` : `Edited `}{" "}
|
||||
{getRelativeTime(createdAt)}
|
||||
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="users-list-container" ref={kebabRef}>
|
||||
<div className="user-profile">
|
||||
{userName ? userName.charAt(0).toUpperCase() : "A"}
|
||||
</div>
|
||||
<button
|
||||
className="kebab-wrapper"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsKebabOpen((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
<KebabIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isKebabOpen && active !== "trash" && (
|
||||
<div className="kebab-options-wrapper">
|
||||
{["rename", "delete", "duplicate", "open in new tab"].map(
|
||||
(option) => (
|
||||
<button
|
||||
key={option}
|
||||
className="option"
|
||||
title={""}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOptionClick(option);
|
||||
}}
|
||||
>
|
||||
{option}
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{isKebabOpen && active && active == "trash" && (
|
||||
<div className="kebab-options-wrapper">
|
||||
{["restore", "delete"].map((option) => (
|
||||
<button
|
||||
key={option}
|
||||
className="option"
|
||||
onClick={(e) => {
|
||||
console.log("option", option);
|
||||
e.stopPropagation();
|
||||
handleOptionClick(option);
|
||||
}}
|
||||
>
|
||||
{option}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardCard;
|
||||
168
app/src/components/Dashboard/DashboardHome.tsx
Normal file
168
app/src/components/Dashboard/DashboardHome.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import DashboardCard from "./DashboardCard";
|
||||
import DashboardNavBar from "./DashboardNavBar";
|
||||
import MarketPlaceBanner from "./MarketPlaceBanner";
|
||||
import { getUserData } from "./functions/getUserData";
|
||||
import { useSocketStore } from "../../store/builder/store";
|
||||
import { recentlyViewed } from "../../services/dashboard/recentlyViewed";
|
||||
import { searchProject } from "../../services/dashboard/searchProjects";
|
||||
import { deleteProject } from "../../services/dashboard/deleteProject";
|
||||
import ProjectSocketRes from "./socket/projectSocketRes.dev";
|
||||
|
||||
interface Project {
|
||||
_id: string;
|
||||
projectName: string;
|
||||
thumbnail: string;
|
||||
createdBy: string;
|
||||
projectUuid?: string;
|
||||
createdAt: string;
|
||||
isViewed?: string
|
||||
}
|
||||
|
||||
interface RecentProjectsData {
|
||||
[key: string]: Project[];
|
||||
}
|
||||
|
||||
const DashboardHome: React.FC = () => {
|
||||
const [recentProjects, setRecentProjects] = useState<RecentProjectsData>({});
|
||||
const [isSearchActive, setIsSearchActive] = useState<boolean>(false);
|
||||
const { userId, organization } = getUserData();
|
||||
const { projectSocket } = useSocketStore();
|
||||
const [recentDuplicateData, setRecentDuplicateData] = useState<Object>({});
|
||||
|
||||
|
||||
const fetchRecentProjects = async () => {
|
||||
try {
|
||||
const projects = await recentlyViewed(organization, userId);
|
||||
console.log("RecentlyViewed: ", projects);
|
||||
|
||||
if (JSON.stringify(projects) !== JSON.stringify(recentProjects)) {
|
||||
setRecentProjects(projects);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching recent projects:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRecentProjectSearch = async (inputValue: string) => {
|
||||
if (!inputValue.trim()) {
|
||||
setIsSearchActive(false);
|
||||
return;
|
||||
}
|
||||
const filterRecentProcess = await searchProject(
|
||||
organization,
|
||||
userId,
|
||||
inputValue
|
||||
);
|
||||
setIsSearchActive(true);
|
||||
setRecentProjects(filterRecentProcess.message ? {} : filterRecentProcess);
|
||||
};
|
||||
|
||||
const handleDeleteProject = async (projectId: any) => {
|
||||
console.log("projectId:delete ", projectId);
|
||||
try {
|
||||
//API for delete project
|
||||
// const deletedProject = await deleteProject(
|
||||
// projectId,
|
||||
// userId,
|
||||
// organization
|
||||
// );
|
||||
// console.log('deletedProject: ', deletedProject);
|
||||
|
||||
//socket for delete Project
|
||||
const deleteProject = {
|
||||
projectId,
|
||||
organization,
|
||||
userId,
|
||||
};
|
||||
|
||||
if (projectSocket) {
|
||||
projectSocket.emit("v1:project:delete", deleteProject);
|
||||
}
|
||||
|
||||
setRecentProjects((prevDiscardedProjects: RecentProjectsData) => {
|
||||
if (!Array.isArray(prevDiscardedProjects?.RecentlyViewed)) {
|
||||
return prevDiscardedProjects;
|
||||
}
|
||||
const updatedProjectDatas = prevDiscardedProjects.RecentlyViewed.filter(
|
||||
(project) => project._id !== projectId
|
||||
);
|
||||
return {
|
||||
...prevDiscardedProjects,
|
||||
RecentlyViewed: updatedProjectDatas,
|
||||
};
|
||||
});
|
||||
setIsSearchActive(false);
|
||||
} catch (error) {
|
||||
console.error("Error deleting project:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDuplicateRecentProject = async (
|
||||
projectId: string,
|
||||
projectName: string,
|
||||
thumbnail: string
|
||||
) => {
|
||||
const duplicateRecentProjectData = {
|
||||
userId,
|
||||
thumbnail,
|
||||
organization,
|
||||
projectUuid: projectId,
|
||||
projectName,
|
||||
};
|
||||
projectSocket.emit("v1:project:Duplicate", duplicateRecentProjectData);
|
||||
};
|
||||
|
||||
const renderProjects = () => {
|
||||
const projectList = recentProjects[Object.keys(recentProjects)[0]];
|
||||
|
||||
console.log('projectList: ', projectList);
|
||||
if (!projectList?.length) {
|
||||
return <div className="empty-state">No recent projects found</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
projectList &&
|
||||
projectList.map((project) => (
|
||||
<DashboardCard
|
||||
key={project._id}
|
||||
projectName={project.projectName}
|
||||
thumbnail={project.thumbnail}
|
||||
projectId={project._id}
|
||||
createdAt={project.isViewed}
|
||||
handleDeleteProject={handleDeleteProject}
|
||||
handleDuplicateRecentProject={handleDuplicateRecentProject}
|
||||
setRecentDuplicateData={setRecentDuplicateData}
|
||||
/>
|
||||
))
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSearchActive) {
|
||||
fetchRecentProjects();
|
||||
}
|
||||
}, [isSearchActive]);
|
||||
|
||||
return (
|
||||
<div className="dashboard-home-container">
|
||||
<DashboardNavBar
|
||||
page="home"
|
||||
handleRecentProjectSearch={handleRecentProjectSearch}
|
||||
/>
|
||||
<MarketPlaceBanner />
|
||||
<div className="container">
|
||||
<h2 className="section-header">Recents</h2>
|
||||
<div className="cards-container">{renderProjects()}</div>
|
||||
</div>
|
||||
{recentDuplicateData && Object.keys(recentDuplicateData).length > 0 && (
|
||||
<ProjectSocketRes
|
||||
setIsSearchActive={setIsSearchActive}
|
||||
setRecentProjects={setRecentProjects}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardHome;
|
||||
43
app/src/components/Dashboard/DashboardNavBar.tsx
Normal file
43
app/src/components/Dashboard/DashboardNavBar.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
import Search from "../ui/inputs/Search";
|
||||
import { CartIcon } from "../icons/ExportModuleIcons";
|
||||
|
||||
interface DashboardNavBarProps {
|
||||
page: React.ReactNode;
|
||||
handleProjectsSearch?: (inputValue: string) => Promise<void>;
|
||||
handleTrashSearch?: (inputValue: string) => Promise<void>;
|
||||
handleRecentProjectSearch?: (inputValue: string) => Promise<void>;
|
||||
}
|
||||
|
||||
const DashboardNavBar: React.FC<DashboardNavBarProps> = ({
|
||||
page,
|
||||
handleProjectsSearch,
|
||||
handleTrashSearch,
|
||||
handleRecentProjectSearch,
|
||||
}) => {
|
||||
const handleSearch = async (inputValue: string) => {
|
||||
try {
|
||||
if (handleProjectsSearch) {
|
||||
await handleProjectsSearch(inputValue);
|
||||
} else if (handleTrashSearch) {
|
||||
await handleTrashSearch(inputValue);
|
||||
} else if (handleRecentProjectSearch) {
|
||||
await handleRecentProjectSearch(inputValue);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Search failed:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="dashboard-navbar-container">
|
||||
<div className="title">{page}</div>
|
||||
<div className="market-place-button">
|
||||
<CartIcon isActive /> Market Place
|
||||
</div>
|
||||
<Search onChange={handleSearch} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardNavBar;
|
||||
185
app/src/components/Dashboard/DashboardProjects.tsx
Normal file
185
app/src/components/Dashboard/DashboardProjects.tsx
Normal file
@@ -0,0 +1,185 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import DashboardNavBar from "./DashboardNavBar";
|
||||
import DashboardCard from "./DashboardCard";
|
||||
import { getUserData } from "./functions/getUserData";
|
||||
import { useSocketStore } from "../../store/builder/store";
|
||||
import { getAllProjects } from "../../services/dashboard/getAllProjects";
|
||||
import { searchProject } from "../../services/dashboard/searchProjects";
|
||||
import { deleteProject } from "../../services/dashboard/deleteProject";
|
||||
import ProjectSocketRes from "./socket/projectSocketRes.dev";
|
||||
|
||||
interface Project {
|
||||
_id: string;
|
||||
projectName: string;
|
||||
thumbnail: string;
|
||||
createdBy: string;
|
||||
projectUuid?: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface WorkspaceProjects {
|
||||
[key: string]: Project[];
|
||||
}
|
||||
|
||||
const DashboardProjects: React.FC = () => {
|
||||
const [workspaceProjects, setWorkspaceProjects] = useState<WorkspaceProjects>(
|
||||
{}
|
||||
);
|
||||
const [projectDuplicateData, setProjectDuplicateData] = useState<Object>({});
|
||||
const [isSearchActive, setIsSearchActive] = useState<boolean>(false);
|
||||
const [activeFolder, setActiveFolder] = useState<string>("myProjects");
|
||||
const { projectSocket } = useSocketStore();
|
||||
const { userId, organization } = getUserData();
|
||||
|
||||
const fetchAllProjects = async () => {
|
||||
try {
|
||||
const projects = await getAllProjects(userId, organization);
|
||||
|
||||
if (JSON.stringify(projects) !== JSON.stringify(workspaceProjects)) {
|
||||
setWorkspaceProjects(projects);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching projects:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleProjectsSearch = async (inputValue: string) => {
|
||||
if (!inputValue.trim()) {
|
||||
setIsSearchActive(false);
|
||||
return;
|
||||
}
|
||||
if (!setWorkspaceProjects || !setIsSearchActive) return;
|
||||
|
||||
const searchedProject = await searchProject(organization, userId, inputValue);
|
||||
setIsSearchActive(true);
|
||||
setWorkspaceProjects(searchedProject.message ? {} : searchedProject);
|
||||
};
|
||||
|
||||
const handleDeleteProject = async (projectId: any) => {
|
||||
try {
|
||||
// const deletedProject = await deleteProject(
|
||||
// projectId,
|
||||
// userId,
|
||||
// organization
|
||||
// );
|
||||
// console.log('deletedProject: ', deletedProject);
|
||||
const deleteProjects = {
|
||||
projectId,
|
||||
organization: organization,
|
||||
userId,
|
||||
};
|
||||
|
||||
//socket for deleting the project
|
||||
if (projectSocket) {
|
||||
projectSocket.emit("v1:project:delete", deleteProjects);
|
||||
} else {
|
||||
console.error("Socket is not connected.");
|
||||
}
|
||||
setWorkspaceProjects((prevDiscardedProjects: WorkspaceProjects) => {
|
||||
if (!Array.isArray(prevDiscardedProjects?.Projects)) {
|
||||
return prevDiscardedProjects;
|
||||
}
|
||||
const updatedProjectDatas = prevDiscardedProjects.Projects.filter(
|
||||
(project) => project._id !== projectId
|
||||
);
|
||||
return {
|
||||
...prevDiscardedProjects,
|
||||
Projects: updatedProjectDatas,
|
||||
};
|
||||
});
|
||||
setIsSearchActive(false);
|
||||
} catch (error) {
|
||||
console.error("Error deleting project:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDuplicateWorkspaceProject = async (
|
||||
projectId: string,
|
||||
projectName: string,
|
||||
thumbnail: string
|
||||
) => {
|
||||
// await handleDuplicateProjects({
|
||||
// userId,
|
||||
// organization,
|
||||
// projectId,
|
||||
// projectName,
|
||||
// projectSocket,
|
||||
// thumbnail,
|
||||
// setWorkspaceProjects,
|
||||
// setIsSearchActive
|
||||
// });
|
||||
const duplicateProjectData = {
|
||||
userId,
|
||||
thumbnail,
|
||||
organization,
|
||||
projectUuid: projectId,
|
||||
projectName,
|
||||
};
|
||||
projectSocket.emit("v1:project:Duplicate", duplicateProjectData);
|
||||
};
|
||||
|
||||
const renderProjects = () => {
|
||||
if (activeFolder !== "myProjects") return null;
|
||||
|
||||
const projectList = workspaceProjects[Object.keys(workspaceProjects)[0]];
|
||||
|
||||
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}
|
||||
createdAt={project.createdAt}
|
||||
handleDeleteProject={handleDeleteProject}
|
||||
setIsSearchActive={setIsSearchActive}
|
||||
handleDuplicateWorkspaceProject={handleDuplicateWorkspaceProject}
|
||||
setProjectDuplicateData={setProjectDuplicateData}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSearchActive) {
|
||||
fetchAllProjects();
|
||||
}
|
||||
}, [isSearchActive]);
|
||||
|
||||
return (
|
||||
<div className="dashboard-home-container">
|
||||
<DashboardNavBar
|
||||
page="projects"
|
||||
handleProjectsSearch={handleProjectsSearch}
|
||||
/>
|
||||
|
||||
<div className="container" style={{ height: "calc(100% - 87px)" }}>
|
||||
<div className="header-wrapper" style={{ display: "flex", gap: "7px" }}>
|
||||
<button
|
||||
className={`header ${activeFolder === "myProjects" && "active"}`}
|
||||
onClick={() => setActiveFolder("myProjects")}
|
||||
>
|
||||
My Projects
|
||||
</button>
|
||||
<button
|
||||
className={`header ${activeFolder === "shared" && "active"}`}
|
||||
onClick={() => setActiveFolder("shared")}
|
||||
>
|
||||
Shared with me
|
||||
</button>
|
||||
</div>
|
||||
<div className="cards-container">{renderProjects()}</div>
|
||||
{projectDuplicateData && Object.keys(projectDuplicateData).length > 0 && (
|
||||
<ProjectSocketRes
|
||||
setIsSearchActive={setIsSearchActive}
|
||||
setWorkspaceProjects={setWorkspaceProjects}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardProjects;
|
||||
160
app/src/components/Dashboard/DashboardTrash.tsx
Normal file
160
app/src/components/Dashboard/DashboardTrash.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import DashboardCard from "./DashboardCard";
|
||||
import DashboardNavBar from "./DashboardNavBar";
|
||||
import { getUserData } from "./functions/getUserData";
|
||||
import { trashSearchProject } from "../../services/dashboard/trashSearchProject";
|
||||
import { restoreTrash } from "../../services/dashboard/restoreTrash";
|
||||
import { getTrash } from "../../services/dashboard/getTrash";
|
||||
import { deleteTrash } from "../../services/dashboard/deleteTrash";
|
||||
import { useSocketStore } from "../../store/builder/store";
|
||||
|
||||
interface Project {
|
||||
_id: string;
|
||||
projectName: string;
|
||||
thumbnail: string;
|
||||
createdBy: string;
|
||||
projectUuid?: string;
|
||||
DeletedAt: string;
|
||||
}
|
||||
|
||||
interface DiscardedProjects {
|
||||
[key: string]: Project[];
|
||||
}
|
||||
|
||||
const DashboardTrash: React.FC = () => {
|
||||
const [discardedProjects, setDiscardedProjects] = useState<DiscardedProjects>(
|
||||
{}
|
||||
);
|
||||
const [isSearchActive, setIsSearchActive] = useState(false);
|
||||
const { userId, organization } = getUserData();
|
||||
const { projectSocket } = useSocketStore();
|
||||
|
||||
const fetchTrashProjects = async () => {
|
||||
try {
|
||||
const projects = await getTrash(organization);
|
||||
|
||||
if (JSON.stringify(projects) !== JSON.stringify(discardedProjects)) {
|
||||
setDiscardedProjects(projects);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching trash projects:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTrashSearch = async (inputValue: string) => {
|
||||
if (!inputValue.trim()) {
|
||||
setIsSearchActive(false);
|
||||
return;
|
||||
}
|
||||
if (!setDiscardedProjects || !setIsSearchActive) return;
|
||||
|
||||
const filterTrashedProcess = await trashSearchProject(
|
||||
organization,
|
||||
userId,
|
||||
inputValue
|
||||
);
|
||||
setIsSearchActive(true);
|
||||
setDiscardedProjects(
|
||||
filterTrashedProcess.message ? {} : filterTrashedProcess
|
||||
);
|
||||
};
|
||||
|
||||
const handleRestoreProject = async (projectId: any) => {
|
||||
console.log("projectId: ", projectId);
|
||||
try {
|
||||
const restoreProject = await restoreTrash(organization, projectId);
|
||||
// console.log('restoreProject: ', restoreProject);
|
||||
|
||||
setDiscardedProjects((prevDiscardedProjects: DiscardedProjects) => {
|
||||
// Check if TrashDatas exists and is an array
|
||||
if (!Array.isArray(prevDiscardedProjects?.TrashDatas)) {
|
||||
console.error("TrashDatas is not an array", prevDiscardedProjects);
|
||||
return prevDiscardedProjects;
|
||||
}
|
||||
const updatedTrashDatas = prevDiscardedProjects.TrashDatas.filter(
|
||||
(project) => project._id !== projectId
|
||||
);
|
||||
return {
|
||||
...prevDiscardedProjects,
|
||||
TrashDatas: updatedTrashDatas,
|
||||
};
|
||||
});
|
||||
setIsSearchActive(false);
|
||||
} catch (error) {
|
||||
console.error("Error deleting project:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTrashDeleteProject = async (projectId: any) => {
|
||||
try {
|
||||
// const deletedProject = await deleteTrash(
|
||||
// organization, projectId
|
||||
// );
|
||||
|
||||
const deleteProjectTrash = {
|
||||
projectId,
|
||||
organization,
|
||||
userId,
|
||||
};
|
||||
if (projectSocket) {
|
||||
projectSocket.emit("v1:trash:delete", deleteProjectTrash);
|
||||
}
|
||||
setDiscardedProjects((prevDiscardedProjects: DiscardedProjects) => {
|
||||
if (!Array.isArray(prevDiscardedProjects?.TrashDatas)) {
|
||||
return prevDiscardedProjects;
|
||||
}
|
||||
const updatedProjectDatas = prevDiscardedProjects.TrashDatas.filter(
|
||||
(project) => project._id !== projectId
|
||||
);
|
||||
// console.log('updatedProjectDatas: ', updatedProjectDatas);
|
||||
return {
|
||||
...prevDiscardedProjects,
|
||||
TrashDatas: updatedProjectDatas,
|
||||
};
|
||||
});
|
||||
setIsSearchActive(false);
|
||||
} catch (error) {
|
||||
console.error("Error deleting project:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const renderTrashProjects = () => {
|
||||
const projectList = discardedProjects[Object.keys(discardedProjects)[0]];
|
||||
|
||||
if (!projectList?.length) {
|
||||
return <div className="empty-state">No deleted projects found</div>;
|
||||
}
|
||||
|
||||
return projectList.map((project) => (
|
||||
<DashboardCard
|
||||
key={project._id}
|
||||
projectName={project.projectName}
|
||||
thumbnail={project.thumbnail}
|
||||
projectId={project._id}
|
||||
handleRestoreProject={handleRestoreProject}
|
||||
handleTrashDeleteProject={handleTrashDeleteProject}
|
||||
active={"trash"}
|
||||
createdAt={project.DeletedAt}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSearchActive) {
|
||||
fetchTrashProjects();
|
||||
}
|
||||
}, [isSearchActive]);
|
||||
|
||||
return (
|
||||
<div className="dashboard-home-container">
|
||||
<DashboardNavBar page="trash" handleTrashSearch={handleTrashSearch} />
|
||||
|
||||
<div className="container" style={{ height: "calc(100% - 87px)" }}>
|
||||
<div className="header" style={{ display: "flex", gap: "7px" }}></div>
|
||||
<div className="cards-container">{renderTrashProjects()}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardTrash;
|
||||
62
app/src/components/Dashboard/DashboardTutorial.tsx
Normal file
62
app/src/components/Dashboard/DashboardTutorial.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import DashboardNavBar from './DashboardNavBar';
|
||||
import DashboardCard from './DashboardCard';
|
||||
import { projectTutorial } from '../../services/dashboard/projectTutorial';
|
||||
interface Project {
|
||||
_id: string;
|
||||
projectName: string;
|
||||
thumbnail: string;
|
||||
createdBy: string;
|
||||
projectUuid?: string;
|
||||
}
|
||||
|
||||
interface DiscardedProjects {
|
||||
[key: string]: Project[];
|
||||
}
|
||||
|
||||
const DashboardTutorial = () => {
|
||||
const [tutorialProject, setTutorialProject] = useState<DiscardedProjects>({})
|
||||
const handleIcon = async () => {
|
||||
try {
|
||||
let tutorial = await projectTutorial()
|
||||
setTutorialProject(tutorial)
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
handleIcon()
|
||||
}, [])
|
||||
const renderTrashProjects = () => {
|
||||
const projectList = tutorialProject[Object.keys(tutorialProject)[0]];
|
||||
|
||||
if (!projectList?.length) {
|
||||
return <div className="empty-state">No deleted projects found</div>;
|
||||
}
|
||||
|
||||
return projectList.map((tutorials: any) => (
|
||||
<DashboardCard
|
||||
key={tutorials._id}
|
||||
projectName={tutorials.projectName}
|
||||
thumbnail={tutorials.thumbnail}
|
||||
projectId={tutorials._id}
|
||||
/>
|
||||
));
|
||||
};
|
||||
return (
|
||||
<div className="dashboard-home-container">
|
||||
<DashboardNavBar
|
||||
page="tutorial"
|
||||
/>
|
||||
|
||||
<div className="container" style={{ height: "calc(100% - 87px)" }}>
|
||||
<div className="header" style={{ display: 'flex', gap: '7px' }}></div>
|
||||
<div className="cards-container">
|
||||
{renderTrashProjects()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardTutorial;
|
||||
44
app/src/components/Dashboard/MarketPlaceBanner.tsx
Normal file
44
app/src/components/Dashboard/MarketPlaceBanner.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from "react";
|
||||
import banner from "../../assets/image/banner.png";
|
||||
|
||||
const MarketPlaceBanner = () => {
|
||||
return (
|
||||
<div className="market-place-banner-container">
|
||||
{/* market place banner */}
|
||||
<img src={banner} alt="" />
|
||||
<div className="hero-text">
|
||||
NEW
|
||||
<br /> FALL
|
||||
<br /> COLLECTION
|
||||
</div>
|
||||
<div className="context">Unlock Creativity with Premium 3D Assets!</div>
|
||||
<div className="arrow-context">
|
||||
<svg
|
||||
width="169"
|
||||
height="120"
|
||||
viewBox="0 0 169 120"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M167.189 2C154.638 36.335 104.466 106.204 4.18872 111"
|
||||
stroke="white"
|
||||
strokeWidth="3"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M10.662 118.326L1.59439 111.524L9.47334 103.374"
|
||||
stroke="white"
|
||||
strokeWidth="3"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="explore-button">Explore Now</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarketPlaceBanner;
|
||||
180
app/src/components/Dashboard/SidePannel.tsx
Normal file
180
app/src/components/Dashboard/SidePannel.tsx
Normal file
@@ -0,0 +1,180 @@
|
||||
import React from "react";
|
||||
import {
|
||||
DocumentationIcon,
|
||||
HelpIcon,
|
||||
HomeIcon,
|
||||
LogoutIcon,
|
||||
NotificationIcon,
|
||||
ProjectsIcon,
|
||||
TutorialsIcon,
|
||||
} from "../icons/DashboardIcon";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import darkThemeImage from "../../assets/image/darkThemeProject.png";
|
||||
import lightThemeImage from "../../assets/image/lightThemeProject.png";
|
||||
import { SettingsIcon, TrashIcon } from "../icons/ExportCommonIcons";
|
||||
import { getUserData } from "./functions/getUserData";
|
||||
import { useLoadingProgress, useSocketStore } from "../../store/builder/store";
|
||||
import { createProject } from "../../services/dashboard/createProject";
|
||||
|
||||
interface SidePannelProps {
|
||||
setActiveTab: React.Dispatch<React.SetStateAction<string>>;
|
||||
activeTab: string;
|
||||
}
|
||||
|
||||
const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
|
||||
const { email, userName, userId, organization } = getUserData();
|
||||
const navigate = useNavigate();
|
||||
const { setLoadingProgress } = useLoadingProgress();
|
||||
const { projectSocket } = useSocketStore();
|
||||
const savedTheme = localStorage.getItem("theme") ?? "light";
|
||||
|
||||
function generateProjectId() {
|
||||
const randomBytes = new Uint8Array(12);
|
||||
crypto.getRandomValues(randomBytes);
|
||||
return Array.from(randomBytes, (byte) =>
|
||||
byte.toString(16).padStart(2, "0")
|
||||
).join("");
|
||||
}
|
||||
|
||||
const handleCreateNewProject = async () => {
|
||||
const token = localStorage.getItem("token");
|
||||
try {
|
||||
const projectId = generateProjectId();
|
||||
useSocketStore.getState().initializeSocket(email, organization, token);
|
||||
|
||||
|
||||
//API for creating new Project
|
||||
// const project = await createProject(
|
||||
// projectId,
|
||||
// userId,
|
||||
// savedTheme === "dark" ? darkThemeImage : lightThemeImage,
|
||||
// organization
|
||||
// );
|
||||
// console.log('Created project: ', project);
|
||||
const addProject = {
|
||||
userId,
|
||||
thumbnail: savedTheme === "dark" ? darkThemeImage : lightThemeImage,
|
||||
organization: organization,
|
||||
projectUuid: projectId,
|
||||
};
|
||||
console.log("projectSocket: ", projectSocket);
|
||||
if (projectSocket) {
|
||||
// console.log('addProject: ', addProject);
|
||||
const handleResponse = (data: any) => {
|
||||
console.log('Project add response:', data);
|
||||
if (data.message === "Project created successfully") {
|
||||
setLoadingProgress(1)
|
||||
navigate(`/${data.data.projectId}`);
|
||||
}
|
||||
projectSocket.off("v1-project:response:add", handleResponse); // Clean up
|
||||
};
|
||||
projectSocket.on("v1-project:response:add", handleResponse);
|
||||
|
||||
console.log('addProject: ', addProject);
|
||||
projectSocket.emit("v1:project:add", addProject);
|
||||
} else {
|
||||
console.error("Socket is not connected.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error creating project:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="side-pannel-container">
|
||||
<div className="side-pannel-header">
|
||||
<div className="user-container">
|
||||
<div className="user-profile">
|
||||
{userName?.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<div className="user-name">
|
||||
{userName
|
||||
? userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase()
|
||||
: "Anonymous"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="notifications-container">
|
||||
<NotificationIcon />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="new-project-button"
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={handleCreateNewProject}
|
||||
>
|
||||
+ New project
|
||||
</div>
|
||||
<div className="side-bar-content-container">
|
||||
<div className="side-bar-options-container">
|
||||
<div
|
||||
className={
|
||||
activeTab === "Home" ? "option-list active" : "option-list"
|
||||
}
|
||||
onClick={() => setActiveTab("Home")}
|
||||
>
|
||||
<HomeIcon />
|
||||
Home
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
activeTab === "Projects" ? "option-list active" : "option-list"
|
||||
}
|
||||
title="Projects"
|
||||
onClick={() => setActiveTab("Projects")}
|
||||
>
|
||||
<ProjectsIcon />
|
||||
Projects
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
activeTab === "Trash" ? "option-list active" : "option-list"
|
||||
}
|
||||
title="Trash"
|
||||
onClick={() => setActiveTab("Trash")}
|
||||
>
|
||||
<TrashIcon />
|
||||
Trash
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
activeTab === "Tutorials" ? "option-list active" : "option-list"
|
||||
}
|
||||
title="coming soon"
|
||||
onClick={() => setActiveTab("Tutorials")}
|
||||
>
|
||||
<TutorialsIcon />
|
||||
Tutorials
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
activeTab === "Documentation"
|
||||
? "option-list active"
|
||||
: "option-list"
|
||||
}
|
||||
title="coming soon"
|
||||
onClick={() => setActiveTab("Documentation")}
|
||||
>
|
||||
<DocumentationIcon />
|
||||
Documentation
|
||||
</div>
|
||||
</div>
|
||||
<div className="side-bar-options-container" title="coming soon">
|
||||
<div className="option-list">
|
||||
<SettingsIcon />
|
||||
Settings
|
||||
</div>
|
||||
<div className="option-list" style={{ cursor: "pointer" }}>
|
||||
<LogoutIcon />
|
||||
Log out
|
||||
</div>
|
||||
<div className="option-list">
|
||||
<HelpIcon />
|
||||
Help & Feedback
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidePannel;
|
||||
36
app/src/components/Dashboard/functions/getUserData.ts
Normal file
36
app/src/components/Dashboard/functions/getUserData.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
interface UserData {
|
||||
email: string;
|
||||
userId: string;
|
||||
userName?: string; // Optional
|
||||
organization: string;
|
||||
}
|
||||
|
||||
export const getUserData = (): UserData => {
|
||||
const email = localStorage.getItem("email");
|
||||
const userId = localStorage.getItem("userId");
|
||||
const userName = localStorage.getItem("userName");
|
||||
|
||||
if (!email || !userId) {
|
||||
return {
|
||||
email: '',
|
||||
userId: '',
|
||||
userName: '',
|
||||
organization: '',
|
||||
};
|
||||
}
|
||||
|
||||
const [_, emailDomain] = email.split("@");
|
||||
|
||||
if (!emailDomain) {
|
||||
throw new Error("Invalid email format");
|
||||
}
|
||||
|
||||
const [organization] = emailDomain.split(".");
|
||||
|
||||
return {
|
||||
email: email,
|
||||
userId: userId,
|
||||
userName: userName ?? undefined,
|
||||
organization,
|
||||
};
|
||||
};
|
||||
89
app/src/components/Dashboard/socket/projectSocketRes.dev.tsx
Normal file
89
app/src/components/Dashboard/socket/projectSocketRes.dev.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { useSocketStore } from '../../../store/builder/store';
|
||||
import { getUserData } from '../functions/getUserData';
|
||||
import { getAllProjects } from '../../../services/dashboard/getAllProjects';
|
||||
import { recentlyViewed } from '../../../services/dashboard/recentlyViewed';
|
||||
|
||||
interface Project {
|
||||
_id: string;
|
||||
projectName: string;
|
||||
thumbnail: string;
|
||||
createdBy: string;
|
||||
projectUuid?: string;
|
||||
createdAt: string;
|
||||
isViewed?: string
|
||||
}
|
||||
|
||||
interface ProjectsData {
|
||||
[key: string]: Project[];
|
||||
}
|
||||
interface ProjectSocketResProps {
|
||||
setRecentProjects?: React.Dispatch<React.SetStateAction<ProjectsData>>;
|
||||
setWorkspaceProjects?: React.Dispatch<React.SetStateAction<ProjectsData>>;
|
||||
setIsSearchActive?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
const ProjectSocketRes = ({
|
||||
setRecentProjects,
|
||||
setWorkspaceProjects,
|
||||
setIsSearchActive,
|
||||
}: ProjectSocketResProps) => {
|
||||
const { projectSocket } = useSocketStore();
|
||||
const { userId, organization } = getUserData();
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectSocket) return;
|
||||
|
||||
const handleAdd = (data: any) => {
|
||||
// console.log('Add:', data);
|
||||
};
|
||||
|
||||
const handleDelete = (data: any) => {
|
||||
// console.log('Delete:', data);
|
||||
};
|
||||
|
||||
const handleUpdate = (data: any) => {
|
||||
// console.log('Update:', data);
|
||||
};
|
||||
|
||||
const handleTrashDelete = (data: any) => {
|
||||
// console.log('Trash Delete:', data);
|
||||
};
|
||||
|
||||
const handleDuplicate = async (data: any) => {
|
||||
console.log("Project duplicate response:", data);
|
||||
if (data?.message === "Project Duplicated successfully") {
|
||||
if (setWorkspaceProjects) {
|
||||
const allProjects = await getAllProjects(userId, organization);
|
||||
console.log('allProjects: ', allProjects);
|
||||
setWorkspaceProjects(allProjects);
|
||||
} else if (setRecentProjects) {
|
||||
const recentProjects = await recentlyViewed(organization, userId);
|
||||
setRecentProjects && setRecentProjects(recentProjects);
|
||||
}
|
||||
setIsSearchActive && setIsSearchActive(false);
|
||||
} else {
|
||||
console.warn("Duplication failed or unexpected response.");
|
||||
}
|
||||
};
|
||||
|
||||
projectSocket.on("v1-project:response:add", handleAdd);
|
||||
projectSocket.on("v1-project:response:delete", handleDelete);
|
||||
projectSocket.on("v1-project:response:update", handleUpdate);
|
||||
projectSocket.on("v1-project:response:Duplicate", handleDuplicate);
|
||||
projectSocket.on("v1:trash:response:delete", handleTrashDelete);
|
||||
|
||||
return () => {
|
||||
projectSocket.off("v1-project:response:add", handleAdd);
|
||||
projectSocket.off("v1-project:response:delete", handleDelete);
|
||||
projectSocket.off("v1-project:response:update", handleUpdate);
|
||||
projectSocket.off("v1-project:response:Duplicate", handleDuplicate);
|
||||
projectSocket.off("v1:trash:response:delete", handleTrashDelete);
|
||||
};
|
||||
}, [projectSocket, userId, organization]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default ProjectSocketRes;
|
||||
Reference in New Issue
Block a user