[WEB-3203] fix: dashboard widgets' empty state content and assets (#6450)
* fix: empty state content * chore: replace margin with padding
This commit is contained in:
parent
0b53912295
commit
d08c03f557
20 changed files with 157 additions and 162 deletions
|
|
@ -105,14 +105,14 @@ ul[data-type="taskList"] li > div {
|
||||||
}
|
}
|
||||||
|
|
||||||
ul[data-type="taskList"] li > label input[type="checkbox"] {
|
ul[data-type="taskList"] li > label input[type="checkbox"] {
|
||||||
border: 1px solid rgba(var(--color-border-300)) !important;
|
border: 1px solid rgba(var(--color-text-100), 0.2) !important;
|
||||||
outline: none;
|
outline: none;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror[contenteditable="true"] input[type="checkbox"]:hover {
|
.ProseMirror[contenteditable="true"] input[type="checkbox"]:hover {
|
||||||
background-color: rgba(var(--color-background-80));
|
background-color: rgba(var(--color-text-100), 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror[contenteditable="false"] input[type="checkbox"] {
|
.ProseMirror[contenteditable="false"] input[type="checkbox"] {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { getEditorFileHandlers } from "@/helpers/editor.helper";
|
||||||
// plane web hooks
|
// plane web hooks
|
||||||
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
|
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
|
||||||
import { useFileSize } from "@/plane-web/hooks/use-file-size";
|
import { useFileSize } from "@/plane-web/hooks/use-file-size";
|
||||||
import { Toolbar } from "./toolbar";
|
import { StickyEditorToolbar } from "./toolbar";
|
||||||
|
|
||||||
interface StickyEditorWrapperProps
|
interface StickyEditorWrapperProps
|
||||||
extends Omit<ILiteTextEditor, "disabledExtensions" | "fileHandler" | "mentionHandler"> {
|
extends Omit<ILiteTextEditor, "disabledExtensions" | "fileHandler" | "mentionHandler"> {
|
||||||
|
|
@ -87,7 +87,7 @@ export const StickyEditor = React.forwardRef<EditorRefApi, StickyEditorWrapperPr
|
||||||
isFocused ? "max-h-[200px] opacity-100 scale-y-100 mt-3" : "max-h-0 opacity-0 scale-y-0 invisible"
|
isFocused ? "max-h-[200px] opacity-100 scale-y-100 mt-3" : "max-h-0 opacity-0 scale-y-0 invisible"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Toolbar
|
<StickyEditorToolbar
|
||||||
executeCommand={(item) => {
|
executeCommand={(item) => {
|
||||||
// TODO: update this while toolbar homogenization
|
// TODO: update this while toolbar homogenization
|
||||||
// @ts-expect-error type mismatch here
|
// @ts-expect-error type mismatch here
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ type Props = {
|
||||||
|
|
||||||
const toolbarItems = TOOLBAR_ITEMS.sticky;
|
const toolbarItems = TOOLBAR_ITEMS.sticky;
|
||||||
|
|
||||||
export const Toolbar: React.FC<Props> = (props) => {
|
export const StickyEditorToolbar: React.FC<Props> = (props) => {
|
||||||
const { executeCommand, editorRef, handleColorChange, handleDelete } = props;
|
const { executeCommand, editorRef, handleColorChange, handleDelete } = props;
|
||||||
|
|
||||||
// State to manage active states of toolbar items
|
// State to manage active states of toolbar items
|
||||||
|
|
@ -69,7 +69,11 @@ export const Toolbar: React.FC<Props> = (props) => {
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<button onClick={() => setShowColorPalette(!showColorPalette)} className="flex text-custom-text-300">
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowColorPalette(!showColorPalette)}
|
||||||
|
className="flex text-custom-text-100/50"
|
||||||
|
>
|
||||||
<Palette className="size-4 my-auto" />
|
<Palette className="size-4 my-auto" />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
@ -95,7 +99,7 @@ export const Toolbar: React.FC<Props> = (props) => {
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => executeCommand(item)}
|
onClick={() => executeCommand(item)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"grid place-items-center aspect-square rounded-sm p-0.5 text-custom-text-300",
|
"grid place-items-center aspect-square rounded-sm p-0.5 text-custom-text-100/50",
|
||||||
{}
|
{}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
@ -122,7 +126,7 @@ export const Toolbar: React.FC<Props> = (props) => {
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<button onClick={handleDelete} className="my-auto text-custom-text-300">
|
<button type="button" onClick={handleDelete} className="my-auto text-custom-text-100/50">
|
||||||
<Trash2 className="size-4" />
|
<Trash2 className="size-4" />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
||||||
|
|
@ -1 +1,4 @@
|
||||||
export * from "./root";
|
export * from "./links";
|
||||||
|
export * from "./no-projects";
|
||||||
|
export * from "./recents";
|
||||||
|
export * from "./stickies";
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import { Link2 } from "lucide-react";
|
import { Link2 } from "lucide-react";
|
||||||
|
|
||||||
export const LinksEmptyState = () => (
|
export const LinksEmptyState = () => (
|
||||||
<div className="min-h-[110px] flex w-full justify-center py-6 bg-custom-border-100 rounded">
|
<div className="min-h-[110px] w-full flex items-center justify-center gap-2 py-6 bg-custom-background-90 text-custom-text-400 rounded">
|
||||||
<div className="m-auto flex gap-2">
|
<div className="flex-shrink-0 size-[30px] grid place-items-center">
|
||||||
<Link2 size={30} className="text-custom-text-400/40 -rotate-45" />
|
<Link2 className="size-6 -rotate-45" />
|
||||||
<div className="text-custom-text-400 text-sm text-center my-auto">
|
|
||||||
Add any links you need for quick access to your work.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-sm text-center font-medium">Save links to work things that you{"'"}d like handy.</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import React from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { Briefcase, Hotel, Users } from "lucide-react";
|
import { Briefcase, Hotel, Users } from "lucide-react";
|
||||||
|
// plane ui
|
||||||
|
import { Avatar } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { getFileURL } from "@/helpers/file.helper";
|
import { getFileURL } from "@/helpers/file.helper";
|
||||||
// hooks
|
// hooks
|
||||||
|
|
@ -9,7 +11,7 @@ import { useCommandPalette, useEventTracker, useUser, useUserPermissions } from
|
||||||
// plane web constants
|
// plane web constants
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants";
|
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants";
|
||||||
|
|
||||||
export const EmptyWorkspace = () => {
|
export const NoProjectsEmptyState = () => {
|
||||||
// navigation
|
// navigation
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
// store hooks
|
// store hooks
|
||||||
|
|
@ -26,11 +28,11 @@ export const EmptyWorkspace = () => {
|
||||||
const EMPTY_STATE_DATA = [
|
const EMPTY_STATE_DATA = [
|
||||||
{
|
{
|
||||||
id: "create-project",
|
id: "create-project",
|
||||||
title: "Create a project",
|
title: "Create a project.",
|
||||||
description: "Create your first project now to get started",
|
description: "Most things start with a project in Plane.",
|
||||||
icon: <Briefcase className="w-[40px] h-[40px] text-custom-primary-100" />,
|
icon: <Briefcase className="size-12 text-custom-primary-100" />,
|
||||||
cta: {
|
cta: {
|
||||||
text: "Create Project",
|
text: "Get started",
|
||||||
onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
if (!canCreateProject) return;
|
if (!canCreateProject) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -42,66 +44,56 @@ export const EmptyWorkspace = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "invite-team",
|
id: "invite-team",
|
||||||
title: "Invite your team",
|
title: "Invite your team.",
|
||||||
description: "The sub text will be of two lines and that will be placed.",
|
description: "Build, ship, and manage with coworkers.",
|
||||||
icon: <Users className="w-[40px] h-[40px] text-custom-primary-100" />,
|
icon: <Users className="size-12 text-custom-primary-100" />,
|
||||||
cta: {
|
cta: {
|
||||||
text: "Invite now",
|
text: "Get them in",
|
||||||
link: `/${workspaceSlug}/settings/members`,
|
link: `/${workspaceSlug}/settings/members`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "configure-workspace",
|
id: "configure-workspace",
|
||||||
title: "Configure your workspace",
|
title: "Set up your workspace.",
|
||||||
description: "The sub text will be of two lines and that will be placed.",
|
description: "Turn features on or off or go beyond that.",
|
||||||
icon: <Hotel className="w-[40px] h-[40px] text-custom-primary-100" />,
|
icon: <Hotel className="size-12 text-custom-primary-100" />,
|
||||||
cta: {
|
cta: {
|
||||||
text: "Configure workspace",
|
text: "Configure this workspace",
|
||||||
link: "settings",
|
link: "settings",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "personalize-account",
|
id: "personalize-account",
|
||||||
title: "Personalize your account",
|
title: "Make Plane yours.",
|
||||||
description: "The sub text will be of two lines and that will be placed.",
|
description: "Choose your picture, colors, and more.",
|
||||||
icon:
|
icon: (
|
||||||
currentUser?.avatar_url && currentUser?.avatar_url.trim() !== "" ? (
|
<Avatar
|
||||||
<Link href={`/${workspaceSlug}/profile/${currentUser?.id}`}>
|
src={getFileURL(currentUser?.avatar_url ?? "")}
|
||||||
<span className="relative flex h-6 w-6 items-center justify-center rounded-full p-4 capitalize text-white">
|
name={currentUser?.display_name}
|
||||||
<img
|
size={48}
|
||||||
src={getFileURL(currentUser?.avatar_url)}
|
className="text-xl"
|
||||||
className="absolute left-0 top-0 h-full w-full rounded-full object-cover"
|
showTooltip={false}
|
||||||
alt={currentUser?.display_name || currentUser?.email}
|
|
||||||
/>
|
/>
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<Link href={`/${workspaceSlug}/profile/${currentUser?.id}`}>
|
|
||||||
<span className="relative flex h-6 w-6 items-center justify-center rounded-full bg-gray-700 p-4 capitalize text-white text-sm">
|
|
||||||
{(currentUser?.email ?? currentUser?.display_name ?? "?")[0]}
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
),
|
),
|
||||||
cta: {
|
cta: {
|
||||||
text: "Personalize account",
|
text: "Personalize now",
|
||||||
link: "/profile",
|
link: "/profile",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
|
||||||
{EMPTY_STATE_DATA.map((item) => (
|
{EMPTY_STATE_DATA.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className="flex flex-col items-center justify-center py-8 bg-custom-background-100 rounded-lg text-center border border-custom-border-200/40"
|
className="flex flex-col items-center justify-center p-6 bg-custom-background-100 rounded-lg text-center border border-custom-border-200/40"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-center bg-custom-primary-100/10 rounded-full w-[80px] h-[80px] mb-4">
|
<div className="grid place-items-center bg-custom-primary-100/10 rounded-full size-24 mb-3">
|
||||||
<span className="text-3xl my-auto">{item.icon}</span>
|
<span className="text-3xl my-auto">{item.icon}</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-medium text-custom-text-100 mb-2">{item.title}</h3>
|
<h3 className="text-base font-medium text-custom-text-100 mb-2">{item.title}</h3>
|
||||||
<p className="text-sm text-custom-text-200 mb-4 w-[80%] flex-1">{item.description}</p>
|
<p className="text-sm text-custom-text-300 mb-2">{item.description}</p>
|
||||||
|
|
||||||
{item.cta.link ? (
|
{item.cta.link ? (
|
||||||
<Link
|
<Link
|
||||||
href={item.cta.link}
|
href={item.cta.link}
|
||||||
|
|
@ -111,6 +103,7 @@ export const EmptyWorkspace = () => {
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
className="text-custom-primary-100 hover:text-custom-primary-200 text-sm font-medium"
|
className="text-custom-primary-100 hover:text-custom-primary-200 text-sm font-medium"
|
||||||
onClick={item.cta.onClick}
|
onClick={item.cta.onClick}
|
||||||
>
|
>
|
||||||
|
|
@ -1,38 +1,41 @@
|
||||||
import { Briefcase, FileText, History } from "lucide-react";
|
import { Briefcase, FileText, History } from "lucide-react";
|
||||||
|
// plane ui
|
||||||
import { LayersIcon } from "@plane/ui";
|
import { LayersIcon } from "@plane/ui";
|
||||||
|
|
||||||
export const RecentsEmptyState = ({ type }: { type: string }) => {
|
const getDisplayContent = (type: string) => {
|
||||||
const getDisplayContent = () => {
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "project":
|
case "project":
|
||||||
return {
|
return {
|
||||||
icon: <Briefcase size={30} className="text-custom-text-400/40" />,
|
icon: Briefcase,
|
||||||
text: "Your recent projects will appear here once you visit one.",
|
text: "Projects you go into or have assigned work in will show up here.",
|
||||||
};
|
};
|
||||||
case "page":
|
case "page":
|
||||||
return {
|
return {
|
||||||
icon: <FileText size={30} className="text-custom-text-400/40" />,
|
icon: FileText,
|
||||||
text: "Your recent pages will appear here once you visit one.",
|
text: "Create, see, or change something on pages you have access to and see them here.",
|
||||||
};
|
};
|
||||||
case "issue":
|
case "issue":
|
||||||
return {
|
return {
|
||||||
icon: <LayersIcon className="text-custom-text-400/40 w-[30px] h-[30px]" />,
|
icon: LayersIcon,
|
||||||
text: "Your recent issues will appear here once you visit one.",
|
text: "Let's see some issues to see them show up here.",
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
icon: <History size={30} className="text-custom-text-400/40" />,
|
icon: History,
|
||||||
text: "You don’t have any recent items yet.",
|
text: "Whatever you see and act on in Plane will show up here.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const { icon, text } = getDisplayContent();
|
|
||||||
|
export const RecentsEmptyState = ({ type }: { type: string }) => {
|
||||||
|
const displayContent = getDisplayContent(type);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-[120px] flex w-full justify-center py-6 bg-custom-border-100 rounded">
|
<div className="min-h-[110px] w-full flex items-center justify-center gap-2 py-6 bg-custom-background-90 text-custom-text-400 rounded">
|
||||||
<div className="m-auto flex gap-2">
|
<div className="flex-shrink-0 size-[30px] grid place-items-center">
|
||||||
{icon} <div className="text-custom-text-400 text-sm text-center my-auto">{text}</div>
|
<displayContent.icon className="size-6" />
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-sm text-center font-medium">{displayContent.text}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@
|
||||||
import { RecentStickyIcon } from "@plane/ui";
|
import { RecentStickyIcon } from "@plane/ui";
|
||||||
|
|
||||||
export const StickiesEmptyState = () => (
|
export const StickiesEmptyState = () => (
|
||||||
<div className="min-h-[110px] flex w-full justify-center py-6 bg-custom-border-100 rounded">
|
<div className="min-h-[110px] w-full flex items-center justify-center gap-2 py-6 bg-custom-background-90 text-custom-text-400 rounded">
|
||||||
<div className="m-auto flex gap-2">
|
<div className="flex-shrink-0 size-[30px] grid place-items-center">
|
||||||
<RecentStickyIcon className="h-[30px] w-[30px] text-custom-text-400/40" />
|
<RecentStickyIcon className="size-6" />
|
||||||
<div className="text-custom-text-400 text-sm text-center my-auto">
|
|
||||||
No stickies yet. Add one to start making quick notes.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-sm text-center font-medium">
|
||||||
|
Jot down an idea, capture an aha, or record a brainwave. Add a sticky to get started.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export const AddLink = (props: TProps) => {
|
||||||
<div className="rounded p-2 bg-custom-background-80/40 w-8 h-8 my-auto">
|
<div className="rounded p-2 bg-custom-background-80/40 w-8 h-8 my-auto">
|
||||||
<PlusIcon className="h-4 w-4 stroke-2 text-custom-text-350" />
|
<PlusIcon className="h-4 w-4 stroke-2 text-custom-text-350" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm font-medium my-auto">Add quick Link</div>
|
<div className="text-sm font-medium my-auto">Add quicklink</div>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -66,9 +66,7 @@ export const LinkCreateUpdateModal: FC<TLinkCreateEditModal> = observer((props)
|
||||||
<ModalCore isOpen={isModalOpen} handleClose={onClose}>
|
<ModalCore isOpen={isModalOpen} handleClose={onClose}>
|
||||||
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||||
<div className="space-y-5 p-5">
|
<div className="space-y-5 p-5">
|
||||||
<h3 className="text-xl font-medium text-custom-text-200">
|
<h3 className="text-xl font-medium text-custom-text-200">{preloadedData?.id ? "Update" : "Add"} quicklink</h3>
|
||||||
{preloadedData?.id ? "Update" : "Add"} quick link
|
|
||||||
</h3>
|
|
||||||
<div className="mt-2 space-y-3">
|
<div className="mt-2 space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="url" className="mb-2 text-custom-text-200 text-base font-medium">
|
<label htmlFor="url" className="mb-2 text-custom-text-200 text-base font-medium">
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
// hooks
|
|
||||||
// ui
|
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Pencil, Trash2, ExternalLink, EllipsisVertical, Link2, Link } from "lucide-react";
|
import { Pencil, Trash2, ExternalLink, Link2, Link } from "lucide-react";
|
||||||
|
// plane ui
|
||||||
import { TOAST_TYPE, setToast, CustomMenu, TContextMenuItem } from "@plane/ui";
|
import { TOAST_TYPE, setToast, CustomMenu, TContextMenuItem } from "@plane/ui";
|
||||||
// helpers
|
// plane utils
|
||||||
import { cn, copyTextToClipboard } from "@plane/utils";
|
import { cn, copyTextToClipboard } from "@plane/utils";
|
||||||
|
// helpers
|
||||||
import { calculateTimeAgo } from "@/helpers/date-time.helper";
|
import { calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||||
|
// hooks
|
||||||
import { useHome } from "@/hooks/store/use-home";
|
import { useHome } from "@/hooks/store/use-home";
|
||||||
|
// types
|
||||||
import { TLinkOperations } from "./use-links";
|
import { TLinkOperations } from "./use-links";
|
||||||
|
|
||||||
export type TProjectLinkDetail = {
|
export type TProjectLinkDetail = {
|
||||||
|
|
@ -75,25 +77,17 @@ export const ProjectLinkDetail: FC<TProjectLinkDetail> = observer((props) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onClick={handleOpenInNewTab}
|
onClick={handleOpenInNewTab}
|
||||||
className="cursor-pointer group btn btn-primary flex bg-custom-background-100 px-4 w-[230px] h-[56px] border-[0.5px] border-custom-border-200 rounded-md gap-4 hover:shadow-md"
|
className="cursor-pointer group flex items-center bg-custom-background-100 px-4 w-[230px] h-[56px] border-[0.5px] border-custom-border-200 rounded-md gap-4 hover:shadow-md transition-shadow"
|
||||||
>
|
>
|
||||||
<div className="rounded p-2 bg-custom-background-80/40 w-8 h-8 my-auto">
|
<div className="flex-shrink-0 size-8 rounded p-2 bg-custom-background-80 grid place-items-center">
|
||||||
<Link2 className="h-4 w-4 stroke-2 text-custom-text-350 -rotate-45" />
|
<Link2 className="size-4 stroke-2 text-custom-text-350 -rotate-45" />
|
||||||
</div>
|
</div>
|
||||||
<div className="my-auto flex-1">
|
<div className="flex-1 truncate">
|
||||||
<div className="text-sm font-medium truncate">{linkDetail.title || linkDetail.url}</div>
|
<div className="text-sm font-medium truncate">{linkDetail.title || linkDetail.url}</div>
|
||||||
<div className="text-xs font-medium text-custom-text-400">{calculateTimeAgo(linkDetail.created_at)}</div>
|
<div className="text-xs font-medium text-custom-text-400">{calculateTimeAgo(linkDetail.created_at)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="hidden group-hover:block">
|
||||||
<CustomMenu
|
<CustomMenu placement="bottom-end" menuItemsClassName="z-20" closeOnSelect verticalEllipsis>
|
||||||
customButton={
|
|
||||||
<EllipsisVertical className="opacity-0 h-4 w-4 stroke-2 text-custom-text-350 group-hover:opacity-100" />
|
|
||||||
}
|
|
||||||
placement="bottom-end"
|
|
||||||
menuItemsClassName="z-20"
|
|
||||||
closeOnSelect
|
|
||||||
className=" my-auto"
|
|
||||||
>
|
|
||||||
{MENU_ITEMS.map((item) => (
|
{MENU_ITEMS.map((item) => (
|
||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem
|
||||||
key={item.key}
|
key={item.key}
|
||||||
|
|
@ -124,5 +118,6 @@ export const ProjectLinkDetail: FC<TProjectLinkDetail> = observer((props) => {
|
||||||
))}
|
))}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,9 @@ export const ProjectLinkList: FC<TProjectLinkList> = observer((props) => {
|
||||||
buttonClassName="bg-custom-background-90/20"
|
buttonClassName="bg-custom-background-90/20"
|
||||||
>
|
>
|
||||||
<div className="flex gap-2 mb-2 flex-wrap flex-1">
|
<div className="flex gap-2 mb-2 flex-wrap flex-1">
|
||||||
{links &&
|
{links.map((linkId) => (
|
||||||
links.length > 0 &&
|
<ProjectLinkDetail key={linkId} linkId={linkId} linkOperations={linkOperations} />
|
||||||
links.map((linkId) => <ProjectLinkDetail key={linkId} linkId={linkId} linkOperations={linkOperations} />)}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</ContentOverflowWrapper>
|
</ContentOverflowWrapper>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,7 @@ import { LayersIcon } from "@plane/ui";
|
||||||
import { ContentOverflowWrapper } from "@/components/core/content-overflow-HOC";
|
import { ContentOverflowWrapper } from "@/components/core/content-overflow-HOC";
|
||||||
import { useProject } from "@/hooks/store";
|
import { useProject } from "@/hooks/store";
|
||||||
import { WorkspaceService } from "@/plane-web/services";
|
import { WorkspaceService } from "@/plane-web/services";
|
||||||
import { EmptyWorkspace } from "../empty-states";
|
import { NoProjectsEmptyState, RecentsEmptyState } from "../empty-states";
|
||||||
import { RecentsEmptyState } from "../empty-states/recents";
|
|
||||||
import { EWidgetKeys, WidgetLoader } from "../loaders";
|
import { EWidgetKeys, WidgetLoader } from "../loaders";
|
||||||
import { FiltersDropdown } from "./filters";
|
import { FiltersDropdown } from "./filters";
|
||||||
import { RecentIssue } from "./issue";
|
import { RecentIssue } from "./issue";
|
||||||
|
|
@ -34,6 +33,7 @@ export const RecentActivityWidget: React.FC<THomeWidgetProps> = observer((props)
|
||||||
const [filter, setFilter] = useState<TRecentActivityFilterKeys>(filters[0].name);
|
const [filter, setFilter] = useState<TRecentActivityFilterKeys>(filters[0].name);
|
||||||
// ref
|
// ref
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
// store hooks
|
||||||
const { joinedProjectIds, loader } = useProject();
|
const { joinedProjectIds, loader } = useProject();
|
||||||
|
|
||||||
const { data: recents, isLoading } = useSWR(
|
const { data: recents, isLoading } = useSWR(
|
||||||
|
|
@ -65,7 +65,8 @@ export const RecentActivityWidget: React.FC<THomeWidgetProps> = observer((props)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!loader && joinedProjectIds?.length === 0) return <EmptyWorkspace />;
|
if (!loader && joinedProjectIds?.length === 0) return <NoProjectsEmptyState />;
|
||||||
|
|
||||||
if (!isLoading && recents?.length === 0)
|
if (!isLoading && recents?.length === 0)
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className="max-h-[500px] overflow-y-scroll">
|
<div ref={ref} className="max-h-[500px] overflow-y-scroll">
|
||||||
|
|
@ -94,10 +95,9 @@ export const RecentActivityWidget: React.FC<THomeWidgetProps> = observer((props)
|
||||||
<div className="min-h-[250px] flex flex-col">
|
<div className="min-h-[250px] flex flex-col">
|
||||||
{isLoading && <WidgetLoader widgetKey={WIDGET_KEY} />}
|
{isLoading && <WidgetLoader widgetKey={WIDGET_KEY} />}
|
||||||
{!isLoading &&
|
{!isLoading &&
|
||||||
recents?.length > 0 &&
|
|
||||||
recents
|
recents
|
||||||
.filter((recent: TActivityEntityData) => recent.entity_data)
|
?.filter((recent) => recent.entity_data)
|
||||||
.map((activity: TActivityEntityData) => <div key={activity.id}>{resolveRecent(activity)}</div>)}
|
.map((activity) => <div key={activity.id}>{resolveRecent(activity)}</div>)}
|
||||||
</div>
|
</div>
|
||||||
</ContentOverflowWrapper>
|
</ContentOverflowWrapper>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ export const StickiesList = observer((props: TProps) => {
|
||||||
|
|
||||||
if (loader === "loaded" && workspaceStickyIds.length === 0) {
|
if (loader === "loaded" && workspaceStickyIds.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="size-full grid place-items-center">
|
<div className="size-full grid place-items-center px-2">
|
||||||
{isStickiesPage || searchQuery ? (
|
{isStickiesPage || searchQuery ? (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
type={searchQuery ? EmptyStateType.STICKIES_SEARCH : EmptyStateType.STICKIES}
|
type={searchQuery ? EmptyStateType.STICKIES_SEARCH : EmptyStateType.STICKIES}
|
||||||
|
|
|
||||||
|
|
@ -922,7 +922,7 @@ const emptyStateDetails: Record<EmptyStateType, EmptyStateDetails> = {
|
||||||
key: EmptyStateType.STICKIES,
|
key: EmptyStateType.STICKIES,
|
||||||
title: "Stickies are quick notes and to-dos you take down on the fly.",
|
title: "Stickies are quick notes and to-dos you take down on the fly.",
|
||||||
description:
|
description:
|
||||||
"Capture your thoughts and ideas effortlessly by creating stickies that you can access anytime and from anywhere.",
|
"Capture ideas, ahas, brainwaves, light-bulb moments without scrambling for a pen and paper, hunting for the recorder app on your phone, or firing up a notes app only to forget all about it later. Keep them all right next to your work so you can easily come back, build them up, or well, discard them.",
|
||||||
path: "/empty-state/stickies/stickies",
|
path: "/empty-state/stickies/stickies",
|
||||||
primaryButton: {
|
primaryButton: {
|
||||||
icon: <Plus className="size-4" />,
|
icon: <Plus className="size-4" />,
|
||||||
|
|
@ -945,8 +945,8 @@ const emptyStateDetails: Record<EmptyStateType, EmptyStateDetails> = {
|
||||||
},
|
},
|
||||||
[EmptyStateType.HOME_WIDGETS]: {
|
[EmptyStateType.HOME_WIDGETS]: {
|
||||||
key: EmptyStateType.HOME_WIDGETS,
|
key: EmptyStateType.HOME_WIDGETS,
|
||||||
title: "It's Quiet Without Widgets, Turn Them On",
|
title: "So much to add, yet such empty",
|
||||||
description: "It looks like all your widgets are turned off. Enable them\nnow to enhance your experience!",
|
description: "You have turned off all your widgets. Turn some or\nall of them back on to see delightful things.",
|
||||||
path: "/empty-state/dashboard/widgets",
|
path: "/empty-state/dashboard/widgets",
|
||||||
primaryButton: {
|
primaryButton: {
|
||||||
icon: <Shapes className="size-4" />,
|
icon: <Shapes className="size-4" />,
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
TSearchResponse,
|
TSearchResponse,
|
||||||
TSearchEntityRequestPayload,
|
TSearchEntityRequestPayload,
|
||||||
TWidgetEntityData,
|
TWidgetEntityData,
|
||||||
|
TActivityEntityData,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
import { APIService } from "@/services/api.service";
|
import { APIService } from "@/services/api.service";
|
||||||
// helpers
|
// helpers
|
||||||
|
|
@ -329,7 +330,7 @@ export class WorkspaceService extends APIService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// recents
|
// recents
|
||||||
async fetchWorkspaceRecents(workspaceSlug: string, entity_name?: string) {
|
async fetchWorkspaceRecents(workspaceSlug: string, entity_name?: string): Promise<TActivityEntityData[]> {
|
||||||
return this.get(`/api/workspaces/${workspaceSlug}/recent-visits/`, {
|
return this.get(`/api/workspaces/${workspaceSlug}/recent-visits/`, {
|
||||||
params: {
|
params: {
|
||||||
entity_name,
|
entity_name,
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 76 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 83 KiB |
Loading…
Add table
Add a link
Reference in a new issue