promote: develop to stage-release (#1589)

* fix: onboarding invitations overflow (#1575)

* fix: onboarding invitations overflow

* fix: user avatar in the notification card

* style: update graph grid color

* fix: no 'Create by me' label coming up (#1573)

* feat: added new issue subscriber table

* dev: notification model

* feat: added CRUD operation for issue subscriber

* Revert "feat: added CRUD operation for issue subscriber"

This reverts commit b22e0625768f0b096b5898936ace76d6882b0736.

* feat: added CRUD operation for issue subscriber

* dev: notification models and operations

* dev: remove delete endpoint response data

* dev: notification endpoints and fix bg worker for saving notifications

* feat: added list and unsubscribe function in issue subscriber

* dev: filter by snoozed and response update for list and permissions

* dev: update issue notifications

* dev: notification  segregation

* dev: update notifications

* dev: notification filtering

* dev: add issue name in notifications

* dev: notification new endpoints

* fix: pushing local settings

* feat: notification workflow setup and made basic UI

* style: improved UX with toast alerts and other interactions

refactor: changed classnames according to new theme structure, changed all icons to material icons

* feat: showing un-read notification count

* feat: not showing 'subscribe' button on issue created by user & assigned to user

not showing 'Create by you' for view & guest of the workspace

* fix: 'read' -> 'unread' heading, my issue wrong filter

* feat: made snooze dropdown & modal

feat: switched to calendar

* fix: minor ui fixes

* feat: snooze modal date/time select

* fix: params for read/un-read notification

* style: snooze notification modal

* fix: no label for 'Create by me'

* fix: no label for 'Create by me'

* fix: removed console log

* fix: tooltip going behind popover

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* style: tooltip on notification header actions (#1577)

* style: tooltip on notification header

* chore: update tooltip content

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* fix: user migrations for back population (#1578)

* fix: total notifications count (#1579)

* fix: notification card (#1583)

* feat: add new icons package (#1586)

* feat: add material icons package

* chore: replace issue view icons

* chore: notification ordering (#1584)

* fix: uuid error when cycle and module updates (#1585)

* refactor: height of popover & api fetch call (#1587)

* fix: snooze dropdown overflow (#1588)

---------

Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
This commit is contained in:
Aaryan Khandelwal 2023-07-20 15:14:57 +05:30 committed by GitHub
parent b38898753f
commit 9275e6f373
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 736 additions and 1018 deletions

View file

@ -30,7 +30,6 @@ class NotificationViewSet(BaseViewSet):
def list(self, request, slug): def list(self, request, slug):
try: try:
order_by = request.GET.get("order_by", "-created_at")
snoozed = request.GET.get("snoozed", "false") snoozed = request.GET.get("snoozed", "false")
archived = request.GET.get("archived", "false") archived = request.GET.get("archived", "false")
read = request.GET.get("read", "true") read = request.GET.get("read", "true")
@ -40,7 +39,7 @@ class NotificationViewSet(BaseViewSet):
notifications = Notification.objects.filter( notifications = Notification.objects.filter(
workspace__slug=slug, receiver_id=request.user.id workspace__slug=slug, receiver_id=request.user.id
).order_by(order_by) ).order_by("snoozed_till", "-created_at")
# Filter for snoozed notifications # Filter for snoozed notifications
if snoozed == "false": if snoozed == "false":

View file

@ -1028,7 +1028,12 @@ def issue_activity(
actor = User.objects.get(pk=actor_id) actor = User.objects.get(pk=actor_id)
project = Project.objects.get(pk=project_id) project = Project.objects.get(pk=project_id)
if type not in [
"cycle.activity.created",
"cycle.activity.deleted",
"module.activity.created",
"module.activity.deleted",
]:
issue = Issue.objects.filter(pk=issue_id, project_id=project_id).first() issue = Issue.objects.filter(pk=issue_id, project_id=project_id).first()
if issue is not None: if issue is not None:
@ -1094,6 +1099,12 @@ def issue_activity(
except Exception as e: except Exception as e:
capture_exception(e) capture_exception(e)
if type not in [
"cycle.activity.created",
"cycle.activity.deleted",
"module.activity.created",
"module.activity.deleted",
]:
# Create Notifications # Create Notifications
bulk_notifications = [] bulk_notifications = []
@ -1114,7 +1125,11 @@ def issue_activity(
issue = Issue.objects.filter(pk=issue_id, project_id=project_id).first() issue = Issue.objects.filter(pk=issue_id, project_id=project_id).first()
# Add bot filtering # Add bot filtering
if issue is not None and issue.created_by_id is not None and not issue.created_by.is_bot: if (
issue is not None
and issue.created_by_id is not None
and not issue.created_by.is_bot
):
issue_subscribers = issue_subscribers + [issue.created_by_id] issue_subscribers = issue_subscribers + [issue.created_by_id]
for subscriber in issue_subscribers: for subscriber in issue_subscribers:
@ -1146,7 +1161,9 @@ def issue_activity(
"new_value": str(issue_activity.new_value), "new_value": str(issue_activity.new_value),
"old_value": str(issue_activity.old_value), "old_value": str(issue_activity.old_value),
"issue_comment": str( "issue_comment": str(
issue_activity.issue_comment.comment_stripped if issue_activity.issue_comment is not None else "" issue_activity.issue_comment.comment_stripped
if issue_activity.issue_comment is not None
else ""
), ),
}, },
}, },

View file

@ -8,6 +8,7 @@ import plane.db.models.user
import uuid import uuid
def onboarding_default_steps(apps, schema_editor): def onboarding_default_steps(apps, schema_editor):
default_onboarding_schema = { default_onboarding_schema = {
"workspace_join": True, "workspace_join": True,
@ -23,7 +24,7 @@ def onboarding_default_steps(apps, schema_editor):
obj.is_tour_completed = True obj.is_tour_completed = True
updated_user.append(obj) updated_user.append(obj)
Model.objects.bulk_update(updated_user, ["onboarding_step"], batch_size=100) Model.objects.bulk_update(updated_user, ["onboarding_step", "is_tour_completed"], batch_size=100)
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -79,6 +80,7 @@ class Migration(migrations.Migration):
name="onboarding_step", name="onboarding_step",
field=models.JSONField(default=plane.db.models.user.get_default_onboarding), field=models.JSONField(default=plane.db.models.user.get_default_onboarding),
), ),
migrations.RunPython(onboarding_default_steps),
migrations.CreateModel( migrations.CreateModel(
name="Notification", name="Notification",
fields=[ fields=[

View file

@ -120,7 +120,7 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
Please check your inbox at <span className="font-medium">{watch("email")}</span> Please check your inbox at <span className="font-medium">{watch("email")}</span>
</p> </p>
)} )}
<form className="space-y-4 mt-10"> <form className="space-y-4 mt-10 sm:w-[360px] mx-auto">
<div className="space-y-1"> <div className="space-y-1">
<Input <Input
id="email" id="email"

View file

@ -11,14 +11,16 @@ import useEstimateOption from "hooks/use-estimate-option";
// components // components
import { SelectFilters } from "components/views"; import { SelectFilters } from "components/views";
// ui // ui
import { CustomMenu, Icon, ToggleSwitch, Tooltip } from "components/ui"; import { CustomMenu, ToggleSwitch, Tooltip } from "components/ui";
// icons // icons
import { ChevronDownIcon } from "@heroicons/react/24/outline";
import { import {
ChevronDownIcon, CalendarMonthOutlined,
ListBulletIcon, FormatListBulletedOutlined,
Squares2X2Icon, GridViewOutlined,
CalendarDaysIcon, TableChartOutlined,
} from "@heroicons/react/24/outline"; WaterfallChartOutlined,
} from "@mui/icons-material";
// helpers // helpers
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
import { checkIfArraysHaveSameElements } from "helpers/array.helper"; import { checkIfArraysHaveSameElements } from "helpers/array.helper";
@ -27,26 +29,26 @@ import { Properties, TIssueViewOptions } from "types";
// constants // constants
import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue"; import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue";
const issueViewOptions: { type: TIssueViewOptions; icon: any }[] = [ const issueViewOptions: { type: TIssueViewOptions; Icon: any }[] = [
{ {
type: "list", type: "list",
icon: <ListBulletIcon className="h-4 w-4" />, Icon: FormatListBulletedOutlined,
}, },
{ {
type: "kanban", type: "kanban",
icon: <Squares2X2Icon className="h-4 w-4" />, Icon: GridViewOutlined,
}, },
{ {
type: "calendar", type: "calendar",
icon: <CalendarDaysIcon className="h-4 w-4" />, Icon: CalendarMonthOutlined,
}, },
{ {
type: "spreadsheet", type: "spreadsheet",
icon: <Icon iconName="table_chart" />, Icon: TableChartOutlined,
}, },
{ {
type: "gantt_chart", type: "gantt_chart",
icon: <Icon iconName="waterfall_chart" className="rotate-90" />, Icon: WaterfallChartOutlined,
}, },
]; ];
@ -98,7 +100,12 @@ export const IssuesFilterView: React.FC = () => {
}`} }`}
onClick={() => setIssueView(option.type)} onClick={() => setIssueView(option.type)}
> >
{option.icon} <option.Icon
sx={{
fontSize: 16,
}}
className={option.type === "gantt_chart" ? "rotate-90" : ""}
/>
</button> </button>
</Tooltip> </Tooltip>
))} ))}
@ -177,7 +184,6 @@ export const IssuesFilterView: React.FC = () => {
GROUP_BY_OPTIONS.find((option) => option.key === groupByProperty) GROUP_BY_OPTIONS.find((option) => option.key === groupByProperty)
?.name ?? "Select" ?.name ?? "Select"
} }
width="lg"
> >
{GROUP_BY_OPTIONS.map((option) => {GROUP_BY_OPTIONS.map((option) =>
issueView === "kanban" && option.key === null ? null : ( issueView === "kanban" && option.key === null ? null : (
@ -198,7 +204,6 @@ export const IssuesFilterView: React.FC = () => {
ORDER_BY_OPTIONS.find((option) => option.key === orderBy)?.name ?? ORDER_BY_OPTIONS.find((option) => option.key === orderBy)?.name ??
"Select" "Select"
} }
width="lg"
> >
{ORDER_BY_OPTIONS.map((option) => {ORDER_BY_OPTIONS.map((option) =>
groupByProperty === "priority" && option.key === "priority" ? null : ( groupByProperty === "priority" && option.key === "priority" ? null : (
@ -223,7 +228,6 @@ export const IssuesFilterView: React.FC = () => {
FILTER_ISSUE_OPTIONS.find((option) => option.key === filters.type) FILTER_ISSUE_OPTIONS.find((option) => option.key === filters.type)
?.name ?? "Select" ?.name ?? "Select"
} }
width="lg"
> >
{FILTER_ISSUE_OPTIONS.map((option) => ( {FILTER_ISSUE_OPTIONS.map((option) => (
<CustomMenu.MenuItem <CustomMenu.MenuItem

View file

@ -10,7 +10,7 @@ import useToast from "hooks/use-toast";
import { CustomMenu, Icon, Tooltip } from "components/ui"; import { CustomMenu, Icon, Tooltip } from "components/ui";
// helper // helper
import { stripHTML, replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; import { stripHTML, replaceUnderscoreIfSnakeCase, truncateText } from "helpers/string.helper";
import { import {
formatDateDistance, formatDateDistance,
render12HourFormatTime, render12HourFormatTime,
@ -32,7 +32,7 @@ type NotificationCardProps = {
const snoozeOptions = [ const snoozeOptions = [
{ {
label: "1 days", label: "1 day",
value: new Date(new Date().getTime() + 24 * 60 * 60 * 1000), value: new Date(new Date().getTime() + 24 * 60 * 60 * 1000),
}, },
{ {
@ -79,34 +79,35 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
`/${workspaceSlug}/projects/${notification.project}/issues/${notification.data.issue.id}` `/${workspaceSlug}/projects/${notification.project}/issues/${notification.data.issue.id}`
); );
}} }}
className={`group relative py-3 px-6 cursor-pointer ${ className={`group w-full flex items-center gap-4 p-3 pl-6 relative cursor-pointer ${
notification.read_at === null ? "bg-custom-primary-70/5" : "hover:bg-custom-background-200" notification.read_at === null ? "bg-custom-primary-70/5" : "hover:bg-custom-background-200"
}`} }`}
> >
{notification.read_at === null && ( {notification.read_at === null && (
<span className="absolute top-1/2 left-2 -translate-y-1/2 w-1.5 h-1.5 bg-custom-primary-100 rounded-full" /> <span className="absolute top-1/2 left-2 -translate-y-1/2 w-1.5 h-1.5 bg-custom-primary-100 rounded-full" />
)} )}
<div className="flex items-center gap-4 w-full">
<div className="relative w-12 h-12 rounded-full"> <div className="relative w-12 h-12 rounded-full">
{notification.triggered_by_details.avatar && {notification.triggered_by_details.avatar &&
notification.triggered_by_details.avatar !== "" ? ( notification.triggered_by_details.avatar !== "" ? (
<div className="h-12 w-12 rounded-full">
<Image <Image
src={notification.triggered_by_details.avatar} src={notification.triggered_by_details.avatar}
alt="profile image" alt="Profile Image"
layout="fill" layout="fill"
objectFit="cover" objectFit="cover"
className="rounded-full" className="rounded-full"
/> />
</div>
) : ( ) : (
<div className="w-12 h-12 bg-custom-background-100 rounded-full flex justify-center items-center"> <div className="w-12 h-12 bg-custom-background-80 rounded-full flex justify-center items-center">
<span className="text-custom-text-100 font-medium text-lg"> <span className="text-custom-text-100 font-medium text-lg">
{notification.triggered_by_details.first_name[0].toUpperCase()} {notification.triggered_by_details.first_name[0].toUpperCase()}
</span> </span>
</div> </div>
)} )}
</div> </div>
<div className="w-full space-y-2.5"> <div className="space-y-2.5 w-full overflow-hidden">
<div className="text-sm"> <div className="text-sm w-full break-words">
<span className="font-semibold"> <span className="font-semibold">
{notification.triggered_by_details.first_name}{" "} {notification.triggered_by_details.first_name}{" "}
{notification.triggered_by_details.last_name}{" "} {notification.triggered_by_details.last_name}{" "}
@ -150,31 +151,33 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
</span> </span>
</div> </div>
<div className="w-full flex justify-between text-xs"> <div className="flex justify-between gap-2 text-xs">
<p className="truncate inline max-w-lg text-custom-text-300 mr-3"> <p className="text-custom-text-300">
{notification.data.issue.identifier}-{notification.data.issue.sequence_id}{" "} {truncateText(
{notification.data.issue.name} `${notification.data.issue.identifier}-${notification.data.issue.sequence_id} ${notification.data.issue.name}`,
50
)}
</p> </p>
{notification.snoozed_till ? ( {notification.snoozed_till ? (
<p className="text-custom-text-300 flex items-center gap-x-1"> <p className="text-custom-text-300 flex items-center justify-end gap-x-1 flex-shrink-0">
<Icon iconName="schedule" /> <Icon iconName="schedule" className="!text-base -my-0.5" />
<span> <span>
Till {renderShortDate(notification.snoozed_till)},{" "} Till {renderShortDate(notification.snoozed_till)},{" "}
{render12HourFormatTime(notification.snoozed_till)} {render12HourFormatTime(notification.snoozed_till)}
</span> </span>
</p> </p>
) : ( ) : (
<p className="text-custom-text-300">{formatDateDistance(notification.created_at)}</p> <p className="text-custom-text-300 flex-shrink-0">
{formatDateDistance(notification.created_at)}
</p>
)} )}
</div> </div>
</div> </div>
</div>
<div className="absolute py-1 gap-x-3 right-3 top-3 hidden group-hover:flex"> <div className="absolute py-1 gap-x-3 right-3 top-3 hidden group-hover:flex">
{[ {[
{ {
id: 1, id: 1,
name: notification.read_at ? "Mark as Unread" : "Mark as Read", name: notification.read_at ? "Mark as unread" : "Mark as read",
icon: "chat_bubble", icon: "chat_bubble",
onClick: () => { onClick: () => {
markNotificationReadStatus(notification.id).then(() => { markNotificationReadStatus(notification.id).then(() => {
@ -189,8 +192,8 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
}, },
{ {
id: 2, id: 2,
name: notification.archived_at ? "Unarchive Notification" : "Archive Notification", name: notification.archived_at ? "Unarchive" : "Archive",
icon: "archive", icon: notification.archived_at ? "unarchive" : "archive",
onClick: () => { onClick: () => {
markNotificationArchivedStatus(notification.id).then(() => { markNotificationArchivedStatus(notification.id).then(() => {
setToastAlert({ setToastAlert({
@ -203,7 +206,7 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
}, },
}, },
].map((item) => ( ].map((item) => (
<Tooltip tooltipContent={item.name} position="top-left"> <Tooltip tooltipContent={item.name}>
<button <button
type="button" type="button"
onClick={(e) => { onClick={(e) => {
@ -211,14 +214,15 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
item.onClick(); item.onClick();
}} }}
key={item.id} key={item.id}
className="text-sm flex w-full items-center gap-x-2 bg-custom-background-80 hover:bg-custom-background-100 p-0.5 rounded" className="text-sm flex w-full items-center gap-x-2 bg-custom-background-80 hover:bg-custom-background-100 p-0.5 rounded outline-none"
> >
<Icon iconName={item.icon} className="h-5 w-5 text-custom-text-300" /> <Icon iconName={item.icon} className="h-5 w-5 text-custom-text-300" />
</button> </button>
</Tooltip> </Tooltip>
))} ))}
<Tooltip tooltipContent="Snooze Notification" position="top-left"> <Tooltip tooltipContent="Snooze">
<div>
<CustomMenu <CustomMenu
menuButtonOnClick={(e) => { menuButtonOnClick={(e) => {
e.stopPropagation(); e.stopPropagation();
@ -257,6 +261,7 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
))} ))}
</CustomMenu> </CustomMenu>
</div>
</Tooltip> </Tooltip>
</div> </div>
</div> </div>

View file

@ -12,8 +12,10 @@ import useWorkspaceMembers from "hooks/use-workspace-members";
import useUserNotification from "hooks/use-user-notifications"; import useUserNotification from "hooks/use-user-notifications";
// components // components
import { Icon, Loader, EmptyState } from "components/ui"; import { Icon, Loader, EmptyState, Tooltip } from "components/ui";
import { SnoozeNotificationModal, NotificationCard } from "components/notifications"; import { SnoozeNotificationModal, NotificationCard } from "components/notifications";
// icons
import { NotificationsOutlined } from "@mui/icons-material";
// images // images
import emptyNotification from "public/empty-state/notification.svg"; import emptyNotification from "public/empty-state/notification.svg";
// helpers // helpers
@ -69,7 +71,7 @@ export const NotificationPopover = () => {
{ {
label: "Subscribed", label: "Subscribed",
value: "watching", value: "watching",
unreadCount: notificationCount?.watching_notifications, unreadCount: notificationCount?.watching_issues,
}, },
]; ];
@ -96,10 +98,10 @@ export const NotificationPopover = () => {
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${ className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
isActive isActive
? "bg-custom-primary-100/10 text-custom-primary-100" ? "bg-custom-primary-100/10 text-custom-primary-100"
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80" : "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80"
} ${sidebarCollapse ? "justify-center" : ""}`} } ${sidebarCollapse ? "justify-center" : ""}`}
> >
<Icon iconName="notifications" /> <NotificationsOutlined fontSize="small" />
{sidebarCollapse ? null : <span>Notifications</span>} {sidebarCollapse ? null : <span>Notifications</span>}
{totalNotificationCount && totalNotificationCount > 0 ? ( {totalNotificationCount && totalNotificationCount > 0 ? (
<span className="ml-auto bg-custom-primary-300 rounded-full text-xs text-white px-1.5"> <span className="ml-auto bg-custom-primary-300 rounded-full text-xs text-white px-1.5">
@ -116,10 +118,11 @@ export const NotificationPopover = () => {
leaveFrom="opacity-100 translate-y-0" leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1" leaveTo="opacity-0 translate-y-1"
> >
<Popover.Panel className="absolute bg-custom-background-100 flex flex-col left-0 md:left-full ml-8 z-10 top-0 md:w-[36rem] w-[20rem] h-[27rem] border border-custom-border-300 shadow-lg rounded-xl"> <Popover.Panel className="absolute bg-custom-background-100 flex flex-col left-0 md:left-full ml-8 z-10 top-0 md:w-[36rem] w-[20rem] h-[50vh] border border-custom-border-300 shadow-lg rounded-xl">
<div className="flex items-center justify-between px-5 pt-5"> <div className="flex items-center justify-between px-5 pt-5">
<h2 className="text-xl font-semibold mb-2">Notifications</h2> <h2 className="text-xl font-semibold mb-2">Notifications</h2>
<div className="flex gap-x-4 justify-center items-center text-custom-text-200"> <div className="flex gap-x-4 justify-center items-center text-custom-text-200">
<Tooltip tooltipContent="Refresh">
<button <button
type="button" type="button"
onClick={(e) => { onClick={(e) => {
@ -134,6 +137,8 @@ export const NotificationPopover = () => {
> >
<Icon iconName="refresh" /> <Icon iconName="refresh" />
</button> </button>
</Tooltip>
<Tooltip tooltipContent="Unread notifications">
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
@ -144,6 +149,8 @@ export const NotificationPopover = () => {
> >
<Icon iconName="filter_list" /> <Icon iconName="filter_list" />
</button> </button>
</Tooltip>
<Tooltip tooltipContent="Snoozed notifications">
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
@ -154,6 +161,8 @@ export const NotificationPopover = () => {
> >
<Icon iconName="schedule" /> <Icon iconName="schedule" />
</button> </button>
</Tooltip>
<Tooltip tooltipContent="Archived notifications">
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
@ -164,6 +173,7 @@ export const NotificationPopover = () => {
> >
<Icon iconName="archive" /> <Icon iconName="archive" />
</button> </button>
</Tooltip>
<button type="button" onClick={() => closePopover()}> <button type="button" onClick={() => closePopover()}>
<Icon iconName="close" /> <Icon iconName="close" />
</button> </button>
@ -205,6 +215,7 @@ export const NotificationPopover = () => {
: "border-transparent text-custom-text-200" : "border-transparent text-custom-text-200"
}`} }`}
> >
{tab.label}
{tab.unreadCount && tab.unreadCount > 0 ? ( {tab.unreadCount && tab.unreadCount > 0 ? (
<span <span
className={`ml-2 rounded-full text-xs px-2 py-0.5 ${ className={`ml-2 rounded-full text-xs px-2 py-0.5 ${
@ -250,7 +261,7 @@ export const NotificationPopover = () => {
{notifications ? ( {notifications ? (
notifications.length > 0 ? ( notifications.length > 0 ? (
<div className="divide-y divide-custom-border-100 overflow-y-auto"> <div className="divide-y divide-custom-border-100 overflow-y-auto h-full">
{notifications.map((notification) => ( {notifications.map((notification) => (
<NotificationCard <NotificationCard
key={notification.id} key={notification.id}
@ -273,7 +284,7 @@ export const NotificationPopover = () => {
</div> </div>
) )
) : ( ) : (
<Loader className="p-5 space-y-4"> <Loader className="p-5 space-y-4 overflow-y-auto">
<Loader.Item height="50px" /> <Loader.Item height="50px" />
<Loader.Item height="50px" /> <Loader.Item height="50px" />
<Loader.Item height="50px" /> <Loader.Item height="50px" />

View file

@ -88,7 +88,7 @@ export const JoinWorkspaces: React.FC<Props> = ({ stepChange }) => {
<div className="w-full space-y-7 sm:space-y-10"> <div className="w-full space-y-7 sm:space-y-10">
<h5 className="sm:text-lg">We see that someone has invited you to</h5> <h5 className="sm:text-lg">We see that someone has invited you to</h5>
<h4 className="text-xl sm:text-2xl font-semibold">Join a workspace</h4> <h4 className="text-xl sm:text-2xl font-semibold">Join a workspace</h4>
<div className="md:w-3/5 space-y-4"> <div className="max-h-[37vh] overflow-y-auto md:w-3/5 space-y-4">
{invitations && {invitations &&
invitations.map((invitation) => { invitations.map((invitation) => {
const isSelected = invitationsRespond.includes(invitation.id); const isSelected = invitationsRespond.includes(invitation.id);
@ -146,7 +146,11 @@ export const JoinWorkspaces: React.FC<Props> = ({ stepChange }) => {
> >
Accept & Join Accept & Join
</PrimaryButton> </PrimaryButton>
<SecondaryButton className="border border-none bg-transparent" size="md" onClick={finishOnboarding} > <SecondaryButton
className="border border-none bg-transparent"
size="md"
onClick={finishOnboarding}
>
Skip for now Skip for now
</SecondaryButton> </SecondaryButton>
</div> </div>

View file

@ -1,31 +1,37 @@
// ui // icons
import { Icon } from "components/ui"; import {
ArticleOutlined,
ContrastOutlined,
DatasetOutlined,
FilterNoneOutlined,
PhotoFilterOutlined,
} from "@mui/icons-material";
// types // types
import { TTourSteps } from "./root"; import { TTourSteps } from "./root";
const sidebarOptions: { const sidebarOptions: {
key: TTourSteps; key: TTourSteps;
icon: string; Icon: any;
}[] = [ }[] = [
{ {
key: "issues", key: "issues",
icon: "stack", Icon: FilterNoneOutlined,
}, },
{ {
key: "cycles", key: "cycles",
icon: "contrast", Icon: ContrastOutlined,
}, },
{ {
key: "modules", key: "modules",
icon: "dataset", Icon: DatasetOutlined,
}, },
{ {
key: "views", key: "views",
icon: "photo_filter", Icon: PhotoFilterOutlined,
}, },
{ {
key: "pages", key: "pages",
icon: "article", Icon: ArticleOutlined,
}, },
]; ];
@ -52,11 +58,10 @@ export const TourSidebar: React.FC<Props> = ({ step, setStep }) => (
}`} }`}
onClick={() => setStep(option.key)} onClick={() => setStep(option.key)}
> >
<Icon <option.Icon
iconName={option.icon} sx={{
className={`h-5 w-5 flex-shrink-0 ${ fontSize: 18,
step === option.key ? "text-custom-primary-100" : "text-custom-text-200" }}
}`}
aria-hidden="true" aria-hidden="true"
/> />
{option.key} {option.key}

View file

@ -10,9 +10,19 @@ import projectService from "services/project.service";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// ui // ui
import { CustomMenu, Icon, Tooltip } from "components/ui"; import { CustomMenu, Tooltip } from "components/ui";
// icons // icons
import { LinkIcon, StarIcon, TrashIcon } from "@heroicons/react/24/outline"; import { LinkIcon, StarIcon, TrashIcon } from "@heroicons/react/24/outline";
import {
ArchiveOutlined,
ArticleOutlined,
ContrastOutlined,
DatasetOutlined,
ExpandMoreOutlined,
FilterNoneOutlined,
PhotoFilterOutlined,
SettingsOutlined,
} from "@mui/icons-material";
// helpers // helpers
import { truncateText } from "helpers/string.helper"; import { truncateText } from "helpers/string.helper";
import { renderEmoji } from "helpers/emoji.helper"; import { renderEmoji } from "helpers/emoji.helper";
@ -33,32 +43,32 @@ const navigation = (workspaceSlug: string, projectId: string) => [
{ {
name: "Issues", name: "Issues",
href: `/${workspaceSlug}/projects/${projectId}/issues`, href: `/${workspaceSlug}/projects/${projectId}/issues`,
icon: "stack", Icon: FilterNoneOutlined,
}, },
{ {
name: "Cycles", name: "Cycles",
href: `/${workspaceSlug}/projects/${projectId}/cycles`, href: `/${workspaceSlug}/projects/${projectId}/cycles`,
icon: "contrast", Icon: ContrastOutlined,
}, },
{ {
name: "Modules", name: "Modules",
href: `/${workspaceSlug}/projects/${projectId}/modules`, href: `/${workspaceSlug}/projects/${projectId}/modules`,
icon: "dataset", Icon: DatasetOutlined,
}, },
{ {
name: "Views", name: "Views",
href: `/${workspaceSlug}/projects/${projectId}/views`, href: `/${workspaceSlug}/projects/${projectId}/views`,
icon: "photo_filter", Icon: PhotoFilterOutlined,
}, },
{ {
name: "Pages", name: "Pages",
href: `/${workspaceSlug}/projects/${projectId}/pages`, href: `/${workspaceSlug}/projects/${projectId}/pages`,
icon: "article", Icon: ArticleOutlined,
}, },
{ {
name: "Settings", name: "Settings",
href: `/${workspaceSlug}/projects/${projectId}/settings`, href: `/${workspaceSlug}/projects/${projectId}/settings`,
icon: "settings", Icon: SettingsOutlined,
}, },
]; ];
@ -164,8 +174,8 @@ export const SingleSidebarProject: React.FC<Props> = ({
)} )}
</div> </div>
{!sidebarCollapse && ( {!sidebarCollapse && (
<Icon <ExpandMoreOutlined
iconName="expand_more" fontSize="small"
className={`${open ? "rotate-180" : ""} text-custom-text-200 duration-300`} className={`${open ? "rotate-180" : ""} text-custom-text-200 duration-300`}
/> />
)} )}
@ -211,7 +221,7 @@ export const SingleSidebarProject: React.FC<Props> = ({
} }
> >
<div className="flex items-center justify-start gap-2"> <div className="flex items-center justify-start gap-2">
<Icon iconName="archive" className="h-4 w-4" /> <ArchiveOutlined fontSize="small" />
<span>Archived Issues</span> <span>Archived Issues</span>
</div> </div>
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
@ -248,13 +258,17 @@ export const SingleSidebarProject: React.FC<Props> = ({
disabled={!sidebarCollapse} disabled={!sidebarCollapse}
> >
<div <div
className={`group flex items-center rounded-md px-2 py-1.5 gap-2 text-xs font-medium outline-none ${ className={`group flex items-center rounded-md px-2 py-1.5 gap-2.5 text-xs font-medium outline-none ${
router.asPath.includes(item.href) router.asPath.includes(item.href)
? "bg-custom-primary-100/10 text-custom-primary-100" ? "bg-custom-primary-100/10 text-custom-primary-100"
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80" : "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
} ${sidebarCollapse ? "justify-center" : ""}`} } ${sidebarCollapse ? "justify-center" : ""}`}
> >
<Icon iconName={item.icon} /> <item.Icon
sx={{
fontSize: 18,
}}
/>
{!sidebarCollapse && item.name} {!sidebarCollapse && item.name}
</div> </div>
</Tooltip> </Tooltip>

View file

@ -1,187 +0,0 @@
import React from "react";
// next
import Link from "next/link";
// headless ui
import { Menu, Transition } from "@headlessui/react";
// icons
import { ChevronDownIcon } from "@heroicons/react/24/outline";
import { Icon } from "./icon";
type Props = {
children: React.ReactNode;
label?: string | JSX.Element;
className?: string;
ellipsis?: boolean;
verticalEllipsis?: boolean;
height?: "sm" | "md" | "rg" | "lg";
width?: "sm" | "md" | "lg" | "xl" | "auto";
textAlignment?: "left" | "center" | "right";
noBorder?: boolean;
noChevron?: boolean;
position?: "left" | "right";
verticalPosition?: "top" | "bottom";
menuItemsClassName?: string;
customButton?: JSX.Element;
menuItemsWhiteBg?: boolean;
};
type MenuItemProps = {
children: JSX.Element | string;
renderAs?: "button" | "a";
href?: string;
onClick?: (args?: any) => void;
className?: string;
};
const CustomMenu = ({
children,
label,
className = "",
ellipsis = false,
verticalEllipsis = false,
height = "md",
width = "auto",
textAlignment,
noBorder = false,
noChevron = false,
position = "right",
verticalPosition = "bottom",
menuItemsClassName = "",
customButton,
menuItemsWhiteBg = false,
}: Props) => (
<Menu as="div" className={`relative w-min whitespace-nowrap text-left ${className}`}>
{({ open }) => (
<>
{customButton ? (
<Menu.Button as="div">{customButton}</Menu.Button>
) : (
<div>
{ellipsis || verticalEllipsis ? (
<Menu.Button
type="button"
className="relative grid place-items-center rounded p-1 text-custom-text-200 hover:bg-custom-background-80 outline-none"
>
<Icon
iconName="more_horiz"
className={`${verticalEllipsis ? "rotate-90" : ""} text-brand-secondary`}
/>
</Menu.Button>
) : (
<Menu.Button
type="button"
className={`flex cursor-pointer items-center justify-between gap-1 px-2.5 py-1 text-xs duration-300 hover:bg-custom-background-80 ${
open ? "bg-custom-background-90 text-custom-text-100" : "text-custom-text-200"
} ${
textAlignment === "right"
? "text-right"
: textAlignment === "center"
? "text-center"
: "text-left"
} ${
noBorder
? "rounded-md"
: "rounded-md border border-custom-border-200 shadow-sm focus:outline-none"
} ${
width === "sm"
? "w-10"
: width === "md"
? "w-20"
: width === "lg"
? "w-32"
: width === "xl"
? "w-48"
: "w-full"
}`}
>
{label}
{!noChevron && <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />}
</Menu.Button>
)}
</div>
)}
<Transition
as={React.Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
className={`absolute z-20 overflow-y-scroll whitespace-nowrap rounded-md border p-1 text-xs shadow-lg focus:outline-none ${
position === "left" ? "left-0 origin-top-left" : "right-0 origin-top-right"
} ${verticalPosition === "top" ? "bottom-full mb-1" : "mt-1"} ${
height === "sm"
? "max-h-28"
: height === "md"
? "max-h-44"
: height === "rg"
? "max-h-56"
: height === "lg"
? "max-h-80"
: ""
} ${
width === "sm"
? "w-10"
: width === "md"
? "w-20"
: width === "lg"
? "w-32"
: width === "xl"
? "w-48"
: "min-w-full"
} ${
menuItemsWhiteBg
? "border-custom-border-200 bg-custom-background-100"
: "border-custom-border-200 bg-custom-background-90"
} ${menuItemsClassName}`}
>
<div className="py-1">{children}</div>
</Menu.Items>
</Transition>
</>
)}
</Menu>
);
const MenuItem: React.FC<MenuItemProps> = ({
children,
renderAs,
href,
onClick,
className = "",
}) => (
<Menu.Item as="div">
{({ active, close }) =>
renderAs === "a" ? (
<Link href={href ?? ""}>
<a
className={`${className} ${
active ? "bg-custom-background-80" : ""
} hover:text-custom-text-200 inline-block w-full select-none gap-2 truncate rounded px-1 py-1.5 text-left text-custom-text-200 hover:bg-custom-background-80`}
onClick={close}
>
{children}
</a>
</Link>
) : (
<button
type="button"
className={`${className} ${
active ? "bg-custom-background-80" : ""
} hover:text-custom-text-200 w-full select-none gap-2 truncate rounded px-1 py-1.5 text-left text-custom-text-200 hover:bg-custom-background-80`}
onClick={onClick}
>
{children}
</button>
)
}
</Menu.Item>
);
CustomMenu.MenuItem = MenuItem;
export { CustomMenu };

View file

@ -4,9 +4,10 @@ import Link from "next/link";
// headless ui // headless ui
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
// ui
import { DropdownProps } from "components/ui";
// icons // icons
import { DropdownProps, Icon } from "components/ui"; import { ExpandMoreOutlined, MoreHorizOutlined } from "@mui/icons-material";
import { ChevronDownIcon } from "@heroicons/react/24/outline";
export type CustomMenuProps = DropdownProps & { export type CustomMenuProps = DropdownProps & {
children: React.ReactNode; children: React.ReactNode;
@ -53,8 +54,8 @@ const CustomMenu = ({
disabled ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-80" disabled ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`} } ${buttonClassName}`}
> >
<Icon <MoreHorizOutlined
iconName="more_horiz" fontSize="small"
className={`${verticalEllipsis ? "rotate-90" : ""} text-custom-text-200`} className={`${verticalEllipsis ? "rotate-90" : ""} text-custom-text-200`}
/> />
</Menu.Button> </Menu.Button>
@ -72,7 +73,14 @@ const CustomMenu = ({
} ${buttonClassName}`} } ${buttonClassName}`}
> >
{label} {label}
{!noChevron && <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />} {!noChevron && (
<ExpandMoreOutlined
sx={{
fontSize: 14,
}}
aria-hidden="true"
/>
)}
</Menu.Button> </Menu.Button>
)} )}
</> </>

View file

@ -1,33 +1,16 @@
import React, { useRef, useState } from "react"; import React, { useRef, useState } from "react";
import { useRouter } from "next/router";
import Link from "next/link"; import Link from "next/link";
import useSWR from "swr";
// headless ui // headless ui
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
// services
import workspaceService from "services/workspace.service";
// hooks // hooks
import useTheme from "hooks/use-theme"; import useTheme from "hooks/use-theme";
import useUser from "hooks/use-user";
import useOutsideClickDetector from "hooks/use-outside-click-detector"; import useOutsideClickDetector from "hooks/use-outside-click-detector";
// components
import UpgradeToProModal from "./upgrade-to-pro-modal";
// ui
import { CircularProgress, Icon } from "components/ui";
// icons // icons
import { import { Bolt, HelpOutlineOutlined, WestOutlined } from "@mui/icons-material";
ArrowLongLeftIcon, import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
ChatBubbleOvalLeftEllipsisIcon, import { DocumentIcon, DiscordIcon, GithubIcon } from "components/icons";
ArrowUpCircleIcon,
XMarkIcon,
} from "@heroicons/react/24/outline";
import { QuestionMarkCircleIcon, DocumentIcon, DiscordIcon, GithubIcon } from "components/icons";
// fetch-keys
import { WORKSPACE_DETAILS } from "constants/fetch-keys";
const helpOptions = [ const helpOptions = [
{ {
@ -58,109 +41,35 @@ export interface WorkspaceHelpSectionProps {
} }
export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = ({ setSidebarActive }) => { export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = ({ setSidebarActive }) => {
const [alert, setAlert] = useState(false);
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false); const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
const helpOptionsRef = useRef<HTMLDivElement | null>(null); const helpOptionsRef = useRef<HTMLDivElement | null>(null);
const router = useRouter();
const { workspaceSlug } = router.query;
const { collapsed: sidebarCollapse, toggleCollapsed } = useTheme(); const { collapsed: sidebarCollapse, toggleCollapsed } = useTheme();
useOutsideClickDetector(helpOptionsRef, () => setIsNeedHelpOpen(false)); useOutsideClickDetector(helpOptionsRef, () => setIsNeedHelpOpen(false));
const { user } = useUser();
const [upgradeModal, setUpgradeModal] = useState(false);
const { data: workspaceDetails } = useSWR(
workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null,
workspaceSlug ? () => workspaceService.getWorkspace(workspaceSlug as string) : null
);
const issueNumber = workspaceDetails?.total_issues || 0;
return ( return (
<> <>
<UpgradeToProModal
isOpen={upgradeModal}
onClose={() => setUpgradeModal(false)}
user={user}
issueNumber={issueNumber}
/>
{!sidebarCollapse && (alert || issueNumber >= 750) && (
<div <div
className={`border-t border-custom-sidebar-border-200 p-4 ${ className={`flex w-full items-center justify-between gap-1 self-baseline border-t border-custom-border-200 bg-custom-sidebar-background-100 py-2 px-4 ${
issueNumber >= 750
? "bg-red-500/10 text-red-600"
: issueNumber >= 500
? "bg-yellow-500/10 text-yellow-600"
: "text-green-600"
}`}
>
<div className="flex items-center gap-2">
<CircularProgress progress={(issueNumber / 1024) * 100} />
<div>Free Plan</div>
{issueNumber < 750 && (
<div
className="ml-auto text-custom-text-200 cursor-pointer"
onClick={() => setAlert(false)}
>
<XMarkIcon className="h-4 w-4" />
</div>
)}
</div>
<div className="text-custom-text-200 text-xs mt-2">
This workspace has used {issueNumber} of its 1024 issues creation limit (
{((issueNumber / 1024) * 100).toFixed(0)}
%).
</div>
</div>
)}
<div
className={`flex w-full items-center justify-between gap-1 self-baseline border-t border-custom-border-200 bg-custom-sidebar-background-100 px-4 py-2 ${
sidebarCollapse ? "flex-col" : "" sidebarCollapse ? "flex-col" : ""
}`} }`}
> >
{alert || issueNumber >= 750 ? ( {!sidebarCollapse && (
<button <div className="w-1/2 text-center rounded-md px-2.5 py-1.5 font-medium outline-none text-sm bg-green-500/10 text-green-500">
type="button" Free Plan
className={`flex items-center gap-2 rounded-md px-2.5 py-1.5 font-medium outline-none text-sm ${ </div>
issueNumber >= 750
? "bg-red-500/10 text-red-500"
: "bg-blue-500/10 text-custom-primary-100"
} ${sidebarCollapse ? "w-full justify-center" : ""}`}
title="Shortcuts"
onClick={() => setUpgradeModal(true)}
>
<ArrowUpCircleIcon className="h-4 w-4" />
{!sidebarCollapse && <span>Learn more</span>}
</button>
) : (
<button
type="button"
className={`flex items-center gap-2 rounded-md px-2.5 py-1.5 font-medium outline-none text-sm ${
issueNumber >= 750
? "bg-red-500/10 text-red-600"
: issueNumber >= 500
? "bg-yellow-500/10 text-yellow-600"
: "bg-green-500/10 text-green-600"
}
${sidebarCollapse ? "w-full justify-center" : ""}`}
title="Shortcuts"
onClick={() => setAlert(true)}
>
<CircularProgress
progress={(issueNumber / 1024) * 100 > 100 ? 100 : (issueNumber / 1024) * 100}
/>
{!sidebarCollapse && <span>Free Plan</span>}
</button>
)} )}
<div
className={`flex items-center gap-1 ${
sidebarCollapse ? "flex-col justify-center" : "justify-evenly w-1/2"
}`}
>
<button <button
type="button" type="button"
className={`flex items-center gap-x-1 rounded-md px-2 py-2 text-xs font-medium text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 ${ className={`rounded-md p-1.5 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-90 outline-none ${
sidebarCollapse ? "w-full justify-center" : "" sidebarCollapse ? "w-full" : ""
}`} }`}
onClick={() => { onClick={() => {
const e = new KeyboardEvent("keydown", { const e = new KeyboardEvent("keydown", {
@ -168,40 +77,38 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = ({ setS
}); });
document.dispatchEvent(e); document.dispatchEvent(e);
}} }}
title="Shortcuts"
> >
<Icon iconName="bolt" /> <Bolt fontSize="small" />
</button> </button>
<button <button
type="button" type="button"
className={`flex items-center gap-x-1 rounded-md px-2 py-2 text-xs font-medium text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 ${ className={`rounded-md p-1.5 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-90 outline-none ${
sidebarCollapse ? "w-full justify-center" : "" sidebarCollapse ? "w-full" : ""
}`} }`}
onClick={() => setIsNeedHelpOpen((prev) => !prev)} onClick={() => setIsNeedHelpOpen((prev) => !prev)}
title="Help"
> >
<QuestionMarkCircleIcon className="h-4 w-4 text-custom-text-200" /> <HelpOutlineOutlined fontSize="small" />
</button> </button>
<button <button
type="button" type="button"
className="flex items-center gap-3 rounded-md px-2 py-2 text-xs font-medium text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 md:hidden" className="rounded-md p-1.5 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-90 outline-none md:hidden"
onClick={() => setSidebarActive(false)} onClick={() => setSidebarActive(false)}
> >
<ArrowLongLeftIcon className="h-4 w-4 flex-shrink-0 text-custom-text-200 group-hover:text-custom-text-100" /> <WestOutlined fontSize="small" />
</button> </button>
<button <button
type="button" type="button"
className={`hidden items-center gap-3 rounded-md px-2 py-2 text-xs font-medium text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 md:flex ${ className={`hidden md:flex rounded-md p-1.5 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-90 outline-none ${
sidebarCollapse ? "w-full justify-center" : "" sidebarCollapse ? "w-full" : ""
}`} }`}
onClick={() => toggleCollapsed()} onClick={() => toggleCollapsed()}
> >
<ArrowLongLeftIcon <WestOutlined
className={`h-4 w-4 flex-shrink-0 text-custom-text-200 duration-300 group-hover:text-custom-text-100 ${ fontSize="small"
sidebarCollapse ? "rotate-180" : "" className={`duration-300 ${sidebarCollapse ? "rotate-180" : ""}`}
}`}
/> />
</button> </button>
</div>
<div className="relative"> <div className="relative">
<Transition <Transition

View file

@ -43,7 +43,7 @@ const userLinks = (workspaceSlug: string) => [
export const WorkspaceSidebarDropdown = () => { export const WorkspaceSidebarDropdown = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
// fetching user details
const { user, mutateUser } = useUser(); const { user, mutateUser } = useUser();
const { collapsed: sidebarCollapse } = useThemeHook(); const { collapsed: sidebarCollapse } = useThemeHook();
@ -139,8 +139,8 @@ export const WorkspaceSidebarDropdown = () => {
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items <Menu.Items
className="fixed left-2 z-20 mt-1 flex w-full max-w-[17rem] origin-top-left flex-col rounded-md className="fixed left-4 z-20 mt-1 flex flex-col w-full max-w-[17rem] origin-top-left rounded-md
border border-custom-sidebar-border-200 bg-custom-sidebar-background-90 shadow-lg focus:outline-none" border border-custom-sidebar-border-200 bg-custom-sidebar-background-90 shadow-lg outline-none"
> >
<div className="flex flex-col items-start justify-start gap-3 p-3"> <div className="flex flex-col items-start justify-start gap-3 p-3">
<div className="text-sm text-custom-sidebar-text-200">{user?.email}</div> <div className="text-sm text-custom-sidebar-text-200">{user?.email}</div>

View file

@ -5,40 +5,45 @@ import { useRouter } from "next/router";
// hooks // hooks
import useTheme from "hooks/use-theme"; import useTheme from "hooks/use-theme";
// components
import { NotificationPopover } from "components/notifications"; import { NotificationPopover } from "components/notifications";
// ui
import { Tooltip } from "components/ui";
// icons
import {
BarChartRounded,
GridViewOutlined,
TaskAltOutlined,
WorkOutlineOutlined,
} from "@mui/icons-material";
const workspaceLinks = (workspaceSlug: string) => [ const workspaceLinks = (workspaceSlug: string) => [
{ {
icon: "grid_view", Icon: GridViewOutlined,
name: "Dashboard", name: "Dashboard",
href: `/${workspaceSlug}`, href: `/${workspaceSlug}`,
}, },
{ {
icon: "bar_chart", Icon: BarChartRounded,
name: "Analytics", name: "Analytics",
href: `/${workspaceSlug}/analytics`, href: `/${workspaceSlug}/analytics`,
}, },
{ {
icon: "work", Icon: WorkOutlineOutlined,
name: "Projects", name: "Projects",
href: `/${workspaceSlug}/projects`, href: `/${workspaceSlug}/projects`,
}, },
{ {
icon: "task_alt", Icon: TaskAltOutlined,
name: "My Issues", name: "My Issues",
href: `/${workspaceSlug}/me/my-issues`, href: `/${workspaceSlug}/me/my-issues`,
}, },
]; ];
// components
import { Icon, Tooltip } from "components/ui";
export const WorkspaceSidebarMenu = () => { export const WorkspaceSidebarMenu = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
// theme context
const { collapsed: sidebarCollapse } = useTheme(); const { collapsed: sidebarCollapse } = useTheme();
return ( return (
@ -65,7 +70,7 @@ export const WorkspaceSidebarMenu = () => {
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80" : "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
} ${sidebarCollapse ? "justify-center" : ""}`} } ${sidebarCollapse ? "justify-center" : ""}`}
> >
<Icon iconName={`${link.icon}`} /> {<link.Icon fontSize="small" />}
{!sidebarCollapse && link.name} {!sidebarCollapse && link.name}
</div> </div>
</Tooltip> </Tooltip>

View file

@ -1,248 +0,0 @@
import React, { useState, useEffect } from "react";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// icons
import { XCircleIcon, RocketLaunchIcon } from "@heroicons/react/24/outline";
import { CheckCircleIcon } from "@heroicons/react/24/solid";
// ui
import { CircularProgress } from "components/ui";
// types
import type { ICurrentUserResponse, IWorkspace } from "types";
declare global {
interface Window {
supabase: any;
}
}
type Props = {
isOpen: boolean;
onClose: () => void;
user: ICurrentUserResponse | undefined;
issueNumber: number;
};
const UpgradeToProModal: React.FC<Props> = ({ isOpen, onClose, user, issueNumber }) => {
const [supabaseClient, setSupabaseClient] = useState<any>(null);
useEffect(() => {
// Create a Supabase client
if (process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {
const { createClient } = window.supabase;
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
auth: {
autoRefreshToken: false,
persistSession: false,
},
}
);
if (supabase) {
setSupabaseClient(supabase);
}
}
}, []);
const [isLoading, setIsLoading] = useState(false);
const handleClose = () => {
onClose();
setIsLoading(false);
};
const proFeatures = [
"Everything in free",
"Unlimited users",
"Unlimited file uploads",
"Priority Support",
"Custom Theming",
"Access to Roadmap",
"Plane AI (GPT unlimited)",
];
const [errorMessage, setErrorMessage] = useState<null | { status: String; message: string }>(
null
);
const [loader, setLoader] = useState(false);
const submitEmail = async () => {
setLoader(true);
const payload = { email: user?.email || "" };
if (supabaseClient) {
if (payload?.email) {
const emailExists = await supabaseClient
.from("web-waitlist")
.select("id,email,count")
.eq("email", payload?.email);
if (emailExists.data.length === 0) {
const emailCreation = await supabaseClient
.from("web-waitlist")
.insert([{ email: payload?.email, count: 1, last_visited: new Date() }])
.select("id,email,count");
if (emailCreation.status === 201)
setErrorMessage({ status: "success", message: "Successfully registered." });
else setErrorMessage({ status: "insert_error", message: "Insertion Error." });
} else {
const emailCountUpdate = await supabaseClient
.from("web-waitlist")
.upsert({
id: emailExists.data[0]?.id,
count: emailExists.data[0]?.count + 1,
last_visited: new Date(),
})
.select("id,email,count");
if (emailCountUpdate.status === 201)
setErrorMessage({
status: "email_already_exists",
message: "Email already exists.",
});
else setErrorMessage({ status: "update_error", message: "Update Error." });
}
} else setErrorMessage({ status: "email_required", message: "Please provide email." });
} else
setErrorMessage({
status: "supabase_error",
message: "Network error. Please try again later.",
});
setLoader(false);
};
return (
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-4xl">
<div className="flex flex-wrap">
<div className="w-full md:w-3/5 p-6 flex flex-col gap-y-6">
<div className="flex gap-2">
<div
className={`font-semibold outline-none text-sm mt-1.5 ${
issueNumber >= 750
? "text-red-600"
: issueNumber >= 500
? "text-yellow-600"
: "text-green-600"
}`}
title="Shortcuts"
>
<CircularProgress
progress={
(issueNumber / 1024) * 100 > 100 ? 100 : (issueNumber / 1024) * 100
}
/>
</div>
<div className="">
<div className="font-semibold text-lg">Upgrade to pro</div>
<div className="text-custom-text-200 text-sm">
This workspace has used {issueNumber} of its 1024 issues creation limit (
{((issueNumber / 1024) * 100).toFixed(2)}%).
</div>
</div>
<div
onClick={handleClose}
className="w-5 h-5 text-custom-text-200 cursor-pointer mt-1.5 md:hidden block ml-auto"
>
<XCircleIcon />
</div>
</div>
<div className="flex gap-2 pt-6">
<div
className={`font-semibold outline-none text-sm mt-1.5 w-5 h-5 text-[#892FFF] flex-shrink-0`}
title="Shortcuts"
>
<RocketLaunchIcon />
</div>
<div className="">
<div className="font-semibold text-lg">Order summary</div>
<div className="text-custom-text-200 text-sm">
Priority support, file uploads, and access to premium features.
</div>
<div className="flex flex-wrap my-4">
{proFeatures.map((feature, index) => (
<div key={index} className="w-1/2 py-2 flex gap-2 my-1.5">
<div className="w-5 h-5 mt-0.5 text-green-600">
<CheckCircleIcon />
</div>
<div>{feature}</div>
</div>
))}
</div>
</div>
</div>
</div>
<div className="w-full md:w-2/5 bg-custom-background-90 p-6 flex flex-col">
<div className="flex justify-between items-center">
<div className="font-semibold text-lg">Summary</div>
<div
onClick={handleClose}
className="w-5 h-5 text-custom-text-200 cursor-pointer mt-1.5 hidden md:block"
>
<XCircleIcon />
</div>
</div>
<div className="text-custom-text-200 text-sm mt-4">
Plane application is currently in dev-mode. We will soon introduce Pro plans
once general availability has been established. Stay tuned for more updates.
In the meantime, Plane remains free and unrestricted.
<br /> <br />
We{"'"}ll ensure a smooth transition from the community version to the Pro
plan for you.
</div>
<button
disabled={loader}
onClick={() => submitEmail()}
type="button"
className="mt-5 md:mt-auto whitespace-nowrap max-w-min items-center gap-x-1 rounded-md px-3 py-2 font-medium outline-none text-sm bg-custom-primary-100 text-white"
>
{loader ? "Loading.." : " Join waitlist"}
</button>
{errorMessage && (
<div
className={`mt-1 text-sm ${
errorMessage && errorMessage?.status === "success"
? "text-green-500"
: " text-red-500"
}`}
>
{errorMessage?.message}
</div>
)}
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
};
export default UpgradeToProModal;

View file

@ -17,12 +17,12 @@ export const CHARTS_THEME: Theme = {
background: "rgb(var(--color-background-80))", background: "rgb(var(--color-background-80))",
color: "rgb(var(--color-text-200))", color: "rgb(var(--color-text-200))",
fontSize: "0.8rem", fontSize: "0.8rem",
border: "1px solid rgb(var(--color-background-80))", border: "1px solid rgb(var(--color-border-300))",
}, },
}, },
grid: { grid: {
line: { line: {
stroke: "rgb(var(--color-background-80))", stroke: "rgb(var(--color-border-100))",
}, },
}, },
}; };

View file

@ -217,7 +217,7 @@ export const render12HourFormatTime = (date: string | Date): string => {
if (hours > 12) hours -= 12; if (hours > 12) hours -= 12;
} }
return hours + ":" + minutes + " " + period; return hours + ":" + (minutes < 10 ? `0${minutes}` : minutes) + " " + period;
}; };
export const render24HourFormatTime = (date: string | Date): string => { export const render24HourFormatTime = (date: string | Date): string => {

View file

@ -54,10 +54,8 @@ const useUserNotification = () => {
notifications?.find((notification) => notification.id === notificationId)?.read_at !== null; notifications?.find((notification) => notification.id === notificationId)?.read_at !== null;
if (isRead) { if (isRead) {
await userNotificationServices notificationsMutate(
.markUserNotificationAsUnread(workspaceSlug.toString(), notificationId) (prev) =>
.then(() => {
notificationsMutate((prev) =>
prev?.map((prevNotification) => { prev?.map((prevNotification) => {
if (prevNotification.id === notificationId) { if (prevNotification.id === notificationId) {
return { return {
@ -66,18 +64,20 @@ const useUserNotification = () => {
}; };
} }
return prevNotification; return prevNotification;
}) }),
false
); );
mutateNotificationCount(); await userNotificationServices
}) .markUserNotificationAsUnread(workspaceSlug.toString(), notificationId)
.catch(() => { .catch(() => {
throw new Error("Something went wrong"); throw new Error("Something went wrong");
})
.finally(() => {
notificationsMutate();
}); });
} else { } else {
await userNotificationServices notificationsMutate(
.markUserNotificationAsRead(workspaceSlug.toString(), notificationId) (prev) =>
.then(() => {
notificationsMutate((prev) =>
prev?.map((prevNotification) => { prev?.map((prevNotification) => {
if (prevNotification.id === notificationId) { if (prevNotification.id === notificationId) {
return { return {
@ -86,12 +86,16 @@ const useUserNotification = () => {
}; };
} }
return prevNotification; return prevNotification;
}) }),
false
); );
mutateNotificationCount(); await userNotificationServices
}) .markUserNotificationAsRead(workspaceSlug.toString(), notificationId)
.catch(() => { .catch(() => {
throw new Error("Something went wrong"); throw new Error("Something went wrong");
})
.finally(() => {
notificationsMutate();
}); });
} }
}; };
@ -105,22 +109,24 @@ const useUserNotification = () => {
if (isArchived) { if (isArchived) {
await userNotificationServices await userNotificationServices
.markUserNotificationAsUnarchived(workspaceSlug.toString(), notificationId) .markUserNotificationAsUnarchived(workspaceSlug.toString(), notificationId)
.then(() => {
notificationsMutate();
})
.catch(() => { .catch(() => {
throw new Error("Something went wrong"); throw new Error("Something went wrong");
})
.finally(() => {
notificationsMutate();
}); });
} else { } else {
notificationsMutate(
(prev) => prev?.filter((prevNotification) => prevNotification.id !== notificationId),
false
);
await userNotificationServices await userNotificationServices
.markUserNotificationAsArchived(workspaceSlug.toString(), notificationId) .markUserNotificationAsArchived(workspaceSlug.toString(), notificationId)
.then(() => {
notificationsMutate((prev) =>
prev?.filter((prevNotification) => prevNotification.id !== notificationId)
);
})
.catch(() => { .catch(() => {
throw new Error("Something went wrong"); throw new Error("Something went wrong");
})
.finally(() => {
notificationsMutate();
}); });
} }
}; };
@ -137,19 +143,25 @@ const useUserNotification = () => {
.patchUserNotification(workspaceSlug.toString(), notificationId, { .patchUserNotification(workspaceSlug.toString(), notificationId, {
snoozed_till: null, snoozed_till: null,
}) })
.then(() => { .finally(() => {
notificationsMutate(); notificationsMutate();
}); });
} else } else {
notificationsMutate(
(prevData) => prevData?.filter((prev) => prev.id !== notificationId) || [],
false
);
await userNotificationServices await userNotificationServices
.patchUserNotification(workspaceSlug.toString(), notificationId, { .patchUserNotification(workspaceSlug.toString(), notificationId, {
snoozed_till: dateTime, snoozed_till: dateTime,
}) })
.then(() => { .catch(() => {
notificationsMutate( new Error("Something went wrong");
(prevData) => prevData?.filter((prev) => prev.id !== notificationId) || [] })
); .finally(() => {
notificationsMutate();
}); });
}
}; };
return { return {
@ -170,7 +182,7 @@ const useUserNotification = () => {
setSelectedTab, setSelectedTab,
totalNotificationCount: notificationCount totalNotificationCount: notificationCount
? notificationCount.created_issues + ? notificationCount.created_issues +
notificationCount.watching_notifications + notificationCount.watching_issues +
notificationCount.my_issues notificationCount.my_issues
: null, : null,
notificationCount, notificationCount,

View file

@ -14,6 +14,8 @@
"@headlessui/react": "^1.7.3", "@headlessui/react": "^1.7.3",
"@heroicons/react": "^2.0.12", "@heroicons/react": "^2.0.12",
"@jitsu/nextjs": "^3.1.5", "@jitsu/nextjs": "^3.1.5",
"@mui/icons-material": "^5.14.1",
"@mui/material": "^5.14.1",
"@nivo/bar": "0.80.0", "@nivo/bar": "0.80.0",
"@nivo/calendar": "0.80.0", "@nivo/calendar": "0.80.0",
"@nivo/core": "0.80.0", "@nivo/core": "0.80.0",

View file

@ -24,7 +24,9 @@ import {
} from "components/workspace"; } from "components/workspace";
import { TourRoot } from "components/onboarding"; import { TourRoot } from "components/onboarding";
// ui // ui
import { Icon, PrimaryButton, ProductUpdatesModal } from "components/ui"; import { PrimaryButton, ProductUpdatesModal } from "components/ui";
// icons
import { BoltOutlined, GridViewOutlined } from "@mui/icons-material";
// images // images
import emptyDashboard from "public/empty-state/dashboard.svg"; import emptyDashboard from "public/empty-state/dashboard.svg";
import githubBlackImage from "/public/logos/github-black.png"; import githubBlackImage from "/public/logos/github-black.png";
@ -70,7 +72,7 @@ const WorkspacePage: NextPage = () => {
<WorkspaceAuthorizationLayout <WorkspaceAuthorizationLayout
left={ left={
<div className="flex items-center gap-2 pl-3"> <div className="flex items-center gap-2 pl-3">
<Icon iconName="grid_view" /> <GridViewOutlined fontSize="small" />
Dashboard Dashboard
</div> </div>
} }
@ -80,7 +82,7 @@ const WorkspacePage: NextPage = () => {
onClick={() => setIsProductUpdatesModalOpen(true)} onClick={() => setIsProductUpdatesModalOpen(true)}
className="flex items-center gap-1.5 bg-custom-background-80 text-xs font-medium py-1.5 px-3 rounded" className="flex items-center gap-1.5 bg-custom-background-80 text-xs font-medium py-1.5 px-3 rounded"
> >
<Icon iconName="bolt" className="!text-base -my-1" /> <BoltOutlined fontSize="small" className="-my-1" />
What{"'"}s New? What{"'"}s New?
</button> </button>
<Link href="https://github.com/makeplane/plane" target="_blank" rel="noopener noreferrer"> <Link href="https://github.com/makeplane/plane" target="_blank" rel="noopener noreferrer">

View file

@ -44,28 +44,18 @@ const BillingSettings: NextPage = () => {
<section className="space-y-8"> <section className="space-y-8">
<div> <div>
<h3 className="text-2xl font-semibold leading-6">Billing & Plans</h3> <h3 className="text-2xl font-semibold leading-6">Billing & Plans</h3>
<p className="mt-4 text-sm text-custom-text-200">[Free launch preview] plan Pro</p> <p className="mt-4 text-sm text-custom-text-200">Free launch preview</p>
</div> </div>
<div className="space-y-8 md:w-2/3"> <div className="space-y-8 md:w-2/3">
<div>
<div className="w-80 rounded-md border border-custom-border-200 bg-custom-background-100 p-4 text-center">
<h4 className="text-md mb-1 leading-6">Payment due</h4>
<h2 className="text-3xl font-extrabold">--</h2>
</div>
</div>
<div> <div>
<h4 className="text-md mb-1 leading-6">Current plan</h4> <h4 className="text-md mb-1 leading-6">Current plan</h4>
<p className="mb-3 text-sm text-custom-text-200"> <p className="mb-3 text-sm text-custom-text-200">
You are currently using the free plan You are currently using the free plan
</p> </p>
<a href="https://plane.so/pricing" target="_blank" rel="noreferrer"> <a href="https://plane.so/pricing" target="_blank" rel="noreferrer">
<SecondaryButton outline>View Plans and Upgrade</SecondaryButton> <SecondaryButton outline>View Plans</SecondaryButton>
</a> </a>
</div> </div>
<div>
<h4 className="text-md mb-1 leading-6">Billing history</h4>
<p className="mb-3 text-sm text-custom-text-200">There are no invoices to display</p>
</div>
</div> </div>
</section> </section>
</div> </div>

View file

@ -207,7 +207,10 @@ const WorkspaceSettings: NextPage = () => {
{isImageUploading ? "Uploading..." : "Upload"} {isImageUploading ? "Uploading..." : "Upload"}
</SecondaryButton> </SecondaryButton>
{activeWorkspace.logo && activeWorkspace.logo !== "" && ( {activeWorkspace.logo && activeWorkspace.logo !== "" && (
<DangerButton onClick={() => handleDelete(activeWorkspace.logo)}> <DangerButton
onClick={() => handleDelete(activeWorkspace.logo)}
loading={isImageRemoving}
>
{isImageRemoving ? "Removing..." : "Remove"} {isImageRemoving ? "Removing..." : "Remove"}
</DangerButton> </DangerButton>
)} )}

View file

@ -145,11 +145,11 @@ const HomePage: NextPage = () => {
<h1 className="text-center text-2xl sm:text-2.5xl font-semibold text-custom-text-100"> <h1 className="text-center text-2xl sm:text-2.5xl font-semibold text-custom-text-100">
Sign in to Plane Sign in to Plane
</h1> </h1>
<div className="flex flex-col divide-y divide-custom-border-200 sm:w-[360px] mx-auto"> <div className="flex flex-col divide-y divide-custom-border-200">
<div className="pb-7"> <div className="pb-7">
<EmailCodeForm handleSignIn={handleEmailCodeSignIn} /> <EmailCodeForm handleSignIn={handleEmailCodeSignIn} />
</div> </div>
<div className="flex flex-col items-center justify-center gap-4 pt-7 overflow-hidden"> <div className="flex flex-col items-center justify-center gap-4 pt-7 sm:w-[360px] mx-auto overflow-hidden">
<GoogleLoginButton handleSignIn={handleGoogleSignIn} /> <GoogleLoginButton handleSignIn={handleGoogleSignIn} />
<GithubLoginButton handleSignIn={handleGitHubSignIn} /> <GithubLoginButton handleSignIn={handleGitHubSignIn} />
</div> </div>

View file

@ -105,12 +105,13 @@ const OnBoard: NextPage = () => {
{user?.email} {user?.email}
</div> </div>
</div> </div>
{invitations && invitations.length > 0 ? ( {invitations ? (
invitations.length > 0 ? (
<div className="relative flex justify-center sm:justify-start sm:items-center h-full px-8 pb-8 sm:p-0 sm:pr-[8.33%] sm:w-10/12 md:w-9/12 lg:w-4/5"> <div className="relative flex justify-center sm:justify-start sm:items-center h-full px-8 pb-8 sm:p-0 sm:pr-[8.33%] sm:w-10/12 md:w-9/12 lg:w-4/5">
<div className="w-full space-y-10"> <div className="w-full space-y-10">
<h5 className="text-lg">We see that someone has invited you to</h5> <h5 className="text-lg">We see that someone has invited you to</h5>
<h4 className="text-2xl font-semibold">Join a workspace</h4> <h4 className="text-2xl font-semibold">Join a workspace</h4>
<div className="md:w-3/5 space-y-4"> <div className="max-h-[37vh] md:w-3/5 space-y-4 overflow-y-auto">
{invitations.map((invitation) => { {invitations.map((invitation) => {
const isSelected = invitationsRespond.includes(invitation.id); const isSelected = invitationsRespond.includes(invitation.id);
@ -189,7 +190,8 @@ const OnBoard: NextPage = () => {
onClick={() => router.push("/")} onClick={() => router.push("/")}
/> />
</div> </div>
)} )
) : null}
</div> </div>
</DefaultLayout> </DefaultLayout>
</UserAuthorizationLayout> </UserAuthorizationLayout>

View file

@ -159,7 +159,7 @@ class UserNotificationsServices extends APIService {
async getUnreadNotificationsCount(workspaceSlug: string): Promise<{ async getUnreadNotificationsCount(workspaceSlug: string): Promise<{
created_issues: number; created_issues: number;
my_issues: number; my_issues: number;
watching_notifications: number; watching_issues: number;
}> { }> {
return this.get(`/api/workspaces/${workspaceSlug}/users/notifications/unread/`) return this.get(`/api/workspaces/${workspaceSlug}/users/notifications/unread/`)
.then((response) => response?.data) .then((response) => response?.data)

View file

@ -295,3 +295,7 @@ body {
:-ms-input-placeholder { :-ms-input-placeholder {
color: rgb(var(--color-text-400)); color: rgb(var(--color-text-400));
} }
.bp4-overlay-content {
z-index: 555 !important;
}

157
yarn.lock
View file

@ -899,6 +899,13 @@
dependencies: dependencies:
regenerator-runtime "^0.13.11" regenerator-runtime "^0.13.11"
"@babel/runtime@^7.22.5", "@babel/runtime@^7.22.6":
version "7.22.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438"
integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==
dependencies:
regenerator-runtime "^0.13.11"
"@babel/template@^7.18.10", "@babel/template@^7.20.7": "@babel/template@^7.18.10", "@babel/template@^7.20.7":
version "7.20.7" version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8"
@ -1005,6 +1012,17 @@
"@emotion/weak-memoize" "^0.3.0" "@emotion/weak-memoize" "^0.3.0"
stylis "4.1.4" stylis "4.1.4"
"@emotion/cache@^11.11.0":
version "11.11.0"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff"
integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==
dependencies:
"@emotion/memoize" "^0.8.1"
"@emotion/sheet" "^1.2.2"
"@emotion/utils" "^1.2.1"
"@emotion/weak-memoize" "^0.3.1"
stylis "4.2.0"
"@emotion/css@^11.10.6": "@emotion/css@^11.10.6":
version "11.10.8" version "11.10.8"
resolved "https://registry.yarnpkg.com/@emotion/css/-/css-11.10.8.tgz#9dec9996ad9a1cc28ec8d26b1b27ab0b8f6fb053" resolved "https://registry.yarnpkg.com/@emotion/css/-/css-11.10.8.tgz#9dec9996ad9a1cc28ec8d26b1b27ab0b8f6fb053"
@ -1028,11 +1046,23 @@
dependencies: dependencies:
"@emotion/memoize" "^0.8.0" "@emotion/memoize" "^0.8.0"
"@emotion/is-prop-valid@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc"
integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==
dependencies:
"@emotion/memoize" "^0.8.1"
"@emotion/memoize@^0.8.0": "@emotion/memoize@^0.8.0":
version "0.8.0" version "0.8.0"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f"
integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA== integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==
"@emotion/memoize@^0.8.1":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17"
integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==
"@emotion/react@^11.10.6": "@emotion/react@^11.10.6":
version "11.10.8" version "11.10.8"
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.8.tgz#02e274ecb45e03ab9d7a8eb9f0f0c064613eaf7b" resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.8.tgz#02e274ecb45e03ab9d7a8eb9f0f0c064613eaf7b"
@ -1063,6 +1093,11 @@
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.1.tgz#0767e0305230e894897cadb6c8df2c51e61a6c2c" resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.1.tgz#0767e0305230e894897cadb6c8df2c51e61a6c2c"
integrity sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA== integrity sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==
"@emotion/sheet@^1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec"
integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==
"@emotion/styled@^11.10.6": "@emotion/styled@^11.10.6":
version "11.10.8" version "11.10.8"
resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.10.8.tgz#a3fd68efd90bd7e8a06b82b95adec643d386fa69" resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.10.8.tgz#a3fd68efd90bd7e8a06b82b95adec643d386fa69"
@ -1090,11 +1125,21 @@
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.0.tgz#9716eaccbc6b5ded2ea5a90d65562609aab0f561" resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.0.tgz#9716eaccbc6b5ded2ea5a90d65562609aab0f561"
integrity sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw== integrity sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==
"@emotion/utils@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4"
integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==
"@emotion/weak-memoize@^0.3.0": "@emotion/weak-memoize@^0.3.0":
version "0.3.0" version "0.3.0"
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb" resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb"
integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg== integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==
"@emotion/weak-memoize@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6"
integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==
"@eslint-community/eslint-utils@^4.2.0": "@eslint-community/eslint-utils@^4.2.0":
version "4.4.0" version "4.4.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
@ -1333,11 +1378,37 @@
prop-types "^15.8.1" prop-types "^15.8.1"
react-is "^18.2.0" react-is "^18.2.0"
"@mui/base@5.0.0-beta.8":
version "5.0.0-beta.8"
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.8.tgz#a0a9531ae9147be92d17e4f0e3b9accc57916841"
integrity sha512-b4vVjMZx5KzzEMf4arXKoeV5ZegAMOoPwoy1vfUBwhvXc2QtaaAyBp50U7OA2L06Leubc1A+lEp3eqwZoFn87g==
dependencies:
"@babel/runtime" "^7.22.6"
"@emotion/is-prop-valid" "^1.2.1"
"@mui/types" "^7.2.4"
"@mui/utils" "^5.14.1"
"@popperjs/core" "^2.11.8"
clsx "^1.2.1"
prop-types "^15.8.1"
react-is "^18.2.0"
"@mui/core-downloads-tracker@^5.12.3": "@mui/core-downloads-tracker@^5.12.3":
version "5.12.3" version "5.12.3"
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.12.3.tgz#3dffe62dccc065ddd7338e97d7be4b917004287e" resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.12.3.tgz#3dffe62dccc065ddd7338e97d7be4b917004287e"
integrity sha512-yiJZ+knaknPHuRKhRk4L6XiwppwkAahVal3LuYpvBH7GkA2g+D9WLEXOEnNYtVFUggyKf6fWGLGnx0iqzkU5YA== integrity sha512-yiJZ+knaknPHuRKhRk4L6XiwppwkAahVal3LuYpvBH7GkA2g+D9WLEXOEnNYtVFUggyKf6fWGLGnx0iqzkU5YA==
"@mui/core-downloads-tracker@^5.14.1":
version "5.14.1"
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.1.tgz#af156cb3e15b202f5c09f66e7d8b71ca86aef525"
integrity sha512-mIa1WmDmNr1LoupV1Rbxt9bTFKMbIn10RHG1bnZ/FJCkAYpuU/D4n+R+ttiycgcZNngU++zyh/OQeJblzbQPzg==
"@mui/icons-material@^5.14.1":
version "5.14.1"
resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.14.1.tgz#2f145c15047a0c7f01353ce620cb88276dadba9e"
integrity sha512-xV/f26muQqtWzerzOIdGPrXoxp/OKaE2G2Wp9gnmG47mHua5Slup/tMc3fA4ZYUreGGrK6+tT81TEvt1Wsng8Q==
dependencies:
"@babel/runtime" "^7.22.6"
"@mui/material@^5.12.1": "@mui/material@^5.12.1":
version "5.12.3" version "5.12.3"
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.12.3.tgz#398c1b123fb065763558bc1f9fc47d1f8cb87d0c" resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.12.3.tgz#398c1b123fb065763558bc1f9fc47d1f8cb87d0c"
@ -1356,6 +1427,24 @@
react-is "^18.2.0" react-is "^18.2.0"
react-transition-group "^4.4.5" react-transition-group "^4.4.5"
"@mui/material@^5.14.1":
version "5.14.1"
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.14.1.tgz#2711e4ca5c9bdc67b916d01faee650a7a5260bb8"
integrity sha512-WtsgYuageTunLfxH3Ri+o1RuQTFImtRHxMcVNyD0Hhd2/znjW6KODNz0XfjvLRnNCAynBxZNiflcoIBW40h9PQ==
dependencies:
"@babel/runtime" "^7.22.6"
"@mui/base" "5.0.0-beta.8"
"@mui/core-downloads-tracker" "^5.14.1"
"@mui/system" "^5.14.1"
"@mui/types" "^7.2.4"
"@mui/utils" "^5.14.1"
"@types/react-transition-group" "^4.4.6"
clsx "^1.2.1"
csstype "^3.1.2"
prop-types "^15.8.1"
react-is "^18.2.0"
react-transition-group "^4.4.5"
"@mui/private-theming@^5.12.3": "@mui/private-theming@^5.12.3":
version "5.12.3" version "5.12.3"
resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.12.3.tgz#f5e4704e25d9d91b906561cae573cda8f3801e10" resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.12.3.tgz#f5e4704e25d9d91b906561cae573cda8f3801e10"
@ -1365,6 +1454,15 @@
"@mui/utils" "^5.12.3" "@mui/utils" "^5.12.3"
prop-types "^15.8.1" prop-types "^15.8.1"
"@mui/private-theming@^5.13.7":
version "5.13.7"
resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.13.7.tgz#2f8ef5da066f3c6c6423bd4260d003a28d10b099"
integrity sha512-qbSr+udcij5F9dKhGX7fEdx2drXchq7htLNr2Qg2Ma+WJ6q0ERlEqGSBiPiVDJkptcjeVL4DGmcf1wl5+vD4EA==
dependencies:
"@babel/runtime" "^7.22.5"
"@mui/utils" "^5.13.7"
prop-types "^15.8.1"
"@mui/styled-engine@^5.12.3": "@mui/styled-engine@^5.12.3":
version "5.12.3" version "5.12.3"
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.12.3.tgz#3307643d52c81947a624cdd0437536cc8109c4f0" resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.12.3.tgz#3307643d52c81947a624cdd0437536cc8109c4f0"
@ -1375,6 +1473,16 @@
csstype "^3.1.2" csstype "^3.1.2"
prop-types "^15.8.1" prop-types "^15.8.1"
"@mui/styled-engine@^5.13.2":
version "5.13.2"
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.13.2.tgz#c87bd61c0ab8086d34828b6defe97c02bcd642ef"
integrity sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==
dependencies:
"@babel/runtime" "^7.21.0"
"@emotion/cache" "^11.11.0"
csstype "^3.1.2"
prop-types "^15.8.1"
"@mui/system@^5.12.3": "@mui/system@^5.12.3":
version "5.12.3" version "5.12.3"
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.12.3.tgz#306b3cdffa3046067640219c1e5dd7e3dae38ff9" resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.12.3.tgz#306b3cdffa3046067640219c1e5dd7e3dae38ff9"
@ -1389,6 +1497,20 @@
csstype "^3.1.2" csstype "^3.1.2"
prop-types "^15.8.1" prop-types "^15.8.1"
"@mui/system@^5.14.1":
version "5.14.1"
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.14.1.tgz#ec8ae69f63963b5916dad4bca2f8a86a001a2392"
integrity sha512-u+xlsU34Jdkgx1CxmBnIC4Y08uPdVX5iEd3S/1dggDFtOGp+Lj8xmKRJAQ8PJOOJLOh8pDwaZx4AwXikL4l1QA==
dependencies:
"@babel/runtime" "^7.22.6"
"@mui/private-theming" "^5.13.7"
"@mui/styled-engine" "^5.13.2"
"@mui/types" "^7.2.4"
"@mui/utils" "^5.14.1"
clsx "^1.2.1"
csstype "^3.1.2"
prop-types "^15.8.1"
"@mui/types@^7.2.4": "@mui/types@^7.2.4":
version "7.2.4" version "7.2.4"
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.4.tgz#b6fade19323b754c5c6de679a38f068fd50b9328" resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.4.tgz#b6fade19323b754c5c6de679a38f068fd50b9328"
@ -1405,6 +1527,17 @@
prop-types "^15.8.1" prop-types "^15.8.1"
react-is "^18.2.0" react-is "^18.2.0"
"@mui/utils@^5.13.7", "@mui/utils@^5.14.1":
version "5.14.1"
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.14.1.tgz#29696371016552a6eb3af975bc7af429ec88b29a"
integrity sha512-39KHKK2JeqRmuUcLDLwM+c2XfVC136C5/yUyQXmO2PVbOb2Bol4KxtkssEqCbTwg87PSCG3f1Tb0keRsK7cVGw==
dependencies:
"@babel/runtime" "^7.22.6"
"@types/prop-types" "^15.7.5"
"@types/react-is" "^18.2.1"
prop-types "^15.8.1"
react-is "^18.2.0"
"@next/env@12.3.2": "@next/env@12.3.2":
version "12.3.2" version "12.3.2"
resolved "https://registry.yarnpkg.com/@next/env/-/env-12.3.2.tgz#fb819366771f5721e9438ca3a42ad18684f0949b" resolved "https://registry.yarnpkg.com/@next/env/-/env-12.3.2.tgz#fb819366771f5721e9438ca3a42ad18684f0949b"
@ -1684,6 +1817,11 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.7.tgz#ccab5c8f7dc557a52ca3288c10075c9ccd37fff7" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.7.tgz#ccab5c8f7dc557a52ca3288c10075c9ccd37fff7"
integrity sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw== integrity sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==
"@popperjs/core@^2.11.8":
version "2.11.8"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
"@radix-ui/primitive@1.0.0": "@radix-ui/primitive@1.0.0":
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.0.tgz#e1d8ef30b10ea10e69c76e896f608d9276352253" resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.0.tgz#e1d8ef30b10ea10e69c76e896f608d9276352253"
@ -3230,6 +3368,13 @@
dependencies: dependencies:
"@types/react" "^17" "@types/react" "^17"
"@types/react-is@^18.2.1":
version "18.2.1"
resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-18.2.1.tgz#61d01c2a6fc089a53520c0b66996d458fdc46863"
integrity sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==
dependencies:
"@types/react" "*"
"@types/react-redux@^7.1.20": "@types/react-redux@^7.1.20":
version "7.1.25" version "7.1.25"
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.25.tgz#de841631205b24f9dfb4967dd4a7901e048f9a88" resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.25.tgz#de841631205b24f9dfb4967dd4a7901e048f9a88"
@ -3247,6 +3392,13 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react-transition-group@^4.4.6":
version "4.4.6"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.6.tgz#18187bcda5281f8e10dfc48f0943e2fdf4f75e2e"
integrity sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^18.0.17": "@types/react@*", "@types/react@^18.0.17":
version "18.2.3" version "18.2.3"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.3.tgz#509ad6c4c77378e686f9bb6e0f8756936392f0e8" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.3.tgz#509ad6c4c77378e686f9bb6e0f8756936392f0e8"
@ -7947,6 +8099,11 @@ stylis@4.1.4:
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.4.tgz#9cb60e7153d8ac6d02d773552bf51c7a0344535b" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.4.tgz#9cb60e7153d8ac6d02d773552bf51c7a0344535b"
integrity sha512-USf5pszRYwuE6hg9by0OkKChkQYEXfkeTtm0xKw+jqQhwyjCVLdYyMBK7R+n7dhzsblAWJnGxju4vxq5eH20GQ== integrity sha512-USf5pszRYwuE6hg9by0OkKChkQYEXfkeTtm0xKw+jqQhwyjCVLdYyMBK7R+n7dhzsblAWJnGxju4vxq5eH20GQ==
stylis@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51"
integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==
sucrase@^3.32.0: sucrase@^3.32.0:
version "3.32.0" version "3.32.0"
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.32.0.tgz#c4a95e0f1e18b6847127258a75cf360bc568d4a7" resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.32.0.tgz#c4a95e0f1e18b6847127258a75cf360bc568d4a7"