diff --git a/app/src/components/Dashboard/DashboardCard.tsx b/app/src/components/Dashboard/DashboardCard.tsx index 863948a..c15765e 100644 --- a/app/src/components/Dashboard/DashboardCard.tsx +++ b/app/src/components/Dashboard/DashboardCard.tsx @@ -1,7 +1,7 @@ -import React, { useState, useRef } from "react"; +import React, { useState, useRef, useCallback, useEffect } from "react"; import { createPortal } from "react-dom"; -import img from "../../assets/image/image.png"; import { useNavigate } from "react-router-dom"; +import img from "../../assets/image/image.png"; import { getUserData } from "../../functions/getUserData"; import { useLoadingProgress, @@ -16,7 +16,7 @@ import { getAllProjects } from "../../services/dashboard/getAllProjects"; interface DashBoardCardProps { projectName: string; - thumbnail: any; + thumbnail: string; projectId: string; createdAt?: string; isViewed?: string; @@ -35,13 +35,29 @@ interface DashBoardCardProps { projectName: string, thumbnail: string ) => Promise; - active?: string; + active?: "shared" | "trash" | "recent" | string; setIsSearchActive?: React.Dispatch>; - setRecentDuplicateData?: React.Dispatch>; - setProjectDuplicateData?: React.Dispatch>; + setRecentDuplicateData?: React.Dispatch>; + setProjectDuplicateData?: React.Dispatch>; setActiveFolder?: React.Dispatch>; + openKebabProjectId: string | null; + setOpenKebabProjectId: React.Dispatch>; } -type RelativeTimeFormatUnit = any; + +type RelativeTimeFormatUnit = + | "year" + | "month" + | "week" + | "day" + | "hour" + | "minute" + | "second"; + +const kebabOptionsMap: Record = { + default: ["rename", "delete", "duplicate", "open in new tab"], + trash: ["restore", "delete"], + shared: ["duplicate", "open in new tab"], +}; const DashboardCard: React.FC = ({ projectName, @@ -58,131 +74,143 @@ const DashboardCard: React.FC = ({ setRecentDuplicateData, setProjectDuplicateData, setActiveFolder, + openKebabProjectId, + setOpenKebabProjectId, }) => { 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 isKebabOpen = openKebabProjectId === projectId; + const [renameValue, setRenameValue] = useState(projectName); + const [isRenaming, setIsRenaming] = useState(false); const kebabRef = useRef(null); - const navigateToProject = async (e: any) => { - if (active && active === "trash") return; - try { - // const viewProjects = await viewProject(organization, projectId, userId); + // Close kebab when clicking outside + OuterClick({ + contextClassName: [`tag-${projectId}`], + setMenuVisible: () => { + if (isKebabOpen) setOpenKebabProjectId(null); + }, + }); - setLoadingProgress(1); - setProjectName(projectName); - navigate(`/projects/${projectId}`); - } catch { } - }; + const navigateToProject = useCallback(() => { + if (active === "trash") return; + setLoadingProgress(1); + setProjectName(projectName); + navigate(`/projects/${projectId}`); + }, [ + active, + projectId, + projectName, + navigate, + setLoadingProgress, + setProjectName, + ]); - const handleOptionClick = async (option: string) => { - switch (option) { - case "delete": - if (handleDeleteProject) { - handleDeleteProject(projectId); - } else if (handleTrashDeleteProject) { - handleTrashDeleteProject(projectId); + const getOptions = useCallback(() => { + if (active === "trash") return kebabOptionsMap.trash; + if (active === "shared" || (createdBy && createdBy._id !== userId)) { + return kebabOptionsMap.shared; + } + return kebabOptionsMap.default; + }, [active, createdBy, userId]); + + const handleProjectName = useCallback( + async (newName: string) => { + setRenameValue(newName); + if (!projectId) return; + + try { + const projects = await getAllProjects(userId, organization); + const projectUuid = projects?.Projects?.find( + (val: any) => val.projectUuid === projectId || val._id === projectId + ); + if (!projectUuid) return; + + const updatePayload = { + projectId: projectUuid._id, + organization, + userId, + projectName: newName, + }; + + if (projectSocket) { + projectSocket.emit("v1:project:update", updatePayload); } - break; - case "restore": - if (handleRestoreProject) { - await handleRestoreProject(projectId); - } - break; - case "open in new tab": - try { - if (active === "shared" && createdBy) { - // const newTab = await viewProject( - // organization, - // projectId, - // createdBy?._id - // ); - } else { - // const newTab = await viewProject(organization, projectId, userId); + } catch { + // silent fail + } + }, + [projectId, userId, organization, projectSocket] + ); - setProjectName(projectName); - setIsKebabOpen(false); - } - } catch (error) { } - window.open(`/projects/${projectId}`, "_blank"); - break; - case "rename": - setIsRenaming(true); - break; - case "duplicate": - if (handleDuplicateWorkspaceProject) { - setProjectDuplicateData && - setProjectDuplicateData({ + const handleOptionClick = useCallback( + async (option: string) => { + switch (option) { + case "delete": + await (handleDeleteProject?.(projectId) ?? + handleTrashDeleteProject?.(projectId)); + break; + case "restore": + await handleRestoreProject?.(projectId); + break; + case "open in new tab": + setProjectName(projectName); + window.open(`/projects/${projectId}`, "_blank"); + break; + case "rename": + setIsRenaming(true); + break; + case "duplicate": + if (handleDuplicateWorkspaceProject) { + setProjectDuplicateData?.({ projectId, projectName, thumbnail }); + await handleDuplicateWorkspaceProject( projectId, projectName, thumbnail, - }); - await handleDuplicateWorkspaceProject( - projectId, - projectName, - thumbnail, - userId - ); - if (active === "shared" && setActiveFolder) { - setActiveFolder("myProjects"); - } - } else if (handleDuplicateRecentProject) { - setRecentDuplicateData && - setRecentDuplicateData({ + userId + ); + if (active === "shared") { + setActiveFolder?.("myProjects"); + } + } else if (handleDuplicateRecentProject) { + setRecentDuplicateData?.({ projectId, projectName, thumbnail, userId, }); - 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); - if (!projects || !projects.Projects) return; - let projectUuid = projects.Projects.find( - (val: any) => val.projectUuid === projectId || val._id === projectId - ); - const updateProjects = { - projectId: projectUuid?._id, - organization, - userId, - projectName, - thumbnail: undefined, - }; - // const updatedProjectName = await updateProject( - // projectUuid._id, - // userId, - // organization, - // undefined, - // projectName - // ); - if (projectSocket) { - projectSocket.emit("v1:project:update", updateProjects); + await handleDuplicateRecentProject( + projectId, + projectName, + thumbnail + ); + } + break; } - } catch (error) { } - }; + }, + [ + projectId, + projectName, + thumbnail, + userId, + active, + handleDeleteProject, + handleTrashDeleteProject, + handleRestoreProject, + handleDuplicateWorkspaceProject, + handleDuplicateRecentProject, + setProjectName, + setProjectDuplicateData, + setRecentDuplicateData, + setActiveFolder, + ] + ); - function getRelativeTime(dateString: string): string { + const getRelativeTime = useCallback((dateString: string): string => { const date = new Date(dateString); const now = new Date(); const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); @@ -198,69 +226,56 @@ const DashboardCard: React.FC = ({ }; 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); - } + for (const [unit, seconds] of Object.entries(intervals)) { + const diff = Math.floor(diffInSeconds / seconds); + if (diff >= 1) return rtf.format(-diff, unit as RelativeTimeFormatUnit); } return "just now"; - } + }, []); - const kebabOptionsMap: Record = { - default: ["rename", "delete", "duplicate", "open in new tab"], - trash: ["restore", "delete"], - shared: ["duplicate", "open in new tab"], - }; + const [kebabPosition, setKebabPosition] = useState({ top: 0, left: 0 }); - const getOptions = () => { - if (active === "trash") return kebabOptionsMap.trash; - if (active === "shared") return kebabOptionsMap.shared; - if (createdBy && createdBy?._id !== userId) return kebabOptionsMap.shared; - return kebabOptionsMap.default; - }; + useEffect(() => { + if (isKebabOpen && kebabRef.current) { + const rect = kebabRef.current.getBoundingClientRect(); + setKebabPosition({ + top: rect.bottom + window.scrollY, + left: rect.left + window.scrollX - 80, + }); + } + }, [isKebabOpen]); return ( - + {isKebabOpen && createPortal(
{getOptions().map((option) => (
, document.body )} - + ); }; diff --git a/app/src/components/Dashboard/DashboardHome.tsx b/app/src/components/Dashboard/DashboardHome.tsx deleted file mode 100644 index 96761f8..0000000 --- a/app/src/components/Dashboard/DashboardHome.tsx +++ /dev/null @@ -1,166 +0,0 @@ -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"; -import { generateUniqueId } from "../../functions/generateUniqueId"; - -interface Project { - _id: string; - projectName: string; - thumbnail: string; - createdBy: { _id: string; userName: string }; - projectUuid?: string; - createdAt: string; - isViewed?: string; -} - -interface RecentProjectsData { - [key: string]: Project[]; -} - -const DashboardHome: React.FC = () => { - const [recentProjects, setRecentProjects] = useState({}); - - const [isSearchActive, setIsSearchActive] = useState(false); - const { userId, organization } = getUserData(); - const { projectSocket } = useSocketStore(); - const [recentDuplicateData, setRecentDuplicateData] = useState({}); - - const fetchRecentProjects = async () => { - try { - const projects = await recentlyViewed(organization, userId); - - if (JSON.stringify(projects) !== JSON.stringify(recentProjects)) { - setRecentProjects(projects); - } - } catch (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) => { - try { - //API for delete project - // const deletedProject = await deleteProject( - // projectId, - // userId, - // organization - // ); - // - - // 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) { } - }; - - const handleDuplicateRecentProject = async ( - projectId: string, - projectName: string, - thumbnail: string - ) => { - if (projectSocket) { - - const duplicateRecentProjectData = { - userId, - thumbnail, - organization, - projectUuid: generateUniqueId(), - refProjectID: projectId, - projectName, - }; - projectSocket.emit("v1:project:Duplicate", duplicateRecentProjectData); - } - }; - - const renderProjects = () => { - const projectList = recentProjects[Object.keys(recentProjects)[0]]; - - if (!projectList?.length) { - return
No recent projects found
; - } - - return ( - projectList.map((project) => ( - - )) - ); - }; - - useEffect(() => { - if (!isSearchActive) { - fetchRecentProjects(); - } - }, [isSearchActive]); - - return ( -
- - -
-

Recents

-
{renderProjects()}
-
- {recentDuplicateData && Object.keys(recentDuplicateData).length > 0 && ( - - )} -
- ); -}; - -export default DashboardHome; diff --git a/app/src/components/Dashboard/DashboardMain.tsx b/app/src/components/Dashboard/DashboardMain.tsx new file mode 100644 index 0000000..c8f6ea7 --- /dev/null +++ b/app/src/components/Dashboard/DashboardMain.tsx @@ -0,0 +1,272 @@ +import React, { useEffect, useState } from "react"; +import DashboardNavBar from "./DashboardNavBar"; +import DashboardCard from "./DashboardCard"; +import MarketPlaceBanner from "./MarketPlaceBanner"; +import { getUserData } from "../../functions/getUserData"; +import { useSocketStore } from "../../store/builder/store"; +import { getAllProjects } from "../../services/dashboard/getAllProjects"; +import { sharedWithMeProjects } from "../../services/dashboard/sharedWithMeProject"; +import { recentlyViewed } from "../../services/dashboard/recentlyViewed"; +import { getTrash } from "../../services/dashboard/getTrash"; +import { searchProject } from "../../services/dashboard/searchProjects"; +import { trashSearchProject } from "../../services/dashboard/trashSearchProject"; +import { restoreTrash } from "../../services/dashboard/restoreTrash"; +import { generateUniqueId } from "../../functions/generateUniqueId"; +import ProjectSocketRes from "./socket/projectSocketRes"; + +interface Project { + _id: string; + projectName: string; + thumbnail: string; + createdBy: { _id: string; userName: string }; + projectUuid?: string; + createdAt?: string; + DeletedAt?: string; +} + +interface ProjectCollection { + [key: string]: Project[]; +} + +type Folder = "home" | "projects" | "shared" | "trash"; + +interface DashboardMainProps { + activeFolder: Folder; +} + +const DashboardMain: React.FC = ({ activeFolder }) => { + const [activeSubFolder, setActiveSubFolder] = useState("myProjects"); + const [projectsData, setProjectsData] = useState({}); + const [isSearchActive, setIsSearchActive] = useState(false); + const [duplicateData, setDuplicateData] = useState({}); + const [openKebabProjectId, setOpenKebabProjectId] = useState( + null + ); + const [projectsCache, setProjectsCache] = useState<{ + [key: string]: ProjectCollection; + }>({}); + + const { userId, organization } = getUserData(); + const { projectSocket } = useSocketStore(); + + // #region API Fetchers + const fetchData = async () => { + const cacheKey = + activeFolder + (activeFolder === "projects" ? `-${activeSubFolder}` : ""); + + if (projectsCache[cacheKey] && !isSearchActive) { + setProjectsData(projectsCache[cacheKey]); + return; + } + + try { + let projects: ProjectCollection = {}; // initialize as empty object + + switch (activeFolder) { + case "home": + projects = await recentlyViewed(organization, userId); + break; + case "projects": + if (activeSubFolder === "myProjects") { + projects = await getAllProjects(userId, organization); + } else { + projects = await sharedWithMeProjects(); + } + break; + case "trash": + projects = await getTrash(organization); + break; + } + + // Only update cache if projects is not empty + if ( + projects && + JSON.stringify(projects) !== JSON.stringify(projectsData) + ) { + setProjectsCache((prev) => ({ ...prev, [cacheKey]: projects })); + setProjectsData(projects); + } + } catch (error) { + console.error(error); + } + }; + + // #region Search Handlers + const handleSearch = async (inputValue: string) => { + if (!inputValue.trim()) { + setIsSearchActive(false); + return; + } + + let results; + if (activeFolder === "trash") { + results = await trashSearchProject(organization, userId, inputValue); + } else { + results = await searchProject(organization, userId, inputValue); + } + + setIsSearchActive(true); + setProjectsData(results?.message ? {} : results); + }; + + // #region Socket Actions + const handleDelete = async (projectId: string): Promise => { + if (projectSocket) { + projectSocket.emit("v1:project:delete", { + projectId, + organization, + userId, + }); + } + updateStateAfterRemove(projectId); + }; + + const handlePermanentDelete = async (projectId: string): Promise => { + if (projectSocket) { + projectSocket.emit("v1:trash:delete", { + projectId, + organization, + userId, + }); + } + updateStateAfterRemove(projectId); + }; + + const handleRestore = async (projectId: string): Promise => { + await restoreTrash(organization, projectId); + updateStateAfterRemove(projectId); + }; + + const handleDuplicate = async ( + projectId: string, + projectName: string, + thumbnail: string + ): Promise => { + if (projectSocket) { + projectSocket.emit("v1:project:Duplicate", { + userId, + thumbnail, + organization, + projectUuid: generateUniqueId(), + refProjectID: projectId, + projectName, + }); + } + }; + + // #region Project Map + const updateStateAfterRemove = (projectId: string) => { + setProjectsData((prev: ProjectCollection) => { + const key = Object.keys(prev)[0]; + const updatedList = prev[key]?.filter((p) => p._id !== projectId) || []; + return { ...prev, [key]: updatedList }; + }); + setIsSearchActive(false); + }; + + const renderProjects = () => { + const key = Object.keys(projectsData)[0]; + const projectList = projectsData[key]; + + if (!projectList?.length) { + return
No projects found
; + } + + return projectList.map((project) => ( + + )); + }; + + // #region Use Effects + useEffect(() => { + if (!isSearchActive) fetchData(); + // eslint-disable-next-line + }, [activeFolder, isSearchActive, activeSubFolder]); + + return ( +
+ + + {activeFolder === "home" && } + +
+ {activeFolder === "projects" && ( +
+ + +
+ )} + +
{renderProjects()}
+ + {duplicateData && Object.keys(duplicateData).length > 0 && ( + + )} +
+
+ ); +}; + +export default DashboardMain; diff --git a/app/src/components/Dashboard/DashboardNavBar.tsx b/app/src/components/Dashboard/DashboardNavBar.tsx index c86ef69..7ebec4d 100644 --- a/app/src/components/Dashboard/DashboardNavBar.tsx +++ b/app/src/components/Dashboard/DashboardNavBar.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useCallback, useMemo } from "react"; import Search from "../ui/inputs/Search"; import { CartIcon } from "../icons/ExportModuleIcons"; @@ -15,19 +15,23 @@ const DashboardNavBar: React.FC = ({ 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); + // Determine active search handler + const activeSearchHandler = useMemo( + () => handleProjectsSearch || handleTrashSearch || handleRecentProjectSearch, + [handleProjectsSearch, handleTrashSearch, handleRecentProjectSearch] + ); + + const handleSearch = useCallback( + async (inputValue: string) => { + if (!activeSearchHandler) return; + try { + await activeSearchHandler(inputValue); + } catch (error) { + console.error("Search failed:", error); } - } catch (error) { - console.error("Search failed:", error); - } - }; + }, + [activeSearchHandler] + ); return (
@@ -40,4 +44,4 @@ const DashboardNavBar: React.FC = ({ ); }; -export default DashboardNavBar; \ No newline at end of file +export default DashboardNavBar; diff --git a/app/src/components/Dashboard/DashboardProjects.tsx b/app/src/components/Dashboard/DashboardProjects.tsx deleted file mode 100644 index 536dd24..0000000 --- a/app/src/components/Dashboard/DashboardProjects.tsx +++ /dev/null @@ -1,228 +0,0 @@ -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"; -import { sharedWithMeProjects } from "../../services/dashboard/sharedWithMeProject"; -import { duplicateProject } from "../../services/dashboard/duplicateProject"; -import { generateUniqueId } from "../../functions/generateUniqueId"; - -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( - {} - ); - const [sharedwithMeProject, setSharedWithMeProjects] = useState([]); - const [projectDuplicateData, setProjectDuplicateData] = useState({}); - const [isSearchActive, setIsSearchActive] = useState(false); - const [activeFolder, setActiveFolder] = useState("myProjects"); - const { projectSocket } = useSocketStore(); - const { userId, organization } = getUserData(); - - 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 fetchAllProjects = async () => { - try { - const projects = await getAllProjects(userId, organization); - - if (!projects || !projects.Projects) return; - - if (JSON.stringify(projects) !== JSON.stringify(workspaceProjects)) { - setWorkspaceProjects(projects); - } - } catch (error) { } - }; - - const handleDeleteProject = async (projectId: any) => { - try { - // const deletedProject = await deleteProject( - // projectId, - // userId, - // organization - // ); - // - const deleteProjects = { - projectId, - organization, - userId, - }; - - // SOCKET for deleting the project - if (projectSocket) { - projectSocket.emit("v1:project:delete", deleteProjects); - } else { - } - 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) { } - }; - - const handleDuplicateWorkspaceProject = async ( - projectId: string, - projectName: string, - thumbnail: string - ) => { - // const duplicatedProject = await duplicateProject( - // projectId, - // generateUniqueId(), - // thumbnail, - // projectName - // ); - // console.log("duplicatedProject: ", duplicatedProject); - if (projectSocket) { - const duplicateProjectData = { - userId, - thumbnail, - organization, - projectUuid: generateUniqueId(), - refProjectID: 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
No projects found
; - } - - return projectList.map((project) => ( - - )); - }; - - const renderSharedProjects = () => { - return sharedwithMeProject?.map((project: any) => ( - - )); - }; - - const sharedProject = async () => { - try { - const sharedWithMe = await sharedWithMeProjects(); - setSharedWithMeProjects(sharedWithMe); - } catch { } - }; - - useEffect(() => { - if (!isSearchActive) { - fetchAllProjects(); - } - }, [isSearchActive]); - - useEffect(() => { - if (activeFolder === "shared") { - sharedProject(); - } - }, [activeFolder]); - - return ( -
- - -
-
- - -
-
- {activeFolder == "myProjects" - ? renderProjects() - : renderSharedProjects()} -
- - {projectDuplicateData && - Object.keys(projectDuplicateData).length > 0 && ( - - )} -
-
- ); -}; - -export default DashboardProjects; diff --git a/app/src/components/Dashboard/DashboardTrash.tsx b/app/src/components/Dashboard/DashboardTrash.tsx deleted file mode 100644 index 8bac359..0000000 --- a/app/src/components/Dashboard/DashboardTrash.tsx +++ /dev/null @@ -1,157 +0,0 @@ -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({}); - 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) => { - 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
No deleted projects found
; - } - - return projectList.map((project) => ( - - )); - }; - - useEffect(() => { - if (!isSearchActive) { - fetchTrashProjects(); - } - }, [isSearchActive]); - - return ( -
- - -
-
-
{renderTrashProjects()}
-
-
- ); -}; - -export default DashboardTrash; \ No newline at end of file diff --git a/app/src/components/Dashboard/DashboardTutorial.tsx b/app/src/components/Dashboard/DashboardTutorial.tsx index dedcccf..5a06130 100644 --- a/app/src/components/Dashboard/DashboardTutorial.tsx +++ b/app/src/components/Dashboard/DashboardTutorial.tsx @@ -1,62 +1,67 @@ -import React, { useEffect, useState } from 'react'; -import DashboardNavBar from './DashboardNavBar'; -import DashboardCard from './DashboardCard'; -import { projectTutorial } from '../../services/dashboard/projectTutorial'; +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; + _id: string; + projectName: string; + thumbnail: string; + createdBy: string; + projectUuid?: string; } interface DiscardedProjects { - [key: string]: Project[]; + [key: string]: Project[]; } const DashboardTutorial = () => { - const [tutorialProject, setTutorialProject] = useState({}) - const handleIcon = async () => { - try { - let tutorial = await projectTutorial() - setTutorialProject(tutorial) - } catch { + const [tutorialProject, setTutorialProject] = useState({}); + const handleIcon = async () => { + try { + let tutorial = await projectTutorial(); + setTutorialProject(tutorial); + } catch {} + }; - } + const [openKebabProjectId, setOpenKebabProjectId] = useState( + null + ); + + useEffect(() => { + handleIcon(); + }, []); + const renderTrashProjects = () => { + const projectList = tutorialProject[Object.keys(tutorialProject)[0]]; + + if (!projectList?.length) { + return
No deleted projects found
; } - useEffect(() => { - handleIcon() - }, []) - const renderTrashProjects = () => { - const projectList = tutorialProject[Object.keys(tutorialProject)[0]]; - if (!projectList?.length) { - return
No deleted projects found
; - } + return projectList.map((tutorials: any) => ( + + )); + }; + return ( +
+ - return projectList.map((tutorials: any) => ( - - )); - }; - return ( -
- - -
-
-
- {renderTrashProjects()} -
-
-
- ); -} +
+
+
{renderTrashProjects()}
+
+
+ ); +}; export default DashboardTutorial; diff --git a/app/src/components/Dashboard/MarketPlaceBanner.tsx b/app/src/components/Dashboard/MarketPlaceBanner.tsx index 68b631a..7480538 100644 --- a/app/src/components/Dashboard/MarketPlaceBanner.tsx +++ b/app/src/components/Dashboard/MarketPlaceBanner.tsx @@ -1,4 +1,3 @@ -import React from "react"; import banner from "../../assets/image/banner.png"; const MarketPlaceBanner = () => { diff --git a/app/src/components/Dashboard/SidePannel.tsx b/app/src/components/Dashboard/SidePannel.tsx index 06ce8ac..b612ba6 100644 --- a/app/src/components/Dashboard/SidePannel.tsx +++ b/app/src/components/Dashboard/SidePannel.tsx @@ -92,16 +92,12 @@ const SidePannel: React.FC = ({ setActiveTab, activeTab }) => { -
+
+ New project
-
= ({ setActiveTab, activeTab }) => { > Home -
-
+
-
+
-
+
-
+
+
-
+
-
+
-
+ +
+
diff --git a/app/src/components/Dashboard/socket/projectSocketRes.dev.tsx b/app/src/components/Dashboard/socket/projectSocketRes.dev.tsx deleted file mode 100644 index 9fac261..0000000 --- a/app/src/components/Dashboard/socket/projectSocketRes.dev.tsx +++ /dev/null @@ -1,101 +0,0 @@ - -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 RecentProject { - _id: string; - projectName: string; - thumbnail: string; - createdBy: { _id: string, userName: string }; - projectUuid?: string; - createdAt: string; - isViewed?: string -} - -interface RecentProjectData { - [key: string]: RecentProject[]; -} -interface ProjectsData { - [key: string]: Project[]; -} -interface ProjectSocketResProps { - setRecentProjects?: React.Dispatch>; - setWorkspaceProjects?: React.Dispatch>; - setIsSearchActive?: React.Dispatch>; -} - -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; diff --git a/app/src/components/Dashboard/socket/projectSocketRes.tsx b/app/src/components/Dashboard/socket/projectSocketRes.tsx new file mode 100644 index 0000000..49285c2 --- /dev/null +++ b/app/src/components/Dashboard/socket/projectSocketRes.tsx @@ -0,0 +1,90 @@ +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: { _id: string; userName: string }; + projectUuid?: string; + createdAt?: string; + DeletedAt?: string; + isViewed?: string; +} + +interface ProjectCollection { + [key: string]: Project[]; +} + +interface ProjectSocketResProps { + setRecentProjects?: React.Dispatch>; + setWorkspaceProjects?: React.Dispatch>; + setIsSearchActive?: React.Dispatch>; +} + +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); + setWorkspaceProjects(allProjects); + } else if (setRecentProjects) { + const recentProjects = await recentlyViewed(organization, userId); + 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); + }; + // eslint-disable-next-line + }, [projectSocket, userId, organization]); + + return null; +}; + +export default ProjectSocketRes; diff --git a/app/src/components/footer/Footer.tsx b/app/src/components/footer/Footer.tsx index 8475ddb..d6075f8 100644 --- a/app/src/components/footer/Footer.tsx +++ b/app/src/components/footer/Footer.tsx @@ -1,16 +1,10 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useMemo, useCallback } from "react"; import { HelpIcon, WifiIcon } from "../icons/DashboardIcon"; import { useLogger } from "../ui/log/LoggerContext"; import { GetLogIcon } from "./getLogIcons"; -import { - CurserLeftIcon, - CurserMiddleIcon, - CurserRightIcon, -} from "../icons/LogIcons"; +import { CurserLeftIcon, CurserMiddleIcon, CurserRightIcon } from "../icons/LogIcons"; import ShortcutHelper from "./shortcutHelper"; -import useVersionHistoryVisibleStore, { - useShortcutStore, -} from "../../store/builder/store"; +import useVersionHistoryVisibleStore, { useShortcutStore } from "../../store/builder/store"; import { usePlayButtonStore } from "../../store/ui/usePlayButtonStore"; import useModuleStore, { useSubModuleStore } from "../../store/ui/useModuleStore"; import { useVersionContext } from "../../modules/builder/version/versionContext"; @@ -19,7 +13,7 @@ import { useMouseNoteStore } from "../../store/ui/useUIToggleStore"; const Footer: React.FC = () => { const { logs, setIsLogListVisible } = useLogger(); - const lastLog = logs.length > 0 ? logs[logs.length - 1] : null; + const lastLog = logs[logs.length - 1] || null; const { setActiveModule } = useModuleStore(); const { setSubModule } = useSubModuleStore(); @@ -30,46 +24,40 @@ const Footer: React.FC = () => { const { selectedVersion } = selectedVersionStore(); const { Leftnote, Middlenote, Rightnote } = useMouseNoteStore(); - const [isOnline, setIsOnline] = useState(navigator.onLine); - useEffect(() => { - const handleOnline = () => { - echo.success('You are back Online'); - setIsOnline(true); - }; - const handleOffline = () => { - echo.warn('Changes made now might not be saved'); - echo.error('You are now Offline.'); - setIsOnline(false); - }; + // -------------------- Online/Offline Handlers -------------------- + const handleOnline = useCallback(() => { + echo.success("You are back Online"); + setIsOnline(true); + }, []); + const handleOffline = useCallback(() => { + echo.warn("Changes made now might not be saved"); + echo.error("You are now Offline."); + setIsOnline(false); + }, []); + + useEffect(() => { window.addEventListener("online", handleOnline); window.addEventListener("offline", handleOffline); - return () => { window.removeEventListener("online", handleOnline); window.removeEventListener("offline", handleOffline); }; - }, []); - const mouseButtons = [ - { - icon: , - label: Leftnote !== "" ? Leftnote : "Pan", - mouse: "left", - }, - { - icon: , - label: Middlenote !== "" ? Middlenote : "Scroll Zoom", - mouse: "middle", - }, - { - icon: , - label: Rightnote !== "" ? Rightnote : "Orbit / Cancel action", - mouse: "right", - }, - ]; + }, [handleOnline, handleOffline]); + // -------------------- Mouse Buttons -------------------- + const mouseButtons = useMemo( + () => [ + { icon: , label: Leftnote || "Pan", mouse: "left" }, + { icon: , label: Middlenote || "Scroll Zoom", mouse: "middle" }, + { icon: , label: Rightnote || "Orbit / Cancel action", mouse: "right" }, + ], + [Leftnote, Middlenote, Rightnote] + ); + + // -------------------- Mouse Helper -------------------- useEffect(() => { const cleanup = mouseActionHelper(); return () => cleanup(); @@ -78,6 +66,7 @@ const Footer: React.FC = () => { return (
+ {/* Mouse Button Info */}
{mouseButtons.map(({ icon, label, mouse }) => (
@@ -87,13 +76,15 @@ const Footer: React.FC = () => { ))}
+ {/* Logs and Version */}
-
-
+
+
+
+
{ @@ -119,11 +111,8 @@ const Footer: React.FC = () => {
-
+ +
@@ -132,12 +121,9 @@ const Footer: React.FC = () => {
- {!isPlaying && ( -
+ {/* Shortcut Helper */} + {!isPlaying && showShortcuts && ( +
)} diff --git a/app/src/components/icons/DashboardIcon.tsx b/app/src/components/icons/DashboardIcon.tsx index 3eb5301..dbcfb7b 100644 --- a/app/src/components/icons/DashboardIcon.tsx +++ b/app/src/components/icons/DashboardIcon.tsx @@ -34,7 +34,7 @@ export function HomeIcon() { > ); @@ -51,7 +51,7 @@ export function ProjectsIcon() { > ); @@ -70,103 +70,103 @@ export function TutorialsIcon() { cx="8.157" cy="8.35866" r="6.17928" - stroke="var(--text-button-color)" + stroke="var(--text-color)" strokeWidth="0.562865" /> @@ -184,12 +184,12 @@ export function DocumentationIcon() { > @@ -208,7 +208,7 @@ export function HelpIcon() { @@ -236,17 +236,17 @@ export function LogoutIcon() { > diff --git a/app/src/components/templates/CreateNewWindow.tsx b/app/src/components/templates/CreateNewWindow.tsx index 91085ab..bddc2ba 100644 --- a/app/src/components/templates/CreateNewWindow.tsx +++ b/app/src/components/templates/CreateNewWindow.tsx @@ -37,7 +37,7 @@ export const RenderInNewWindow: React.FC = ({ copyStyles = true, noopener = true, className, - theme = "light", + theme = localStorage.getItem('theme') ?? 'light', }) => { const [mounted, setMounted] = useState(false); const childWindowRef = useRef(null); diff --git a/app/src/components/ui/log/LogList.tsx b/app/src/components/ui/log/LogList.tsx index a58c7ce..b7b39dc 100644 --- a/app/src/components/ui/log/LogList.tsx +++ b/app/src/components/ui/log/LogList.tsx @@ -162,7 +162,6 @@ const LogList: React.FC = () => { ) : ( { setOpen(false); setIsLogListVisible(false); diff --git a/app/src/pages/Dashboard.tsx b/app/src/pages/Dashboard.tsx index 93cc51a..2481adc 100644 --- a/app/src/pages/Dashboard.tsx +++ b/app/src/pages/Dashboard.tsx @@ -1,34 +1,36 @@ import React, { useEffect, useState } from "react"; import { useSocketStore } from "../store/builder/store"; -import DashboardHome from "../components/Dashboard/DashboardHome"; -import DashboardProjects from "../components/Dashboard/DashboardProjects"; -import DashboardTrash from "../components/Dashboard/DashboardTrash"; import { getUserData } from "../functions/getUserData"; import SidePannel from "../components/Dashboard/SidePannel"; import DashboardTutorial from "../components/Dashboard/DashboardTutorial"; +import DashboardMain from "../components/Dashboard/DashboardMain"; const Dashboard: React.FC = () => { - const [activeTab, setActiveTab] = useState("Home"); - const { socket } = useSocketStore(); - const { organization, email } = getUserData(); + const [activeTab, setActiveTab] = useState("Home"); + const { socket } = useSocketStore(); + const { organization, email } = getUserData(); - useEffect(() => { - const token = localStorage.getItem("token"); - const refreshToken = localStorage.getItem("refreshToken") - if (token && refreshToken) { - useSocketStore.getState().initializeSocket(email, organization, token, refreshToken); + useEffect(() => { + const token = localStorage.getItem("token"); + const refreshToken = localStorage.getItem("refreshToken"); + if (token && refreshToken) { + useSocketStore + .getState() + .initializeSocket(email, organization, token, refreshToken); + } + }, [socket, email, organization]); + + return ( +
+ + - - {activeTab === "Home" && } - {activeTab === "Projects" && } - {activeTab === "Trash" && } - {activeTab === "Tutorials" && } -
- ); + /> + {activeTab === "Tutorials" && } +
+ ); }; export default Dashboard; diff --git a/app/src/styles/pages/_dashboard.scss b/app/src/styles/pages/_dashboard.scss index cfb2770..eb98525 100644 --- a/app/src/styles/pages/_dashboard.scss +++ b/app/src/styles/pages/_dashboard.scss @@ -51,11 +51,36 @@ } .new-project-button { + position: relative; padding: 12px 16px; - cursor: not-allowed; color: var(--text-color); - background: var(--background-color-secondary); + background: #7b4cd323; border-radius: #{$border-radius-xxx}; + overflow: hidden; + cursor: pointer; + transition: color 0.3s; + &::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + transform: translate(-50%, -50%); + height: 0px; + width: 0px; + border-radius: 50%; + background: var(--background-color-accent); + z-index: -1; + transition: all 0.25s ease-in-out; + } + &:hover { + color: var(--text-button-color); + &::after { + height: 260px; + width: 260px; + left: 50%; + scale: 1; + } + } } .side-bar-content-container { @@ -67,15 +92,42 @@ .side-bar-options-container { .option-list { display: flex; + position: relative; align-items: center; gap: 8px; padding: 8px 10px; margin: 4px 0; border-radius: #{$border-radius-extra-large}; + width: 100%; + overflow: hidden; cursor: pointer; + transition: color 0.3s; + + &::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + transform: translate(-50%, -50%); + height: 0px; + width: 0px; + border-radius: 50%; + background: #7b4cd323; + z-index: -1; + transition: all 0.5s ease-in-out; + } + + &:disabled { + cursor: help; + } &:hover { - background: var(--background-color-secondary); + &::after { + height: 260px; + width: 260px; + left: 50%; + scale: 1; + } } } @@ -164,20 +216,31 @@ min-width: 260px; position: relative; border: 1px solid var(--border-color); - border-radius: #{$border-radius-extra-large}; + border-radius: 22px; cursor: pointer; - overflow: visible; + overflow: hidden; + &:hover { + border-color: var(--accent-color); + .preview-container { + img { + scale: 1.05; + } + } + } .dashboard-card-wrapper { width: 100%; height: 100%; position: relative; overflow: hidden; + padding-bottom: 1px; } .preview-container { height: 100%; width: 100%; + border-radius: #{$border-radius-extra-large}; + overflow: hidden; img { height: 100%; @@ -186,21 +249,21 @@ vertical-align: top; border: none; outline: none; - border-radius: #{$border-radius-extra-large}; + transition: scale 0.2s; } } .project-details-container { @include flex-space-between; position: absolute; - bottom: 0; + bottom: 1px; width: 100%; padding: 13px 16px; background: var(--background-color); - - border-radius: #{$border-radius-xlarge}; + border-radius: #{$border-radius-extra-large}; + backdrop-filter: blur(6px); // transform: translateY(100%);///////hovered - transition: transform 0.25s linear; + transition: transform 0.2s linear; .project-details { display: flex; @@ -212,6 +275,7 @@ } .project-data { + text-align: start; color: var(--input-text-color); } } @@ -226,8 +290,8 @@ width: 26px; line-height: 26px; text-align: center; - background: var(--accent-color); - color: var(--text-color); + background: var(--background-color-accent); + color: var(--text-button-color); border-radius: #{$border-radius-circle}; } @@ -308,10 +372,7 @@ } .kebab-options-wrapper { - // position: absolute; - // bottom: 40px; - // right: 40px; - // z-index: 100; + min-width: 140px; background: var(--background-color); border: 1px solid var(--border-color); border-radius: 8px;