diff --git a/web/components/empty-state/empty.tsx b/web/components/empty-state/empty.tsx new file mode 100644 index 000000000..d864180e8 --- /dev/null +++ b/web/components/empty-state/empty.tsx @@ -0,0 +1,128 @@ +import React from "react"; + +import Image from "next/image"; + +import { useTheme } from "next-themes"; +// hooks +import { useUser } from "hooks/store"; +// components +import { ComicBoxButton } from "./comic-box-button"; +import { Button } from "@plane/ui"; +// constant +import { EMPTY_STATE_DETAILS, EmptyStateKeys } from "constants/empty-state"; +// helpers +import { cn } from "helpers/common.helper"; + +type Props = { + type: EmptyStateKeys; + size?: "sm" | "md" | "lg"; + layout?: "widget-simple" | "screen-detailed" | "screen-simple"; + additionalPath?: string; + primaryButtonOnClick?: () => void; + secondaryButtonOnClick?: () => void; +}; + +export const EmptyStateComponent: React.FC = (props) => { + const { + type, + size = "lg", + layout = "screen-detailed", + additionalPath = "", + primaryButtonOnClick, + secondaryButtonOnClick, + } = props; + // store + const { + membership: { currentWorkspaceRole, currentProjectRole }, + } = useUser(); + // theme + const { resolvedTheme } = useTheme(); + // current empty state details + const { key, title, description, path, primaryButton, secondaryButton, accessType, access } = + EMPTY_STATE_DETAILS[type]; + // resolved empty state path + const resolvedEmptyStatePath = `${additionalPath && additionalPath !== "" ? `${path}${additionalPath}` : path}-${ + resolvedTheme === "light" ? "light" : "dark" + }.webp`; + // current access type + const currentAccessType = accessType === "workspace" ? currentWorkspaceRole : currentProjectRole; + // permission + const isEditingAllowed = currentAccessType && access && access >= currentAccessType; + const anyButton = primaryButton || secondaryButton; + + if (layout === "screen-detailed") + return ( +
+
+
+ {description ? ( + <> +

{title}

+

{description}

+ + ) : ( +

{title}

+ )} +
+ + {path && ( + {key + )} + + {anyButton && ( + <> +
+ {primaryButton && ( +
+ {primaryButton.comicBox ? ( + + ) : ( + + )} +
+ )} + {secondaryButton && ( + + )} +
+ + )} +
+
+ ); +}; diff --git a/web/components/empty-state/index.ts b/web/components/empty-state/index.ts index 57e63c1bb..6ae5f01f0 100644 --- a/web/components/empty-state/index.ts +++ b/web/components/empty-state/index.ts @@ -1,3 +1,4 @@ export * from "./empty-state"; +export * from "./empty"; export * from "./helper"; export * from "./comic-box-button"; diff --git a/web/constants/empty-state.ts b/web/constants/empty-state.ts index eaf7f4b05..a7e92a6d4 100644 --- a/web/constants/empty-state.ts +++ b/web/constants/empty-state.ts @@ -1,9 +1,42 @@ -// workspace empty state -export const WORKSPACE_EMPTY_STATE_DETAILS = { - dashboard: { +import { EUserProjectRoles } from "./project"; +import { EUserWorkspaceRoles } from "./workspace"; + +export interface EmptyStateDetails { + key: string; + title?: string; + description?: string; + path?: string; + primaryButton?: { + icon?: any; + text: string; + comicBox?: { + title?: string; + description?: string; + }; + }; + secondaryButton?: { + icon?: any; + text: string; + comicBox?: { + title?: string; + description?: string; + }; + }; + accessType?: "workspace" | "project"; + access?: EUserWorkspaceRoles | EUserProjectRoles; +} + +export type EmptyStateKeys = keyof typeof emptyStateDetails; + +const emptyStateDetails = { + // workspace + "workspace-dashboard": { + key: "workspace-dashboard", title: "Overview of your projects, activity, and metrics", description: " Welcome to Plane, we are excited to have you here. Create your first project and track your issues, and this page will transform into a space that helps you progress. Admins will also see items which help their team progress.", + path: "/empty-state/onboarding/dashboard", + // path: "/empty-state/onboarding/", primaryButton: { text: "Build your first project", }, @@ -11,356 +44,394 @@ export const WORKSPACE_EMPTY_STATE_DETAILS = { title: "Everything starts with a project in Plane", description: "A project could be a product’s roadmap, a marketing campaign, or launching a new car.", }, + accessType: "workspace", + access: EUserWorkspaceRoles.ADMIN, }, - analytics: { + "workspace-analytics": { + key: "workspace-analytics", title: "Track progress, workloads, and allocations. Spot trends, remove blockers, and move work faster", description: "See scope versus demand, estimates, and scope creep. Get performance by team members and teams, and make sure your project runs on time.", + path: "/empty-state/onboarding/analytics", primaryButton: { text: "Create Cycles and Modules first", + comicBox: { + title: "Analytics works best with Cycles + Modules", + description: + "First, timebox your issues into Cycles and, if you can, group issues that span more than a cycle into Modules. Check out both on the left nav.", + }, }, - comicBox: { - title: "Analytics works best with Cycles + Modules", - description: - "First, timebox your issues into Cycles and, if you can, group issues that span more than a cycle into Modules. Check out both on the left nav.", - }, + accessType: "workspace", + access: EUserWorkspaceRoles.MEMBER, }, - projects: { + "workspace-projects": { + key: "workspace-projects", title: "Start a Project", description: "Think of each project as the parent for goal-oriented work. Projects are where Jobs, Cycles, and Modules live and, along with your colleagues, help you achieve that goal.", + path: "/empty-state/onboarding/projects", primaryButton: { text: "Start your first project", + comicBox: { + title: "Everything starts with a project in Plane", + description: "A project could be a product’s roadmap, a marketing campaign, or launching a new car.", + }, }, - comicBox: { - title: "Everything starts with a project in Plane", - description: "A project could be a product’s roadmap, a marketing campaign, or launching a new car.", - }, + accessType: "workspace", + access: EUserWorkspaceRoles.MEMBER, }, - "assigned-notification": { - key: "assigned-notification", - title: "No issues assigned", - description: "Updates for issues assigned to you can be seen here", - }, - "created-notification": { - key: "created-notification", - title: "No updates to issues", - description: "Updates to issues created by you can be seen here", - }, - "subscribed-notification": { - key: "subscribed-notification", - title: "No updates to issues", - description: "Updates to any issue you are subscribed to can be seen here", - }, -}; - -export const ALL_ISSUES_EMPTY_STATE_DETAILS = { - "all-issues": { - key: "all-issues", + // all-issues + "workspace-all-issues": { + key: "workspace-all-issues", title: "No issues in the project", description: "First project done! Now, slice your work into trackable pieces with issues. Let's go!", + path: "/empty-state/all-issues/all-issues", }, - assigned: { - key: "assigned", + "workspace-assigned": { + key: "workspace-assigned", title: "No issues yet", description: "Issues assigned to you can be tracked from here.", + path: "/empty-state/all-issues/assigned", }, - created: { - key: "created", + "workspace-created": { + key: "workspace-created", title: "No issues yet", description: "All issues created by you come here, track them here directly.", + path: "/empty-state/all-issues/created", }, - subscribed: { - key: "subscribed", + "workspace-subscribed": { + key: "workspace-subscribed", title: "No issues yet", description: "Subscribe to issues you are interested in, track all of them here.", + path: "/empty-state/all-issues/subscribed", }, - "custom-view": { - key: "custom-view", + "workspace-custom-view": { + key: "workspace-custom-view", title: "No issues yet", description: "Issues that applies to the filters, track all of them here.", + path: "/empty-state/all-issues/custom-view", }, -}; - -export const SEARCH_EMPTY_STATE_DETAILS = { - views: { - key: "views", - title: "No matching views", - description: "No views match the search criteria. Create a new view instead.", - }, - projects: { - key: "projects", - title: "No matching projects", - description: "No projects detected with the matching criteria. Create a new project instead.", - }, - commandK: { - key: "commandK", - title: "No results found. ", - }, - members: { - key: "members", - title: "No matching members", - description: "Add them to the project if they are already a part of the workspace", - }, -}; - -export const WORKSPACE_SETTINGS_EMPTY_STATE_DETAILS = { - "api-tokens": { - key: "api-tokens", + // workspace settings + "workspace-settings-api-tokens": { + key: "workspace-settings-api-tokens", title: "No API tokens created", description: "Plane APIs can be used to integrate your data in Plane with any external system. Create a token to get started.", + path: "/empty-state/workspace-settings/api-tokens", }, - webhooks: { - key: "webhooks", + "workspace-settings-webhooks": { + key: "workspace-settings-webhooks", title: "No webhooks added", description: "Create webhooks to receive real-time updates and automate actions.", + path: "/empty-state/workspace-settings/webhooks", }, - export: { - key: "export", + "workspace-settings-export": { + key: "workspace-settings-export", title: "No previous exports yet", description: "Anytime you export, you will also have a copy here for reference.", + path: "/empty-state/workspace-settings/exports", }, - import: { - key: "export", + "workspace-settings-import": { + key: "workspace-settings-import", title: "No previous imports yet", description: "Find all your previous imports here and download them.", + path: "/empty-state/workspace-settings/imports", }, -}; - -// profile empty state -export const PROFILE_EMPTY_STATE_DETAILS = { - assigned: { - key: "assigned", + // profile + "profile-assigned": { + key: "profile-assigned", title: "No issues are assigned to you", description: "Issues assigned to you can be tracked from here.", + path: "/empty-state/profile/assigned", }, - subscribed: { - key: "created", + "profile-created": { + key: "profile-created", title: "No issues yet", description: "All issues created by you come here, track them here directly.", + path: "/empty-state/profile/created", }, - created: { - key: "subscribed", + "profile-subscribed": { + key: "profile-subscribed", title: "No issues yet", description: "Subscribe to issues you are interested in, track all of them here.", + path: "/empty-state/profile/subscribed", }, -}; - -// project empty state - -export const PROJECT_SETTINGS_EMPTY_STATE_DETAILS = { - labels: { - key: "labels", + // project settings + "project-settings-labels": { + key: "project-settings-labels", title: "No labels yet", description: "Create labels to help organize and filter issues in you project.", + path: "/empty-state/project-settings/labels", }, - integrations: { - key: "integrations", + "project-settings-integrations": { + key: "project-settings-integrations", title: "No integrations configured", description: "Configure GitHub and other integrations to sync your project issues.", + path: "/empty-state/project-settings/integrations", }, - estimate: { - key: "estimate", + "project-settings-estimate": { + key: "project-settings-estimate", title: "No estimates added", description: "Create a set of estimates to communicate the amount of work per issue.", + path: "/empty-state/project-settings/esitmates", }, -}; - -export const CYCLE_EMPTY_STATE_DETAILS = { - cycles: { + // project cycles + "project-cycles": { + key: "project-cycles", title: "Group and timebox your work in Cycles.", description: "Break work down by timeboxed chunks, work backwards from your project deadline to set dates, and make tangible progress as a team.", - comicBox: { - title: "Cycles are repetitive time-boxes.", - description: - "A sprint, an iteration, and or any other term you use for weekly or fortnightly tracking of work is a cycle.", - }, + path: "/empty-state/onboarding/cycles", primaryButton: { text: "Set your first cycle", + comicBox: { + title: "Cycles are repetitive time-boxes.", + description: + "A sprint, an iteration, and or any other term you use for weekly or fortnightly tracking of work is a cycle.", + }, }, + accessType: "workspace", + access: EUserWorkspaceRoles.MEMBER, }, - "no-issues": { - key: "no-issues", + "project-cycle-no-issues": { + key: "project-cycle-no-issues", title: "No issues added to the cycle", description: "Add or create issues you wish to timebox and deliver within this cycle", + path: "/empty-state/cycle-issue/", primaryButton: { text: "Create new issue ", }, secondaryButton: { text: "Add an existing issue", }, + accessType: "project", + access: EUserProjectRoles.MEMBER, }, - active: { - key: "active", + "project-cycle-active": { + key: "project-cycle-active", title: "No active cycles", description: "An active cycle includes any period that encompasses today's date within its range. Find the progress and details of the active cycle here.", + path: "/empty-state/cycle/active", }, - upcoming: { - key: "upcoming", + "project-cycle-upcoming": { + key: "project-cycle-upcoming", title: "No upcoming cycles", description: "Upcoming cycles on deck! Just add dates to cycles in draft, and they'll show up right here.", + path: "/empty-state/cycle/upcoming", }, - completed: { - key: "completed", + "project-cycle-completed": { + key: "project-cycle-completed", title: "No completed cycles", description: "Any cycle with a past due date is considered completed. Explore all completed cycles here.", + path: "/empty-state/cycle/completed", }, - draft: { - key: "draft", + "project-cycle-draft": { + key: "project-cycle-draft", title: "No draft cycles", description: "No dates added in cycles? Find them here as drafts.", + path: "/empty-state/cycle/draft", }, -}; - -export const EMPTY_FILTER_STATE_DETAILS = { - archived: { - key: "archived", + // empty filters + "project-empty-filter": { + key: "project-empty-filter", title: "No issues found matching the filters applied", + path: "/empty-state/empty-filters/", secondaryButton: { text: "Clear all filters", }, + accessType: "project", + access: EUserProjectRoles.MEMBER, }, - draft: { - key: "draft", + "project-archived-empty-filter": { + key: "project-archived-empty-filter", title: "No issues found matching the filters applied", + path: "/empty-state/empty-filters/", secondaryButton: { text: "Clear all filters", }, + accessType: "project", + access: EUserProjectRoles.MEMBER, }, - project: { - key: "project", + "project-draft-empty-filter": { + key: "project-draft-empty-filter", title: "No issues found matching the filters applied", + path: "/empty-state/empty-filters/", secondaryButton: { text: "Clear all filters", }, + accessType: "project", + access: EUserProjectRoles.MEMBER, }, -}; - -export const EMPTY_ISSUE_STATE_DETAILS = { - archived: { - key: "archived", - title: "No archived issues yet", - description: - "Archived issues help you remove issues you completed or cancelled from focus. You can set automation to auto archive issues and find them here.", - primaryButton: { - text: "Set Automation", - }, - }, - draft: { - key: "draft", - title: "No draft issues yet", - description: - "Quickly stepping away but want to keep your place? No worries – save a draft now. Your issues will be right here waiting for you.", - }, - project: { - key: "project", + // project issues + "project-no-issues": { + key: "project-no-issues", title: "Create an issue and assign it to someone, even yourself", description: "Think of issues as jobs, tasks, work, or JTBD. Which we like. An issue and its sub-issues are usually time-based actionables assigned to members of your team. Your team creates, assigns, and completes issues to move your project towards its goal.", - comicBox: { - title: "Issues are building blocks in Plane.", - description: - "Redesign the Plane UI, Rebrand the company, or Launch the new fuel injection system are examples of issues that likely have sub-issues.", - }, + path: "/empty-state/onboarding/issues", primaryButton: { text: "Create your first issue", + comicBox: { + title: "Issues are building blocks in Plane.", + description: + "Redesign the Plane UI, Rebrand the company, or Launch the new fuel injection system are examples of issues that likely have sub-issues.", + }, }, + accessType: "project", + access: EUserProjectRoles.MEMBER, }, -}; - -export const MODULE_EMPTY_STATE_DETAILS = { - "no-issues": { - key: "no-issues", + "project-archived-no-issues": { + key: "project-archived-no-issues", + title: "No archived issues yet", + description: + "Archived issues help you remove issues you completed or cancelled from focus. You can set automation to auto archive issues and find them here.", + path: "/empty-state/archived/empty-issues", + primaryButton: { + text: "Set Automation", + }, + accessType: "project", + access: EUserProjectRoles.MEMBER, + }, + "project-draft-no-issues": { + key: "project-draft-no-issues", + title: "No draft issues yet", + description: + "Quickly stepping away but want to keep your place? No worries – save a draft now. Your issues will be right here waiting for you.", + path: "/empty-state/draft/draft-issues-empty", + }, + "views-empty-search": { + key: "views-empty-search", + title: "No matching views", + description: "No views match the search criteria. Create a new view instead.", + path: "/empty-state/search/search", + }, + "projects-empty-search": { + key: "projects-empty-search", + title: "No matching projects", + description: "No projects detected with the matching criteria. Create a new project instead.", + path: "/empty-state/search/project", + }, + "commandK-empty-search": { + key: "commandK-empty-search", + title: "No results found. ", + path: "/empty-state/search/search", + }, + "members-empty-search": { + key: "members-empty-search", + title: "No matching members", + description: "Add them to the project if they are already a part of the workspace", + path: "/empty-state/search/member", + }, + // project module + "project-module-issues": { + key: "project-modules-issues", title: "No issues in the module", description: "Create or add issues which you want to accomplish as part of this module", + path: "/empty-state/module-issues/", primaryButton: { text: "Create new issue ", }, secondaryButton: { text: "Add an existing issue", }, + accessType: "project", + access: EUserProjectRoles.MEMBER, }, - modules: { + "project-module": { + key: "project-module", title: "Map your project milestones to Modules and track aggregated work easily.", description: "A group of issues that belong to a logical, hierarchical parent form a module. Think of them as a way to track work by project milestones. They have their own periods and deadlines as well as analytics to help you see how close or far you are from a milestone.", - - comicBox: { - title: "Modules help group work by hierarchy.", - description: "A cart module, a chassis module, and a warehouse module are all good example of this grouping.", - }, + path: "/empty-state/onboarding/modules", primaryButton: { text: "Build your first module", + comicBox: { + title: "Modules help group work by hierarchy.", + description: "A cart module, a chassis module, and a warehouse module are all good example of this grouping.", + }, }, + accessType: "project", + access: EUserProjectRoles.MEMBER, }, -}; - -export const VIEW_EMPTY_STATE_DETAILS = { - "project-views": { + // project views + "project-view": { + key: "project-view", title: "Save filtered views for your project. Create as many as you need", description: "Views are a set of saved filters that you use frequently or want easy access to. All your colleagues in a project can see everyone’s views and choose whichever suits their needs best.", - comicBox: { - title: "Views work atop Issue properties.", - description: "You can create a view from here with as many properties as filters as you see fit.", - }, + path: "/empty-state/onboarding/views", primaryButton: { text: "Create your first view", + comicBox: { + title: "Views work atop Issue properties.", + description: "You can create a view from here with as many properties as filters as you see fit.", + }, }, + accessType: "project", + access: EUserProjectRoles.MEMBER, }, -}; - -export const PAGE_EMPTY_STATE_DETAILS = { - pages: { + // project pages + "project-page": { key: "pages", title: "Write a note, a doc, or a full knowledge base. Get Galileo, Plane’s AI assistant, to help you get started", description: "Pages are thoughts potting space in Plane. Take down meeting notes, format them easily, embed issues, lay them out using a library of components, and keep them all in your project’s context. To make short work of any doc, invoke Galileo, Plane’s AI, with a shortcut or the click of a button.", + path: "/empty-state/onboarding/pages", primaryButton: { text: "Create your first page", + comicBox: { + title: "A page can be a doc or a doc of docs.", + description: + "We wrote Nikhil and Meera’s love story. You could write your project’s mission, goals, and eventual vision.", + }, }, - comicBox: { - title: "A page can be a doc or a doc of docs.", - description: - "We wrote Nikhil and Meera’s love story. You could write your project’s mission, goals, and eventual vision.", - }, + accessType: "project", + access: EUserProjectRoles.MEMBER, }, - All: { - key: "all", + "project-page-all": { + key: "project-page-all", title: "Write a note, a doc, or a full knowledge base", description: "Pages help you organise your thoughts to create wikis, discussions or even document heated takes for your project. Use it wisely!", + path: "/empty-state/pages/all", }, - Favorites: { - key: "favorites", + "project-page-favorite": { + key: "project-page-favorite", title: "No favorite pages yet", description: "Favorites for quick access? mark them and find them right here.", + path: "/empty-state/pages/favorites", }, - Private: { - key: "private", + "project-page-private": { + key: "project-page-private", title: "No private pages yet", description: "Keep your private thoughts here. When you're ready to share, the team's just a click away.", + path: "/empty-state/pages/private", }, - Shared: { - key: "shared", + "project-page-shared": { + key: "project-page-shared", title: "No shared pages yet", description: "See pages shared with everyone in your project right here.", + path: "/empty-state/pages/shared", }, - Archived: { - key: "archived", + "project-page-archived": { + key: "project-page-archived", title: "No archived pages yet", description: "Archive pages not on your radar. Access them here when needed.", + path: "/empty-state/pages/archived", }, - Recent: { - key: "recent", + "project-page-recent": { + key: "project-page-recent", title: "Write a note, a doc, or a full knowledge base", description: "Pages help you organise your thoughts to create wikis, discussions or even document heated takes for your project. Use it wisely! Pages will be sorted and grouped by last updated", + path: "/empty-state/pages/recent", primaryButton: { text: "Create new page", }, + accessType: "project", + access: EUserProjectRoles.MEMBER, }, -}; +} as const; + +export const EMPTY_STATE_DETAILS: Record = emptyStateDetails;