Refactor Dashboard components and styles for improved functionality and UI consistency

This commit is contained in:
Nalvazhuthi 2025-05-21 17:46:16 +05:30
parent 0ecb85a211
commit 8af4442a28
8 changed files with 520 additions and 328 deletions

View File

@ -1,10 +1,11 @@
import React from "react"; import React, { useState, useRef } from "react";
import { KebabIcon } from "../../icons/ExportCommonIcons"; import { KebabIcon } from "../../icons/ExportCommonIcons";
import img from "../../../assets/image/image.png"; import img from "../../../assets/image/image.png";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useProjectName } from "../../../store/builder/store"; import { useProjectName } from "../../../store/builder/store";
import { viewProject } from "../../../services/dashboard/viewProject"; import { viewProject } from "../../../services/dashboard/viewProject";
import { getUserData } from "./functions/getUserData"; import { getUserData } from "./functions/getUserData";
import OuterClick from "../../../utils/outerClick";
interface DashBoardCardProps { interface DashBoardCardProps {
projectName: string; projectName: string;
@ -21,57 +22,112 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
projectId, projectId,
handleRestoreProject, handleRestoreProject,
}) => { }) => {
let navigate = useNavigate(); const navigate = useNavigate();
const { setProjectName } = useProjectName(); const { setProjectName } = useProjectName();
const { userId, organization, userName } = getUserData(); const { userId, organization, userName } = getUserData();
const [isKebabOpen, setIsKebabOpen] = useState(false);
const handleKebabIconClick = async () => { const kebabRef = useRef<HTMLDivElement>(null);
try {
if (handleRestoreProject) {
await handleRestoreProject(projectId);
} else if (handleDeleteProject) {
await handleDeleteProject(projectId);
}
} catch { }
};
const navigateToProject = async () => { const navigateToProject = async () => {
try { try {
const viewedProject = await viewProject(organization, projectId, userId); const viewedProject = await viewProject(organization, projectId, userId);
console.log("Saved viewwdProject:", viewedProject); console.log("Viewed project:", viewedProject);
} catch (error) { } catch (error) {
console.error("Error deleting project:", error); console.error("Error opening project:", error);
} }
setProjectName(projectName); setProjectName(projectName);
navigate(`/${projectId}`); 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 ( return (
<div className="dashboard-card-container" onClick={navigateToProject} title={projectName}> <button
<div className="preview-container"> className="dashboard-card-container"
{thumbnail ? <img src={thumbnail} alt="" /> : <img src={img} alt="" />} onClick={navigateToProject}
</div> title={projectName}
<div className="project-details-container"> >
<div className="project-details"> <div className="dashboard-card-wrapper">
<div className="project-name">{projectName}</div> <div className="preview-container">
<div className="project-data">24-12-2025</div> {thumbnail ? (
<img src={thumbnail} alt="" />
) : (
<img src={img} alt="" />
)}
</div> </div>
<div className="users-list-container"> <div className="project-details-container">
<div className="user-profile"> <div className="project-details">
{userName ? userName.charAt(0).toUpperCase() : "Anonymous"} <div className="project-name">{projectName}</div>
<div className="project-data">24-12-2025</div>
</div> </div>
<div <div className="users-list-container" ref={kebabRef}>
onClick={(e) => { <div className="user-profile">
e.stopPropagation(); {userName ? userName.charAt(0).toUpperCase() : "A"}
handleKebabIconClick(); </div>
}}
> <button
<KebabIcon /> 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>
</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>
); );
}; };

View File

@ -43,7 +43,11 @@ const DashboardHome: React.FC = () => {
setIsSearchActive(false); setIsSearchActive(false);
return; return;
} }
const filterRecentProcess = await searchProject(organization, userId, inputValue); const filterRecentProcess = await searchProject(
organization,
userId,
inputValue
);
setIsSearchActive(true); setIsSearchActive(true);
setRecentProjects(filterRecentProcess.message ? {} : filterRecentProcess); setRecentProjects(filterRecentProcess.message ? {} : filterRecentProcess);
}; };
@ -127,7 +131,7 @@ const DashboardHome: React.FC = () => {
<MarketPlaceBanner /> <MarketPlaceBanner />
<div className="container"> <div className="container">
<h2 className="section-header">Recent Projects</h2> <h2 className="section-header">Recents</h2>
<div className="cards-container">{renderProjects()}</div> <div className="cards-container">{renderProjects()}</div>
</div> </div>
</div> </div>
@ -135,3 +139,5 @@ const DashboardHome: React.FC = () => {
}; };
export default DashboardHome; export default DashboardHome;

View File

@ -1,155 +1,154 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from "react";
import DashboardNavBar from './DashboardNavBar'; import DashboardNavBar from "./DashboardNavBar";
import DashboardCard from './DashboardCard'; import DashboardCard from "./DashboardCard";
import { getAllProjects } from '../../../services/dashboard/getAllProjects'; import { getAllProjects } from "../../../services/dashboard/getAllProjects";
import { getUserData } from './functions/getUserData'; import { getUserData } from "./functions/getUserData";
import { searchProject } from '../../../services/dashboard/searchProjects'; import { searchProject } from "../../../services/dashboard/searchProjects";
import { deleteProject } from '../../../services/dashboard/deleteProject'; import { deleteProject } from "../../../services/dashboard/deleteProject";
import { useSocketStore } from '../../../store/builder/store'; import { useSocketStore } from "../../../store/builder/store";
interface Project { interface Project {
_id: string; _id: string;
projectName: string; projectName: string;
thumbnail: string; thumbnail: string;
createdBy: string; createdBy: string;
projectUuid?: string; projectUuid?: string;
} }
interface WorkspaceProjects { interface WorkspaceProjects {
[key: string]: Project[]; [key: string]: Project[];
} }
const DashboardProjects: React.FC = () => { const DashboardProjects: React.FC = () => {
const [workspaceProjects, setWorkspaceProjects] = useState<WorkspaceProjects>({}); const [workspaceProjects, setWorkspaceProjects] = useState<WorkspaceProjects>(
const [isSearchActive, setIsSearchActive] = useState<boolean>(false); {}
const [activeFolder, setActiveFolder] = useState<string>("myProjects"); );
const { dashBoardSocket } = useSocketStore(); const [isSearchActive, setIsSearchActive] = useState<boolean>(false);
const { userId, organization } = getUserData(); const [activeFolder, setActiveFolder] = useState<string>("myProjects");
const { dashBoardSocket } = useSocketStore();
const { userId, organization } = getUserData();
const fetchAllProjects = async () => { const fetchAllProjects = async () => {
try { try {
const projects = await getAllProjects(userId, organization); const projects = await getAllProjects(userId, organization);
if (JSON.stringify(projects) !== JSON.stringify(workspaceProjects)) { if (JSON.stringify(projects) !== JSON.stringify(workspaceProjects)) {
setWorkspaceProjects(projects); setWorkspaceProjects(projects);
} }
} catch (error) { } catch (error) {
console.error("Error fetching projects:", error); console.error("Error fetching projects:", error);
} }
}; };
const handleProjectsSearch = async ( const handleProjectsSearch = async (inputValue: string) => {
inputValue: string if (!inputValue.trim()) {
) => { setIsSearchActive(false);
if (!inputValue.trim()) { return;
setIsSearchActive(false); }
return; if (!setWorkspaceProjects || !setIsSearchActive) return;
}
if (!setWorkspaceProjects || !setIsSearchActive) return;
const searchedProject = await searchProject(organization, userId, inputValue); const searchedProject = await searchProject(
setIsSearchActive(true); organization,
setWorkspaceProjects(searchedProject.message ? {} : searchedProject); userId,
}; inputValue
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>
); );
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; export default DashboardProjects;

View File

@ -1,122 +1,120 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from "react";
import { getTrash } from '../../../services/dashboard/getTrash'; import { getTrash } from "../../../services/dashboard/getTrash";
import DashboardCard from './DashboardCard'; import DashboardCard from "./DashboardCard";
import DashboardNavBar from './DashboardNavBar'; import DashboardNavBar from "./DashboardNavBar";
import { getUserData } from './functions/getUserData'; import { getUserData } from "./functions/getUserData";
import { trashSearchProject } from '../../../services/dashboard/trashSearchProject'; import { trashSearchProject } from "../../../services/dashboard/trashSearchProject";
import { restoreTrash } from '../../../services/dashboard/restoreTrash'; import { restoreTrash } from "../../../services/dashboard/restoreTrash";
interface Project { interface Project {
_id: string; _id: string;
projectName: string; projectName: string;
thumbnail: string; thumbnail: string;
createdBy: string; createdBy: string;
projectUuid?: string; projectUuid?: string;
} }
interface DiscardedProjects { interface DiscardedProjects {
[key: string]: Project[]; [key: string]: Project[];
} }
const DashboardTrash: React.FC = () => { const DashboardTrash: React.FC = () => {
const [discardedProjects, setDiscardedProjects] = useState<DiscardedProjects>({}); const [discardedProjects, setDiscardedProjects] = useState<DiscardedProjects>(
console.log('discardedProjects: ', discardedProjects); {}
const [isSearchActive, setIsSearchActive] = useState(false); );
const { userId, organization } = getUserData(); console.log("discardedProjects: ", discardedProjects);
const [isSearchActive, setIsSearchActive] = useState(false);
const { userId, organization } = getUserData();
const fetchTrashProjects = async () => { const fetchTrashProjects = async () => {
try { try {
const projects = await getTrash(organization); const projects = await getTrash(organization);
console.log('organization: ', organization); console.log("organization: ", organization);
console.log('trashedprojects: ', projects); console.log("trashedprojects: ", projects);
if (JSON.stringify(projects) !== JSON.stringify(discardedProjects)) { if (JSON.stringify(projects) !== JSON.stringify(discardedProjects)) {
setDiscardedProjects(projects); setDiscardedProjects(projects);
} }
} catch (error) { } catch (error) {
console.error('Error fetching trash projects:', error); console.error("Error fetching trash projects:", error);
} }
}; };
const handleTrashSearch = async ( const handleTrashSearch = async (inputValue: string) => {
inputValue: string if (!inputValue.trim()) {
) => { setIsSearchActive(false);
if (!inputValue.trim()) { return;
setIsSearchActive(false); }
return; if (!setDiscardedProjects || !setIsSearchActive) return;
}
if (!setDiscardedProjects || !setIsSearchActive) return;
const filterTrashedProcess = await trashSearchProject(organization, userId, inputValue); const filterTrashedProcess = await trashSearchProject(
setIsSearchActive(true); organization,
setDiscardedProjects(filterTrashedProcess.message ? {} : filterTrashedProcess); userId,
}; inputValue
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>
); );
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; export default DashboardTrash;

View File

@ -49,7 +49,7 @@ const DashboardTutorial = () => {
page="tutorial" page="tutorial"
/> />
<div className="container"> <div className="container" style={{ height: "calc(100% - 87px)" }}>
<div className="header" style={{ display: 'flex', gap: '7px' }}></div> <div className="header" style={{ display: 'flex', gap: '7px' }}></div>
<div className="cards-container"> <div className="cards-container">
{renderTrashProjects()} {renderTrashProjects()}

View File

@ -1,15 +1,18 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from "react";
import SidePannel from '../components/layout/Dashboard/SidePannel'; import SidePannel from "../components/layout/Dashboard/SidePannel";
import DashboardHome from '../components/layout/Dashboard/DashboardHome'; import DashboardHome from "../components/layout/Dashboard/DashboardHome";
import DashboardProjects from '../components/layout/Dashboard/DashboardProjects'; import DashboardProjects from "../components/layout/Dashboard/DashboardProjects";
import DashboardTrash from '../components/layout/Dashboard/DashboardTrash'; import DashboardTrash from "../components/layout/Dashboard/DashboardTrash";
import { useOrganization, useSocketStore, useUserName, useZones } from '../store/builder/store'; import {
import { getUserData } from '../components/layout/Dashboard/functions/getUserData'; useOrganization,
import DashboardTutorial from '../components/layout/Dashboard/DashboardTutorial'; 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 Dashboard: React.FC = () => {
const [activeTab, setActiveTab] = useState<string>('Home'); const [activeTab, setActiveTab] = useState<string>("Home");
const { setUserName } = useUserName(); const { setUserName } = useUserName();
const { setOrganization } = useOrganization(); const { setOrganization } = useOrganization();
const { userId, organization, email, userName } = getUserData(); const { userId, organization, email, userName } = getUserData();
@ -21,14 +24,14 @@ const Dashboard: React.FC = () => {
setUserName(userName); setUserName(userName);
} }
} }
}, []) }, []);
return ( return (
<div className="dashboard-main"> <div className="dashboard-main">
<SidePannel setActiveTab={setActiveTab} activeTab={activeTab} /> <SidePannel setActiveTab={setActiveTab} activeTab={activeTab} />
{activeTab == 'Home' && <DashboardHome />} {activeTab == "Home" && <DashboardHome />}
{activeTab == 'Projects' && <DashboardProjects />} {activeTab == "Projects" && <DashboardProjects />}
{activeTab == 'Trash' && <DashboardTrash />} {activeTab == "Trash" && <DashboardTrash />}
{activeTab == 'Tutorials' && <DashboardTutorial />} {activeTab == "Tutorials" && <DashboardTutorial />}
</div> </div>
); );
}; };

View File

@ -5,19 +5,28 @@
height: 100vh; height: 100vh;
width: 100vw; width: 100vw;
display: flex; display: flex;
padding: 27px 17px;
.side-pannel-container { .side-pannel-container {
padding: 32px; padding: 32px;
min-width: 240px; min-width: 280px;
height: 100vh; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; 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 { .side-pannel-header {
@include flex-space-between; @include flex-space-between;
.user-container { .user-container {
@include flex-center; @include flex-center;
gap: 6px; gap: 6px;
.user-profile { .user-profile {
height: 32px; height: 32px;
width: 32px; width: 32px;
@ -28,10 +37,12 @@
color: var(--primary-color); color: var(--primary-color);
border-radius: #{$border-radius-circle}; border-radius: #{$border-radius-circle};
} }
.user-name { .user-name {
color: var(--accent-color); color: var(--accent-color);
} }
} }
.notifications-container { .notifications-container {
@include flex-center; @include flex-center;
height: 24px; height: 24px;
@ -39,82 +50,117 @@
cursor: pointer; cursor: pointer;
} }
} }
.new-project-button { .new-project-button {
padding: 12px 16px; padding: 12px 16px;
cursor: not-allowed; cursor: not-allowed;
color: var(--accent-color); color: var(--text-color);
background: var(--background-color-secondary); background: var(--background-color-secondary);
border-radius: #{$border-radius-large}; border-radius: #{$border-radius-xxx};
} }
.side-bar-content-container { .side-bar-content-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
height: 100%; height: 100%;
.side-bar-options-container { .side-bar-options-container {
.option-list { .option-list {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
padding: 6px 10px; padding: 8px 10px;
margin: 4px 0; margin: 4px 0;
border-radius: #{$border-radius-medium}; border-radius: #{$border-radius-extra-large};
cursor: pointer;
&:hover { &:hover {
background: var(--background-color-secondary); background: var(--background-color-secondary);
} }
} }
.active { .active {
color: var(--accent-color); color: var(--text-button-color);
font-weight: var(--font-weight-medium); font-weight: var(--font-weight-medium);
background: var(--highlight-accent-color); background: var(--background-color-button);
&:hover { &:hover {
background: var(--highlight-accent-color); background: var(--background-color-button);
} }
} }
} }
} }
} }
.dashboard-home-container { .dashboard-home-container {
width: 100%; width: 100%;
padding-left: 18px;
.dashboard-navbar-container { .dashboard-navbar-container {
margin-top: 28px; margin-top: 28px;
padding: 8px 34px 8px 12px; margin-bottom: 22px;
@include flex-center; @include flex-center;
.title { .title {
text-transform: capitalize; text-transform: capitalize;
font-size: var(--font-size-large); font-size: var(--font-size-large);
width: 100%; width: 100%;
} }
.market-place-button { .market-place-button {
@include flex-center; @include flex-center;
gap: 6px; gap: 6px;
padding: 8px 14px; padding: 8px 14px;
background: var(--accent-gradient-color); background: var(--background-color-button);
white-space: nowrap; white-space: nowrap;
border-radius: #{$border-radius-large}; border-radius: #{$border-radius-extra-large};
color: var(--primary-color);
color: var(--text-button-color);
} }
.search-wrapper { .search-wrapper {
width: 400px; width: 400px;
} }
} }
.container { .container {
margin: 22px 0; margin: 22px 0;
width: 100%; width: 100%;
padding: 0 12px; height: calc(100% - 357px);
.header {
.header-wrapper {
font-size: var(--font-size-large); 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 { .cards-container {
height: 100%;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
position: relative; position: relative;
width: 100%; width: 100%;
padding: 8px; padding-top: 18px;
gap: 18px; gap: 18px;
overflow: auto;
} }
} }
} }
} }
.dashboard-card-container { .dashboard-card-container {
@ -123,11 +169,23 @@
min-width: 260px; min-width: 260px;
position: relative; position: relative;
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: #{$border-radius-large}; border-radius: #{$border-radius-extra-large};
overflow: hidden; overflow: hidden;
cursor: pointer;
overflow: visible;
position: relative;
.dashboard-card-wrapper {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
.preview-container { .preview-container {
height: 100%; height: 100%;
width: 100%; width: 100%;
img { img {
height: 100%; height: 100%;
width: 100%; width: 100%;
@ -135,27 +193,38 @@
vertical-align: top; vertical-align: top;
border: none; border: none;
outline: none; outline: none;
border-radius: #{$border-radius-extra-large};
} }
} }
.project-details-container { .project-details-container {
@include flex-space-between; @include flex-space-between;
position: absolute; position: absolute;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
padding: 8px 16px; padding: 13px 16px;
background: var(--primary-color); background: var(--background-color);
border-radius: #{$border-radius-large}; // backdrop-filter: blur(18px);
border-radius: #{$border-radius-xlarge};
transform: translateY(100%);
transition: transform 0.25s linear;
.project-details { .project-details {
.project-name { .project-name {
margin-bottom: 2px; margin-bottom: 7px;
} }
.project-data { .project-data {
color: var(--accent-color); color: var(--input-text-color);
} }
} }
.users-list-container { .users-list-container {
@include flex-center; @include flex-center;
gap: 6px; gap: 6px;
position: relative; // Needed for absolute positioning of kebab-options-wrapper
.user-profile { .user-profile {
height: 26px; height: 26px;
width: 26px; width: 26px;
@ -165,8 +234,65 @@
color: var(--primary-color); color: var(--primary-color);
border-radius: #{$border-radius-circle}; 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 { .market-place-banner-container {
@ -174,13 +300,14 @@
height: 230px; height: 230px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
padding: 0 24px;
img { img {
height: 100%; height: 100%;
width: 100%; width: 100%;
object-fit: cover; object-fit: cover;
border-radius: #{$border-radius-xxx}; border-radius: #{$border-radius-xxx};
} }
.hero-text { .hero-text {
position: absolute; position: absolute;
left: 52px; left: 52px;
@ -191,6 +318,7 @@
color: #ffffff; color: #ffffff;
text-transform: uppercase; text-transform: uppercase;
} }
.context { .context {
position: absolute; position: absolute;
top: 20px; top: 20px;
@ -201,11 +329,13 @@
color: #ffffff; color: #ffffff;
font-family: #{$font-roboto}; font-family: #{$font-roboto};
} }
.arrow-context { .arrow-context {
position: absolute; position: absolute;
bottom: 27px; bottom: 27px;
right: 300px; right: 300px;
} }
.explore-button { .explore-button {
position: absolute; position: absolute;
top: 95px; top: 95px;