chore: dashboard empty states, fetching logic (#3455)

* chore: remove one-time fetching

* chore: update empty states

* chore: updated icons

* chore: empty state content
This commit is contained in:
Aaryan Khandelwal 2024-01-24 19:41:02 +05:30 committed by GitHub
parent 9d9d703c62
commit 53b41481a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 386 additions and 2640 deletions

View File

@ -50,32 +50,36 @@ export const AssignedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
};
useEffect(() => {
if (!widgetDetails) return;
const filterDates = getCustomDates(widgetDetails?.widget_filters.target_date ?? "this_week");
const filterDates = getCustomDates(widgetDetails.widget_filters.target_date ?? "this_week");
if (!widgetStats)
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
issue_type: widgetDetails.widget_filters.tab ?? "upcoming",
target_date: filterDates,
expand: "issue_relation",
});
}, [dashboardId, fetchWidgetStats, widgetDetails, widgetStats, workspaceSlug]);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
issue_type: widgetDetails?.widget_filters.tab ?? "upcoming",
target_date: filterDates,
expand: "issue_relation",
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const filterParams = getRedirectionFilters(widgetDetails?.widget_filters.tab ?? "upcoming");
if (!widgetDetails || !widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
return (
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full hover:shadow-custom-shadow-4xl duration-300 flex flex-col">
<div className="flex items-center justify-between gap-2 p-6 pl-7">
<Link
href={`/${workspaceSlug}/workspace-views/assigned/${filterParams}`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
All issues assigned
</Link>
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full hover:shadow-custom-shadow-4xl duration-300 flex flex-col min-h-96">
<div className="flex items-start justify-between gap-2 p-6 pl-7">
<div>
<Link
href={`/${workspaceSlug}/workspace-views/assigned/${filterParams}`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Assigned to you
</Link>
<p className="mt-3 text-xs font-medium text-custom-text-300">
Filtered by{" "}
<span className="border-[0.5px] border-custom-border-300 rounded py-1 px-2 ml-0.5">Due date</span>
</p>
</div>
<DurationFilterDropdown
value={widgetDetails.widget_filters.target_date ?? "this_week"}
onChange={(val) =>
@ -97,11 +101,10 @@ export const AssignedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
<div className="px-6">
<TabsList />
</div>
<Tab.Panels as="div" className="mt-7 h-full">
<Tab.Panels as="div" className="h-full">
{ISSUES_TABS_LIST.map((tab) => (
<Tab.Panel key={tab.key} as="div" className="h-full flex flex-col">
<WidgetIssuesList
filter={widgetDetails.widget_filters.target_date}
issues={widgetStats.issues}
tab={tab.key}
totalIssues={widgetStats.count}

View File

@ -49,29 +49,33 @@ export const CreatedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
};
useEffect(() => {
if (!widgetDetails) return;
if (!widgetStats)
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
issue_type: widgetDetails.widget_filters.tab ?? "upcoming",
target_date: getCustomDates(widgetDetails.widget_filters.target_date ?? "this_week"),
});
}, [dashboardId, fetchWidgetStats, widgetDetails, widgetStats, workspaceSlug]);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
issue_type: widgetDetails?.widget_filters.tab ?? "upcoming",
target_date: getCustomDates(widgetDetails?.widget_filters.target_date ?? "this_week"),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const filterParams = getRedirectionFilters(widgetDetails?.widget_filters.tab ?? "upcoming");
if (!widgetDetails || !widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
return (
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full hover:shadow-custom-shadow-4xl duration-300 flex flex-col">
<div className="flex items-center justify-between gap-2 p-6 pl-7">
<Link
href={`/${workspaceSlug}/workspace-views/created/${filterParams}`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
All issues created
</Link>
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full hover:shadow-custom-shadow-4xl duration-300 flex flex-col min-h-96">
<div className="flex items-start justify-between gap-2 p-6 pl-7">
<div>
<Link
href={`/${workspaceSlug}/workspace-views/created/${filterParams}`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Created by you
</Link>
<p className="mt-3 text-xs font-medium text-custom-text-300">
Filtered by{" "}
<span className="border-[0.5px] border-custom-border-300 rounded py-1 px-2 ml-0.5">Due date</span>
</p>
</div>
<DurationFilterDropdown
value={widgetDetails.widget_filters.target_date ?? "this_week"}
onChange={(val) =>
@ -93,11 +97,10 @@ export const CreatedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
<div className="px-6">
<TabsList />
</div>
<Tab.Panels as="div" className="mt-7 h-full">
<Tab.Panels as="div" className="h-full">
{ISSUES_TABS_LIST.map((tab) => (
<Tab.Panel as="div" className="h-full flex flex-col">
<WidgetIssuesList
filter={widgetDetails.widget_filters.target_date}
issues={widgetStats.issues}
tab={tab.key}
totalIssues={widgetStats.count}

View File

@ -1,19 +1,16 @@
import Image from "next/image";
import { useTheme } from "next-themes";
// helpers
import { cn } from "helpers/common.helper";
// types
import { TDurationFilterOptions, TIssuesListTypes } from "@plane/types";
import { TIssuesListTypes } from "@plane/types";
// constants
import { ASSIGNED_ISSUES_EMPTY_STATES } from "constants/dashboard";
type Props = {
filter: TDurationFilterOptions;
type: TIssuesListTypes;
};
export const AssignedIssuesEmptyState: React.FC<Props> = (props) => {
const { filter, type } = props;
const { type } = props;
// next-themes
const { resolvedTheme } = useTheme();
@ -21,22 +18,13 @@ export const AssignedIssuesEmptyState: React.FC<Props> = (props) => {
const image = resolvedTheme === "dark" ? typeDetails.darkImage : typeDetails.lightImage;
// TODO: update empty state logic to use a general component
return (
<div className="text-center space-y-10 mt-16 flex flex-col items-center">
<p className="text-sm font-medium text-custom-text-300">{typeDetails.title(filter)}</p>
<div
className={cn("w-1/2 h-1/3 p-1.5 pb-0 rounded-t-md", {
"border border-custom-border-200": resolvedTheme === "dark",
})}
style={{
background:
resolvedTheme === "light"
? "linear-gradient(135deg, rgba(235, 243, 255, 0.45) 3.57%, rgba(99, 161, 255, 0.24) 94.16%)"
: "",
}}
>
<div className="text-center space-y-6 flex flex-col items-center">
<div className="h-24 w-24">
<Image src={image} className="w-full h-full" alt="Assigned issues" />
</div>
<p className="text-sm font-medium text-custom-text-300 whitespace-pre-line">{typeDetails.title}</p>
</div>
);
};

View File

@ -1,19 +1,16 @@
import Image from "next/image";
import { useTheme } from "next-themes";
// helpers
import { cn } from "helpers/common.helper";
// types
import { TDurationFilterOptions, TIssuesListTypes } from "@plane/types";
import { TIssuesListTypes } from "@plane/types";
// constants
import { CREATED_ISSUES_EMPTY_STATES } from "constants/dashboard";
type Props = {
filter: TDurationFilterOptions;
type: TIssuesListTypes;
};
export const CreatedIssuesEmptyState: React.FC<Props> = (props) => {
const { filter, type } = props;
const { type } = props;
// next-themes
const { resolvedTheme } = useTheme();
@ -22,21 +19,11 @@ export const CreatedIssuesEmptyState: React.FC<Props> = (props) => {
const image = resolvedTheme === "dark" ? typeDetails.darkImage : typeDetails.lightImage;
return (
<div className="text-center space-y-10 mt-16 flex flex-col items-center">
<p className="text-sm font-medium text-custom-text-300">{typeDetails.title(filter)}</p>
<div
className={cn("w-1/2 h-1/3 p-1.5 pb-0 rounded-t-md", {
"border border-custom-border-200": resolvedTheme === "dark",
})}
style={{
background:
resolvedTheme === "light"
? "linear-gradient(135deg, rgba(235, 243, 255, 0.45) 3.57%, rgba(99, 161, 255, 0.24) 94.16%)"
: "",
}}
>
<Image src={image} className="w-full h-full" alt="Created issues" />
<div className="text-center space-y-6 flex flex-col items-center">
<div className="h-24 w-24">
<Image src={image} className="w-full h-full" alt="Assigned issues" />
</div>
<p className="text-sm font-medium text-custom-text-300 whitespace-pre-line">{typeDetails.title}</p>
</div>
);
};

View File

@ -3,43 +3,23 @@ import { useTheme } from "next-themes";
// assets
import DarkImage from "public/empty-state/dashboard/dark/issues-by-priority.svg";
import LightImage from "public/empty-state/dashboard/light/issues-by-priority.svg";
// helpers
import { cn } from "helpers/common.helper";
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
// types
import { TDurationFilterOptions } from "@plane/types";
type Props = {
filter: TDurationFilterOptions;
};
export const IssuesByPriorityEmptyState: React.FC<Props> = (props) => {
const { filter } = props;
export const IssuesByPriorityEmptyState = () => {
// next-themes
const { resolvedTheme } = useTheme();
const image = resolvedTheme === "dark" ? DarkImage : LightImage;
return (
<div className="text-center space-y-10 mt-16 flex flex-col items-center">
<p className="text-sm font-medium text-custom-text-300">
No assigned issues {replaceUnderscoreIfSnakeCase(filter)}.
</p>
<div
className={cn("w-1/2 h-1/3 p-1.5 pb-0 rounded-t-md", {
"border border-custom-border-200": resolvedTheme === "dark",
})}
style={{
background:
resolvedTheme === "light"
? "linear-gradient(135deg, rgba(235, 243, 255, 0.45) 3.57%, rgba(99, 161, 255, 0.24) 94.16%)"
: "",
}}
>
<Image
src={resolvedTheme === "dark" ? DarkImage : LightImage}
className="w-full h-full"
alt="Issues by priority"
/>
<div className="text-center space-y-6 flex flex-col items-center">
<div className="h-24 w-24">
<Image src={image} className="w-full h-full" alt="Issues by state group" />
</div>
<p className="text-sm font-medium text-custom-text-300">
Issues assigned to you, broken down by
<br />
priority will show up here.
</p>
</div>
);
};

View File

@ -3,43 +3,23 @@ import { useTheme } from "next-themes";
// assets
import DarkImage from "public/empty-state/dashboard/dark/issues-by-state-group.svg";
import LightImage from "public/empty-state/dashboard/light/issues-by-state-group.svg";
// helpers
import { cn } from "helpers/common.helper";
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
// types
import { TDurationFilterOptions } from "@plane/types";
type Props = {
filter: TDurationFilterOptions;
};
export const IssuesByStateGroupEmptyState: React.FC<Props> = (props) => {
const { filter } = props;
export const IssuesByStateGroupEmptyState = () => {
// next-themes
const { resolvedTheme } = useTheme();
const image = resolvedTheme === "dark" ? DarkImage : LightImage;
return (
<div className="text-center space-y-10 mt-16 flex flex-col items-center">
<p className="text-sm font-medium text-custom-text-300">
No assigned issues {replaceUnderscoreIfSnakeCase(filter)}.
</p>
<div
className={cn("w-1/2 h-1/3 p-1.5 pb-0 rounded-t-md", {
"border border-custom-border-200": resolvedTheme === "dark",
})}
style={{
background:
resolvedTheme === "light"
? "linear-gradient(135deg, rgba(235, 243, 255, 0.45) 3.57%, rgba(99, 161, 255, 0.24) 94.16%)"
: "",
}}
>
<Image
src={resolvedTheme === "dark" ? DarkImage : LightImage}
className="w-full h-full"
alt="Issues by state group"
/>
<div className="text-center space-y-6 flex flex-col items-center">
<div className="h-24 w-24">
<Image src={image} className="w-full h-full" alt="Issues by state group" />
</div>
<p className="text-sm font-medium text-custom-text-300">
Issue assigned to you, broken down by state,
<br />
will show up here.
</p>
</div>
);
};

View File

@ -3,40 +3,23 @@ import { useTheme } from "next-themes";
// assets
import DarkImage from "public/empty-state/dashboard/dark/recent-activity.svg";
import LightImage from "public/empty-state/dashboard/light/recent-activity.svg";
// helpers
import { cn } from "helpers/common.helper";
type Props = {};
export const RecentActivityEmptyState: React.FC<Props> = (props) => {
const {} = props;
export const RecentActivityEmptyState = () => {
// next-themes
const { resolvedTheme } = useTheme();
const image = resolvedTheme === "dark" ? DarkImage : LightImage;
return (
<div className="text-center space-y-10 mt-16 flex flex-col items-center">
<p className="text-sm font-medium text-custom-text-300">
Feels new, go and explore our tool in depth and come back
<br />
to see your activity.
</p>
<div
className={cn("w-3/5 h-1/3 p-1.5 pb-0 rounded-t-md", {
"border border-custom-border-200": resolvedTheme === "dark",
})}
style={{
background:
resolvedTheme === "light"
? "linear-gradient(135deg, rgba(235, 243, 255, 0.45) 3.57%, rgba(99, 161, 255, 0.24) 94.16%)"
: "",
}}
>
<Image
src={resolvedTheme === "dark" ? DarkImage : LightImage}
className="w-full h-full"
alt="Issues by priority"
/>
<div className="text-center space-y-6 flex flex-col items-center">
<div className="h-24 w-24">
<Image src={image} className="w-full h-full" alt="Issues by state group" />
</div>
<p className="text-sm font-medium text-custom-text-300">
All your issue activities across
<br />
projects will show up here.
</p>
</div>
);
};

View File

@ -1,39 +1,38 @@
import Image from "next/image";
import { useTheme } from "next-themes";
// assets
import DarkImage from "public/empty-state/dashboard/dark/recent-collaborators.svg";
import LightImage from "public/empty-state/dashboard/light/recent-collaborators.svg";
// helpers
import { cn } from "helpers/common.helper";
import DarkImage1 from "public/empty-state/dashboard/dark/recent-collaborators-1.svg";
import DarkImage2 from "public/empty-state/dashboard/dark/recent-collaborators-2.svg";
import DarkImage3 from "public/empty-state/dashboard/dark/recent-collaborators-3.svg";
import LightImage1 from "public/empty-state/dashboard/light/recent-collaborators-1.svg";
import LightImage2 from "public/empty-state/dashboard/light/recent-collaborators-2.svg";
import LightImage3 from "public/empty-state/dashboard/light/recent-collaborators-3.svg";
type Props = {};
export const RecentCollaboratorsEmptyState: React.FC<Props> = (props) => {
const {} = props;
export const RecentCollaboratorsEmptyState = () => {
// next-themes
const { resolvedTheme } = useTheme();
const image1 = resolvedTheme === "dark" ? DarkImage1 : LightImage1;
const image2 = resolvedTheme === "dark" ? DarkImage2 : LightImage2;
const image3 = resolvedTheme === "dark" ? DarkImage3 : LightImage3;
return (
<div className="mt-7 px-7 flex justify-between gap-16">
<p className="text-sm font-medium text-custom-text-300">
People are excited to work with you, once they do you will find your frequent collaborators here.
<div className="mt-7 mb-16 px-36 flex flex-col lg:flex-row items-center justify-between gap-x-24 gap-y-16">
<p className="text-sm font-medium text-custom-text-300 lg:w-2/5 flex-shrink-0 text-center lg:text-left">
Compare your activities with the top
<br />
seven in your project.
</p>
<div
className={cn("w-3/5 h-1/3 p-1.5 pb-0 rounded-t-md flex-shrink-0 self-end", {
"border border-custom-border-200": resolvedTheme === "dark",
})}
style={{
background:
resolvedTheme === "light"
? "linear-gradient(135deg, rgba(235, 243, 255, 0.45) 3.57%, rgba(99, 161, 255, 0.24) 94.16%)"
: "",
}}
>
<Image
src={resolvedTheme === "dark" ? DarkImage : LightImage}
className="w-full h-full"
alt="Recent collaborators"
/>
<div className="flex items-center justify-evenly gap-20 lg:w-3/5 flex-shrink-0">
<div className="h-24 w-24 flex-shrink-0">
<Image src={image1} className="w-full h-full" alt="Recent collaborators" />
</div>
<div className="h-24 w-24 flex-shrink-0">
<Image src={image2} className="w-full h-full" alt="Recent collaborators" />
</div>
<div className="h-24 w-24 flex-shrink-0 hidden xl:block">
<Image src={image3} className="w-full h-full" alt="Recent collaborators" />
</div>
</div>
</div>
);

View File

@ -19,10 +19,9 @@ import { Loader, getButtonStyling } from "@plane/ui";
import { cn } from "helpers/common.helper";
import { getRedirectionFilters } from "helpers/dashboard.helper";
// types
import { TDurationFilterOptions, TIssue, TIssuesListTypes } from "@plane/types";
import { TIssue, TIssuesListTypes } from "@plane/types";
export type WidgetIssuesListProps = {
filter: TDurationFilterOptions | undefined;
isLoading: boolean;
issues: TIssue[];
tab: TIssuesListTypes;
@ -32,7 +31,7 @@ export type WidgetIssuesListProps = {
};
export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
const { filter, isLoading, issues, tab, totalIssues, type, workspaceSlug } = props;
const { isLoading, issues, tab, totalIssues, type, workspaceSlug } = props;
// store hooks
const { setPeekIssue } = useIssueDetail();
@ -62,7 +61,7 @@ export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
<>
<div className="h-full">
{isLoading ? (
<Loader className="mx-6 mt-2 space-y-4">
<Loader className="mt-7 mx-6 space-y-4">
<Loader.Item height="25px" />
<Loader.Item height="25px" />
<Loader.Item height="25px" />
@ -70,7 +69,7 @@ export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
</Loader>
) : issues.length > 0 ? (
<>
<div className="mx-6 border-b-[0.5px] border-custom-border-200 grid grid-cols-6 gap-1 text-xs text-custom-text-300 pb-1">
<div className="mt-7 mx-6 border-b-[0.5px] border-custom-border-200 grid grid-cols-6 gap-1 text-xs text-custom-text-300 pb-1">
<h6
className={cn("pl-1 flex items-center gap-1 col-span-4", {
"col-span-6": type === "assigned" && tab === "completed",
@ -105,9 +104,9 @@ export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
</div>
</>
) : (
<div className="h-full grid items-end">
{type === "assigned" && <AssignedIssuesEmptyState filter={filter ?? "this_week"} type={tab} />}
{type === "created" && <CreatedIssuesEmptyState filter={filter ?? "this_week"} type={tab} />}
<div className="h-full grid place-items-center">
{type === "assigned" && <AssignedIssuesEmptyState type={tab} />}
{type === "created" && <CreatedIssuesEmptyState type={tab} />}
</div>
)}
</div>

View File

@ -91,14 +91,12 @@ export const IssuesByPriorityWidget: React.FC<WidgetProps> = observer((props) =>
};
useEffect(() => {
if (!widgetDetails) return;
if (!widgetStats)
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
target_date: getCustomDates(widgetDetails.widget_filters.target_date ?? "this_week"),
});
}, [dashboardId, fetchWidgetStats, widgetDetails, widgetStats, workspaceSlug]);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
target_date: getCustomDates(widgetDetails?.widget_filters.target_date ?? "this_week"),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!widgetDetails || !widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
@ -130,14 +128,20 @@ export const IssuesByPriorityWidget: React.FC<WidgetProps> = observer((props) =>
};
return (
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full py-6 hover:shadow-custom-shadow-4xl duration-300 overflow-hidden">
<div className="flex items-center justify-between gap-2 pl-7 pr-6">
<Link
href={`/${workspaceSlug}/workspace-views/assigned`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Priority of assigned issues
</Link>
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full py-6 hover:shadow-custom-shadow-4xl duration-300 overflow-hidden min-h-96">
<div className="flex items-start justify-between gap-2 pl-7 pr-6">
<div>
<Link
href={`/${workspaceSlug}/workspace-views/assigned`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Assigned by priority
</Link>
<p className="mt-3 text-xs font-medium text-custom-text-300">
Filtered by{" "}
<span className="border-[0.5px] border-custom-border-300 rounded py-1 px-2 ml-0.5">Due date</span>
</p>
</div>
<DurationFilterDropdown
value={widgetDetails.widget_filters.target_date ?? "this_week"}
onChange={(val) =>
@ -196,8 +200,8 @@ export const IssuesByPriorityWidget: React.FC<WidgetProps> = observer((props) =>
</div>
</div>
) : (
<div className="h-full grid items-end">
<IssuesByPriorityEmptyState filter={widgetDetails.widget_filters.target_date ?? "this_week"} />
<div className="h-full grid place-items-center">
<IssuesByPriorityEmptyState />
</div>
)}
</div>

View File

@ -51,14 +51,12 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
// fetch widget stats
useEffect(() => {
if (!widgetDetails) return;
if (!widgetStats)
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
target_date: getCustomDates(widgetDetails.widget_filters.target_date ?? "this_week"),
});
}, [dashboardId, fetchWidgetStats, widgetDetails, widgetStats, workspaceSlug]);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
target_date: getCustomDates(widgetDetails?.widget_filters.target_date ?? "this_week"),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// set active group for center metric
useEffect(() => {
@ -129,14 +127,20 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
};
return (
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full py-6 hover:shadow-custom-shadow-4xl duration-300 overflow-hidden">
<div className="flex items-center justify-between gap-2 pl-7 pr-6">
<Link
href={`/${workspaceSlug}/workspace-views/assigned`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
State of assigned issues
</Link>
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full py-6 hover:shadow-custom-shadow-4xl duration-300 overflow-hidden min-h-96">
<div className="flex items-start justify-between gap-2 pl-7 pr-6">
<div>
<Link
href={`/${workspaceSlug}/workspace-views/assigned`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Assigned by state
</Link>
<p className="mt-3 text-xs font-medium text-custom-text-300">
Filtered by{" "}
<span className="border-[0.5px] border-custom-border-300 rounded py-1 px-2 ml-0.5">Due date</span>
</p>
</div>
<DurationFilterDropdown
value={widgetDetails.widget_filters.target_date ?? "this_week"}
onChange={(val) =>
@ -204,8 +208,8 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
</div>
</div>
) : (
<div className="h-full grid items-end">
<IssuesByStateGroupEmptyState filter={widgetDetails.widget_filters.target_date ?? "this_week"} />
<div className="h-full grid place-items-center">
<IssuesByStateGroupEmptyState />
</div>
)}
</div>

View File

@ -54,11 +54,11 @@ export const OverviewStatsWidget: React.FC<WidgetProps> = observer((props) => {
];
useEffect(() => {
if (!widgetStats)
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
});
}, [dashboardId, fetchWidgetStats, widgetStats, workspaceSlug]);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;

View File

@ -25,18 +25,18 @@ export const RecentActivityWidget: React.FC<WidgetProps> = observer((props) => {
const widgetStats = getWidgetStats<TRecentActivityWidgetResponse[]>(workspaceSlug, dashboardId, WIDGET_KEY);
useEffect(() => {
if (!widgetStats)
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
});
}, [dashboardId, fetchWidgetStats, widgetStats, workspaceSlug]);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
return (
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full py-6 hover:shadow-custom-shadow-4xl duration-300">
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full py-6 hover:shadow-custom-shadow-4xl duration-300 min-h-96">
<Link href="/profile/activity" className="text-lg font-semibold text-custom-text-300 mx-7 hover:underline">
My activity
Your issue activities
</Link>
{widgetStats.length > 0 ? (
<div className="space-y-6 mt-4 mx-7">
@ -85,7 +85,7 @@ export const RecentActivityWidget: React.FC<WidgetProps> = observer((props) => {
))}
</div>
) : (
<div className="h-full grid items-end">
<div className="h-full grid place-items-center">
<RecentActivityEmptyState />
</div>
)}

View File

@ -57,18 +57,21 @@ export const RecentCollaboratorsWidget: React.FC<WidgetProps> = observer((props)
const widgetStats = getWidgetStats<TRecentCollaboratorsWidgetResponse[]>(workspaceSlug, dashboardId, WIDGET_KEY);
useEffect(() => {
if (!widgetStats)
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
});
}, [dashboardId, fetchWidgetStats, widgetStats, workspaceSlug]);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
return (
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full hover:shadow-custom-shadow-4xl duration-300">
<div className="flex items-center justify-between gap-2 px-7 pt-6">
<h4 className="text-lg font-semibold text-custom-text-300">Collaborators</h4>
<div className="px-7 pt-6">
<h4 className="text-lg font-semibold text-custom-text-300">Most active members</h4>
<p className="mt-2 text-xs font-medium text-custom-text-300">
Top eight active members in your project by last activity
</p>
</div>
{widgetStats.length > 1 ? (
<div className="mt-7 mb-6 grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 xl:grid-cols-8 gap-2 gap-y-8">
@ -82,7 +85,7 @@ export const RecentCollaboratorsWidget: React.FC<WidgetProps> = observer((props)
))}
</div>
) : (
<div className="h-full grid items-end">
<div className="h-full grid place-items-center">
<RecentCollaboratorsEmptyState />
</div>
)}

View File

@ -81,21 +81,21 @@ export const RecentProjectsWidget: React.FC<WidgetProps> = observer((props) => {
const canCreateProject = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
useEffect(() => {
if (!widgetStats)
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
});
}, [dashboardId, fetchWidgetStats, widgetStats, workspaceSlug]);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
return (
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full py-6 hover:shadow-custom-shadow-4xl duration-300">
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full py-6 hover:shadow-custom-shadow-4xl duration-300 min-h-96">
<Link
href={`/${workspaceSlug}/projects`}
className="text-lg font-semibold text-custom-text-300 mx-7 hover:underline"
>
My projects
Your projects
</Link>
<div className="space-y-8 mt-4 mx-7">
{canCreateProject && (

View File

@ -1,17 +1,11 @@
import { linearGradientDef } from "@nivo/core";
// assets
import UpcomingAssignedIssuesDark from "public/empty-state/dashboard/dark/upcoming-assigned-issues.svg";
import UpcomingAssignedIssuesLight from "public/empty-state/dashboard/light/upcoming-assigned-issues.svg";
import OverdueAssignedIssuesDark from "public/empty-state/dashboard/dark/overdue-assigned-issues.svg";
import OverdueAssignedIssuesLight from "public/empty-state/dashboard/light/overdue-assigned-issues.svg";
import CompletedAssignedIssuesDark from "public/empty-state/dashboard/dark/completed-assigned-issues.svg";
import CompletedAssignedIssuesLight from "public/empty-state/dashboard/light/completed-assigned-issues.svg";
import UpcomingCreatedIssuesDark from "public/empty-state/dashboard/dark/upcoming-created-issues.svg";
import UpcomingCreatedIssuesLight from "public/empty-state/dashboard/light/upcoming-created-issues.svg";
import OverdueCreatedIssuesDark from "public/empty-state/dashboard/dark/overdue-created-issues.svg";
import OverdueCreatedIssuesLight from "public/empty-state/dashboard/light/overdue-created-issues.svg";
import CompletedCreatedIssuesDark from "public/empty-state/dashboard/dark/completed-created-issues.svg";
import CompletedCreatedIssuesLight from "public/empty-state/dashboard/light/completed-created-issues.svg";
import UpcomingIssuesDark from "public/empty-state/dashboard/dark/upcoming-issues.svg";
import UpcomingIssuesLight from "public/empty-state/dashboard/light/upcoming-issues.svg";
import OverdueIssuesDark from "public/empty-state/dashboard/dark/overdue-issues.svg";
import OverdueIssuesLight from "public/empty-state/dashboard/light/overdue-issues.svg";
import CompletedIssuesDark from "public/empty-state/dashboard/dark/completed-issues.svg";
import CompletedIssuesLight from "public/empty-state/dashboard/light/completed-issues.svg";
// types
import { TDurationFilterOptions, TIssuesListTypes, TStateGroups } from "@plane/types";
import { Props } from "components/icons/types";
@ -172,84 +166,43 @@ export const ISSUES_TABS_LIST: {
},
{
key: "completed",
label: "Completed",
label: "Marked completed",
},
];
// empty state constants
const ASSIGNED_ISSUES_DURATION_TITLES: {
[type in TIssuesListTypes]: {
[duration in TDurationFilterOptions]: string;
};
} = {
upcoming: {
today: "today",
this_week: "yet in this week",
this_month: "yet in this month",
this_year: "yet in this year",
},
overdue: {
today: "today",
this_week: "in this week",
this_month: "in this month",
this_year: "in this year",
},
completed: {
today: "today",
this_week: "this week",
this_month: "this month",
this_year: "this year",
},
};
const CREATED_ISSUES_DURATION_TITLES: {
[duration in TDurationFilterOptions]: string;
} = {
today: "today",
this_week: "in this week",
this_month: "in this month",
this_year: "in this year",
};
export const ASSIGNED_ISSUES_EMPTY_STATES = {
upcoming: {
title: (duration: TDurationFilterOptions) =>
`No issues assigned to you ${ASSIGNED_ISSUES_DURATION_TITLES.upcoming[duration]}.`,
darkImage: UpcomingAssignedIssuesDark,
lightImage: UpcomingAssignedIssuesLight,
title: "Upcoming issues assigned to\nyou will show up here.",
darkImage: UpcomingIssuesDark,
lightImage: UpcomingIssuesLight,
},
overdue: {
title: (duration: TDurationFilterOptions) =>
`No issues with due dates ${ASSIGNED_ISSUES_DURATION_TITLES.overdue[duration]} are open.`,
darkImage: OverdueAssignedIssuesDark,
lightImage: OverdueAssignedIssuesLight,
title: "Issues assigned to you that are past\ntheir due date will show up here.",
darkImage: OverdueIssuesDark,
lightImage: OverdueIssuesLight,
},
completed: {
title: (duration: TDurationFilterOptions) =>
`No issues completed by you ${ASSIGNED_ISSUES_DURATION_TITLES.completed[duration]}.`,
darkImage: CompletedAssignedIssuesDark,
lightImage: CompletedAssignedIssuesLight,
title: "Issues assigned to you that you have\nmarked Completed will show up here.",
darkImage: CompletedIssuesDark,
lightImage: CompletedIssuesLight,
},
};
export const CREATED_ISSUES_EMPTY_STATES = {
upcoming: {
title: (duration: TDurationFilterOptions) =>
`No created issues have deadlines coming up ${CREATED_ISSUES_DURATION_TITLES[duration]}.`,
darkImage: UpcomingCreatedIssuesDark,
lightImage: UpcomingCreatedIssuesLight,
title: "Upcoming issues you created\nwill show up here.",
darkImage: UpcomingIssuesDark,
lightImage: UpcomingIssuesLight,
},
overdue: {
title: (duration: TDurationFilterOptions) =>
`No created issues with due dates ${CREATED_ISSUES_DURATION_TITLES[duration]} are open.`,
darkImage: OverdueCreatedIssuesDark,
lightImage: OverdueCreatedIssuesLight,
title: "Issues created by you that are past their\ndue date will show up here.",
darkImage: OverdueIssuesDark,
lightImage: OverdueIssuesLight,
},
completed: {
title: (duration: TDurationFilterOptions) =>
`No created issues are completed ${CREATED_ISSUES_DURATION_TITLES[duration]}.`,
darkImage: CompletedCreatedIssuesDark,
lightImage: CompletedCreatedIssuesLight,
title: "Issues created by you that you have\nmarked completed will show up here.",
darkImage: CompletedIssuesDark,
lightImage: CompletedIssuesLight,
},
};

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 144 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 249 KiB

View File

@ -0,0 +1,5 @@
<svg width="94" height="94" viewBox="0 0 94 94" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="46.7188" cy="46.7188" r="46.7188" fill="#171717"/>
<path d="M47.0013 72.8346C61.2687 72.8346 72.8346 61.2687 72.8346 47.0013C72.8346 32.7339 61.2687 21.168 47.0013 21.168C32.7339 21.168 21.168 32.7339 21.168 47.0013C21.168 61.2687 32.7339 72.8346 47.0013 72.8346Z" stroke="#4C4C4F" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M34.082 47L41.832 54.75L57.332 39.25" stroke="#696B74" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 593 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 666 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 202 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 275 KiB

View File

@ -0,0 +1,9 @@
<svg width="94" height="94" viewBox="0 0 94 94" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="46.7188" cy="46.7188" r="46.7188" fill="#171717"/>
<path d="M69.625 35.4362V31.7487C69.625 30.4447 69.107 29.1941 68.1849 28.2721C67.2629 27.35 66.0123 26.832 64.7083 26.832H30.2917C28.9877 26.832 27.7371 27.35 26.8151 28.2721C25.893 29.1941 25.375 30.4447 25.375 31.7487V66.1654C25.375 67.4693 25.893 68.7199 26.8151 69.642C27.7371 70.564 28.9877 71.082 30.2917 71.082H38.8958" stroke="#4C4C4F" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M57.332 21.918V31.7513" stroke="#696B74" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M37.668 21.918V31.7513" stroke="#696B74" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M25.375 41.582H37.6667" stroke="#4C4C4F" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M61.0195 60.0221L57.332 57.0721V51.418" stroke="#696B74" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M57.332 71.082C65.4782 71.082 72.082 64.4782 72.082 56.332C72.082 48.1858 65.4782 41.582 57.332 41.582C49.1858 41.582 42.582 48.1858 42.582 56.332C42.582 64.4782 49.1858 71.082 57.332 71.082Z" stroke="#696B74" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 363 KiB

After

Width:  |  Height:  |  Size: 941 B

View File

@ -0,0 +1,6 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="100" fill="#171717"/>
<path d="M99.6846 98.4238C113.579 98.4238 124.842 87.1606 124.842 73.2666C124.842 59.3726 113.579 48.1094 99.6846 48.1094C85.7906 48.1094 74.5273 59.3726 74.5273 73.2666C74.5273 87.1606 85.7906 98.4238 99.6846 98.4238Z" fill="#696B74"/>
<path d="M145.28 139.701C145.28 129.228 139.926 119.183 132.014 111.777C124.101 104.372 113.37 100.211 102.18 100.211C90.9898 100.211 80.2583 104.372 72.3459 111.777C64.4334 119.183 59.9883 129.228 59.9883 139.701" fill="#363638" stroke="#363638" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M144.723 135.61C146.059 140.899 140.278 147.705 132.365 152.07C124.453 156.436 115.016 158.204 103.826 158.204C92.636 158.204 81.9044 155.751 73.992 151.386C65.8129 148.307 57.5454 140.561 61.6344 134.926L103.826 134.926L144.723 135.61Z" fill="#363638"/>
</svg>

After

Width:  |  Height:  |  Size: 972 B

View File

@ -0,0 +1,6 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="100" fill="#171717"/>
<path d="M99.6846 98.4238C113.579 98.4238 124.842 87.1606 124.842 73.2666C124.842 59.3727 113.579 48.1094 99.6846 48.1094C85.7906 48.1094 74.5273 59.3727 74.5273 73.2666C74.5273 87.1606 85.7906 98.4238 99.6846 98.4238Z" fill="#323232"/>
<path d="M145.28 139.701C145.28 129.228 139.926 119.183 132.014 111.777C124.101 104.372 113.37 100.211 102.18 100.211C90.9898 100.211 80.2583 104.372 72.3459 111.777C64.4334 119.183 59.9883 129.228 59.9883 139.701" fill="#505050" stroke="#505050" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M144.723 135.61C146.059 140.899 140.278 147.705 132.365 152.07C124.453 156.436 115.016 158.204 103.826 158.204C92.636 158.204 81.9044 155.751 73.992 151.386C65.8129 148.307 57.5454 140.561 61.6344 134.926L103.826 134.926L144.723 135.61Z" fill="#505050"/>
</svg>

After

Width:  |  Height:  |  Size: 972 B

View File

@ -0,0 +1,6 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="100" fill="#171717"/>
<path d="M99.6846 98.4238C113.579 98.4238 124.842 87.1606 124.842 73.2666C124.842 59.3726 113.579 48.1094 99.6846 48.1094C85.7906 48.1094 74.5273 59.3726 74.5273 73.2666C74.5273 87.1606 85.7906 98.4238 99.6846 98.4238Z" fill="#3B3D40"/>
<path d="M145.28 139.701C145.28 129.228 139.926 119.183 132.014 111.777C124.101 104.372 113.37 100.211 102.18 100.211C90.9898 100.211 80.2583 104.372 72.3459 111.777C64.4334 119.183 59.9883 129.228 59.9883 139.701" fill="#4E4E54" stroke="#4E4E54" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M144.723 135.61C146.059 140.899 140.278 147.705 132.365 152.07C124.453 156.436 115.016 158.204 103.826 158.204C92.636 158.204 81.9044 155.751 73.992 151.386C65.8129 148.307 57.5454 140.561 61.6344 134.926L103.826 134.926L144.723 135.61Z" fill="#4E4E54"/>
</svg>

After

Width:  |  Height:  |  Size: 972 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 306 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 215 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 285 KiB

View File

@ -0,0 +1,13 @@
<svg width="94" height="94" viewBox="0 0 94 94" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="46.7188" cy="46.7188" r="46.7188" fill="#171717"/>
<g clip-path="url(#clip0_6556_126714)">
<path d="M22.3683 30.8508L46.374 21.6169C47.6902 21.1106 49.1029 22.0889 49.0919 23.4991L49.0807 24.9392L48.5304 51.0826C48.5125 51.9323 47.9595 52.6779 47.1516 52.9418L27.2534 59.4394L23.7288 60.635C22.4318 61.075 21.0863 60.1106 21.0863 58.741L21.0863 32.7175C21.0863 31.89 21.5959 31.1479 22.3683 30.8508Z" fill="#5D5D63"/>
<path d="M32.9995 38.1587L57.0052 28.9248C58.3214 28.4185 59.7342 29.3968 59.7232 30.807L59.712 32.2471L59.7119 57.5857C59.7119 58.4323 59.1789 59.1872 58.3811 59.4704L37.8846 66.7473L34.36 67.9429C33.0631 68.3829 31.7175 67.4185 31.7175 66.0489L31.7175 40.0254C31.7175 39.1979 32.2272 38.4558 32.9995 38.1587Z" fill="#4E4E54"/>
<path d="M68.8117 65.0919C69.5835 64.795 70.0929 64.0537 70.0935 63.2267L70.1116 37.3209C70.1126 35.9167 68.7041 34.9487 67.3936 35.4528L43.3671 44.6948C42.5947 44.9919 42.0851 45.7339 42.0851 46.5615L42.0851 72.4629C42.0851 73.8666 43.4932 74.8336 44.8033 74.3295L68.8117 65.0919Z" fill="#323333"/>
</g>
<defs>
<clipPath id="clip0_6556_126714">
<rect width="69" height="69" fill="white" transform="translate(81 13) rotate(90)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 152 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 249 KiB

View File

@ -0,0 +1,5 @@
<svg width="94" height="94" viewBox="0 0 94 94" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="46.7188" cy="46.7188" r="46.7188" fill="#F9F9FB"/>
<path d="M47.0013 72.8346C61.2687 72.8346 72.8346 61.2687 72.8346 47.0013C72.8346 32.7339 61.2687 21.168 47.0013 21.168C32.7339 21.168 21.168 32.7339 21.168 47.0013C21.168 61.2687 32.7339 72.8346 47.0013 72.8346Z" stroke="#CDCED6" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M34.082 47L41.832 54.75L57.332 39.25" stroke="#B9BBC6" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 593 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 666 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 204 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 275 KiB

View File

@ -0,0 +1,9 @@
<svg width="94" height="94" viewBox="0 0 94 94" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="46.7188" cy="46.7188" r="46.7188" fill="#F9F9FB"/>
<path d="M74.5 34.2487V29.9987C74.5 28.4958 73.903 27.0545 72.8403 25.9918C71.7776 24.9291 70.3362 24.332 68.8333 24.332H29.1667C27.6638 24.332 26.2224 24.9291 25.1597 25.9918C24.097 27.0545 23.5 28.4958 23.5 29.9987V69.6654C23.5 71.1683 24.097 72.6096 25.1597 73.6723C26.2224 74.735 27.6638 75.332 29.1667 75.332H39.0833" stroke="#D9D9E0" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M60.332 18.668V30.0013" stroke="#CDCED6" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M37.668 18.668V30.0013" stroke="#CDCED6" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M23.5 41.332H37.6667" stroke="#D9D9E0" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M64.582 62.5846L60.332 59.1846V52.668" stroke="#B9BBC6" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M60.332 75.332C69.7209 75.332 77.332 67.7209 77.332 58.332C77.332 48.9432 69.7209 41.332 60.332 41.332C50.9432 41.332 43.332 48.9432 43.332 58.332C43.332 67.7209 50.9432 75.332 60.332 75.332Z" stroke="#B9BBC6" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 363 KiB

After

Width:  |  Height:  |  Size: 941 B

View File

@ -0,0 +1,7 @@
<svg width="98" height="98" viewBox="0 0 98 98" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="49" cy="49" r="49" fill="#F8F9FA"/>
<path d="M48.8466 48.2283C55.6546 48.2283 61.1736 42.7093 61.1736 35.9013C61.1736 29.0932 55.6546 23.5742 48.8466 23.5742C42.0385 23.5742 36.5195 29.0932 36.5195 35.9013C36.5195 42.7093 42.0385 48.2283 48.8466 48.2283Z" fill="#F0F0F3"/>
<path d="M71.1877 68.4519C71.1877 63.3199 68.564 58.398 64.6869 54.7691C60.8098 51.1402 55.5514 49.1016 50.0683 49.1016C44.5853 49.1016 39.3268 51.1402 35.4497 54.7691C31.5727 58.398 29.3945 63.3199 29.3945 68.4519" fill="#CED4DA"/>
<path d="M71.1877 68.4519C71.1877 63.3199 68.564 58.398 64.6869 54.7691C60.8098 51.1402 55.5514 49.1016 50.0683 49.1016C44.5853 49.1016 39.3268 51.1402 35.4497 54.7691C31.5727 58.398 29.3945 63.3199 29.3945 68.4519C32.1264 78.8118 67.0106 81.1231 71.1877 68.4519Z" stroke="#CED4DA" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M70.9144 66.4486C71.5691 69.0402 68.7363 72.3749 64.8592 74.514C60.9821 76.6531 56.3578 77.5195 50.8747 77.5195C45.3917 77.5195 40.1332 76.3178 36.2562 74.1787C32.2484 72.6698 28.1973 68.8746 30.2009 66.1133L50.8747 66.1133L70.9144 66.4486Z" fill="#CED4DA"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,7 @@
<svg width="98" height="98" viewBox="0 0 98 98" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="49" cy="49" r="49" fill="#F8F9FA"/>
<path d="M48.8466 48.2283C55.6546 48.2283 61.1736 42.7093 61.1736 35.9013C61.1736 29.0932 55.6546 23.5742 48.8466 23.5742C42.0385 23.5742 36.5195 29.0932 36.5195 35.9013C36.5195 42.7093 42.0385 48.2283 48.8466 48.2283Z" fill="#E0E0E0"/>
<path d="M71.1877 68.4519C71.1877 63.3199 68.564 58.398 64.6869 54.7691C60.8098 51.1402 55.5514 49.1016 50.0683 49.1016C44.5853 49.1016 39.3268 51.1402 35.4497 54.7691C31.5727 58.398 29.3945 63.3199 29.3945 68.4519" fill="#B9BBC6"/>
<path d="M71.1877 68.4519C71.1877 63.3199 68.564 58.398 64.6869 54.7691C60.8098 51.1402 55.5514 49.1016 50.0683 49.1016C44.5853 49.1016 39.3268 51.1402 35.4497 54.7691C31.5727 58.398 29.3945 63.3199 29.3945 68.4519C32.1264 78.8118 67.0106 81.1231 71.1877 68.4519Z" stroke="#B9BBC6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M70.9144 66.4486C71.5691 69.0402 68.7363 72.3749 64.8592 74.514C60.9821 76.6531 56.3578 77.5195 50.8747 77.5195C45.3917 77.5195 40.1332 76.3178 36.2562 74.1787C32.2484 72.6698 28.1973 68.8746 30.2009 66.1133L50.8747 66.1133L70.9144 66.4486Z" fill="#B9BBC6"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,7 @@
<svg width="98" height="98" viewBox="0 0 98 98" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="49" cy="49" r="49" fill="#F8F9FA"/>
<path d="M48.8466 48.2283C55.6546 48.2283 61.1736 42.7093 61.1736 35.9013C61.1736 29.0932 55.6546 23.5742 48.8466 23.5742C42.0385 23.5742 36.5195 29.0932 36.5195 35.9013C36.5195 42.7093 42.0385 48.2283 48.8466 48.2283Z" fill="#CDCED6"/>
<path d="M71.1877 68.4519C71.1877 63.3199 68.564 58.398 64.6869 54.7691C60.8098 51.1402 55.5514 49.1016 50.0683 49.1016C44.5853 49.1016 39.3268 51.1402 35.4497 54.7691C31.5727 58.398 29.3945 63.3199 29.3945 68.4519" fill="#E0E1E6"/>
<path d="M71.1877 68.4519C71.1877 63.3199 68.564 58.398 64.6869 54.7691C60.8098 51.1402 55.5514 49.1016 50.0683 49.1016C44.5853 49.1016 39.3268 51.1402 35.4497 54.7691C31.5727 58.398 29.3945 63.3199 29.3945 68.4519C32.1264 78.8118 67.0106 81.1231 71.1877 68.4519Z" stroke="#E0E1E6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M70.9144 66.4486C71.5691 69.0402 68.7363 72.3749 64.8592 74.514C60.9821 76.6531 56.3578 77.5195 50.8747 77.5195C45.3917 77.5195 40.1332 76.3178 36.2562 74.1787C32.2484 72.6698 28.1973 68.8746 30.2009 66.1133L50.8747 66.1133L70.9144 66.4486Z" fill="#E0E1E6"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 305 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 218 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 285 KiB

View File

@ -0,0 +1,13 @@
<svg width="94" height="94" viewBox="0 0 94 94" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="46.7188" cy="46.7188" r="46.7188" fill="#F9F9FB"/>
<g clip-path="url(#clip0_6465_168324)">
<path d="M22.3683 30.8508L46.374 21.6169C47.6902 21.1106 49.1029 22.0889 49.0919 23.4991L49.0807 24.9392L48.5304 51.0826C48.5125 51.9323 47.9595 52.6779 47.1516 52.9418L27.2534 59.4394L23.7288 60.635C22.4318 61.075 21.0863 60.1106 21.0863 58.741L21.0863 32.7175C21.0863 31.89 21.5959 31.1479 22.3683 30.8508Z" fill="#E8E8EC"/>
<path d="M32.9995 38.1587L57.0052 28.9248C58.3214 28.4185 59.7342 29.3968 59.7232 30.807L59.712 32.2471L59.7119 57.5857C59.7119 58.4323 59.1789 59.1872 58.3811 59.4704L37.8846 66.7473L34.36 67.9429C33.0631 68.3829 31.7175 67.4185 31.7175 66.0489L31.7175 40.0254C31.7175 39.1979 32.2272 38.4558 32.9995 38.1587Z" fill="#D9D9E0"/>
<path d="M68.8117 65.0919C69.5835 64.795 70.0929 64.0537 70.0935 63.2267L70.1116 37.3209C70.1126 35.9167 68.7041 34.9487 67.3936 35.4528L43.3671 44.6948C42.5947 44.9919 42.0851 45.7339 42.0851 46.5615L42.0851 72.4629C42.0851 73.8666 43.4932 74.8336 44.8033 74.3295L68.8117 65.0919Z" fill="#B9BBC6"/>
</g>
<defs>
<clipPath id="clip0_6465_168324">
<rect width="69" height="69" fill="white" transform="translate(81 13) rotate(90)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB