diff --git a/app/src/components/layout/Dashboard/DashboardCard.tsx b/app/src/components/layout/Dashboard/DashboardCard.tsx index b6e818c..7adc59f 100644 --- a/app/src/components/layout/Dashboard/DashboardCard.tsx +++ b/app/src/components/layout/Dashboard/DashboardCard.tsx @@ -1,10 +1,11 @@ -import React from "react"; +import React, { useState, useRef } from "react"; import { KebabIcon } from "../../icons/ExportCommonIcons"; import img from "../../../assets/image/image.png"; import { useNavigate } from "react-router-dom"; import { useProjectName } from "../../../store/builder/store"; import { viewProject } from "../../../services/dashboard/viewProject"; import { getUserData } from "./functions/getUserData"; +import OuterClick from "../../../utils/outerClick"; interface DashBoardCardProps { projectName: string; @@ -21,58 +22,113 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({ projectId, handleRestoreProject, }) => { - let navigate = useNavigate(); + const navigate = useNavigate(); const { setProjectName } = useProjectName(); const { userId, organization, userName } = getUserData(); + const [isKebabOpen, setIsKebabOpen] = useState(false); - const handleKebabIconClick = async () => { - try { - if (handleRestoreProject) { - await handleRestoreProject(projectId); - } else if (handleDeleteProject) { - await handleDeleteProject(projectId); - } - } catch { } - }; + const kebabRef = useRef<HTMLDivElement>(null); const navigateToProject = async () => { try { const viewedProject = await viewProject(organization, projectId, userId); - console.log("Saved viewwdProject:", viewedProject); + console.log("Viewed project:", viewedProject); } catch (error) { - console.error("Error deleting project:", error); + console.error("Error opening project:", error); } setProjectName(projectName); navigate(`/${projectId}`); }; + const handleOptionClick = async (option: string) => { + switch (option) { + case "delete": + if (handleDeleteProject) { + await handleDeleteProject(projectId); + } + break; + case "restore": + if (handleRestoreProject) { + await handleRestoreProject(projectId); + } + break; + case "openInNewTab": + window.open(`/${projectId}`, "_blank"); + break; + case "rename": + // Add rename logic here + break; + case "duplicate": + // Add duplication logic here + break; + default: + break; + } + setIsKebabOpen(false); + }; + + OuterClick({ + contextClassName: ["kebab-wrapper", "kebab-options-wrapper"], + setMenuVisible: () => setIsKebabOpen(false), + }); return ( - <div className="dashboard-card-container" onClick={navigateToProject} title={projectName}> - <div className="preview-container"> - {thumbnail ? <img src={thumbnail} alt="" /> : <img src={img} alt="" />} - </div> - <div className="project-details-container"> - <div className="project-details"> - <div className="project-name">{projectName}</div> - <div className="project-data">24-12-2025</div> + <button + className="dashboard-card-container" + onClick={navigateToProject} + title={projectName} + > + <div className="dashboard-card-wrapper"> + <div className="preview-container"> + {thumbnail ? ( + <img src={thumbnail} alt="" /> + ) : ( + <img src={img} alt="" /> + )} </div> - <div className="users-list-container"> - <div className="user-profile"> - {userName ? userName.charAt(0).toUpperCase() : "Anonymous"} + <div className="project-details-container"> + <div className="project-details"> + <div className="project-name">{projectName}</div> + <div className="project-data">24-12-2025</div> </div> - <div - onClick={(e) => { - e.stopPropagation(); - handleKebabIconClick(); - }} - > - <KebabIcon /> + <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(); // Prevents click from bubbling up + console.log("Kebab menu clicked"); + setIsKebabOpen((prev) => !prev); + }} + > + <KebabIcon /> + </button> </div> </div> </div> - </div> + + {isKebabOpen && ( + <div className="kebab-options-wrapper"> + {["rename", "delete", "duplicate", "open in new tab"].map((option) => ( + <button + key={option} + className="option" + onClick={(e) => { + console.log(option); + e.stopPropagation(); + handleOptionClick(option); + }} + > + {option} + </button> + ))} + </div> + )} + </button> ); }; -export default DashboardCard; \ No newline at end of file +export default DashboardCard; diff --git a/app/src/components/layout/Dashboard/DashboardHome.tsx b/app/src/components/layout/Dashboard/DashboardHome.tsx index 98b8a5c..364d3f9 100644 --- a/app/src/components/layout/Dashboard/DashboardHome.tsx +++ b/app/src/components/layout/Dashboard/DashboardHome.tsx @@ -43,7 +43,11 @@ const DashboardHome: React.FC = () => { setIsSearchActive(false); return; } - const filterRecentProcess = await searchProject(organization, userId, inputValue); + const filterRecentProcess = await searchProject( + organization, + userId, + inputValue + ); setIsSearchActive(true); setRecentProjects(filterRecentProcess.message ? {} : filterRecentProcess); }; @@ -127,11 +131,13 @@ const DashboardHome: React.FC = () => { <MarketPlaceBanner /> <div className="container"> - <h2 className="section-header">Recent Projects</h2> + <h2 className="section-header">Recents</h2> <div className="cards-container">{renderProjects()}</div> </div> </div> ); }; -export default DashboardHome; \ No newline at end of file +export default DashboardHome; + + diff --git a/app/src/components/layout/Dashboard/DashboardProjects.tsx b/app/src/components/layout/Dashboard/DashboardProjects.tsx index 8df8216..7afc7db 100644 --- a/app/src/components/layout/Dashboard/DashboardProjects.tsx +++ b/app/src/components/layout/Dashboard/DashboardProjects.tsx @@ -1,155 +1,154 @@ -import React, { useEffect, useState } from 'react'; -import DashboardNavBar from './DashboardNavBar'; -import DashboardCard from './DashboardCard'; -import { getAllProjects } from '../../../services/dashboard/getAllProjects'; -import { getUserData } from './functions/getUserData'; -import { searchProject } from '../../../services/dashboard/searchProjects'; -import { deleteProject } from '../../../services/dashboard/deleteProject'; -import { useSocketStore } from '../../../store/builder/store'; +import React, { useEffect, useState } from "react"; +import DashboardNavBar from "./DashboardNavBar"; +import DashboardCard from "./DashboardCard"; +import { getAllProjects } from "../../../services/dashboard/getAllProjects"; +import { getUserData } from "./functions/getUserData"; +import { searchProject } from "../../../services/dashboard/searchProjects"; +import { deleteProject } from "../../../services/dashboard/deleteProject"; +import { useSocketStore } from "../../../store/builder/store"; interface Project { - _id: string; - projectName: string; - thumbnail: string; - createdBy: string; - projectUuid?: string; + _id: string; + projectName: string; + thumbnail: string; + createdBy: string; + projectUuid?: string; } interface WorkspaceProjects { - [key: string]: Project[]; + [key: string]: Project[]; } const DashboardProjects: React.FC = () => { - const [workspaceProjects, setWorkspaceProjects] = useState<WorkspaceProjects>({}); - const [isSearchActive, setIsSearchActive] = useState<boolean>(false); - const [activeFolder, setActiveFolder] = useState<string>("myProjects"); - const { dashBoardSocket } = useSocketStore(); - const { userId, organization } = getUserData(); + const [workspaceProjects, setWorkspaceProjects] = useState<WorkspaceProjects>( + {} + ); + const [isSearchActive, setIsSearchActive] = useState<boolean>(false); + const [activeFolder, setActiveFolder] = useState<string>("myProjects"); + const { dashBoardSocket } = useSocketStore(); + const { userId, organization } = getUserData(); - const fetchAllProjects = async () => { - try { - const projects = await getAllProjects(userId, organization); + 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; + 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 Organization = organization - // const deletedProject = await deleteProject( - // projectId, - // userId, - // Organization - // ); - const deleteProject = { - projectId, - organization: organization, - userId - } - if (dashBoardSocket) { - const handleResponse = (data: any) => { - console.log('Project add response:', data); - dashBoardSocket.off("v1-project:response:delete", handleResponse); // Clean up - }; - - dashBoardSocket.on("v1-project:response:delete", handleResponse); - - dashBoardSocket.emit("v1:project:delete", deleteProject); - } 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 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} - handleDeleteProject={handleDeleteProject} - /> - )); - }; - - useEffect(() => { - if (!isSearchActive) { - fetchAllProjects(); - } - }, [isSearchActive]); - - return ( - <div className="dashboard-home-container"> - <DashboardNavBar - page="projects" - handleProjectsSearch={handleProjectsSearch} - /> - - <div className="container"> - <div className="header" style={{ display: "flex", gap: "7px" }}> - <div - style={{ color: activeFolder === "myProjects" ? "#c4abf1" : "black" }} - onClick={() => setActiveFolder("myProjects")} - > - My Projects - </div> - <div - style={{ color: activeFolder === "shared" ? "#c4abf1" : "black" }} - onClick={() => setActiveFolder("shared")} - > - Shared with me - </div> - </div> - <div className="cards-container"> - {renderProjects()} - </div> - </div> - </div> + const searchedProject = await searchProject( + organization, + userId, + inputValue ); + setIsSearchActive(true); + setWorkspaceProjects(searchedProject.message ? {} : searchedProject); + }; + const handleDeleteProject = async (projectId: any) => { + try { + const Organization = organization; + // const deletedProject = await deleteProject( + // projectId, + // userId, + // Organization + // ); + const deleteProject = { + projectId, + organization: organization, + userId, + }; + if (dashBoardSocket) { + const handleResponse = (data: any) => { + console.log("Project add response:", data); + dashBoardSocket.off("v1-project:response:delete", handleResponse); // Clean up + }; + + dashBoardSocket.on("v1-project:response:delete", handleResponse); + + dashBoardSocket.emit("v1:project:delete", deleteProject); + } 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 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} + handleDeleteProject={handleDeleteProject} + /> + )); + }; + + 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> + </div> + </div> + ); }; -export default DashboardProjects; \ No newline at end of file +export default DashboardProjects; diff --git a/app/src/components/layout/Dashboard/DashboardTrash.tsx b/app/src/components/layout/Dashboard/DashboardTrash.tsx index b66a05c..9d2cdf6 100644 --- a/app/src/components/layout/Dashboard/DashboardTrash.tsx +++ b/app/src/components/layout/Dashboard/DashboardTrash.tsx @@ -1,122 +1,120 @@ -import React, { useEffect, useState } from 'react'; -import { getTrash } from '../../../services/dashboard/getTrash'; -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 React, { useEffect, useState } from "react"; +import { getTrash } from "../../../services/dashboard/getTrash"; +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"; interface Project { - _id: string; - projectName: string; - thumbnail: string; - createdBy: string; - projectUuid?: string; + _id: string; + projectName: string; + thumbnail: string; + createdBy: string; + projectUuid?: string; } interface DiscardedProjects { - [key: string]: Project[]; + [key: string]: Project[]; } const DashboardTrash: React.FC = () => { - const [discardedProjects, setDiscardedProjects] = useState<DiscardedProjects>({}); - console.log('discardedProjects: ', discardedProjects); - const [isSearchActive, setIsSearchActive] = useState(false); - const { userId, organization } = getUserData(); + const [discardedProjects, setDiscardedProjects] = useState<DiscardedProjects>( + {} + ); + console.log("discardedProjects: ", discardedProjects); + const [isSearchActive, setIsSearchActive] = useState(false); + const { userId, organization } = getUserData(); - const fetchTrashProjects = async () => { - try { - const projects = await getTrash(organization); - console.log('organization: ', organization); - console.log('trashedprojects: ', projects); + const fetchTrashProjects = async () => { + try { + const projects = await getTrash(organization); + console.log("organization: ", organization); + console.log("trashedprojects: ", projects); - 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; + 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) => { - try { - const Organization = organization; - const restoreProject = await restoreTrash( - Organization, - projectId - ); - - 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 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} - /> - )); - }; - - useEffect(() => { - console.log('isSearchActive:trash ', isSearchActive); - if (!isSearchActive) { - fetchTrashProjects(); - } - }, [isSearchActive]); - - return ( - <div className="dashboard-home-container"> - <DashboardNavBar - page="trash" - handleTrashSearch={handleTrashSearch} - /> - - <div className="container"> - <div className="header" style={{ display: 'flex', gap: '7px' }}></div> - <div className="cards-container"> - {renderTrashProjects()} - </div> - </div> - </div> + const filterTrashedProcess = await trashSearchProject( + organization, + userId, + inputValue ); + setIsSearchActive(true); + setDiscardedProjects( + filterTrashedProcess.message ? {} : filterTrashedProcess + ); + }; + const handleRestoreProject = async (projectId: any) => { + try { + const Organization = organization; + const restoreProject = await restoreTrash(Organization, projectId); + + 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 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} + /> + )); + }; + + useEffect(() => { + console.log("isSearchActive:trash ", isSearchActive); + 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; \ No newline at end of file +export default DashboardTrash; diff --git a/app/src/components/layout/Dashboard/DashboardTutorial.tsx b/app/src/components/layout/Dashboard/DashboardTutorial.tsx index 5e11ab8..2e96e29 100644 --- a/app/src/components/layout/Dashboard/DashboardTutorial.tsx +++ b/app/src/components/layout/Dashboard/DashboardTutorial.tsx @@ -49,7 +49,7 @@ const DashboardTutorial = () => { page="tutorial" /> - <div className="container"> + <div className="container" style={{ height: "calc(100% - 87px)" }}> <div className="header" style={{ display: 'flex', gap: '7px' }}></div> <div className="cards-container"> {renderTrashProjects()} diff --git a/app/src/pages/Dashboard.tsx b/app/src/pages/Dashboard.tsx index 39afd94..a0593cd 100644 --- a/app/src/pages/Dashboard.tsx +++ b/app/src/pages/Dashboard.tsx @@ -1,15 +1,18 @@ -import React, { useEffect, useState } from 'react'; -import SidePannel from '../components/layout/Dashboard/SidePannel'; -import DashboardHome from '../components/layout/Dashboard/DashboardHome'; -import DashboardProjects from '../components/layout/Dashboard/DashboardProjects'; -import DashboardTrash from '../components/layout/Dashboard/DashboardTrash'; -import { useOrganization, useSocketStore, useUserName, useZones } from '../store/builder/store'; -import { getUserData } from '../components/layout/Dashboard/functions/getUserData'; -import DashboardTutorial from '../components/layout/Dashboard/DashboardTutorial'; - +import React, { useEffect, useState } from "react"; +import SidePannel from "../components/layout/Dashboard/SidePannel"; +import DashboardHome from "../components/layout/Dashboard/DashboardHome"; +import DashboardProjects from "../components/layout/Dashboard/DashboardProjects"; +import DashboardTrash from "../components/layout/Dashboard/DashboardTrash"; +import { + useOrganization, + useSocketStore, + useUserName, +} from "../store/builder/store"; +import { getUserData } from "../components/layout/Dashboard/functions/getUserData"; +import DashboardTutorial from "../components/layout/Dashboard/DashboardTutorial"; const Dashboard: React.FC = () => { - const [activeTab, setActiveTab] = useState<string>('Home'); + const [activeTab, setActiveTab] = useState<string>("Home"); const { setUserName } = useUserName(); const { setOrganization } = useOrganization(); const { userId, organization, email, userName } = getUserData(); @@ -21,16 +24,16 @@ const Dashboard: React.FC = () => { setUserName(userName); } } - }, []) + }, []); return ( <div className="dashboard-main"> <SidePannel setActiveTab={setActiveTab} activeTab={activeTab} /> - {activeTab == 'Home' && <DashboardHome />} - {activeTab == 'Projects' && <DashboardProjects />} - {activeTab == 'Trash' && <DashboardTrash />} - {activeTab == 'Tutorials' && <DashboardTutorial />} + {activeTab == "Home" && <DashboardHome />} + {activeTab == "Projects" && <DashboardProjects />} + {activeTab == "Trash" && <DashboardTrash />} + {activeTab == "Tutorials" && <DashboardTutorial />} </div> ); }; -export default Dashboard; \ No newline at end of file +export default Dashboard; diff --git a/app/src/styles/abstracts/variables.scss b/app/src/styles/abstracts/variables.scss index 0d50177..64978e1 100644 --- a/app/src/styles/abstracts/variables.scss +++ b/app/src/styles/abstracts/variables.scss @@ -192,4 +192,4 @@ $border-radius-large: 12px; $border-radius-circle: 50%; $border-radius-xlarge: 16px; $border-radius-extra-large: 20px; -$border-radius-xxx: 30px; +$border-radius-xxx: 30px; \ No newline at end of file diff --git a/app/src/styles/pages/dashboard.scss b/app/src/styles/pages/dashboard.scss index 38ac24a..d6d650e 100644 --- a/app/src/styles/pages/dashboard.scss +++ b/app/src/styles/pages/dashboard.scss @@ -5,19 +5,28 @@ height: 100vh; width: 100vw; display: flex; + padding: 27px 17px; + .side-pannel-container { padding: 32px; - min-width: 240px; - height: 100vh; + min-width: 280px; + height: 100%; display: flex; flex-direction: column; gap: 16px; - border-right: 1px solid var(--border-color); + // border-right: 1px solid var(--border-color); + background: var(--background-color); + backdrop-filter: blur(20px); + border-radius: 30px; + box-shadow: var(--box-shadow-medium); + .side-pannel-header { @include flex-space-between; + .user-container { @include flex-center; gap: 6px; + .user-profile { height: 32px; width: 32px; @@ -28,10 +37,12 @@ color: var(--primary-color); border-radius: #{$border-radius-circle}; } + .user-name { color: var(--accent-color); } } + .notifications-container { @include flex-center; height: 24px; @@ -39,82 +50,117 @@ cursor: pointer; } } + .new-project-button { padding: 12px 16px; cursor: not-allowed; - color: var(--accent-color); + color: var(--text-color); background: var(--background-color-secondary); - border-radius: #{$border-radius-large}; + border-radius: #{$border-radius-xxx}; } + .side-bar-content-container { display: flex; flex-direction: column; justify-content: space-between; height: 100%; + .side-bar-options-container { .option-list { display: flex; align-items: center; gap: 8px; - padding: 6px 10px; + padding: 8px 10px; margin: 4px 0; - border-radius: #{$border-radius-medium}; + border-radius: #{$border-radius-extra-large}; + cursor: pointer; + &:hover { background: var(--background-color-secondary); } } + .active { - color: var(--accent-color); + color: var(--text-button-color); font-weight: var(--font-weight-medium); - background: var(--highlight-accent-color); + background: var(--background-color-button); + &:hover { - background: var(--highlight-accent-color); + background: var(--background-color-button); } + } } } } + .dashboard-home-container { width: 100%; + padding-left: 18px; + .dashboard-navbar-container { margin-top: 28px; - padding: 8px 34px 8px 12px; + margin-bottom: 22px; @include flex-center; + .title { text-transform: capitalize; font-size: var(--font-size-large); width: 100%; } + .market-place-button { @include flex-center; gap: 6px; padding: 8px 14px; - background: var(--accent-gradient-color); + background: var(--background-color-button); white-space: nowrap; - border-radius: #{$border-radius-large}; - color: var(--primary-color); + border-radius: #{$border-radius-extra-large}; + + color: var(--text-button-color); } + .search-wrapper { width: 400px; } } + .container { margin: 22px 0; width: 100%; - padding: 0 12px; - .header { + height: calc(100% - 357px); + + + .header-wrapper { font-size: var(--font-size-large); + + .header { + color: var(--input-text-color); + padding: 6px 8px; + border-radius: #{$border-radius-extra-large}; + + &.active { + + background: var(--background-color-button); + color: var(--text-color); + } + } } + .cards-container { + height: 100%; display: flex; flex-wrap: wrap; position: relative; width: 100%; - padding: 8px; + padding-top: 18px; gap: 18px; + overflow: auto; } } } + + } .dashboard-card-container { @@ -123,11 +169,23 @@ min-width: 260px; position: relative; border: 1px solid var(--border-color); - border-radius: #{$border-radius-large}; + border-radius: #{$border-radius-extra-large}; overflow: hidden; + cursor: pointer; + overflow: visible; + position: relative; + + .dashboard-card-wrapper { + width: 100%; + height: 100%; + position: relative; + overflow: hidden; + } + .preview-container { height: 100%; width: 100%; + img { height: 100%; width: 100%; @@ -135,27 +193,38 @@ vertical-align: top; border: none; outline: none; + border-radius: #{$border-radius-extra-large}; } } + .project-details-container { @include flex-space-between; position: absolute; bottom: 0; width: 100%; - padding: 8px 16px; - background: var(--primary-color); - border-radius: #{$border-radius-large}; + padding: 13px 16px; + background: var(--background-color); + // backdrop-filter: blur(18px); + + border-radius: #{$border-radius-xlarge}; + transform: translateY(100%); + transition: transform 0.25s linear; + .project-details { .project-name { - margin-bottom: 2px; + margin-bottom: 7px; } + .project-data { - color: var(--accent-color); + color: var(--input-text-color); } } + .users-list-container { @include flex-center; gap: 6px; + position: relative; // Needed for absolute positioning of kebab-options-wrapper + .user-profile { height: 26px; width: 26px; @@ -165,8 +234,65 @@ color: var(--primary-color); border-radius: #{$border-radius-circle}; } + + .kebab { + padding: 10px; + @include flex-center; + transform: rotate(90deg); + } + } + + } + + + .kebab-options-wrapper { + position: absolute; + bottom: 40px; + right: 40px; + background: var(--background-color); + border: 1px solid var(--border-color); + border-radius: 8px; + z-index: 100; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + display: flex; + flex-direction: column; + transform: translate(100%, 100%); + overflow: hidden; + display: none; + + .option { + padding: 8px 12px; + font-size: 14px; + text-align: left; + background: transparent; + border: none; + color: var(--text-color); + cursor: pointer; + transition: background 0.2s ease; + text-transform: capitalize; + + &:hover { + background-color: var(--background-color-secondary); + } } } + + + &:hover { + + + overflow: visible; + + .kebab-options-wrapper { + display: flex; + } + + .project-details-container { + transform: translateY(0); + } + } + + } .market-place-banner-container { @@ -174,13 +300,14 @@ height: 230px; overflow: hidden; position: relative; - padding: 0 24px; + img { height: 100%; width: 100%; object-fit: cover; border-radius: #{$border-radius-xxx}; } + .hero-text { position: absolute; left: 52px; @@ -191,6 +318,7 @@ color: #ffffff; text-transform: uppercase; } + .context { position: absolute; top: 20px; @@ -201,11 +329,13 @@ color: #ffffff; font-family: #{$font-roboto}; } + .arrow-context { position: absolute; bottom: 27px; right: 300px; } + .explore-button { position: absolute; top: 95px; @@ -218,4 +348,4 @@ font-family: #{$font-roboto}; cursor: pointer; } -} +} \ No newline at end of file