Merge remote-tracking branch 'origin/main-demo' into main-dev
This commit is contained in:
76
app/src/components/Dashboard/DasboardUseCases.tsx
Normal file
76
app/src/components/Dashboard/DasboardUseCases.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import DashboardNavBar from "./DashboardNavBar";
|
||||||
|
import { projectTutorialApi } from "../../services/dashboard/projectTutorialApi";
|
||||||
|
import { TutorialCard } from "./TutorialCard";
|
||||||
|
import { getUserData } from "../../functions/getUserData";
|
||||||
|
import { ALPHA_ORG } from "../../pages/Dashboard";
|
||||||
|
|
||||||
|
const DUMMY_DATA = [
|
||||||
|
//remove later
|
||||||
|
{
|
||||||
|
_id: "1",
|
||||||
|
name: "Robotic Arm Control",
|
||||||
|
thumbnail: "https://signfix.com.au/wp-content/uploads/2017/09/placeholder-600x400.png",
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "2",
|
||||||
|
name: "Simulation Basics",
|
||||||
|
thumbnail: "https://signfix.com.au/wp-content/uploads/2017/09/placeholder-600x400.png",
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "3",
|
||||||
|
name: "3D Visualization",
|
||||||
|
thumbnail: "https://signfix.com.au/wp-content/uploads/2017/09/placeholder-600x400.png",
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const DashboardUseCases: React.FC = () => {
|
||||||
|
const [useCases, setUseCases] = useState<Tutorial[]>(DUMMY_DATA || []); // default []
|
||||||
|
const { organization } = getUserData();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchTutorials = async () => {
|
||||||
|
try {
|
||||||
|
const res = await projectTutorialApi();
|
||||||
|
if (res && Array.isArray(res) && res.length > 0) {
|
||||||
|
setUseCases(res);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching useCases:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchTutorials();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="dashboard-home-container">
|
||||||
|
<DashboardNavBar page="tutorial" />
|
||||||
|
<div className="dashboard-container" style={{ height: "calc(100% - 87px)" }}>
|
||||||
|
<div className="tutorials-list">
|
||||||
|
{organization === ALPHA_ORG && (
|
||||||
|
<div className="tutorials-main-header">
|
||||||
|
<div className="tutorial-buttons-container">
|
||||||
|
<button className="add-tutorials-button">
|
||||||
|
<span>+</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{useCases.length > 0 ? (
|
||||||
|
useCases.map((tut) => <TutorialCard key={tut._id} tutorial={tut} />)
|
||||||
|
) : (
|
||||||
|
<div className="empty-state">
|
||||||
|
No Use Cases available click on '+' button to add
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardUseCases;
|
||||||
@@ -8,6 +8,7 @@ import OuterClick from "../../utils/outerClick";
|
|||||||
import { KebabIcon } from "../icons/ExportCommonIcons";
|
import { KebabIcon } from "../icons/ExportCommonIcons";
|
||||||
import { getAllProjectsApi } from "../../services/dashboard/getAllProjectsApi";
|
import { getAllProjectsApi } from "../../services/dashboard/getAllProjectsApi";
|
||||||
import { useSocketStore } from "../../store/socket/useSocketStore";
|
import { useSocketStore } from "../../store/socket/useSocketStore";
|
||||||
|
import { Modal } from "../templates/PreviewModal";
|
||||||
// import { viewProject } from "../../services/dashboard/viewProject";
|
// import { viewProject } from "../../services/dashboard/viewProject";
|
||||||
// import { updateProject } from "../../services/dashboard/updateProject";
|
// import { updateProject } from "../../services/dashboard/updateProject";
|
||||||
|
|
||||||
@@ -25,8 +26,6 @@ interface DashBoardCardProps {
|
|||||||
handleDuplicateRecentProject?: (projectId: string, projectName: string, thumbnail: string) => Promise<void>;
|
handleDuplicateRecentProject?: (projectId: string, projectName: string, thumbnail: string) => Promise<void>;
|
||||||
active?: "shared" | "trash" | "recent" | string;
|
active?: "shared" | "trash" | "recent" | string;
|
||||||
setIsSearchActive?: React.Dispatch<React.SetStateAction<boolean>>;
|
setIsSearchActive?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
setRecentDuplicateData?: React.Dispatch<React.SetStateAction<object>>;
|
|
||||||
setProjectDuplicateData?: React.Dispatch<React.SetStateAction<object>>;
|
|
||||||
setActiveFolder?: React.Dispatch<React.SetStateAction<string>>;
|
setActiveFolder?: React.Dispatch<React.SetStateAction<string>>;
|
||||||
openKebabProjectId: string | null;
|
openKebabProjectId: string | null;
|
||||||
setOpenKebabProjectId: React.Dispatch<React.SetStateAction<string | null>>;
|
setOpenKebabProjectId: React.Dispatch<React.SetStateAction<string | null>>;
|
||||||
@@ -52,8 +51,6 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
|
|||||||
handleDuplicateRecentProject,
|
handleDuplicateRecentProject,
|
||||||
createdAt,
|
createdAt,
|
||||||
createdBy,
|
createdBy,
|
||||||
setRecentDuplicateData,
|
|
||||||
setProjectDuplicateData,
|
|
||||||
setActiveFolder,
|
setActiveFolder,
|
||||||
openKebabProjectId,
|
openKebabProjectId,
|
||||||
setOpenKebabProjectId,
|
setOpenKebabProjectId,
|
||||||
@@ -68,6 +65,8 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
|
|||||||
const [renameValue, setRenameValue] = useState(projectName);
|
const [renameValue, setRenameValue] = useState(projectName);
|
||||||
const [isRenaming, setIsRenaming] = useState(false);
|
const [isRenaming, setIsRenaming] = useState(false);
|
||||||
const kebabRef = useRef<HTMLDivElement>(null);
|
const kebabRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [showDelete, setShowDelete] = useState(false);
|
||||||
|
const [confirmText, setConfirmText] = useState("");
|
||||||
|
|
||||||
// Close kebab when clicking outside
|
// Close kebab when clicking outside
|
||||||
OuterClick({
|
OuterClick({
|
||||||
@@ -92,6 +91,12 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
|
|||||||
return kebabOptionsMap.default;
|
return kebabOptionsMap.default;
|
||||||
}, [active, createdBy, userId]);
|
}, [active, createdBy, userId]);
|
||||||
|
|
||||||
|
const handlePermanentDeleteConformation = () => {
|
||||||
|
handleTrashDeleteProject?.(projectId);
|
||||||
|
setShowDelete(false);
|
||||||
|
setConfirmText("");
|
||||||
|
};
|
||||||
|
|
||||||
const handleProjectName = useCallback(
|
const handleProjectName = useCallback(
|
||||||
async (newName: string) => {
|
async (newName: string) => {
|
||||||
setRenameValue(newName);
|
setRenameValue(newName);
|
||||||
@@ -123,7 +128,7 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
|
|||||||
async (option: string) => {
|
async (option: string) => {
|
||||||
switch (option) {
|
switch (option) {
|
||||||
case "delete":
|
case "delete":
|
||||||
await (active === "trash" ? handleTrashDeleteProject?.(projectId) : handleDeleteProject?.(projectId));
|
await (active === "trash" ? setShowDelete(true) : handleDeleteProject?.(projectId));
|
||||||
break;
|
break;
|
||||||
case "restore":
|
case "restore":
|
||||||
await handleRestoreProject?.(projectId);
|
await handleRestoreProject?.(projectId);
|
||||||
@@ -137,39 +142,17 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
|
|||||||
break;
|
break;
|
||||||
case "duplicate":
|
case "duplicate":
|
||||||
if (handleDuplicateWorkspaceProject) {
|
if (handleDuplicateWorkspaceProject) {
|
||||||
setProjectDuplicateData?.({ projectId, projectName, thumbnail });
|
|
||||||
await handleDuplicateWorkspaceProject(projectId, projectName, thumbnail, userId);
|
await handleDuplicateWorkspaceProject(projectId, projectName, thumbnail, userId);
|
||||||
if (active === "shared") {
|
if (active === "shared") {
|
||||||
setActiveFolder?.("myProjects");
|
setActiveFolder?.("myProjects");
|
||||||
}
|
}
|
||||||
} else if (handleDuplicateRecentProject) {
|
} else if (handleDuplicateRecentProject) {
|
||||||
setRecentDuplicateData?.({
|
|
||||||
projectId,
|
|
||||||
projectName,
|
|
||||||
thumbnail,
|
|
||||||
userId,
|
|
||||||
});
|
|
||||||
await handleDuplicateRecentProject(projectId, projectName, thumbnail);
|
await handleDuplicateRecentProject(projectId, projectName, thumbnail);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[projectId, projectName, thumbnail, userId, active, handleDeleteProject, handleRestoreProject, handleDuplicateWorkspaceProject, handleDuplicateRecentProject, setProjectName, setActiveFolder]
|
||||||
projectId,
|
|
||||||
projectName,
|
|
||||||
thumbnail,
|
|
||||||
userId,
|
|
||||||
active,
|
|
||||||
handleDeleteProject,
|
|
||||||
handleTrashDeleteProject,
|
|
||||||
handleRestoreProject,
|
|
||||||
handleDuplicateWorkspaceProject,
|
|
||||||
handleDuplicateRecentProject,
|
|
||||||
setProjectName,
|
|
||||||
setProjectDuplicateData,
|
|
||||||
setRecentDuplicateData,
|
|
||||||
setActiveFolder,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const getRelativeTime = useCallback((dateString: string): string => {
|
const getRelativeTime = useCallback((dateString: string): string => {
|
||||||
@@ -280,6 +263,32 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
|
|||||||
</div>,
|
</div>,
|
||||||
document.body
|
document.body
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
isOpen={showDelete}
|
||||||
|
onClose={() => setShowDelete(false)}
|
||||||
|
type="confirm"
|
||||||
|
title="Delete Project"
|
||||||
|
description={`This action is permanent. Please type ${projectName} to confirm.`}
|
||||||
|
inputs={[
|
||||||
|
{
|
||||||
|
id: "confirmDelete",
|
||||||
|
label: "Confirmation",
|
||||||
|
placeholder: `Type ${projectName}`,
|
||||||
|
value: confirmText,
|
||||||
|
onChange: setConfirmText,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
buttons={[
|
||||||
|
{ label: "Cancel", onClick: () => setShowDelete(false), variant: "secondary" },
|
||||||
|
{
|
||||||
|
label: "Delete",
|
||||||
|
onClick: handlePermanentDeleteConformation,
|
||||||
|
variant: "danger",
|
||||||
|
disabled: confirmText !== projectName,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react";
|
|||||||
import { getUserData } from "../../functions/getUserData";
|
import { getUserData } from "../../functions/getUserData";
|
||||||
import { useSocketStore } from "../../store/socket/useSocketStore";
|
import { useSocketStore } from "../../store/socket/useSocketStore";
|
||||||
|
|
||||||
import { Modal } from "../templates/PreviewModal";
|
|
||||||
import ProjectSocketRes from "./socket/projectSocketRes";
|
import ProjectSocketRes from "./socket/projectSocketRes";
|
||||||
import DashboardNavBar from "./DashboardNavBar";
|
import DashboardNavBar from "./DashboardNavBar";
|
||||||
import DashboardCard from "./DashboardCard";
|
import DashboardCard from "./DashboardCard";
|
||||||
@@ -24,59 +23,54 @@ interface DashboardMainProps {
|
|||||||
const DashboardMain: React.FC<DashboardMainProps> = ({ activeFolder }) => {
|
const DashboardMain: React.FC<DashboardMainProps> = ({ activeFolder }) => {
|
||||||
const [activeSubFolder, setActiveSubFolder] = useState("myProjects");
|
const [activeSubFolder, setActiveSubFolder] = useState("myProjects");
|
||||||
const [projectsData, setProjectsData] = useState<DashboardProjectCollection>({});
|
const [projectsData, setProjectsData] = useState<DashboardProjectCollection>({});
|
||||||
|
const [projectsCache, setProjectsCache] = useState<Record<string, DashboardProjectCollection>>({});
|
||||||
const [isSearchActive, setIsSearchActive] = useState<boolean>(false);
|
const [isSearchActive, setIsSearchActive] = useState<boolean>(false);
|
||||||
const [duplicateData, setDuplicateData] = useState<Object>({});
|
|
||||||
const [openKebabProjectId, setOpenKebabProjectId] = useState<string | null>(null);
|
const [openKebabProjectId, setOpenKebabProjectId] = useState<string | null>(null);
|
||||||
const [projectsCache, setProjectsCache] = useState<{
|
|
||||||
[key: string]: DashboardProjectCollection;
|
|
||||||
}>({});
|
|
||||||
const [showDelete, setShowDelete] = useState(false);
|
|
||||||
const [confirmText, setConfirmText] = useState(""); // 🔹 For confirmation input
|
|
||||||
const [deleteTargetId, setDeleteTargetId] = useState<string | null>(null); // 🔹 Store project id to delete
|
|
||||||
|
|
||||||
const { userId, organization } = getUserData();
|
const { userId, organization } = getUserData();
|
||||||
const { projectSocket } = useSocketStore();
|
const { projectSocket } = useSocketStore();
|
||||||
|
|
||||||
// #region API Fetchers
|
// 🔹 Fetch all folders on mount and store locally
|
||||||
const fetchData = async () => {
|
const fetchAllData = async () => {
|
||||||
const cacheKey = activeFolder + (activeFolder === "projects" ? `-${activeSubFolder}` : "");
|
|
||||||
|
|
||||||
if (projectsCache[cacheKey] && !isSearchActive) {
|
|
||||||
setProjectsData(projectsCache[cacheKey]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let projects: DashboardProjectCollection = {};
|
const [homeProjects, myProjects, sharedProjects, trashProjects] = await Promise.all([recentlyViewedApi(), getAllProjectsApi(), sharedWithMeProjectsApi(), getTrashApi()]);
|
||||||
|
|
||||||
switch (activeFolder) {
|
const cache: Record<string, DashboardProjectCollection> = {
|
||||||
case "home":
|
home: homeProjects,
|
||||||
projects = await recentlyViewedApi();
|
"projects-myProjects": myProjects,
|
||||||
break;
|
"projects-shared": sharedProjects,
|
||||||
case "projects":
|
trash: trashProjects,
|
||||||
if (activeSubFolder === "myProjects") {
|
};
|
||||||
projects = await getAllProjectsApi();
|
|
||||||
} else {
|
|
||||||
projects = await sharedWithMeProjectsApi();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "trash":
|
|
||||||
projects = await getTrashApi();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (projects && JSON.stringify(projects) !== JSON.stringify(projectsData)) {
|
setProjectsCache(cache);
|
||||||
setProjectsCache((prev) => ({ ...prev, [cacheKey]: projects }));
|
|
||||||
setProjectsData(projects);
|
// set current active data
|
||||||
|
const cacheKey = activeFolder === "projects" ? `projects-${activeSubFolder}` : activeFolder;
|
||||||
|
if (cache[cacheKey]) {
|
||||||
|
setProjectsData(cache[cacheKey]);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error("Failed to fetch dashboard data:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// #region Search Handlers
|
// 🔹 On mount fetch once
|
||||||
|
useEffect(() => {
|
||||||
|
fetchAllData();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 🔹 Switch folder/subFolder from cache
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isSearchActive) {
|
||||||
|
const cacheKey = activeFolder === "projects" ? `projects-${activeSubFolder}` : activeFolder;
|
||||||
|
if (projectsCache[cacheKey]) {
|
||||||
|
setProjectsData(projectsCache[cacheKey]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [activeFolder, activeSubFolder, isSearchActive, projectsCache]);
|
||||||
|
|
||||||
|
// 🔹 Search handler
|
||||||
const handleSearch = async (inputValue: string) => {
|
const handleSearch = async (inputValue: string) => {
|
||||||
if (!inputValue.trim()) {
|
if (!inputValue.trim()) {
|
||||||
setIsSearchActive(false);
|
setIsSearchActive(false);
|
||||||
@@ -94,21 +88,43 @@ const DashboardMain: React.FC<DashboardMainProps> = ({ activeFolder }) => {
|
|||||||
setProjectsData(results?.message ? {} : results);
|
setProjectsData(results?.message ? {} : results);
|
||||||
};
|
};
|
||||||
|
|
||||||
// #region Socket Actions
|
// 🔹 Socket Actions
|
||||||
const handleDelete = async (projectId: string): Promise<void> => {
|
const updateStateAfterRemove = (projectId: string) => {
|
||||||
if (projectSocket) {
|
setProjectsData((prev: DashboardProjectCollection) => {
|
||||||
projectSocket.emit("v1:project:delete", {
|
const key = Object.keys(prev)[0];
|
||||||
projectId,
|
const updatedList = prev[key]?.filter((p) => p._id !== projectId) || [];
|
||||||
organization,
|
return { ...prev, [key]: updatedList };
|
||||||
userId,
|
});
|
||||||
|
|
||||||
|
setProjectsCache((prev) => {
|
||||||
|
const newCache = { ...prev };
|
||||||
|
Object.keys(newCache).forEach((k) => {
|
||||||
|
const key = Object.keys(newCache[k])[0];
|
||||||
|
newCache[k] = {
|
||||||
|
...newCache[k],
|
||||||
|
[key]: newCache[k][key]?.filter((p) => p._id !== projectId) || [],
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
return newCache;
|
||||||
updateStateAfterRemove(projectId);
|
});
|
||||||
|
|
||||||
|
setIsSearchActive(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePermanentDelete = async (): Promise<void> => {
|
const handleDelete = async (projectId: string): Promise<void> => {
|
||||||
if (!deleteTargetId) return;
|
if (projectSocket) {
|
||||||
|
projectSocket.emit("v1:project:delete", { projectId, organization, userId });
|
||||||
|
}
|
||||||
|
updateStateAfterRemove(projectId);
|
||||||
|
|
||||||
|
// 🔹 Refresh trash folder cache (since project moves there)
|
||||||
|
const trashProjects = await getTrashApi();
|
||||||
|
setProjectsCache((prev) => ({ ...prev, trash: trashProjects }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePermanentDelete = async (deleteTargetId: string): Promise<void> => {
|
||||||
|
console.log("deleteTargetId: ", deleteTargetId);
|
||||||
|
if (!deleteTargetId) return;
|
||||||
if (projectSocket) {
|
if (projectSocket) {
|
||||||
projectSocket.emit("v1:trash:delete", {
|
projectSocket.emit("v1:trash:delete", {
|
||||||
projectId: deleteTargetId,
|
projectId: deleteTargetId,
|
||||||
@@ -117,14 +133,24 @@ const DashboardMain: React.FC<DashboardMainProps> = ({ activeFolder }) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
updateStateAfterRemove(deleteTargetId);
|
updateStateAfterRemove(deleteTargetId);
|
||||||
setShowDelete(false);
|
|
||||||
setConfirmText("");
|
// 🔹 Refresh trash folder
|
||||||
setDeleteTargetId(null);
|
const trashProjects = await getTrashApi();
|
||||||
|
console.log("trashProjects: ", trashProjects);
|
||||||
|
setProjectsCache((prev) => ({ ...prev, trash: trashProjects }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRestore = async (projectId: string): Promise<void> => {
|
const handleRestore = async (projectId: string): Promise<void> => {
|
||||||
await restoreTrashApi(projectId);
|
await restoreTrashApi(projectId);
|
||||||
updateStateAfterRemove(projectId);
|
updateStateAfterRemove(projectId);
|
||||||
|
|
||||||
|
// 🔹 Refresh home & projects-myProjects
|
||||||
|
const [homeProjects, myProjects] = await Promise.all([recentlyViewedApi(), getAllProjectsApi()]);
|
||||||
|
setProjectsCache((prev) => ({
|
||||||
|
...prev,
|
||||||
|
home: homeProjects,
|
||||||
|
"projects-myProjects": myProjects,
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDuplicate = async (projectId: string, projectName: string, thumbnail: string): Promise<void> => {
|
const handleDuplicate = async (projectId: string, projectName: string, thumbnail: string): Promise<void> => {
|
||||||
@@ -138,18 +164,11 @@ const DashboardMain: React.FC<DashboardMainProps> = ({ activeFolder }) => {
|
|||||||
projectName,
|
projectName,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 🔹 Refresh home & projects cache
|
||||||
|
await fetchAllData();
|
||||||
};
|
};
|
||||||
|
|
||||||
// #region Project Map
|
// 🔹 Render Projects
|
||||||
const updateStateAfterRemove = (projectId: string) => {
|
|
||||||
setProjectsData((prev: DashboardProjectCollection) => {
|
|
||||||
const key = Object.keys(prev)[0];
|
|
||||||
const updatedList = prev[key]?.filter((p) => p._id !== projectId) || [];
|
|
||||||
return { ...prev, [key]: updatedList };
|
|
||||||
});
|
|
||||||
setIsSearchActive(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderProjects = () => {
|
const renderProjects = () => {
|
||||||
const key = Object.keys(projectsData)[0];
|
const key = Object.keys(projectsData)[0];
|
||||||
const projectList = projectsData[key];
|
const projectList = projectsData[key];
|
||||||
@@ -169,23 +188,19 @@ const DashboardMain: React.FC<DashboardMainProps> = ({ activeFolder }) => {
|
|||||||
{...(activeFolder === "home" && {
|
{...(activeFolder === "home" && {
|
||||||
handleDeleteProject: handleDelete,
|
handleDeleteProject: handleDelete,
|
||||||
handleDuplicateRecentProject: handleDuplicate,
|
handleDuplicateRecentProject: handleDuplicate,
|
||||||
setRecentDuplicateData: setDuplicateData,
|
|
||||||
})}
|
})}
|
||||||
{...(activeSubFolder === "myProjects" && {
|
{...(activeSubFolder === "myProjects" && {
|
||||||
handleDeleteProject: handleDelete,
|
handleDeleteProject: handleDelete,
|
||||||
handleDuplicateWorkspaceProject: handleDuplicate,
|
handleDuplicateWorkspaceProject: handleDuplicate,
|
||||||
setProjectDuplicateData: setDuplicateData,
|
|
||||||
})}
|
})}
|
||||||
{...(activeSubFolder === "shared" && {
|
{...(activeSubFolder === "shared" && {
|
||||||
handleDuplicateWorkspaceProject: handleDuplicate,
|
handleDuplicateWorkspaceProject: handleDuplicate,
|
||||||
setProjectDuplicateData: setDuplicateData,
|
|
||||||
active: "shared",
|
active: "shared",
|
||||||
})}
|
})}
|
||||||
{...(activeFolder === "trash" && {
|
{...(activeFolder === "trash" && {
|
||||||
handleRestoreProject: handleRestore,
|
handleRestoreProject: handleRestore,
|
||||||
handleTrashDeleteProject: async (id: string): Promise<void> => {
|
handleTrashDeleteProject: async (id: string): Promise<void> => {
|
||||||
setDeleteTargetId(id);
|
handlePermanentDelete(id);
|
||||||
setShowDelete(true);
|
|
||||||
},
|
},
|
||||||
active: "trash",
|
active: "trash",
|
||||||
})}
|
})}
|
||||||
@@ -195,12 +210,6 @@ const DashboardMain: React.FC<DashboardMainProps> = ({ activeFolder }) => {
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
// #region Use Effects
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isSearchActive) fetchData();
|
|
||||||
// eslint-disable-next-line
|
|
||||||
}, [activeFolder, isSearchActive, activeSubFolder]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dashboard-home-container">
|
<div className="dashboard-home-container">
|
||||||
<DashboardNavBar
|
<DashboardNavBar
|
||||||
@@ -231,33 +240,6 @@ const DashboardMain: React.FC<DashboardMainProps> = ({ activeFolder }) => {
|
|||||||
|
|
||||||
<ProjectSocketRes setIsSearchActive={setIsSearchActive} {...(activeFolder === "home" ? { setRecentProjects: setProjectsData } : { setWorkspaceProjects: setProjectsData })} />
|
<ProjectSocketRes setIsSearchActive={setIsSearchActive} {...(activeFolder === "home" ? { setRecentProjects: setProjectsData } : { setWorkspaceProjects: setProjectsData })} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 🔹 Delete Confirmation Modal */}
|
|
||||||
<Modal
|
|
||||||
isOpen={showDelete}
|
|
||||||
onClose={() => setShowDelete(false)}
|
|
||||||
type="confirm"
|
|
||||||
title="Delete Project"
|
|
||||||
description="This action is permanent. Please type DELETE to confirm."
|
|
||||||
inputs={[
|
|
||||||
{
|
|
||||||
id: "confirmDelete",
|
|
||||||
label: "Confirmation",
|
|
||||||
placeholder: "Type DELETE",
|
|
||||||
value: confirmText,
|
|
||||||
onChange: setConfirmText,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
buttons={[
|
|
||||||
{ label: "Cancel", onClick: () => setShowDelete(false), variant: "secondary" },
|
|
||||||
{
|
|
||||||
label: "Delete",
|
|
||||||
onClick: handlePermanentDelete,
|
|
||||||
variant: "danger",
|
|
||||||
disabled: confirmText !== "DELETE",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,38 +2,7 @@ import React, { useEffect, useState } from "react";
|
|||||||
import DashboardNavBar from "./DashboardNavBar";
|
import DashboardNavBar from "./DashboardNavBar";
|
||||||
import { projectTutorialApi } from "../../services/dashboard/projectTutorialApi";
|
import { projectTutorialApi } from "../../services/dashboard/projectTutorialApi";
|
||||||
import { AIIcon } from "../icons/ExportCommonIcons";
|
import { AIIcon } from "../icons/ExportCommonIcons";
|
||||||
import { DeleteIcon } from "../icons/ContextMenuIcons";
|
import { TutorialCard } from "./TutorialCard";
|
||||||
|
|
||||||
interface Tutorial {
|
|
||||||
_id: string;
|
|
||||||
name: string;
|
|
||||||
thumbnail?: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TutorialCard: React.FC<{ tutorial: Tutorial }> = ({ tutorial }) => {
|
|
||||||
return (
|
|
||||||
<div className="tutorial-card-container">
|
|
||||||
<div
|
|
||||||
className="thumbnail"
|
|
||||||
style={{
|
|
||||||
backgroundImage: tutorial.thumbnail
|
|
||||||
? `url(${tutorial.thumbnail})`
|
|
||||||
: "linear-gradient(135deg, #ddd, #bbb)",
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
<div className="tutorial-details">
|
|
||||||
<div className="tutorial-name">{tutorial.name}</div>
|
|
||||||
<div className="updated-date">
|
|
||||||
{new Date(tutorial.updatedAt).toLocaleDateString()}
|
|
||||||
</div>
|
|
||||||
<div className="delete-option">
|
|
||||||
<DeleteIcon />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const DashboardTutorial: React.FC = () => {
|
const DashboardTutorial: React.FC = () => {
|
||||||
const [tutorials, setTutorials] = useState<Tutorial[]>([]);
|
const [tutorials, setTutorials] = useState<Tutorial[]>([]);
|
||||||
@@ -65,7 +34,6 @@ const DashboardTutorial: React.FC = () => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="tutorials-list">
|
<div className="tutorials-list">
|
||||||
{tutorials.length > 0 ? (
|
{tutorials.length > 0 ? (
|
||||||
tutorials.map((tut) => <TutorialCard key={tut._id} tutorial={tut} />)
|
tutorials.map((tut) => <TutorialCard key={tut._id} tutorial={tut} />)
|
||||||
|
|||||||
@@ -86,6 +86,18 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
|
|||||||
<TrashIcon isActive={activeTab === "Trash"} />
|
<TrashIcon isActive={activeTab === "Trash"} />
|
||||||
Trash
|
Trash
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className={activeTab === "Use Cases" ? "option-list active" : "option-list"}
|
||||||
|
title="use case"
|
||||||
|
// disabled
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTab("Use Cases");
|
||||||
|
console.warn("Use Cases comming soon");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TutorialsIcon isActive={activeTab === "Use Cases"} />
|
||||||
|
Use Cases
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
className={activeTab === "Tutorials" ? "option-list active" : "option-list"}
|
className={activeTab === "Tutorials" ? "option-list active" : "option-list"}
|
||||||
title="coming soon"
|
title="coming soon"
|
||||||
|
|||||||
32
app/src/components/Dashboard/TutorialCard.tsx
Normal file
32
app/src/components/Dashboard/TutorialCard.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { getUserData } from "../../functions/getUserData";
|
||||||
|
import { ALPHA_ORG } from "../../pages/Dashboard";
|
||||||
|
import { DeleteIcon } from "../icons/ContextMenuIcons";
|
||||||
|
|
||||||
|
export const TutorialCard: React.FC<{ tutorial: Tutorial }> = ({ tutorial }) => {
|
||||||
|
const { organization } = getUserData();
|
||||||
|
return (
|
||||||
|
<div className="tutorial-card-container">
|
||||||
|
<div
|
||||||
|
className="preview-container"
|
||||||
|
style={{
|
||||||
|
backgroundImage: tutorial.thumbnail
|
||||||
|
? `url(${tutorial.thumbnail})`
|
||||||
|
: "linear-gradient(135deg, #ddd, #bbb)",
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<div className="tutorial-details">
|
||||||
|
<div className="context">
|
||||||
|
<div className="tutorial-name">{tutorial.name}</div>
|
||||||
|
<div className="updated-date">
|
||||||
|
{new Date(tutorial.updatedAt).toLocaleDateString()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{organization === ALPHA_ORG && (
|
||||||
|
<div className="delete-option">
|
||||||
|
<DeleteIcon />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { DepthOfField, Bloom, EffectComposer, N8AO } from "@react-three/postprocessing";
|
import { EffectComposer, N8AO } from "@react-three/postprocessing";
|
||||||
import OutlineInstances from "./outlineInstances/outlineInstances";
|
import OutlineInstances from "./outlineInstances/outlineInstances";
|
||||||
import { useDeletableEventSphere, useSelectedEventSphere } from "../../../store/simulation/useSimulationStore";
|
import { useDeletableEventSphere, useSelectedEventSphere } from "../../../store/simulation/useSimulationStore";
|
||||||
|
|
||||||
|
|||||||
@@ -284,25 +284,25 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const HandleCallback = () => {
|
const HandleCallback = () => {
|
||||||
if (armBot.isActive && armBot.state == "running" && currentPhase == "init-to-rest") {
|
if (armBot.isActive && armBot.state === "running" && currentPhase === "init-to-rest") {
|
||||||
logStatus(armBot.modelUuid, "Callback triggered: rest");
|
logStatus(armBot.modelUuid, "Callback triggered: rest");
|
||||||
setArmBotActive(armBot.modelUuid, false);
|
setArmBotActive(armBot.modelUuid, false);
|
||||||
setArmBotState(armBot.modelUuid, "idle");
|
setArmBotState(armBot.modelUuid, "idle");
|
||||||
setCurrentPhase("rest");
|
setCurrentPhase("rest");
|
||||||
setPath([]);
|
setPath([]);
|
||||||
} else if (armBot.state == "running" && currentPhase == "rest-to-start") {
|
} else if (armBot.state === "running" && currentPhase === "rest-to-start") {
|
||||||
logStatus(armBot.modelUuid, "Callback triggered: pick.");
|
logStatus(armBot.modelUuid, "Callback triggered: pick.");
|
||||||
setArmBotActive(armBot.modelUuid, false);
|
setArmBotActive(armBot.modelUuid, false);
|
||||||
setArmBotState(armBot.modelUuid, "running");
|
setArmBotState(armBot.modelUuid, "running");
|
||||||
setCurrentPhase("picking");
|
setCurrentPhase("picking");
|
||||||
setPath([]);
|
setPath([]);
|
||||||
} else if (armBot.isActive && armBot.state == "running" && currentPhase == "start-to-end") {
|
} else if (armBot.isActive && armBot.state === "running" && currentPhase === "start-to-end") {
|
||||||
logStatus(armBot.modelUuid, "Callback triggered: drop.");
|
logStatus(armBot.modelUuid, "Callback triggered: drop.");
|
||||||
setArmBotActive(armBot.modelUuid, false);
|
setArmBotActive(armBot.modelUuid, false);
|
||||||
setArmBotState(armBot.modelUuid, "running");
|
setArmBotState(armBot.modelUuid, "running");
|
||||||
setCurrentPhase("dropping");
|
setCurrentPhase("dropping");
|
||||||
setPath([]);
|
setPath([]);
|
||||||
} else if (armBot.isActive && armBot.state == "running" && currentPhase == "end-to-rest") {
|
} else if (armBot.isActive && armBot.state === "running" && currentPhase === "end-to-rest") {
|
||||||
logStatus(armBot.modelUuid, "Callback triggered: rest, cycle completed.");
|
logStatus(armBot.modelUuid, "Callback triggered: rest, cycle completed.");
|
||||||
setArmBotActive(armBot.modelUuid, false);
|
setArmBotActive(armBot.modelUuid, false);
|
||||||
setArmBotState(armBot.modelUuid, "idle");
|
setArmBotState(armBot.modelUuid, "idle");
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import { getUserData } from "../functions/getUserData";
|
|||||||
import SidePannel from "../components/Dashboard/SidePannel";
|
import SidePannel from "../components/Dashboard/SidePannel";
|
||||||
import DashboardTutorial from "../components/Dashboard/DashboardTutorial";
|
import DashboardTutorial from "../components/Dashboard/DashboardTutorial";
|
||||||
import DashboardMain from "../components/Dashboard/DashboardMain";
|
import DashboardMain from "../components/Dashboard/DashboardMain";
|
||||||
|
import DashboardUseCases from "../components/Dashboard/DasboardUseCases";
|
||||||
|
|
||||||
|
export const ALPHA_ORG = "hexrfactory";
|
||||||
|
|
||||||
const Dashboard: React.FC = () => {
|
const Dashboard: React.FC = () => {
|
||||||
const [activeTab, setActiveTab] = useState<string>("Home");
|
const [activeTab, setActiveTab] = useState<string>("Home");
|
||||||
@@ -22,6 +25,7 @@ const Dashboard: React.FC = () => {
|
|||||||
<SidePannel setActiveTab={setActiveTab} activeTab={activeTab} />
|
<SidePannel setActiveTab={setActiveTab} activeTab={activeTab} />
|
||||||
{["Home", "Projects", "Shared", "Trash"].includes(activeTab) && <DashboardMain activeFolder={activeTab.toLowerCase() as Folder} />}
|
{["Home", "Projects", "Shared", "Trash"].includes(activeTab) && <DashboardMain activeFolder={activeTab.toLowerCase() as Folder} />}
|
||||||
{activeTab === "Tutorials" && <DashboardTutorial />}
|
{activeTab === "Tutorials" && <DashboardTutorial />}
|
||||||
|
{activeTab === "Use Cases" && <DashboardUseCases />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,401 +2,497 @@
|
|||||||
@use "../abstracts/mixins.scss" as *;
|
@use "../abstracts/mixins.scss" as *;
|
||||||
|
|
||||||
.dashboard-main {
|
.dashboard-main {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
display: flex;
|
|
||||||
padding: 27px 17px;
|
|
||||||
|
|
||||||
.side-pannel-container {
|
|
||||||
padding: 32px;
|
|
||||||
min-width: 280px;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
padding: 27px 17px;
|
||||||
gap: 16px;
|
|
||||||
background: var(--background-color);
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
border-radius: 30px;
|
|
||||||
box-shadow: var(--box-shadow-medium);
|
|
||||||
|
|
||||||
.side-pannel-header {
|
.side-pannel-container {
|
||||||
@include flex-space-between;
|
padding: 32px;
|
||||||
|
min-width: 280px;
|
||||||
.user-container {
|
height: 100%;
|
||||||
@include flex-center;
|
|
||||||
gap: 6px;
|
|
||||||
|
|
||||||
.user-profile {
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
line-height: 32px;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: var(--font-weight-medium);
|
|
||||||
background: var(--background-color-accent);
|
|
||||||
color: var(--text-button-color);
|
|
||||||
border-radius: #{$border-radius-circle};
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-name {
|
|
||||||
color: var(--accent-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications-container {
|
|
||||||
@include flex-center;
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-project-button {
|
|
||||||
position: relative;
|
|
||||||
padding: 12px 16px;
|
|
||||||
color: var(--text-color);
|
|
||||||
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 {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.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 {
|
|
||||||
&::after {
|
|
||||||
height: 260px;
|
|
||||||
width: 260px;
|
|
||||||
left: 50%;
|
|
||||||
scale: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.active {
|
|
||||||
color: var(--text-button-color);
|
|
||||||
font-weight: var(--font-weight-medium);
|
|
||||||
background: var(--background-color-button);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--background-color-button);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-home-container {
|
|
||||||
width: 100%;
|
|
||||||
padding-left: 18px;
|
|
||||||
|
|
||||||
.dashboard-navbar-container {
|
|
||||||
margin-top: 28px;
|
|
||||||
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(--background-color-button);
|
|
||||||
white-space: nowrap;
|
|
||||||
border-radius: #{$border-radius-extra-large};
|
|
||||||
|
|
||||||
color: var(--text-button-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-wrapper {
|
|
||||||
width: 400px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-container {
|
|
||||||
margin: 22px 0;
|
|
||||||
width: 100%;
|
|
||||||
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-button-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cards-container {
|
|
||||||
height: auto;
|
|
||||||
max-height: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-direction: column;
|
||||||
position: relative;
|
gap: 16px;
|
||||||
width: 100%;
|
background: var(--background-color);
|
||||||
padding-top: 18px;
|
backdrop-filter: blur(20px);
|
||||||
gap: 18px;
|
border-radius: 30px;
|
||||||
overflow: auto;
|
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;
|
||||||
|
line-height: 32px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
background: var(--background-color-accent);
|
||||||
|
color: var(--text-button-color);
|
||||||
|
border-radius: #{$border-radius-circle};
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-container {
|
||||||
|
@include flex-center;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-project-button {
|
||||||
|
position: relative;
|
||||||
|
padding: 12px 16px;
|
||||||
|
color: var(--text-color);
|
||||||
|
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 {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.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 {
|
||||||
|
&::after {
|
||||||
|
height: 260px;
|
||||||
|
width: 260px;
|
||||||
|
left: 50%;
|
||||||
|
scale: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
color: var(--text-button-color);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
background: var(--background-color-button);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--background-color-button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-home-container {
|
||||||
|
width: 100%;
|
||||||
|
padding-left: 18px;
|
||||||
|
|
||||||
|
.dashboard-navbar-container {
|
||||||
|
margin-top: 28px;
|
||||||
|
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(--background-color-button);
|
||||||
|
white-space: nowrap;
|
||||||
|
border-radius: #{$border-radius-extra-large};
|
||||||
|
|
||||||
|
color: var(--text-button-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-wrapper {
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-container {
|
||||||
|
margin: 22px 0;
|
||||||
|
width: 100%;
|
||||||
|
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-button-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cards-container,
|
||||||
|
.tutorials-list {
|
||||||
|
height: auto;
|
||||||
|
max-height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 18px;
|
||||||
|
gap: 18px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-card-container {
|
.dashboard-card-container,
|
||||||
height: 242px;
|
.tutorial-card-container {
|
||||||
width: calc((100% / 5) - 23px);
|
height: 242px;
|
||||||
min-width: 260px;
|
width: calc((100% / 5) - 23px);
|
||||||
position: relative;
|
min-width: 260px;
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 22px;
|
|
||||||
cursor: pointer;
|
|
||||||
overflow: hidden;
|
|
||||||
&:hover {
|
|
||||||
border-color: var(--accent-color);
|
|
||||||
.preview-container {
|
|
||||||
img {
|
|
||||||
scale: 1.05;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-card-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 22px;
|
||||||
|
cursor: pointer;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-bottom: 1px;
|
&:hover {
|
||||||
}
|
border-color: var(--accent-color);
|
||||||
|
.preview-container {
|
||||||
.preview-container {
|
img {
|
||||||
height: 100%;
|
scale: 1.05;
|
||||||
width: 100%;
|
}
|
||||||
border-radius: #{$border-radius-extra-large};
|
}
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
img {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
vertical-align: top;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
transition: scale 0.2s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-details-container {
|
|
||||||
@include flex-space-between;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 1px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 13px 16px;
|
|
||||||
background: var(--background-color);
|
|
||||||
border-radius: #{$border-radius-extra-large};
|
|
||||||
backdrop-filter: blur(6px);
|
|
||||||
// transform: translateY(100%);///////hovered
|
|
||||||
transition: transform 0.2s linear;
|
|
||||||
|
|
||||||
.project-details {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
.project-name {
|
|
||||||
margin-bottom: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-data {
|
|
||||||
text-align: start;
|
|
||||||
color: var(--input-text-color);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.users-list-container {
|
.dashboard-card-wrapper {
|
||||||
@include flex-center;
|
width: 100%;
|
||||||
gap: 6px;
|
height: 100%;
|
||||||
position: relative; // Needed for absolute positioning of kebab-options-wrapper
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
.user-profile {
|
padding-bottom: 1px;
|
||||||
height: 26px;
|
|
||||||
width: 26px;
|
|
||||||
line-height: 26px;
|
|
||||||
text-align: center;
|
|
||||||
background: var(--background-color-accent);
|
|
||||||
color: var(--text-button-color);
|
|
||||||
border-radius: #{$border-radius-circle};
|
|
||||||
}
|
|
||||||
|
|
||||||
.kebab {
|
|
||||||
padding: 10px;
|
|
||||||
@include flex-center;
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
.preview-container {
|
||||||
overflow: visible;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: #{$border-radius-extra-large};
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.kebab-options-wrapper {
|
img {
|
||||||
display: flex;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
vertical-align: top;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
transition: scale 0.2s;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-details-container {
|
.project-details-container {
|
||||||
transform: translateY(0);
|
@include flex-space-between;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 1px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 13px 16px;
|
||||||
|
background: var(--background-color);
|
||||||
|
border-radius: #{$border-radius-extra-large};
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
// transform: translateY(100%);///////hovered
|
||||||
|
transition: transform 0.2s linear;
|
||||||
|
|
||||||
|
.project-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
.project-name {
|
||||||
|
margin-bottom: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-data {
|
||||||
|
text-align: start;
|
||||||
|
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;
|
||||||
|
line-height: 26px;
|
||||||
|
text-align: center;
|
||||||
|
background: var(--background-color-accent);
|
||||||
|
color: var(--text-button-color);
|
||||||
|
border-radius: #{$border-radius-circle};
|
||||||
|
}
|
||||||
|
|
||||||
|
.kebab {
|
||||||
|
padding: 10px;
|
||||||
|
@include flex-center;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
|
.kebab-options-wrapper {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-details-container {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.market-place-banner-container {
|
.market-place-banner-container {
|
||||||
width: 100%;
|
|
||||||
height: 230px;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
img {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
object-fit: cover;
|
height: 230px;
|
||||||
border-radius: #{$border-radius-xxx};
|
overflow: hidden;
|
||||||
}
|
position: relative;
|
||||||
|
|
||||||
.hero-text {
|
img {
|
||||||
position: absolute;
|
height: 100%;
|
||||||
left: 52px;
|
width: 100%;
|
||||||
bottom: 25px;
|
object-fit: cover;
|
||||||
font-size: 48px;
|
border-radius: #{$border-radius-xxx};
|
||||||
font-family: #{$font-roboto};
|
}
|
||||||
font-weight: 800;
|
|
||||||
color: #ffffff;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context {
|
.hero-text {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 20px;
|
left: 52px;
|
||||||
right: 58px;
|
bottom: 25px;
|
||||||
text-transform: uppercase;
|
font-size: 48px;
|
||||||
font-size: 22px;
|
font-family: #{$font-roboto};
|
||||||
width: 300px;
|
font-weight: 800;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
font-family: #{$font-roboto};
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow-context {
|
.context {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 27px;
|
top: 20px;
|
||||||
right: 300px;
|
right: 58px;
|
||||||
}
|
text-transform: uppercase;
|
||||||
|
font-size: 22px;
|
||||||
|
width: 300px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: #{$font-roboto};
|
||||||
|
}
|
||||||
|
|
||||||
.explore-button {
|
.arrow-context {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 95px;
|
bottom: 27px;
|
||||||
right: 52px;
|
right: 300px;
|
||||||
padding: 10px 20px;
|
}
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 24px;
|
.explore-button {
|
||||||
border: 1px solid #ffffff;
|
position: absolute;
|
||||||
color: #ffffff;
|
top: 95px;
|
||||||
font-family: #{$font-roboto};
|
right: 52px;
|
||||||
cursor: pointer;
|
padding: 10px 20px;
|
||||||
}
|
text-transform: uppercase;
|
||||||
|
font-size: 24px;
|
||||||
|
border: 1px solid #ffffff;
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: #{$font-roboto};
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.kebab-options-wrapper {
|
.kebab-options-wrapper {
|
||||||
min-width: 140px;
|
min-width: 140px;
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
// transform: translate(100%, 100%);
|
// transform: translate(100%, 100%);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
transform: translate(0%, 0%);
|
transform: translate(0%, 0%);
|
||||||
.option {
|
.option {
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s ease;
|
transition: background 0.2s ease;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--background-color-selected);
|
background-color: var(--background-color-selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tutorials
|
||||||
|
.tutorials-list {
|
||||||
|
.tutorials-main-header {
|
||||||
|
position: relative;
|
||||||
|
height: 242px;
|
||||||
|
width: calc((100% / 5) - 23px);
|
||||||
|
min-width: 260px;
|
||||||
|
border-radius: 22px;
|
||||||
|
.tutorial-buttons-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 14px;
|
||||||
|
height: 100%;
|
||||||
|
.add-tutorials-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 24px;
|
||||||
|
background: var(--background-color);
|
||||||
|
transition: background 0.2s;
|
||||||
|
span {
|
||||||
|
font-size: 0.84rem;
|
||||||
|
font-size: 8rem;
|
||||||
|
color: var(--text-disabled);
|
||||||
|
transform: translateY(-8px);
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background: var(--background-color-selected);
|
||||||
|
span {
|
||||||
|
color: var(--text-button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tutorial-card-container {
|
||||||
|
overflow: hidden;
|
||||||
|
.preview-container {
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.tutorial-details {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tutorial-details {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--background-color);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
padding: 8px 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-radius: 16px;
|
||||||
|
transform: translateY(100%);
|
||||||
|
transition: all 0.2s;
|
||||||
|
.context {
|
||||||
|
.tutorial-name {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
.updated-date {
|
||||||
|
color: var(--input-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.delete-option {
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
background: var(--background-color-solid);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
&:hover {
|
||||||
|
background: var(--log-error-background-color);
|
||||||
|
path {
|
||||||
|
stroke: var(--log-error-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
7
app/src/types/uiTypes.d.ts
vendored
7
app/src/types/uiTypes.d.ts
vendored
@@ -70,3 +70,10 @@ interface ListProps {
|
|||||||
items?: ZoneItem[];
|
items?: ZoneItem[];
|
||||||
remove?: boolean;
|
remove?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Tutorial {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
thumbnail?: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user