feat: Refactor Dashboard components and add Use Cases section
- Updated DashboardMain to improve data fetching and caching logic. - Removed unused Modal component and related state management. - Enhanced project deletion and restoration processes with cache updates. - Introduced DashboardUseCases component to display use cases with tutorial cards. - Updated Dashboard to include Use Cases tab and corresponding routing. - Improved styling for dashboard components, including side panel and tutorials list. - Added new Tutorial interface in uiTypes for better type safety.
This commit is contained in:
75
app/src/components/Dashboard/DasboardUseCases.tsx
Normal file
75
app/src/components/Dashboard/DasboardUseCases.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import DashboardNavBar from "./DashboardNavBar";
|
||||
import { projectTutorialApi } from "../../services/dashboard/projectTutorialApi";
|
||||
import { TutorialCard } from "./DashboardTutorial";
|
||||
|
||||
interface Tutorial {
|
||||
_id: string;
|
||||
name: string;
|
||||
thumbnail?: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
const DashboardUseCases: React.FC = () => {
|
||||
const [useCases, setUseCases] = useState<Tutorial[]>([
|
||||
{
|
||||
_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(),
|
||||
},
|
||||
]);
|
||||
|
||||
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">
|
||||
<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;
|
||||
@@ -7,6 +7,7 @@ import { useLoadingProgress, useProjectName, useSocketStore } from "../../store/
|
||||
import OuterClick from "../../utils/outerClick";
|
||||
import { KebabIcon } from "../icons/ExportCommonIcons";
|
||||
import { getAllProjectsApi } from "../../services/dashboard/getAllProjectsApi";
|
||||
import { Modal } from "../templates/PreviewModal";
|
||||
// import { viewProject } from "../../services/dashboard/viewProject";
|
||||
// import { updateProject } from "../../services/dashboard/updateProject";
|
||||
|
||||
@@ -20,12 +21,19 @@ interface DashBoardCardProps {
|
||||
handleDeleteProject?: (projectId: string) => Promise<void>;
|
||||
handleTrashDeleteProject?: (projectId: string) => Promise<void>;
|
||||
handleRestoreProject?: (projectId: string) => Promise<void>;
|
||||
handleDuplicateWorkspaceProject?: (projectId: string, projectName: string, thumbnail: string, userId?: string) => Promise<void>;
|
||||
handleDuplicateRecentProject?: (projectId: string, projectName: string, thumbnail: string) => Promise<void>;
|
||||
handleDuplicateWorkspaceProject?: (
|
||||
projectId: string,
|
||||
projectName: string,
|
||||
thumbnail: string,
|
||||
userId?: string
|
||||
) => Promise<void>;
|
||||
handleDuplicateRecentProject?: (
|
||||
projectId: string,
|
||||
projectName: string,
|
||||
thumbnail: string
|
||||
) => Promise<void>;
|
||||
active?: "shared" | "trash" | "recent" | string;
|
||||
setIsSearchActive?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setRecentDuplicateData?: React.Dispatch<React.SetStateAction<object>>;
|
||||
setProjectDuplicateData?: React.Dispatch<React.SetStateAction<object>>;
|
||||
setActiveFolder?: React.Dispatch<React.SetStateAction<string>>;
|
||||
openKebabProjectId: string | null;
|
||||
setOpenKebabProjectId: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
@@ -51,8 +59,6 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
|
||||
handleDuplicateRecentProject,
|
||||
createdAt,
|
||||
createdBy,
|
||||
setRecentDuplicateData,
|
||||
setProjectDuplicateData,
|
||||
setActiveFolder,
|
||||
openKebabProjectId,
|
||||
setOpenKebabProjectId,
|
||||
@@ -67,6 +73,8 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
|
||||
const [renameValue, setRenameValue] = useState(projectName);
|
||||
const [isRenaming, setIsRenaming] = useState(false);
|
||||
const kebabRef = useRef<HTMLDivElement>(null);
|
||||
const [showDelete, setShowDelete] = useState(false);
|
||||
const [confirmText, setConfirmText] = useState("");
|
||||
|
||||
// Close kebab when clicking outside
|
||||
OuterClick({
|
||||
@@ -91,6 +99,12 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
|
||||
return kebabOptionsMap.default;
|
||||
}, [active, createdBy, userId]);
|
||||
|
||||
const handlePermanentDeleteConformation = () => {
|
||||
handleTrashDeleteProject?.(projectId);
|
||||
setShowDelete(false);
|
||||
setConfirmText("");
|
||||
};
|
||||
|
||||
const handleProjectName = useCallback(
|
||||
async (newName: string) => {
|
||||
setRenameValue(newName);
|
||||
@@ -98,7 +112,9 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
|
||||
|
||||
try {
|
||||
const projects = await getAllProjectsApi();
|
||||
const projectUuid = projects?.Projects?.find((val: any) => val.projectUuid === projectId || val._id === projectId);
|
||||
const projectUuid = projects?.Projects?.find(
|
||||
(val: any) => val.projectUuid === projectId || val._id === projectId
|
||||
);
|
||||
if (!projectUuid) return;
|
||||
|
||||
const updatePayload = {
|
||||
@@ -122,7 +138,9 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
|
||||
async (option: string) => {
|
||||
switch (option) {
|
||||
case "delete":
|
||||
await (active === "trash" ? handleTrashDeleteProject?.(projectId) : handleDeleteProject?.(projectId));
|
||||
await (active === "trash"
|
||||
? setShowDelete(true)
|
||||
: handleDeleteProject?.(projectId));
|
||||
break;
|
||||
case "restore":
|
||||
await handleRestoreProject?.(projectId);
|
||||
@@ -136,18 +154,16 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
|
||||
break;
|
||||
case "duplicate":
|
||||
if (handleDuplicateWorkspaceProject) {
|
||||
setProjectDuplicateData?.({ projectId, projectName, thumbnail });
|
||||
await handleDuplicateWorkspaceProject(projectId, projectName, thumbnail, userId);
|
||||
await handleDuplicateWorkspaceProject(
|
||||
projectId,
|
||||
projectName,
|
||||
thumbnail,
|
||||
userId
|
||||
);
|
||||
if (active === "shared") {
|
||||
setActiveFolder?.("myProjects");
|
||||
}
|
||||
} else if (handleDuplicateRecentProject) {
|
||||
setRecentDuplicateData?.({
|
||||
projectId,
|
||||
projectName,
|
||||
thumbnail,
|
||||
userId,
|
||||
});
|
||||
await handleDuplicateRecentProject(projectId, projectName, thumbnail);
|
||||
}
|
||||
break;
|
||||
@@ -160,13 +176,10 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
|
||||
userId,
|
||||
active,
|
||||
handleDeleteProject,
|
||||
handleTrashDeleteProject,
|
||||
handleRestoreProject,
|
||||
handleDuplicateWorkspaceProject,
|
||||
handleDuplicateRecentProject,
|
||||
setProjectName,
|
||||
setProjectDuplicateData,
|
||||
setRecentDuplicateData,
|
||||
setActiveFolder,
|
||||
]
|
||||
);
|
||||
@@ -237,13 +250,16 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
|
||||
)}
|
||||
{createdAt && (
|
||||
<div className="project-data">
|
||||
{active === "trash" ? "Trashed" : "Edited"} {getRelativeTime(createdAt)}
|
||||
{active === "trash" ? "Trashed" : "Edited"}{" "}
|
||||
{getRelativeTime(createdAt)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="users-list-container" ref={kebabRef}>
|
||||
<div className="user-profile">{(createdBy?.userName || userName || "A").charAt(0).toUpperCase()}</div>
|
||||
<div className="user-profile">
|
||||
{(createdBy?.userName || userName || "A").charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<button
|
||||
className="kebab-wrapper"
|
||||
onClick={(e) => {
|
||||
@@ -262,7 +278,10 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
|
||||
|
||||
{isKebabOpen &&
|
||||
createPortal(
|
||||
<div className={`kebab-options-wrapper tag-${projectId}`} style={{ position: "fixed", zIndex: 9999, ...kebabPosition }}>
|
||||
<div
|
||||
className={`kebab-options-wrapper tag-${projectId}`}
|
||||
style={{ position: "fixed", zIndex: 9999, ...kebabPosition }}
|
||||
>
|
||||
{getOptions().map((option) => (
|
||||
<button
|
||||
key={option}
|
||||
@@ -279,6 +298,32 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
|
||||
</div>,
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,7 +13,6 @@ import { trashSearchProjectApi } from "../../services/dashboard/trashSearchProje
|
||||
import { restoreTrashApi } from "../../services/dashboard/restoreTrashApi";
|
||||
import { generateUniqueId } from "../../functions/generateUniqueId";
|
||||
import ProjectSocketRes from "./socket/projectSocketRes";
|
||||
import { Modal } from "../templates/PreviewModal";
|
||||
|
||||
interface DashboardMainProps {
|
||||
activeFolder: Folder;
|
||||
@@ -22,59 +21,63 @@ interface DashboardMainProps {
|
||||
const DashboardMain: React.FC<DashboardMainProps> = ({ activeFolder }) => {
|
||||
const [activeSubFolder, setActiveSubFolder] = useState("myProjects");
|
||||
const [projectsData, setProjectsData] = useState<DashboardProjectCollection>({});
|
||||
const [projectsCache, setProjectsCache] = useState<Record<string, DashboardProjectCollection>>(
|
||||
{}
|
||||
);
|
||||
const [isSearchActive, setIsSearchActive] = useState<boolean>(false);
|
||||
const [duplicateData, setDuplicateData] = useState<Object>({});
|
||||
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 { projectSocket } = useSocketStore();
|
||||
|
||||
// #region API Fetchers
|
||||
const fetchData = async () => {
|
||||
const cacheKey = activeFolder + (activeFolder === "projects" ? `-${activeSubFolder}` : "");
|
||||
|
||||
if (projectsCache[cacheKey] && !isSearchActive) {
|
||||
setProjectsData(projectsCache[cacheKey]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 🔹 Fetch all folders on mount and store locally
|
||||
const fetchAllData = async () => {
|
||||
try {
|
||||
let projects: DashboardProjectCollection = {};
|
||||
const [homeProjects, myProjects, sharedProjects, trashProjects] = await Promise.all([
|
||||
recentlyViewedApi(),
|
||||
getAllProjectsApi(),
|
||||
sharedWithMeProjectsApi(),
|
||||
getTrashApi(),
|
||||
]);
|
||||
|
||||
switch (activeFolder) {
|
||||
case "home":
|
||||
projects = await recentlyViewedApi();
|
||||
break;
|
||||
case "projects":
|
||||
if (activeSubFolder === "myProjects") {
|
||||
projects = await getAllProjectsApi();
|
||||
} else {
|
||||
projects = await sharedWithMeProjectsApi();
|
||||
}
|
||||
break;
|
||||
case "trash":
|
||||
projects = await getTrashApi();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
const cache: Record<string, DashboardProjectCollection> = {
|
||||
home: homeProjects,
|
||||
"projects-myProjects": myProjects,
|
||||
"projects-shared": sharedProjects,
|
||||
trash: trashProjects,
|
||||
};
|
||||
|
||||
if (projects && JSON.stringify(projects) !== JSON.stringify(projectsData)) {
|
||||
setProjectsCache((prev) => ({ ...prev, [cacheKey]: projects }));
|
||||
setProjectsData(projects);
|
||||
setProjectsCache(cache);
|
||||
|
||||
// set current active data
|
||||
const cacheKey =
|
||||
activeFolder === "projects" ? `projects-${activeSubFolder}` : activeFolder;
|
||||
if (cache[cacheKey]) {
|
||||
setProjectsData(cache[cacheKey]);
|
||||
}
|
||||
} 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) => {
|
||||
if (!inputValue.trim()) {
|
||||
setIsSearchActive(false);
|
||||
@@ -92,21 +95,43 @@ const DashboardMain: React.FC<DashboardMainProps> = ({ activeFolder }) => {
|
||||
setProjectsData(results?.message ? {} : results);
|
||||
};
|
||||
|
||||
// #region Socket Actions
|
||||
const handleDelete = async (projectId: string): Promise<void> => {
|
||||
if (projectSocket) {
|
||||
projectSocket.emit("v1:project:delete", {
|
||||
projectId,
|
||||
organization,
|
||||
userId,
|
||||
// 🔹 Socket Actions
|
||||
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 };
|
||||
});
|
||||
|
||||
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) || [],
|
||||
};
|
||||
});
|
||||
}
|
||||
updateStateAfterRemove(projectId);
|
||||
return newCache;
|
||||
});
|
||||
|
||||
setIsSearchActive(false);
|
||||
};
|
||||
|
||||
const handlePermanentDelete = async (): Promise<void> => {
|
||||
if (!deleteTargetId) return;
|
||||
const handleDelete = async (projectId: string): Promise<void> => {
|
||||
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) {
|
||||
projectSocket.emit("v1:trash:delete", {
|
||||
projectId: deleteTargetId,
|
||||
@@ -115,14 +140,27 @@ const DashboardMain: React.FC<DashboardMainProps> = ({ activeFolder }) => {
|
||||
});
|
||||
}
|
||||
updateStateAfterRemove(deleteTargetId);
|
||||
setShowDelete(false);
|
||||
setConfirmText("");
|
||||
setDeleteTargetId(null);
|
||||
|
||||
// 🔹 Refresh trash folder
|
||||
const trashProjects = await getTrashApi();
|
||||
console.log("trashProjects: ", trashProjects);
|
||||
setProjectsCache((prev) => ({ ...prev, trash: trashProjects }));
|
||||
};
|
||||
|
||||
const handleRestore = async (projectId: string): Promise<void> => {
|
||||
await restoreTrashApi(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 (
|
||||
@@ -140,18 +178,11 @@ const DashboardMain: React.FC<DashboardMainProps> = ({ activeFolder }) => {
|
||||
projectName,
|
||||
});
|
||||
}
|
||||
// 🔹 Refresh home & projects cache
|
||||
await fetchAllData();
|
||||
};
|
||||
|
||||
// #region Project Map
|
||||
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);
|
||||
};
|
||||
|
||||
// 🔹 Render Projects
|
||||
const renderProjects = () => {
|
||||
const key = Object.keys(projectsData)[0];
|
||||
const projectList = projectsData[key];
|
||||
@@ -171,23 +202,19 @@ const DashboardMain: React.FC<DashboardMainProps> = ({ activeFolder }) => {
|
||||
{...(activeFolder === "home" && {
|
||||
handleDeleteProject: handleDelete,
|
||||
handleDuplicateRecentProject: handleDuplicate,
|
||||
setRecentDuplicateData: setDuplicateData,
|
||||
})}
|
||||
{...(activeSubFolder === "myProjects" && {
|
||||
handleDeleteProject: handleDelete,
|
||||
handleDuplicateWorkspaceProject: handleDuplicate,
|
||||
setProjectDuplicateData: setDuplicateData,
|
||||
})}
|
||||
{...(activeSubFolder === "shared" && {
|
||||
handleDuplicateWorkspaceProject: handleDuplicate,
|
||||
setProjectDuplicateData: setDuplicateData,
|
||||
active: "shared",
|
||||
})}
|
||||
{...(activeFolder === "trash" && {
|
||||
handleRestoreProject: handleRestore,
|
||||
handleTrashDeleteProject: async (id: string): Promise<void> => {
|
||||
setDeleteTargetId(id);
|
||||
setShowDelete(true);
|
||||
handlePermanentDelete(id);
|
||||
},
|
||||
active: "trash",
|
||||
})}
|
||||
@@ -197,12 +224,6 @@ const DashboardMain: React.FC<DashboardMainProps> = ({ activeFolder }) => {
|
||||
));
|
||||
};
|
||||
|
||||
// #region Use Effects
|
||||
useEffect(() => {
|
||||
if (!isSearchActive) fetchData();
|
||||
// eslint-disable-next-line
|
||||
}, [activeFolder, isSearchActive, activeSubFolder]);
|
||||
|
||||
return (
|
||||
<div className="dashboard-home-container">
|
||||
<DashboardNavBar
|
||||
@@ -244,33 +265,6 @@ const DashboardMain: React.FC<DashboardMainProps> = ({ activeFolder }) => {
|
||||
: { setWorkspaceProjects: setProjectsData })}
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,18 +4,11 @@ import { projectTutorialApi } from "../../services/dashboard/projectTutorialApi"
|
||||
import { AIIcon } from "../icons/ExportCommonIcons";
|
||||
import { DeleteIcon } from "../icons/ContextMenuIcons";
|
||||
|
||||
interface Tutorial {
|
||||
_id: string;
|
||||
name: string;
|
||||
thumbnail?: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
const TutorialCard: React.FC<{ tutorial: Tutorial }> = ({ tutorial }) => {
|
||||
export const TutorialCard: React.FC<{ tutorial: Tutorial }> = ({ tutorial }) => {
|
||||
return (
|
||||
<div className="tutorial-card-container">
|
||||
<div
|
||||
className="thumbnail"
|
||||
className="preview-container"
|
||||
style={{
|
||||
backgroundImage: tutorial.thumbnail
|
||||
? `url(${tutorial.thumbnail})`
|
||||
@@ -23,9 +16,11 @@ const TutorialCard: React.FC<{ tutorial: Tutorial }> = ({ tutorial }) => {
|
||||
}}
|
||||
></div>
|
||||
<div className="tutorial-details">
|
||||
<div className="tutorial-name">{tutorial.name}</div>
|
||||
<div className="updated-date">
|
||||
{new Date(tutorial.updatedAt).toLocaleDateString()}
|
||||
<div className="context">
|
||||
<div className="tutorial-name">{tutorial.name}</div>
|
||||
<div className="updated-date">
|
||||
{new Date(tutorial.updatedAt).toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="delete-option">
|
||||
<DeleteIcon />
|
||||
@@ -65,7 +60,6 @@ const DashboardTutorial: React.FC = () => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="tutorials-list">
|
||||
{tutorials.length > 0 ? (
|
||||
tutorials.map((tut) => <TutorialCard key={tut._id} tutorial={tut} />)
|
||||
|
||||
@@ -72,7 +72,11 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
|
||||
<div className="side-pannel-header">
|
||||
<div className="user-container">
|
||||
<div className="user-profile">{userName?.charAt(0).toUpperCase()}</div>
|
||||
<div className="user-name">{userName ? userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase() : "Anonymous"}</div>
|
||||
<div className="user-name">
|
||||
{userName
|
||||
? userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase()
|
||||
: "Anonymous"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="notifications-container">
|
||||
<NotificationIcon />
|
||||
@@ -84,34 +88,40 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
|
||||
<div className="side-bar-content-container">
|
||||
<div className="side-bar-options-container">
|
||||
<button
|
||||
className={
|
||||
activeTab === "Home" ? "option-list active" : "option-list"
|
||||
}
|
||||
className={activeTab === "Home" ? "option-list active" : "option-list"}
|
||||
onClick={() => setActiveTab("Home")}
|
||||
>
|
||||
<HomeIcon isActive={activeTab === 'Home'}/>
|
||||
<HomeIcon isActive={activeTab === "Home"} />
|
||||
Home
|
||||
</button>
|
||||
<button
|
||||
className={
|
||||
activeTab === "Projects" ? "option-list active" : "option-list"
|
||||
}
|
||||
className={activeTab === "Projects" ? "option-list active" : "option-list"}
|
||||
title="Projects"
|
||||
onClick={() => setActiveTab("Projects")}
|
||||
>
|
||||
<ProjectsIcon isActive={activeTab === 'Projects'}/>
|
||||
<ProjectsIcon isActive={activeTab === "Projects"} />
|
||||
Projects
|
||||
</button>
|
||||
<button
|
||||
className={
|
||||
activeTab === "Trash" ? "option-list active" : "option-list"
|
||||
}
|
||||
className={activeTab === "Trash" ? "option-list active" : "option-list"}
|
||||
title="Trash"
|
||||
onClick={() => setActiveTab("Trash")}
|
||||
>
|
||||
<TrashIcon isActive={activeTab === 'Trash'}/>
|
||||
<TrashIcon isActive={activeTab === "Trash"} />
|
||||
Trash
|
||||
</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
|
||||
className={activeTab === "Tutorials" ? "option-list active" : "option-list"}
|
||||
title="coming soon"
|
||||
@@ -121,11 +131,13 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
|
||||
console.warn("Tutorials comming soon");
|
||||
}}
|
||||
>
|
||||
<TutorialsIcon isActive={activeTab === 'Tutorials'}/>
|
||||
<TutorialsIcon isActive={activeTab === "Tutorials"} />
|
||||
Tutorials
|
||||
</button>
|
||||
<button
|
||||
className={activeTab === "Documentation" ? "option-list active" : "option-list"}
|
||||
className={
|
||||
activeTab === "Documentation" ? "option-list active" : "option-list"
|
||||
}
|
||||
title="coming soon"
|
||||
disabled // remove when added
|
||||
onClick={() => {
|
||||
@@ -133,7 +145,7 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
|
||||
console.warn("Documentation comming soon");
|
||||
}}
|
||||
>
|
||||
<DocumentationIcon isActive={activeTab === 'Documentation'}/>
|
||||
<DocumentationIcon isActive={activeTab === "Documentation"} />
|
||||
Documentation
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getUserData } from "../functions/getUserData";
|
||||
import SidePannel from "../components/Dashboard/SidePannel";
|
||||
import DashboardTutorial from "../components/Dashboard/DashboardTutorial";
|
||||
import DashboardMain from "../components/Dashboard/DashboardMain";
|
||||
import DashboardUseCases from "../components/Dashboard/DasboardUseCases";
|
||||
|
||||
const Dashboard: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState<string>("Home");
|
||||
@@ -25,6 +26,7 @@ const Dashboard: React.FC = () => {
|
||||
<DashboardMain activeFolder={activeTab.toLowerCase() as Folder} />
|
||||
)}
|
||||
{activeTab === "Tutorials" && <DashboardTutorial />}
|
||||
{activeTab === "Use Cases" && <DashboardUseCases />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,401 +2,497 @@
|
||||
@use "../abstracts/mixins.scss" as *;
|
||||
|
||||
.dashboard-main {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
padding: 27px 17px;
|
||||
|
||||
.side-pannel-container {
|
||||
padding: 32px;
|
||||
min-width: 280px;
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 30px;
|
||||
box-shadow: var(--box-shadow-medium);
|
||||
padding: 27px 17px;
|
||||
|
||||
.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 {
|
||||
height: auto;
|
||||
max-height: 100%;
|
||||
.side-pannel-container {
|
||||
padding: 32px;
|
||||
min-width: 280px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-top: 18px;
|
||||
gap: 18px;
|
||||
overflow: auto;
|
||||
}
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 30px;
|
||||
box-shadow: var(--box-shadow-medium);
|
||||
|
||||
.side-pannel-header {
|
||||
@include flex-space-between;
|
||||
|
||||
.user-container {
|
||||
@include flex-center;
|
||||
gap: 6px;
|
||||
|
||||
.user-profile {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
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 {
|
||||
height: 242px;
|
||||
width: calc((100% / 5) - 23px);
|
||||
min-width: 260px;
|
||||
position: relative;
|
||||
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%;
|
||||
.dashboard-card-container,
|
||||
.tutorial-card-container {
|
||||
height: 242px;
|
||||
width: calc((100% / 5) - 23px);
|
||||
min-width: 260px;
|
||||
position: relative;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 22px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
height: 100%;
|
||||
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);
|
||||
}
|
||||
&:hover {
|
||||
border-color: var(--accent-color);
|
||||
.preview-container {
|
||||
img {
|
||||
scale: 1.05;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
.dashboard-card-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
overflow: visible;
|
||||
.preview-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: #{$border-radius-extra-large};
|
||||
overflow: hidden;
|
||||
|
||||
.kebab-options-wrapper {
|
||||
display: flex;
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
vertical-align: top;
|
||||
border: none;
|
||||
outline: none;
|
||||
transition: scale 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
width: 100%;
|
||||
height: 230px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: #{$border-radius-xxx};
|
||||
}
|
||||
height: 230px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
.hero-text {
|
||||
position: absolute;
|
||||
left: 52px;
|
||||
bottom: 25px;
|
||||
font-size: 48px;
|
||||
font-family: #{$font-roboto};
|
||||
font-weight: 800;
|
||||
color: #ffffff;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: #{$border-radius-xxx};
|
||||
}
|
||||
|
||||
.context {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 58px;
|
||||
text-transform: uppercase;
|
||||
font-size: 22px;
|
||||
width: 300px;
|
||||
color: #ffffff;
|
||||
font-family: #{$font-roboto};
|
||||
}
|
||||
.hero-text {
|
||||
position: absolute;
|
||||
left: 52px;
|
||||
bottom: 25px;
|
||||
font-size: 48px;
|
||||
font-family: #{$font-roboto};
|
||||
font-weight: 800;
|
||||
color: #ffffff;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.arrow-context {
|
||||
position: absolute;
|
||||
bottom: 27px;
|
||||
right: 300px;
|
||||
}
|
||||
.context {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 58px;
|
||||
text-transform: uppercase;
|
||||
font-size: 22px;
|
||||
width: 300px;
|
||||
color: #ffffff;
|
||||
font-family: #{$font-roboto};
|
||||
}
|
||||
|
||||
.explore-button {
|
||||
position: absolute;
|
||||
top: 95px;
|
||||
right: 52px;
|
||||
padding: 10px 20px;
|
||||
text-transform: uppercase;
|
||||
font-size: 24px;
|
||||
border: 1px solid #ffffff;
|
||||
color: #ffffff;
|
||||
font-family: #{$font-roboto};
|
||||
cursor: pointer;
|
||||
}
|
||||
.arrow-context {
|
||||
position: absolute;
|
||||
bottom: 27px;
|
||||
right: 300px;
|
||||
}
|
||||
|
||||
.explore-button {
|
||||
position: absolute;
|
||||
top: 95px;
|
||||
right: 52px;
|
||||
padding: 10px 20px;
|
||||
text-transform: uppercase;
|
||||
font-size: 24px;
|
||||
border: 1px solid #ffffff;
|
||||
color: #ffffff;
|
||||
font-family: #{$font-roboto};
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.kebab-options-wrapper {
|
||||
min-width: 140px;
|
||||
background: var(--background-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(20px);
|
||||
flex-direction: column;
|
||||
// transform: translate(100%, 100%);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transform: translate(0%, 0%);
|
||||
.option {
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
text-transform: capitalize;
|
||||
min-width: 140px;
|
||||
background: var(--background-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(20px);
|
||||
flex-direction: column;
|
||||
// transform: translate(100%, 100%);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transform: translate(0%, 0%);
|
||||
.option {
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
text-transform: capitalize;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--background-color-selected);
|
||||
&:hover {
|
||||
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[];
|
||||
remove?: boolean;
|
||||
}
|
||||
|
||||
interface Tutorial {
|
||||
_id: string;
|
||||
name: string;
|
||||
thumbnail?: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
Reference in New Issue
Block a user