Compare commits
43
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11faf3f810 | ||
|
|
30ea1adf61 | ||
|
|
546aa40aa3 | ||
|
|
78669363b1 | ||
|
|
bca749986a | ||
|
|
229938114d | ||
|
|
c5e418ab47 | ||
|
|
ecdd1f1d03 | ||
|
|
e687cd6f6a | ||
|
|
9b6721790f | ||
|
|
05df65577a | ||
|
|
52d21b9dda | ||
|
|
51f10d5f36 | ||
|
|
9275e6f373 | ||
|
|
4aef8c2242 | ||
|
|
780573dadd | ||
|
|
5e625ab132 | ||
|
|
34123681cf | ||
|
|
c72ff782ac | ||
|
|
6eb72507a5 | ||
|
|
26b18b431b | ||
|
|
1bae9289f5 | ||
|
|
5c5bcb33e3 | ||
|
|
bed5f76082 | ||
|
|
124c2f772e | ||
|
|
b38898753f | ||
|
|
86a120f11e | ||
|
|
da603dc3f8 | ||
|
|
2f3970f641 | ||
|
|
d759438ebd | ||
|
|
0102f1d693 | ||
|
|
fd3f6ab7a4 | ||
|
|
d05d814efe | ||
|
|
6394f517a2 | ||
|
|
379d258375 | ||
|
|
6a27827c16 | ||
|
|
206ca98307 | ||
|
|
473c32d3d4 | ||
|
|
124d1c30aa | ||
|
|
fd7274ba1c | ||
|
|
46d93525be | ||
|
|
dbbce439ff | ||
|
|
4c0857233e |
@@ -19,14 +19,14 @@
|
||||
<p>
|
||||
<a href="https://app.plane.so/#gh-light-mode-only" target="_blank">
|
||||
<img
|
||||
src="https://ik.imagekit.io/killbluedog/Plane_Screen.png?updatedAt=1684942001069"
|
||||
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/Plane+Screen.png"
|
||||
alt="Plane Screens"
|
||||
width="100%"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://app.plane.so/#gh-dark-mode-only" target="_blank">
|
||||
<img
|
||||
src="https://ik.imagekit.io/killbluedog/Plane_Screens_Dark_Mode.png?updatedAt=1684942388044"
|
||||
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/Plane+Screens+Dark+Mode.png"
|
||||
alt="Plane Screens"
|
||||
width="100%"
|
||||
/>
|
||||
@@ -86,7 +86,7 @@ docker compose up -d
|
||||
<p>
|
||||
<a href="https://plane.so" target="_blank">
|
||||
<img
|
||||
src="https://ik.imagekit.io/killbluedog/Plane_Views_Dark_Mode.png?updatedAt=1684943050275"
|
||||
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/Plane+Views+Dark+Mode.png"
|
||||
alt="Plane Views"
|
||||
width="100%"
|
||||
/>
|
||||
@@ -95,7 +95,7 @@ docker compose up -d
|
||||
<p>
|
||||
<a href="https://plane.so" target="_blank">
|
||||
<img
|
||||
src="https://ik.imagekit.io/killbluedog/Plane_Issue_Detail_Dark_Mode.png?updatedAt=1684943050202"
|
||||
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/Plane+Issue+Detail+Dark+Mode.png"
|
||||
alt="Plane Issue Details"
|
||||
width="100%"
|
||||
/>
|
||||
@@ -104,7 +104,7 @@ docker compose up -d
|
||||
<p>
|
||||
<a href="https://plane.so" target="_blank">
|
||||
<img
|
||||
src="https://ik.imagekit.io/killbluedog/Plane_Cycles___Modules_Dark_Mode.png?updatedAt=1684943050281"
|
||||
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/Plane+Cycles+%26+Modules+Dark+Mode.png"
|
||||
alt="Plane Cycles and Modules"
|
||||
width="100%"
|
||||
/>
|
||||
@@ -113,7 +113,7 @@ docker compose up -d
|
||||
<p>
|
||||
<a href="https://plane.so" target="_blank">
|
||||
<img
|
||||
src="https://ik.imagekit.io/killbluedog/Plane_Analytics_Dark_Mode.png?updatedAt=1684944596824"
|
||||
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/Plane+Analytics+Dark+Mode.png"
|
||||
alt="Plane Analytics"
|
||||
width="100%"
|
||||
/>
|
||||
@@ -122,7 +122,7 @@ docker compose up -d
|
||||
<p>
|
||||
<a href="https://plane.so" target="_blank">
|
||||
<img
|
||||
src="https://ik.imagekit.io/killbluedog/Plane_Pages_Dark_Mode.png?updatedAt=1684943050202"
|
||||
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/Plane+Pages+Dark+Mode.png"
|
||||
alt="Plane Pages"
|
||||
width="100%"
|
||||
/>
|
||||
@@ -132,7 +132,7 @@ docker compose up -d
|
||||
<p>
|
||||
<a href="https://plane.so" target="_blank">
|
||||
<img
|
||||
src="https://ik.imagekit.io/killbluedog/Plane_Commad_K_Dark_Mode.png?updatedAt=1684943050312"
|
||||
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/Plane+Commad+K+Dark+Mode.png"
|
||||
alt="Plane Command Menu"
|
||||
width="100%"
|
||||
/>
|
||||
|
||||
@@ -49,7 +49,7 @@ USER root
|
||||
RUN apk --no-cache add "bash~=5.2"
|
||||
COPY ./bin ./bin/
|
||||
|
||||
RUN chmod +x ./bin/takeoff ./bin/worker
|
||||
RUN chmod +x ./bin/takeoff ./bin/worker ./bin/beat
|
||||
RUN chmod -R 777 /code
|
||||
|
||||
USER captain
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
python manage.py wait_for_db
|
||||
celery -A plane beat -l info
|
||||
@@ -6,4 +6,4 @@ python manage.py migrate
|
||||
# Create a Default User
|
||||
python bin/user_script.py
|
||||
|
||||
exec gunicorn -w 8 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --config gunicorn.config.py --max-requests 1200 --max-requests-jitter 1000 --access-logfile -
|
||||
exec gunicorn -w 8 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile -
|
||||
|
||||
@@ -30,7 +30,6 @@ class NotificationViewSet(BaseViewSet):
|
||||
|
||||
def list(self, request, slug):
|
||||
try:
|
||||
order_by = request.GET.get("order_by", "-created_at")
|
||||
snoozed = request.GET.get("snoozed", "false")
|
||||
archived = request.GET.get("archived", "false")
|
||||
read = request.GET.get("read", "true")
|
||||
@@ -40,7 +39,7 @@ class NotificationViewSet(BaseViewSet):
|
||||
|
||||
notifications = Notification.objects.filter(
|
||||
workspace__slug=slug, receiver_id=request.user.id
|
||||
).order_by(order_by)
|
||||
).order_by("snoozed_till", "-created_at")
|
||||
|
||||
# Filter for snoozed notifications
|
||||
if snoozed == "false":
|
||||
@@ -215,6 +214,7 @@ class UnreadNotificationEndpoint(BaseAPIView):
|
||||
workspace__slug=slug,
|
||||
receiver_id=request.user.id,
|
||||
read_at__isnull=True,
|
||||
archived_at__isnull=True,
|
||||
entity_identifier__in=IssueSubscriber.objects.filter(
|
||||
workspace__slug=slug, subscriber_id=request.user.id
|
||||
).values_list("issue_id", flat=True),
|
||||
@@ -225,6 +225,7 @@ class UnreadNotificationEndpoint(BaseAPIView):
|
||||
workspace__slug=slug,
|
||||
receiver_id=request.user.id,
|
||||
read_at__isnull=True,
|
||||
archived_at__isnull=True,
|
||||
entity_identifier__in=IssueAssignee.objects.filter(
|
||||
workspace__slug=slug, assignee_id=request.user.id
|
||||
).values_list("issue_id", flat=True),
|
||||
@@ -235,6 +236,7 @@ class UnreadNotificationEndpoint(BaseAPIView):
|
||||
workspace__slug=slug,
|
||||
receiver_id=request.user.id,
|
||||
read_at__isnull=True,
|
||||
archived_at__isnull=True,
|
||||
entity_identifier__in=Issue.objects.filter(
|
||||
workspace__slug=slug, created_by=request.user
|
||||
).values_list("pk", flat=True),
|
||||
|
||||
@@ -1028,21 +1028,26 @@ def issue_activity(
|
||||
actor = User.objects.get(pk=actor_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:
|
||||
issue.updated_at = timezone.now()
|
||||
issue.save(update_fields=["updated_at"])
|
||||
|
||||
if issue is not None:
|
||||
issue.updated_at = timezone.now()
|
||||
issue.save(update_fields=["updated_at"])
|
||||
|
||||
if subscriber:
|
||||
# add the user to issue subscriber
|
||||
try:
|
||||
_ = IssueSubscriber.objects.get_or_create(
|
||||
issue_id=issue_id, subscriber=actor
|
||||
)
|
||||
except Exception as e:
|
||||
pass
|
||||
if subscriber:
|
||||
# add the user to issue subscriber
|
||||
try:
|
||||
_ = IssueSubscriber.objects.get_or_create(
|
||||
issue_id=issue_id, subscriber=actor
|
||||
)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
ACTIVITY_MAPPER = {
|
||||
"issue.activity.created": create_issue_activity,
|
||||
@@ -1094,67 +1099,80 @@ def issue_activity(
|
||||
except Exception as e:
|
||||
capture_exception(e)
|
||||
|
||||
# Create Notifications
|
||||
bulk_notifications = []
|
||||
if type not in [
|
||||
"cycle.activity.created",
|
||||
"cycle.activity.deleted",
|
||||
"module.activity.created",
|
||||
"module.activity.deleted",
|
||||
]:
|
||||
# Create Notifications
|
||||
bulk_notifications = []
|
||||
|
||||
issue_subscribers = list(
|
||||
IssueSubscriber.objects.filter(project=project, issue_id=issue_id)
|
||||
.exclude(subscriber_id=actor_id)
|
||||
.values_list("subscriber", flat=True)
|
||||
)
|
||||
issue_subscribers = list(
|
||||
IssueSubscriber.objects.filter(project=project, issue_id=issue_id)
|
||||
.exclude(subscriber_id=actor_id)
|
||||
.values_list("subscriber", flat=True)
|
||||
)
|
||||
|
||||
issue_assignees = list(
|
||||
IssueAssignee.objects.filter(project=project, issue_id=issue_id)
|
||||
.exclude(assignee_id=actor_id)
|
||||
.values_list("assignee", flat=True)
|
||||
)
|
||||
issue_assignees = list(
|
||||
IssueAssignee.objects.filter(project=project, issue_id=issue_id)
|
||||
.exclude(assignee_id=actor_id)
|
||||
.values_list("assignee", flat=True)
|
||||
)
|
||||
|
||||
issue_subscribers = issue_subscribers + issue_assignees
|
||||
issue_subscribers = issue_subscribers + issue_assignees
|
||||
|
||||
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
|
||||
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]
|
||||
# Add bot filtering
|
||||
if (
|
||||
issue is not None
|
||||
and issue.created_by_id is not None
|
||||
and not issue.created_by.is_bot
|
||||
and str(issue.created_by_id) != str(actor_id)
|
||||
):
|
||||
issue_subscribers = issue_subscribers + [issue.created_by_id]
|
||||
|
||||
for subscriber in issue_subscribers:
|
||||
for issue_activity in issue_activities_created:
|
||||
bulk_notifications.append(
|
||||
Notification(
|
||||
workspace=project.workspace,
|
||||
sender="in_app:issue_activities",
|
||||
triggered_by_id=actor_id,
|
||||
receiver_id=subscriber,
|
||||
entity_identifier=issue_id,
|
||||
entity_name="issue",
|
||||
project=project,
|
||||
title=issue_activity.comment,
|
||||
data={
|
||||
"issue": {
|
||||
"id": str(issue_id),
|
||||
"name": str(issue.name),
|
||||
"identifier": str(project.identifier),
|
||||
"sequence_id": issue.sequence_id,
|
||||
"state_name": issue.state.name,
|
||||
"state_group": issue.state.group,
|
||||
for subscriber in issue_subscribers:
|
||||
for issue_activity in issue_activities_created:
|
||||
bulk_notifications.append(
|
||||
Notification(
|
||||
workspace=project.workspace,
|
||||
sender="in_app:issue_activities",
|
||||
triggered_by_id=actor_id,
|
||||
receiver_id=subscriber,
|
||||
entity_identifier=issue_id,
|
||||
entity_name="issue",
|
||||
project=project,
|
||||
title=issue_activity.comment,
|
||||
data={
|
||||
"issue": {
|
||||
"id": str(issue_id),
|
||||
"name": str(issue.name),
|
||||
"identifier": str(project.identifier),
|
||||
"sequence_id": issue.sequence_id,
|
||||
"state_name": issue.state.name,
|
||||
"state_group": issue.state.group,
|
||||
},
|
||||
"issue_activity": {
|
||||
"id": str(issue_activity.id),
|
||||
"verb": str(issue_activity.verb),
|
||||
"field": str(issue_activity.field),
|
||||
"actor": str(issue_activity.actor_id),
|
||||
"new_value": str(issue_activity.new_value),
|
||||
"old_value": str(issue_activity.old_value),
|
||||
"issue_comment": str(
|
||||
issue_activity.issue_comment.comment_stripped
|
||||
if issue_activity.issue_comment is not None
|
||||
else ""
|
||||
),
|
||||
},
|
||||
},
|
||||
"issue_activity": {
|
||||
"id": str(issue_activity.id),
|
||||
"verb": str(issue_activity.verb),
|
||||
"field": str(issue_activity.field),
|
||||
"actor": str(issue_activity.actor_id),
|
||||
"new_value": str(issue_activity.new_value),
|
||||
"old_value": str(issue_activity.old_value),
|
||||
"issue_comment": str(
|
||||
issue_activity.issue_comment.comment_stripped if issue_activity.issue_comment is not None else ""
|
||||
),
|
||||
},
|
||||
},
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Bulk create notifications
|
||||
Notification.objects.bulk_create(bulk_notifications, batch_size=100)
|
||||
# Bulk create notifications
|
||||
Notification.objects.bulk_create(bulk_notifications, batch_size=100)
|
||||
|
||||
return
|
||||
except Exception as e:
|
||||
|
||||
@@ -70,7 +70,7 @@ def archive_old_issues():
|
||||
[
|
||||
issue_activity.delay(
|
||||
type="issue.activity.updated",
|
||||
requested_data=json.dumps({"archived_at": issue.archived_at}),
|
||||
requested_data=json.dumps({"archived_at": str(issue.archived_at)}),
|
||||
actor_id=str(project.created_by_id),
|
||||
issue_id=issue.id,
|
||||
project_id=project_id,
|
||||
@@ -140,7 +140,7 @@ def close_old_issues():
|
||||
[
|
||||
issue_activity.delay(
|
||||
type="issue.activity.updated",
|
||||
requested_data=json.dumps({"closed_to": issue.state_id}),
|
||||
requested_data=json.dumps({"closed_to": str(issue.state_id)}),
|
||||
actor_id=str(project.created_by_id),
|
||||
issue_id=issue.id,
|
||||
project_id=project_id,
|
||||
|
||||
@@ -8,6 +8,7 @@ import plane.db.models.user
|
||||
import uuid
|
||||
|
||||
|
||||
|
||||
def onboarding_default_steps(apps, schema_editor):
|
||||
default_onboarding_schema = {
|
||||
"workspace_join": True,
|
||||
@@ -23,7 +24,7 @@ def onboarding_default_steps(apps, schema_editor):
|
||||
obj.is_tour_completed = True
|
||||
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):
|
||||
@@ -79,6 +80,7 @@ class Migration(migrations.Migration):
|
||||
name="onboarding_step",
|
||||
field=models.JSONField(default=plane.db.models.user.get_default_onboarding),
|
||||
),
|
||||
migrations.RunPython(onboarding_default_steps),
|
||||
migrations.CreateModel(
|
||||
name="Notification",
|
||||
fields=[
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# Generated by Django 4.2.3 on 2023-07-20 09:35
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def restructure_theming(apps, schema_editor):
|
||||
Model = apps.get_model("db", "User")
|
||||
updated_user = []
|
||||
for obj in Model.objects.exclude(theme={}).all():
|
||||
current_theme = obj.theme
|
||||
updated_theme = {
|
||||
"primary": current_theme.get("accent", ""),
|
||||
"background": current_theme.get("bgBase", ""),
|
||||
"sidebarBackground": current_theme.get("sidebar", ""),
|
||||
"text": current_theme.get("textBase", ""),
|
||||
"sidebarText": current_theme.get("textBase", ""),
|
||||
"palette": f"""{current_theme.get("bgBase","")},{current_theme.get("textBase", "")},{current_theme.get("accent", "")},{current_theme.get("sidebar","")},{current_theme.get("textBase", "")}""",
|
||||
"darkPalette": current_theme.get("darkPalette", "")
|
||||
}
|
||||
obj.theme = updated_theme
|
||||
updated_user.append(obj)
|
||||
|
||||
Model.objects.bulk_update(
|
||||
updated_user, ["theme"], batch_size=100
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("db", "0037_issue_archived_at_project_archive_in_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(restructure_theming)
|
||||
]
|
||||
@@ -17,7 +17,7 @@ EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql_psycopg2",
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": os.environ.get("PGUSER", "plane"),
|
||||
"USER": "",
|
||||
"PASSWORD": "",
|
||||
|
||||
@@ -17,7 +17,7 @@ DEBUG = int(os.environ.get("DEBUG", 0)) == 1
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql_psycopg2",
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": "plane",
|
||||
"USER": os.environ.get("PGUSER", ""),
|
||||
"PASSWORD": os.environ.get("PGPASSWORD", ""),
|
||||
|
||||
@@ -16,7 +16,7 @@ from .common import * # noqa
|
||||
DEBUG = int(os.environ.get("DEBUG", 1)) == 1
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql_psycopg2",
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": os.environ.get("PGUSER", "plane"),
|
||||
"USER": "",
|
||||
"PASSWORD": "",
|
||||
|
||||
@@ -30,3 +30,5 @@ openai==0.27.8
|
||||
slack-sdk==3.21.3
|
||||
celery==5.3.1
|
||||
django_celery_beat==2.5.0
|
||||
psycopg-binary==3.1.9
|
||||
psycopg-c==3.1.9
|
||||
@@ -7,5 +7,5 @@ django-storages==1.13.2
|
||||
boto3==1.27.0
|
||||
django-anymail==10.0
|
||||
django-debug-toolbar==4.1.0
|
||||
gevent==22.10.2
|
||||
gevent==23.7.0
|
||||
psycogreen==1.0.2
|
||||
@@ -120,7 +120,7 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
|
||||
Please check your inbox at <span className="font-medium">{watch("email")}</span>
|
||||
</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">
|
||||
<Input
|
||||
id="email"
|
||||
|
||||
@@ -11,14 +11,16 @@ import useEstimateOption from "hooks/use-estimate-option";
|
||||
// components
|
||||
import { SelectFilters } from "components/views";
|
||||
// ui
|
||||
import { CustomMenu, Icon, ToggleSwitch, Tooltip } from "components/ui";
|
||||
import { CustomMenu, ToggleSwitch, Tooltip } from "components/ui";
|
||||
// icons
|
||||
import { ChevronDownIcon } from "@heroicons/react/24/outline";
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ListBulletIcon,
|
||||
Squares2X2Icon,
|
||||
CalendarDaysIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
CalendarMonthOutlined,
|
||||
FormatListBulletedOutlined,
|
||||
GridViewOutlined,
|
||||
TableChartOutlined,
|
||||
WaterfallChartOutlined,
|
||||
} from "@mui/icons-material";
|
||||
// helpers
|
||||
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
||||
import { checkIfArraysHaveSameElements } from "helpers/array.helper";
|
||||
@@ -27,26 +29,26 @@ import { Properties, TIssueViewOptions } from "types";
|
||||
// constants
|
||||
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",
|
||||
icon: <ListBulletIcon className="h-4 w-4" />,
|
||||
Icon: FormatListBulletedOutlined,
|
||||
},
|
||||
{
|
||||
type: "kanban",
|
||||
icon: <Squares2X2Icon className="h-4 w-4" />,
|
||||
Icon: GridViewOutlined,
|
||||
},
|
||||
{
|
||||
type: "calendar",
|
||||
icon: <CalendarDaysIcon className="h-4 w-4" />,
|
||||
Icon: CalendarMonthOutlined,
|
||||
},
|
||||
{
|
||||
type: "spreadsheet",
|
||||
icon: <Icon iconName="table_chart" />,
|
||||
Icon: TableChartOutlined,
|
||||
},
|
||||
{
|
||||
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)}
|
||||
>
|
||||
{option.icon}
|
||||
<option.Icon
|
||||
sx={{
|
||||
fontSize: 16,
|
||||
}}
|
||||
className={option.type === "gantt_chart" ? "rotate-90" : ""}
|
||||
/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
))}
|
||||
@@ -177,7 +184,6 @@ export const IssuesFilterView: React.FC = () => {
|
||||
GROUP_BY_OPTIONS.find((option) => option.key === groupByProperty)
|
||||
?.name ?? "Select"
|
||||
}
|
||||
width="lg"
|
||||
>
|
||||
{GROUP_BY_OPTIONS.map((option) =>
|
||||
issueView === "kanban" && option.key === null ? null : (
|
||||
@@ -198,7 +204,6 @@ export const IssuesFilterView: React.FC = () => {
|
||||
ORDER_BY_OPTIONS.find((option) => option.key === orderBy)?.name ??
|
||||
"Select"
|
||||
}
|
||||
width="lg"
|
||||
>
|
||||
{ORDER_BY_OPTIONS.map((option) =>
|
||||
groupByProperty === "priority" && option.key === "priority" ? null : (
|
||||
@@ -223,7 +228,6 @@ export const IssuesFilterView: React.FC = () => {
|
||||
FILTER_ISSUE_OPTIONS.find((option) => option.key === filters.type)
|
||||
?.name ?? "Select"
|
||||
}
|
||||
width="lg"
|
||||
>
|
||||
{FILTER_ISSUE_OPTIONS.map((option) => (
|
||||
<CustomMenu.MenuItem
|
||||
|
||||
@@ -69,8 +69,8 @@ export const CustomThemeSelector: React.FC<Props> = ({ preLoadedData }) => {
|
||||
return { ...prevData, ...res };
|
||||
}, false);
|
||||
|
||||
applyTheme(payload.palette, darkPalette);
|
||||
setTheme("custom");
|
||||
applyTheme(payload.palette, darkPalette);
|
||||
})
|
||||
.catch((err) => console.log(err));
|
||||
};
|
||||
|
||||
@@ -71,7 +71,21 @@ export const ThemeSwitch: React.FC<Props> = ({
|
||||
}
|
||||
onChange={({ value, type }: { value: string; type: string }) => {
|
||||
if (value === "custom") {
|
||||
if (user?.theme.palette) setPreLoadedData(user.theme);
|
||||
if (user?.theme.palette) {
|
||||
setPreLoadedData({
|
||||
background: user.theme.background !== "" ? user.theme.background : "#0d101b",
|
||||
text: user.theme.text !== "" ? user.theme.text : "#c5c5c5",
|
||||
primary: user.theme.primary !== "" ? user.theme.primary : "#3f76ff",
|
||||
sidebarBackground:
|
||||
user.theme.sidebarBackground !== "" ? user.theme.sidebarBackground : "#0d101b",
|
||||
sidebarText: user.theme.sidebarText !== "" ? user.theme.sidebarText : "#c5c5c5",
|
||||
darkPalette: false,
|
||||
palette:
|
||||
user.theme.palette !== ",,,,"
|
||||
? user.theme.palette
|
||||
: "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5",
|
||||
});
|
||||
}
|
||||
|
||||
if (!customThemeSelectorOptions) setCustomThemeSelectorOptions(true);
|
||||
} else {
|
||||
@@ -87,6 +101,7 @@ export const ThemeSwitch: React.FC<Props> = ({
|
||||
document.documentElement.style.removeProperty(`--color-sidebar-border-${i}`);
|
||||
}
|
||||
}
|
||||
|
||||
setTheme(value);
|
||||
document.documentElement.style.setProperty("color-scheme", type);
|
||||
}}
|
||||
|
||||
@@ -10,7 +10,7 @@ import useToast from "hooks/use-toast";
|
||||
import { CustomMenu, Icon, Tooltip } from "components/ui";
|
||||
|
||||
// helper
|
||||
import { stripHTML, replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
||||
import { stripHTML, replaceUnderscoreIfSnakeCase, truncateText } from "helpers/string.helper";
|
||||
import {
|
||||
formatDateDistance,
|
||||
render12HourFormatTime,
|
||||
@@ -32,7 +32,7 @@ type NotificationCardProps = {
|
||||
|
||||
const snoozeOptions = [
|
||||
{
|
||||
label: "1 days",
|
||||
label: "1 day",
|
||||
value: new Date(new Date().getTime() + 24 * 60 * 60 * 1000),
|
||||
},
|
||||
{
|
||||
@@ -79,102 +79,105 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
||||
`/${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 && (
|
||||
<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">
|
||||
{notification.triggered_by_details.avatar &&
|
||||
notification.triggered_by_details.avatar !== "" ? (
|
||||
<div className="relative w-12 h-12 rounded-full">
|
||||
{notification.triggered_by_details.avatar &&
|
||||
notification.triggered_by_details.avatar !== "" ? (
|
||||
<div className="h-12 w-12 rounded-full">
|
||||
<Image
|
||||
src={notification.triggered_by_details.avatar}
|
||||
alt="profile image"
|
||||
alt="Profile Image"
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
className="rounded-full"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-12 h-12 bg-custom-background-100 rounded-full flex justify-center items-center">
|
||||
<span className="text-custom-text-100 font-medium text-lg">
|
||||
{notification.triggered_by_details.first_name[0].toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="w-full space-y-2.5">
|
||||
<div className="text-sm">
|
||||
<span className="font-semibold">
|
||||
{notification.triggered_by_details.first_name}{" "}
|
||||
{notification.triggered_by_details.last_name}{" "}
|
||||
</div>
|
||||
) : (
|
||||
<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">
|
||||
{notification.triggered_by_details.first_name[0].toUpperCase()}
|
||||
</span>
|
||||
{notification.data.issue_activity.field !== "comment" &&
|
||||
notification.data.issue_activity.verb}{" "}
|
||||
{notification.data.issue_activity.field === "comment"
|
||||
? "commented"
|
||||
: notification.data.issue_activity.field === "None"
|
||||
? null
|
||||
: replaceUnderscoreIfSnakeCase(notification.data.issue_activity.field)}{" "}
|
||||
{notification.data.issue_activity.field !== "comment" &&
|
||||
notification.data.issue_activity.field !== "None"
|
||||
? "to"
|
||||
: ""}
|
||||
<span className="font-semibold">
|
||||
{" "}
|
||||
{notification.data.issue_activity.field !== "None" ? (
|
||||
notification.data.issue_activity.field !== "comment" ? (
|
||||
notification.data.issue_activity.field === "target_date" ? (
|
||||
renderShortDateWithYearFormat(notification.data.issue_activity.new_value)
|
||||
) : notification.data.issue_activity.field === "attachment" ? (
|
||||
"the issue"
|
||||
) : stripHTML(notification.data.issue_activity.new_value).length > 55 ? (
|
||||
stripHTML(notification.data.issue_activity.new_value).slice(0, 50) + "..."
|
||||
) : (
|
||||
stripHTML(notification.data.issue_activity.new_value)
|
||||
)
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2.5 w-full overflow-hidden">
|
||||
<div className="text-sm w-full break-words">
|
||||
<span className="font-semibold">
|
||||
{notification.triggered_by_details.first_name}{" "}
|
||||
{notification.triggered_by_details.last_name}{" "}
|
||||
</span>
|
||||
{notification.data.issue_activity.field !== "comment" &&
|
||||
notification.data.issue_activity.verb}{" "}
|
||||
{notification.data.issue_activity.field === "comment"
|
||||
? "commented"
|
||||
: notification.data.issue_activity.field === "None"
|
||||
? null
|
||||
: replaceUnderscoreIfSnakeCase(notification.data.issue_activity.field)}{" "}
|
||||
{notification.data.issue_activity.field !== "comment" &&
|
||||
notification.data.issue_activity.field !== "None"
|
||||
? "to"
|
||||
: ""}
|
||||
<span className="font-semibold">
|
||||
{" "}
|
||||
{notification.data.issue_activity.field !== "None" ? (
|
||||
notification.data.issue_activity.field !== "comment" ? (
|
||||
notification.data.issue_activity.field === "target_date" ? (
|
||||
renderShortDateWithYearFormat(notification.data.issue_activity.new_value)
|
||||
) : notification.data.issue_activity.field === "attachment" ? (
|
||||
"the issue"
|
||||
) : stripHTML(notification.data.issue_activity.new_value).length > 55 ? (
|
||||
stripHTML(notification.data.issue_activity.new_value).slice(0, 50) + "..."
|
||||
) : (
|
||||
<span>
|
||||
{`"`}
|
||||
{notification.data.issue_activity.new_value.length > 55
|
||||
? notification?.data?.issue_activity?.issue_comment?.slice(0, 50) + "..."
|
||||
: notification.data.issue_activity.issue_comment}
|
||||
{`"`}
|
||||
</span>
|
||||
stripHTML(notification.data.issue_activity.new_value)
|
||||
)
|
||||
) : (
|
||||
"the issue and assigned it to you."
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="w-full flex justify-between text-xs">
|
||||
<p className="truncate inline max-w-lg text-custom-text-300 mr-3">
|
||||
{notification.data.issue.identifier}-{notification.data.issue.sequence_id}{" "}
|
||||
{notification.data.issue.name}
|
||||
</p>
|
||||
{notification.snoozed_till ? (
|
||||
<p className="text-custom-text-300 flex items-center gap-x-1">
|
||||
<Icon iconName="schedule" />
|
||||
<span>
|
||||
Till {renderShortDate(notification.snoozed_till)},{" "}
|
||||
{render12HourFormatTime(notification.snoozed_till)}
|
||||
{`"`}
|
||||
{notification.data.issue_activity.new_value.length > 55
|
||||
? notification?.data?.issue_activity?.issue_comment?.slice(0, 50) + "..."
|
||||
: notification.data.issue_activity.issue_comment}
|
||||
{`"`}
|
||||
</span>
|
||||
</p>
|
||||
)
|
||||
) : (
|
||||
<p className="text-custom-text-300">{formatDateDistance(notification.created_at)}</p>
|
||||
"the issue and assigned it to you."
|
||||
)}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between gap-2 text-xs">
|
||||
<p className="text-custom-text-300">
|
||||
{truncateText(
|
||||
`${notification.data.issue.identifier}-${notification.data.issue.sequence_id} ${notification.data.issue.name}`,
|
||||
50
|
||||
)}
|
||||
</p>
|
||||
{notification.snoozed_till ? (
|
||||
<p className="text-custom-text-300 flex items-center justify-end gap-x-1 flex-shrink-0">
|
||||
<Icon iconName="schedule" className="!text-base -my-0.5" />
|
||||
<span>
|
||||
Till {renderShortDate(notification.snoozed_till)},{" "}
|
||||
{render12HourFormatTime(notification.snoozed_till)}
|
||||
</span>
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-custom-text-300 flex-shrink-0">
|
||||
{formatDateDistance(notification.created_at)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute py-1 gap-x-3 right-3 top-3 hidden group-hover:flex">
|
||||
{[
|
||||
{
|
||||
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",
|
||||
onClick: () => {
|
||||
markNotificationReadStatus(notification.id).then(() => {
|
||||
@@ -189,8 +192,8 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: notification.archived_at ? "Unarchive Notification" : "Archive Notification",
|
||||
icon: "archive",
|
||||
name: notification.archived_at ? "Unarchive" : "Archive",
|
||||
icon: notification.archived_at ? "unarchive" : "archive",
|
||||
onClick: () => {
|
||||
markNotificationArchivedStatus(notification.id).then(() => {
|
||||
setToastAlert({
|
||||
@@ -203,7 +206,7 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
||||
},
|
||||
},
|
||||
].map((item) => (
|
||||
<Tooltip tooltipContent={item.name} position="top-left">
|
||||
<Tooltip tooltipContent={item.name}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
@@ -211,52 +214,54 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
||||
item.onClick();
|
||||
}}
|
||||
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" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
))}
|
||||
|
||||
<Tooltip tooltipContent="Snooze Notification" position="top-left">
|
||||
<CustomMenu
|
||||
menuButtonOnClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
customButton={
|
||||
<button
|
||||
type="button"
|
||||
className="text-sm flex w-full items-center gap-x-2 bg-custom-background-80 hover:bg-custom-background-100 p-0.5 rounded"
|
||||
>
|
||||
<Icon iconName="schedule" className="h-5 w-5 text-custom-text-300" />
|
||||
</button>
|
||||
}
|
||||
optionsClassName="!z-20"
|
||||
>
|
||||
{snoozeOptions.map((item) => (
|
||||
<CustomMenu.MenuItem
|
||||
key={item.label}
|
||||
renderAs="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
<Tooltip tooltipContent="Snooze">
|
||||
<div>
|
||||
<CustomMenu
|
||||
menuButtonOnClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
customButton={
|
||||
<button
|
||||
type="button"
|
||||
className="text-sm flex w-full items-center gap-x-2 bg-custom-background-80 hover:bg-custom-background-100 p-0.5 rounded"
|
||||
>
|
||||
<Icon iconName="schedule" className="h-5 w-5 text-custom-text-300" />
|
||||
</button>
|
||||
}
|
||||
optionsClassName="!z-20"
|
||||
>
|
||||
{snoozeOptions.map((item) => (
|
||||
<CustomMenu.MenuItem
|
||||
key={item.label}
|
||||
renderAs="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (!item.value) {
|
||||
setSelectedNotificationForSnooze(notification.id);
|
||||
return;
|
||||
}
|
||||
if (!item.value) {
|
||||
setSelectedNotificationForSnooze(notification.id);
|
||||
return;
|
||||
}
|
||||
|
||||
markSnoozeNotification(notification.id, item.value).then(() => {
|
||||
setToastAlert({
|
||||
title: `Notification snoozed till ${renderLongDateFormat(item.value)}`,
|
||||
type: "success",
|
||||
markSnoozeNotification(notification.id, item.value).then(() => {
|
||||
setToastAlert({
|
||||
title: `Notification snoozed till ${renderLongDateFormat(item.value)}`,
|
||||
type: "success",
|
||||
});
|
||||
});
|
||||
});
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
</CustomMenu>
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
</CustomMenu>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,8 +12,10 @@ import useWorkspaceMembers from "hooks/use-workspace-members";
|
||||
import useUserNotification from "hooks/use-user-notifications";
|
||||
|
||||
// components
|
||||
import { Icon, Loader, EmptyState } from "components/ui";
|
||||
import { Icon, Loader, EmptyState, Tooltip } from "components/ui";
|
||||
import { SnoozeNotificationModal, NotificationCard } from "components/notifications";
|
||||
// icons
|
||||
import { NotificationsOutlined } from "@mui/icons-material";
|
||||
// images
|
||||
import emptyNotification from "public/empty-state/notification.svg";
|
||||
// helpers
|
||||
@@ -69,7 +71,7 @@ export const NotificationPopover = () => {
|
||||
{
|
||||
label: "Subscribed",
|
||||
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 ${
|
||||
isActive
|
||||
? "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" : ""}`}
|
||||
>
|
||||
<Icon iconName="notifications" />
|
||||
<NotificationsOutlined fontSize="small" />
|
||||
{sidebarCollapse ? null : <span>Notifications</span>}
|
||||
{totalNotificationCount && totalNotificationCount > 0 ? (
|
||||
<span className="ml-auto bg-custom-primary-300 rounded-full text-xs text-white px-1.5">
|
||||
@@ -116,54 +118,62 @@ export const NotificationPopover = () => {
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
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">
|
||||
<h2 className="text-xl font-semibold mb-2">Notifications</h2>
|
||||
<div className="flex gap-x-4 justify-center items-center text-custom-text-200">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
notificationsMutate();
|
||||
<Tooltip tooltipContent="Refresh">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
notificationsMutate();
|
||||
|
||||
const target = e.target as HTMLButtonElement;
|
||||
target?.classList.add("animate-spin");
|
||||
setTimeout(() => {
|
||||
target?.classList.remove("animate-spin");
|
||||
}, 1000);
|
||||
}}
|
||||
>
|
||||
<Icon iconName="refresh" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSnoozed(false);
|
||||
setArchived(false);
|
||||
setReadNotification((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
<Icon iconName="filter_list" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setArchived(false);
|
||||
setReadNotification(false);
|
||||
setSnoozed((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
<Icon iconName="schedule" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSnoozed(false);
|
||||
setReadNotification(false);
|
||||
setArchived((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
<Icon iconName="archive" />
|
||||
</button>
|
||||
const target = e.target as HTMLButtonElement;
|
||||
target?.classList.add("animate-spin");
|
||||
setTimeout(() => {
|
||||
target?.classList.remove("animate-spin");
|
||||
}, 1000);
|
||||
}}
|
||||
>
|
||||
<Icon iconName="refresh" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip tooltipContent="Unread notifications">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSnoozed(false);
|
||||
setArchived(false);
|
||||
setReadNotification((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
<Icon iconName="filter_list" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip tooltipContent="Snoozed notifications">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setArchived(false);
|
||||
setReadNotification(false);
|
||||
setSnoozed((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
<Icon iconName="schedule" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip tooltipContent="Archived notifications">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSnoozed(false);
|
||||
setReadNotification(false);
|
||||
setArchived((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
<Icon iconName="archive" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<button type="button" onClick={() => closePopover()}>
|
||||
<Icon iconName="close" />
|
||||
</button>
|
||||
@@ -205,6 +215,7 @@ export const NotificationPopover = () => {
|
||||
: "border-transparent text-custom-text-200"
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
{tab.unreadCount && tab.unreadCount > 0 ? (
|
||||
<span
|
||||
className={`ml-2 rounded-full text-xs px-2 py-0.5 ${
|
||||
@@ -250,7 +261,7 @@ export const NotificationPopover = () => {
|
||||
|
||||
{notifications ? (
|
||||
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) => (
|
||||
<NotificationCard
|
||||
key={notification.id}
|
||||
@@ -263,7 +274,7 @@ export const NotificationPopover = () => {
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid h-full w-full place-items-center overflow-hidden">
|
||||
<div className="grid h-full w-full place-items-center overflow-hidden scale-75">
|
||||
<EmptyState
|
||||
title="You're updated with all the notifications"
|
||||
description="You have read all the notifications."
|
||||
@@ -273,7 +284,7 @@ export const NotificationPopover = () => {
|
||||
</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" />
|
||||
|
||||
@@ -88,7 +88,7 @@ export const JoinWorkspaces: React.FC<Props> = ({ stepChange }) => {
|
||||
<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>
|
||||
<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.map((invitation) => {
|
||||
const isSelected = invitationsRespond.includes(invitation.id);
|
||||
@@ -146,7 +146,11 @@ export const JoinWorkspaces: React.FC<Props> = ({ stepChange }) => {
|
||||
>
|
||||
Accept & Join
|
||||
</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
|
||||
</SecondaryButton>
|
||||
</div>
|
||||
|
||||
@@ -1,31 +1,37 @@
|
||||
// ui
|
||||
import { Icon } from "components/ui";
|
||||
// icons
|
||||
import {
|
||||
ArticleOutlined,
|
||||
ContrastOutlined,
|
||||
DatasetOutlined,
|
||||
FilterNoneOutlined,
|
||||
PhotoFilterOutlined,
|
||||
} from "@mui/icons-material";
|
||||
// types
|
||||
import { TTourSteps } from "./root";
|
||||
|
||||
const sidebarOptions: {
|
||||
key: TTourSteps;
|
||||
icon: string;
|
||||
Icon: any;
|
||||
}[] = [
|
||||
{
|
||||
key: "issues",
|
||||
icon: "stack",
|
||||
Icon: FilterNoneOutlined,
|
||||
},
|
||||
{
|
||||
key: "cycles",
|
||||
icon: "contrast",
|
||||
Icon: ContrastOutlined,
|
||||
},
|
||||
{
|
||||
key: "modules",
|
||||
icon: "dataset",
|
||||
Icon: DatasetOutlined,
|
||||
},
|
||||
{
|
||||
key: "views",
|
||||
icon: "photo_filter",
|
||||
Icon: PhotoFilterOutlined,
|
||||
},
|
||||
{
|
||||
key: "pages",
|
||||
icon: "article",
|
||||
Icon: ArticleOutlined,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -52,11 +58,10 @@ export const TourSidebar: React.FC<Props> = ({ step, setStep }) => (
|
||||
}`}
|
||||
onClick={() => setStep(option.key)}
|
||||
>
|
||||
<Icon
|
||||
iconName={option.icon}
|
||||
className={`h-5 w-5 flex-shrink-0 ${
|
||||
step === option.key ? "text-custom-primary-100" : "text-custom-text-200"
|
||||
}`}
|
||||
<option.Icon
|
||||
sx={{
|
||||
fontSize: 18,
|
||||
}}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{option.key}
|
||||
|
||||
@@ -10,9 +10,19 @@ import projectService from "services/project.service";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { CustomMenu, Icon, Tooltip } from "components/ui";
|
||||
import { CustomMenu, Tooltip } from "components/ui";
|
||||
// icons
|
||||
import { LinkIcon, StarIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||
import {
|
||||
ArchiveOutlined,
|
||||
ArticleOutlined,
|
||||
ContrastOutlined,
|
||||
DatasetOutlined,
|
||||
ExpandMoreOutlined,
|
||||
FilterNoneOutlined,
|
||||
PhotoFilterOutlined,
|
||||
SettingsOutlined,
|
||||
} from "@mui/icons-material";
|
||||
// helpers
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
@@ -33,32 +43,32 @@ const navigation = (workspaceSlug: string, projectId: string) => [
|
||||
{
|
||||
name: "Issues",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/issues`,
|
||||
icon: "stack",
|
||||
Icon: FilterNoneOutlined,
|
||||
},
|
||||
{
|
||||
name: "Cycles",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/cycles`,
|
||||
icon: "contrast",
|
||||
Icon: ContrastOutlined,
|
||||
},
|
||||
{
|
||||
name: "Modules",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/modules`,
|
||||
icon: "dataset",
|
||||
Icon: DatasetOutlined,
|
||||
},
|
||||
{
|
||||
name: "Views",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/views`,
|
||||
icon: "photo_filter",
|
||||
Icon: PhotoFilterOutlined,
|
||||
},
|
||||
{
|
||||
name: "Pages",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/pages`,
|
||||
icon: "article",
|
||||
Icon: ArticleOutlined,
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/settings`,
|
||||
icon: "settings",
|
||||
Icon: SettingsOutlined,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -164,8 +174,8 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
||||
)}
|
||||
</div>
|
||||
{!sidebarCollapse && (
|
||||
<Icon
|
||||
iconName="expand_more"
|
||||
<ExpandMoreOutlined
|
||||
fontSize="small"
|
||||
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">
|
||||
<Icon iconName="archive" className="h-4 w-4" />
|
||||
<ArchiveOutlined fontSize="small" />
|
||||
<span>Archived Issues</span>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
@@ -248,13 +258,17 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
||||
disabled={!sidebarCollapse}
|
||||
>
|
||||
<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)
|
||||
? "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"
|
||||
} ${sidebarCollapse ? "justify-center" : ""}`}
|
||||
>
|
||||
<Icon iconName={item.icon} />
|
||||
<item.Icon
|
||||
sx={{
|
||||
fontSize: 18,
|
||||
}}
|
||||
/>
|
||||
{!sidebarCollapse && item.name}
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
@@ -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 };
|
||||
@@ -4,9 +4,10 @@ import Link from "next/link";
|
||||
|
||||
// headless ui
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
// ui
|
||||
import { DropdownProps } from "components/ui";
|
||||
// icons
|
||||
import { DropdownProps, Icon } from "components/ui";
|
||||
import { ChevronDownIcon } from "@heroicons/react/24/outline";
|
||||
import { ExpandMoreOutlined, MoreHorizOutlined } from "@mui/icons-material";
|
||||
|
||||
export type CustomMenuProps = DropdownProps & {
|
||||
children: React.ReactNode;
|
||||
@@ -53,8 +54,8 @@ const CustomMenu = ({
|
||||
disabled ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-80"
|
||||
} ${buttonClassName}`}
|
||||
>
|
||||
<Icon
|
||||
iconName="more_horiz"
|
||||
<MoreHorizOutlined
|
||||
fontSize="small"
|
||||
className={`${verticalEllipsis ? "rotate-90" : ""} text-custom-text-200`}
|
||||
/>
|
||||
</Menu.Button>
|
||||
@@ -72,7 +73,14 @@ const CustomMenu = ({
|
||||
} ${buttonClassName}`}
|
||||
>
|
||||
{label}
|
||||
{!noChevron && <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />}
|
||||
{!noChevron && (
|
||||
<ExpandMoreOutlined
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
}}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</Menu.Button>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,33 +1,16 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
import useSWR from "swr";
|
||||
|
||||
// headless ui
|
||||
import { Transition } from "@headlessui/react";
|
||||
// services
|
||||
import workspaceService from "services/workspace.service";
|
||||
// hooks
|
||||
import useTheme from "hooks/use-theme";
|
||||
import useUser from "hooks/use-user";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// components
|
||||
import UpgradeToProModal from "./upgrade-to-pro-modal";
|
||||
// ui
|
||||
import { CircularProgress, Icon } from "components/ui";
|
||||
// icons
|
||||
import {
|
||||
ArrowLongLeftIcon,
|
||||
ChatBubbleOvalLeftEllipsisIcon,
|
||||
ArrowUpCircleIcon,
|
||||
XMarkIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { QuestionMarkCircleIcon, DocumentIcon, DiscordIcon, GithubIcon } from "components/icons";
|
||||
// fetch-keys
|
||||
import { WORKSPACE_DETAILS } from "constants/fetch-keys";
|
||||
import { Bolt, HelpOutlineOutlined, WestOutlined } from "@mui/icons-material";
|
||||
import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
|
||||
import { DocumentIcon, DiscordIcon, GithubIcon } from "components/icons";
|
||||
|
||||
const helpOptions = [
|
||||
{
|
||||
@@ -58,150 +41,74 @@ export interface WorkspaceHelpSectionProps {
|
||||
}
|
||||
|
||||
export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = ({ setSidebarActive }) => {
|
||||
const [alert, setAlert] = useState(false);
|
||||
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
|
||||
|
||||
const helpOptionsRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { collapsed: sidebarCollapse, toggleCollapsed } = useTheme();
|
||||
|
||||
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 (
|
||||
<>
|
||||
<UpgradeToProModal
|
||||
isOpen={upgradeModal}
|
||||
onClose={() => setUpgradeModal(false)}
|
||||
user={user}
|
||||
issueNumber={issueNumber}
|
||||
/>
|
||||
{!sidebarCollapse && (alert || issueNumber >= 750) && (
|
||||
<div
|
||||
className={`border-t border-custom-sidebar-border-200 p-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 ${
|
||||
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 ${
|
||||
sidebarCollapse ? "flex-col" : ""
|
||||
}`}
|
||||
>
|
||||
{alert || issueNumber >= 750 ? (
|
||||
<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-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>
|
||||
{!sidebarCollapse && (
|
||||
<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">
|
||||
Free Plan
|
||||
</div>
|
||||
)}
|
||||
<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 ${
|
||||
sidebarCollapse ? "w-full justify-center" : ""
|
||||
<div
|
||||
className={`flex items-center gap-1 ${
|
||||
sidebarCollapse ? "flex-col justify-center" : "justify-evenly w-1/2"
|
||||
}`}
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "h",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
title="Shortcuts"
|
||||
>
|
||||
<Icon iconName="bolt" />
|
||||
</button>
|
||||
<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 ${
|
||||
sidebarCollapse ? "w-full justify-center" : ""
|
||||
}`}
|
||||
onClick={() => setIsNeedHelpOpen((prev) => !prev)}
|
||||
title="Help"
|
||||
>
|
||||
<QuestionMarkCircleIcon className="h-4 w-4 text-custom-text-200" />
|
||||
</button>
|
||||
<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"
|
||||
onClick={() => setSidebarActive(false)}
|
||||
>
|
||||
<ArrowLongLeftIcon className="h-4 w-4 flex-shrink-0 text-custom-text-200 group-hover:text-custom-text-100" />
|
||||
</button>
|
||||
<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 ${
|
||||
sidebarCollapse ? "w-full justify-center" : ""
|
||||
}`}
|
||||
onClick={() => toggleCollapsed()}
|
||||
>
|
||||
<ArrowLongLeftIcon
|
||||
className={`h-4 w-4 flex-shrink-0 text-custom-text-200 duration-300 group-hover:text-custom-text-100 ${
|
||||
sidebarCollapse ? "rotate-180" : ""
|
||||
<button
|
||||
type="button"
|
||||
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" : ""
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "h",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
>
|
||||
<Bolt fontSize="small" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
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" : ""
|
||||
}`}
|
||||
onClick={() => setIsNeedHelpOpen((prev) => !prev)}
|
||||
>
|
||||
<HelpOutlineOutlined fontSize="small" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
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)}
|
||||
>
|
||||
<WestOutlined fontSize="small" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
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" : ""
|
||||
}`}
|
||||
onClick={() => toggleCollapsed()}
|
||||
>
|
||||
<WestOutlined
|
||||
fontSize="small"
|
||||
className={`duration-300 ${sidebarCollapse ? "rotate-180" : ""}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<Transition
|
||||
|
||||
@@ -43,7 +43,7 @@ const userLinks = (workspaceSlug: string) => [
|
||||
export const WorkspaceSidebarDropdown = () => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// fetching user details
|
||||
|
||||
const { user, mutateUser } = useUser();
|
||||
|
||||
const { collapsed: sidebarCollapse } = useThemeHook();
|
||||
@@ -139,8 +139,8 @@ export const WorkspaceSidebarDropdown = () => {
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
className="fixed left-2 z-20 mt-1 flex w-full max-w-[17rem] origin-top-left flex-col rounded-md
|
||||
border border-custom-sidebar-border-200 bg-custom-sidebar-background-90 shadow-lg focus:outline-none"
|
||||
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 outline-none"
|
||||
>
|
||||
<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>
|
||||
|
||||
@@ -5,40 +5,45 @@ import { useRouter } from "next/router";
|
||||
|
||||
// hooks
|
||||
import useTheme from "hooks/use-theme";
|
||||
|
||||
// components
|
||||
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) => [
|
||||
{
|
||||
icon: "grid_view",
|
||||
Icon: GridViewOutlined,
|
||||
name: "Dashboard",
|
||||
href: `/${workspaceSlug}`,
|
||||
},
|
||||
{
|
||||
icon: "bar_chart",
|
||||
Icon: BarChartRounded,
|
||||
name: "Analytics",
|
||||
href: `/${workspaceSlug}/analytics`,
|
||||
},
|
||||
{
|
||||
icon: "work",
|
||||
Icon: WorkOutlineOutlined,
|
||||
name: "Projects",
|
||||
href: `/${workspaceSlug}/projects`,
|
||||
},
|
||||
{
|
||||
icon: "task_alt",
|
||||
Icon: TaskAltOutlined,
|
||||
name: "My Issues",
|
||||
href: `/${workspaceSlug}/me/my-issues`,
|
||||
},
|
||||
];
|
||||
|
||||
// components
|
||||
import { Icon, Tooltip } from "components/ui";
|
||||
|
||||
export const WorkspaceSidebarMenu = () => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// theme context
|
||||
const { collapsed: sidebarCollapse } = useTheme();
|
||||
|
||||
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"
|
||||
} ${sidebarCollapse ? "justify-center" : ""}`}
|
||||
>
|
||||
<Icon iconName={`${link.icon}`} />
|
||||
{<link.Icon fontSize="small" />}
|
||||
{!sidebarCollapse && link.name}
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
@@ -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;
|
||||
@@ -17,12 +17,12 @@ export const CHARTS_THEME: Theme = {
|
||||
background: "rgb(var(--color-background-80))",
|
||||
color: "rgb(var(--color-text-200))",
|
||||
fontSize: "0.8rem",
|
||||
border: "1px solid rgb(var(--color-background-80))",
|
||||
border: "1px solid rgb(var(--color-border-300))",
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
line: {
|
||||
stroke: "rgb(var(--color-background-80))",
|
||||
stroke: "rgb(var(--color-border-100))",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -94,7 +94,12 @@ export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
const theme = localStorage.getItem("theme");
|
||||
if (theme && theme === "custom") {
|
||||
if (user && user.theme.palette) {
|
||||
applyTheme(user.theme.palette, user.theme.darkPalette);
|
||||
applyTheme(
|
||||
user.theme.palette !== ",,,,"
|
||||
? user.theme.palette
|
||||
: "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5",
|
||||
user.theme.darkPalette
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
@@ -217,7 +217,7 @@ export const render12HourFormatTime = (date: string | Date): string => {
|
||||
if (hours > 12) hours -= 12;
|
||||
}
|
||||
|
||||
return hours + ":" + minutes + " " + period;
|
||||
return hours + ":" + (minutes < 10 ? `0${minutes}` : minutes) + " " + period;
|
||||
};
|
||||
|
||||
export const render24HourFormatTime = (date: string | Date): string => {
|
||||
|
||||
@@ -31,11 +31,18 @@ const useUserIssueNotificationSubscription = (
|
||||
const handleUnsubscribe = useCallback(() => {
|
||||
if (!workspaceSlug || !projectId || !issueId) return;
|
||||
|
||||
mutate(
|
||||
{
|
||||
subscribed: false,
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
userNotificationServices
|
||||
.unsubscribeFromIssueNotifications(
|
||||
workspaceSlug as string,
|
||||
projectId as string,
|
||||
issueId as string
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
issueId.toString()
|
||||
)
|
||||
.then(() => {
|
||||
mutate({
|
||||
@@ -47,14 +54,18 @@ const useUserIssueNotificationSubscription = (
|
||||
const handleSubscribe = useCallback(() => {
|
||||
if (!workspaceSlug || !projectId || !issueId || !user) return;
|
||||
|
||||
mutate(
|
||||
{
|
||||
subscribed: true,
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
userNotificationServices
|
||||
.subscribeToIssueNotifications(
|
||||
workspaceSlug as string,
|
||||
projectId as string,
|
||||
issueId as string,
|
||||
{
|
||||
subscriber: user.id,
|
||||
}
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
issueId.toString()
|
||||
)
|
||||
.then(() => {
|
||||
mutate({
|
||||
|
||||
@@ -54,44 +54,50 @@ const useUserNotification = () => {
|
||||
notifications?.find((notification) => notification.id === notificationId)?.read_at !== null;
|
||||
|
||||
if (isRead) {
|
||||
notificationsMutate(
|
||||
(prev) =>
|
||||
prev?.map((prevNotification) => {
|
||||
if (prevNotification.id === notificationId) {
|
||||
return {
|
||||
...prevNotification,
|
||||
read_at: null,
|
||||
};
|
||||
}
|
||||
return prevNotification;
|
||||
}),
|
||||
false
|
||||
);
|
||||
await userNotificationServices
|
||||
.markUserNotificationAsUnread(workspaceSlug.toString(), notificationId)
|
||||
.then(() => {
|
||||
notificationsMutate((prev) =>
|
||||
prev?.map((prevNotification) => {
|
||||
if (prevNotification.id === notificationId) {
|
||||
return {
|
||||
...prevNotification,
|
||||
read_at: null,
|
||||
};
|
||||
}
|
||||
return prevNotification;
|
||||
})
|
||||
);
|
||||
mutateNotificationCount();
|
||||
})
|
||||
.catch(() => {
|
||||
throw new Error("Something went wrong");
|
||||
})
|
||||
.finally(() => {
|
||||
notificationsMutate();
|
||||
mutateNotificationCount();
|
||||
});
|
||||
} else {
|
||||
notificationsMutate(
|
||||
(prev) =>
|
||||
prev?.map((prevNotification) => {
|
||||
if (prevNotification.id === notificationId) {
|
||||
return {
|
||||
...prevNotification,
|
||||
read_at: new Date(),
|
||||
};
|
||||
}
|
||||
return prevNotification;
|
||||
}),
|
||||
false
|
||||
);
|
||||
await userNotificationServices
|
||||
.markUserNotificationAsRead(workspaceSlug.toString(), notificationId)
|
||||
.then(() => {
|
||||
notificationsMutate((prev) =>
|
||||
prev?.map((prevNotification) => {
|
||||
if (prevNotification.id === notificationId) {
|
||||
return {
|
||||
...prevNotification,
|
||||
read_at: new Date(),
|
||||
};
|
||||
}
|
||||
return prevNotification;
|
||||
})
|
||||
);
|
||||
mutateNotificationCount();
|
||||
})
|
||||
.catch(() => {
|
||||
throw new Error("Something went wrong");
|
||||
})
|
||||
.finally(() => {
|
||||
notificationsMutate();
|
||||
mutateNotificationCount();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -105,22 +111,26 @@ const useUserNotification = () => {
|
||||
if (isArchived) {
|
||||
await userNotificationServices
|
||||
.markUserNotificationAsUnarchived(workspaceSlug.toString(), notificationId)
|
||||
.then(() => {
|
||||
notificationsMutate();
|
||||
})
|
||||
.catch(() => {
|
||||
throw new Error("Something went wrong");
|
||||
})
|
||||
.finally(() => {
|
||||
notificationsMutate();
|
||||
mutateNotificationCount();
|
||||
});
|
||||
} else {
|
||||
notificationsMutate(
|
||||
(prev) => prev?.filter((prevNotification) => prevNotification.id !== notificationId),
|
||||
false
|
||||
);
|
||||
await userNotificationServices
|
||||
.markUserNotificationAsArchived(workspaceSlug.toString(), notificationId)
|
||||
.then(() => {
|
||||
notificationsMutate((prev) =>
|
||||
prev?.filter((prevNotification) => prevNotification.id !== notificationId)
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
throw new Error("Something went wrong");
|
||||
})
|
||||
.finally(() => {
|
||||
notificationsMutate();
|
||||
mutateNotificationCount();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -137,19 +147,27 @@ const useUserNotification = () => {
|
||||
.patchUserNotification(workspaceSlug.toString(), notificationId, {
|
||||
snoozed_till: null,
|
||||
})
|
||||
.then(() => {
|
||||
.finally(() => {
|
||||
notificationsMutate();
|
||||
mutateNotificationCount();
|
||||
});
|
||||
} else
|
||||
} else {
|
||||
notificationsMutate(
|
||||
(prevData) => prevData?.filter((prev) => prev.id !== notificationId) || [],
|
||||
false
|
||||
);
|
||||
await userNotificationServices
|
||||
.patchUserNotification(workspaceSlug.toString(), notificationId, {
|
||||
snoozed_till: dateTime,
|
||||
})
|
||||
.then(() => {
|
||||
notificationsMutate(
|
||||
(prevData) => prevData?.filter((prev) => prev.id !== notificationId) || []
|
||||
);
|
||||
.catch(() => {
|
||||
new Error("Something went wrong");
|
||||
})
|
||||
.finally(() => {
|
||||
notificationsMutate();
|
||||
mutateNotificationCount();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -170,7 +188,7 @@ const useUserNotification = () => {
|
||||
setSelectedTab,
|
||||
totalNotificationCount: notificationCount
|
||||
? notificationCount.created_issues +
|
||||
notificationCount.watching_notifications +
|
||||
notificationCount.watching_issues +
|
||||
notificationCount.my_issues
|
||||
: null,
|
||||
notificationCount,
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
"@headlessui/react": "^1.7.3",
|
||||
"@heroicons/react": "^2.0.12",
|
||||
"@jitsu/nextjs": "^3.1.5",
|
||||
"@mui/icons-material": "^5.14.1",
|
||||
"@mui/material": "^5.14.1",
|
||||
"@nivo/bar": "0.80.0",
|
||||
"@nivo/calendar": "0.80.0",
|
||||
"@nivo/core": "0.80.0",
|
||||
|
||||
@@ -24,7 +24,9 @@ import {
|
||||
} from "components/workspace";
|
||||
import { TourRoot } from "components/onboarding";
|
||||
// ui
|
||||
import { Icon, PrimaryButton, ProductUpdatesModal } from "components/ui";
|
||||
import { PrimaryButton, ProductUpdatesModal } from "components/ui";
|
||||
// icons
|
||||
import { BoltOutlined, GridViewOutlined } from "@mui/icons-material";
|
||||
// images
|
||||
import emptyDashboard from "public/empty-state/dashboard.svg";
|
||||
import githubBlackImage from "/public/logos/github-black.png";
|
||||
@@ -70,7 +72,7 @@ const WorkspacePage: NextPage = () => {
|
||||
<WorkspaceAuthorizationLayout
|
||||
left={
|
||||
<div className="flex items-center gap-2 pl-3">
|
||||
<Icon iconName="grid_view" />
|
||||
<GridViewOutlined fontSize="small" />
|
||||
Dashboard
|
||||
</div>
|
||||
}
|
||||
@@ -80,7 +82,7 @@ const WorkspacePage: NextPage = () => {
|
||||
onClick={() => setIsProductUpdatesModalOpen(true)}
|
||||
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?
|
||||
</button>
|
||||
<Link href="https://github.com/makeplane/plane" target="_blank" rel="noopener noreferrer">
|
||||
|
||||
@@ -22,7 +22,22 @@ const ProfilePreferences = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (theme === "custom") {
|
||||
if (myProfile?.theme.palette) setPreLoadedData(myProfile.theme);
|
||||
if (myProfile?.theme.palette)
|
||||
setPreLoadedData({
|
||||
background: myProfile.theme.background !== "" ? myProfile.theme.background : "#0d101b",
|
||||
text: myProfile.theme.text !== "" ? myProfile.theme.text : "#c5c5c5",
|
||||
primary: myProfile.theme.primary !== "" ? myProfile.theme.primary : "#3f76ff",
|
||||
sidebarBackground:
|
||||
myProfile.theme.sidebarBackground !== ""
|
||||
? myProfile.theme.sidebarBackground
|
||||
: "#0d101b",
|
||||
sidebarText: myProfile.theme.sidebarText !== "" ? myProfile.theme.sidebarText : "#c5c5c5",
|
||||
darkPalette: false,
|
||||
palette:
|
||||
myProfile.theme.palette !== ",,,,"
|
||||
? myProfile.theme.palette
|
||||
: "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5",
|
||||
});
|
||||
if (!customThemeSelectorOptions) setCustomThemeSelectorOptions(true);
|
||||
}
|
||||
}, [myProfile, theme, customThemeSelectorOptions]);
|
||||
|
||||
@@ -44,28 +44,18 @@ const BillingSettings: NextPage = () => {
|
||||
<section className="space-y-8">
|
||||
<div>
|
||||
<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 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>
|
||||
<h4 className="text-md mb-1 leading-6">Current plan</h4>
|
||||
<p className="mb-3 text-sm text-custom-text-200">
|
||||
You are currently using the free plan
|
||||
</p>
|
||||
<a href="https://plane.so/pricing" target="_blank" rel="noreferrer">
|
||||
<SecondaryButton outline>View Plans and Upgrade</SecondaryButton>
|
||||
<SecondaryButton outline>View Plans</SecondaryButton>
|
||||
</a>
|
||||
</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>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -207,7 +207,10 @@ const WorkspaceSettings: NextPage = () => {
|
||||
{isImageUploading ? "Uploading..." : "Upload"}
|
||||
</SecondaryButton>
|
||||
{activeWorkspace.logo && activeWorkspace.logo !== "" && (
|
||||
<DangerButton onClick={() => handleDelete(activeWorkspace.logo)}>
|
||||
<DangerButton
|
||||
onClick={() => handleDelete(activeWorkspace.logo)}
|
||||
loading={isImageRemoving}
|
||||
>
|
||||
{isImageRemoving ? "Removing..." : "Remove"}
|
||||
</DangerButton>
|
||||
)}
|
||||
|
||||
@@ -145,11 +145,11 @@ const HomePage: NextPage = () => {
|
||||
<h1 className="text-center text-2xl sm:text-2.5xl font-semibold text-custom-text-100">
|
||||
Sign in to Plane
|
||||
</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">
|
||||
<EmailCodeForm handleSignIn={handleEmailCodeSignIn} />
|
||||
</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} />
|
||||
<GithubLoginButton handleSignIn={handleGitHubSignIn} />
|
||||
</div>
|
||||
|
||||
@@ -105,91 +105,93 @@ const OnBoard: NextPage = () => {
|
||||
{user?.email}
|
||||
</div>
|
||||
</div>
|
||||
{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="w-full space-y-10">
|
||||
<h5 className="text-lg">We see that someone has invited you to</h5>
|
||||
<h4 className="text-2xl font-semibold">Join a workspace</h4>
|
||||
<div className="md:w-3/5 space-y-4">
|
||||
{invitations.map((invitation) => {
|
||||
const isSelected = invitationsRespond.includes(invitation.id);
|
||||
{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="w-full space-y-10">
|
||||
<h5 className="text-lg">We see that someone has invited you to</h5>
|
||||
<h4 className="text-2xl font-semibold">Join a workspace</h4>
|
||||
<div className="max-h-[37vh] md:w-3/5 space-y-4 overflow-y-auto">
|
||||
{invitations.map((invitation) => {
|
||||
const isSelected = invitationsRespond.includes(invitation.id);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={invitation.id}
|
||||
className={`flex cursor-pointer items-center gap-2 border py-5 px-3.5 rounded ${
|
||||
isSelected
|
||||
? "border-custom-primary-100"
|
||||
: "border-custom-border-200 hover:bg-custom-background-80"
|
||||
}`}
|
||||
onClick={() =>
|
||||
handleInvitation(invitation, isSelected ? "withdraw" : "accepted")
|
||||
}
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
<div className="grid place-items-center h-9 w-9 rounded">
|
||||
{invitation.workspace.logo && invitation.workspace.logo !== "" ? (
|
||||
<img
|
||||
src={invitation.workspace.logo}
|
||||
height="100%"
|
||||
width="100%"
|
||||
className="rounded"
|
||||
alt={invitation.workspace.name}
|
||||
/>
|
||||
) : (
|
||||
<span className="grid place-items-center h-9 w-9 py-1.5 px-3 rounded bg-gray-700 uppercase text-white">
|
||||
{invitation.workspace.name[0]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm font-medium">
|
||||
{truncateText(invitation.workspace.name, 30)}
|
||||
</div>
|
||||
<p className="text-xs text-custom-text-200">{ROLE[invitation.role]}</p>
|
||||
</div>
|
||||
<span
|
||||
className={`flex-shrink-0 ${
|
||||
isSelected ? "text-custom-primary-100" : "text-custom-text-200"
|
||||
return (
|
||||
<div
|
||||
key={invitation.id}
|
||||
className={`flex cursor-pointer items-center gap-2 border py-5 px-3.5 rounded ${
|
||||
isSelected
|
||||
? "border-custom-primary-100"
|
||||
: "border-custom-border-200 hover:bg-custom-background-80"
|
||||
}`}
|
||||
onClick={() =>
|
||||
handleInvitation(invitation, isSelected ? "withdraw" : "accepted")
|
||||
}
|
||||
>
|
||||
<CheckCircleIcon className="h-5 w-5" />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<PrimaryButton
|
||||
type="submit"
|
||||
size="md"
|
||||
onClick={submitInvitations}
|
||||
disabled={isJoiningWorkspaces || invitationsRespond.length === 0}
|
||||
>
|
||||
Accept & Join
|
||||
</PrimaryButton>
|
||||
<Link href="/">
|
||||
<a>
|
||||
<SecondaryButton size="md" outline>
|
||||
Go Home
|
||||
</SecondaryButton>
|
||||
</a>
|
||||
</Link>
|
||||
<div className="flex-shrink-0">
|
||||
<div className="grid place-items-center h-9 w-9 rounded">
|
||||
{invitation.workspace.logo && invitation.workspace.logo !== "" ? (
|
||||
<img
|
||||
src={invitation.workspace.logo}
|
||||
height="100%"
|
||||
width="100%"
|
||||
className="rounded"
|
||||
alt={invitation.workspace.name}
|
||||
/>
|
||||
) : (
|
||||
<span className="grid place-items-center h-9 w-9 py-1.5 px-3 rounded bg-gray-700 uppercase text-white">
|
||||
{invitation.workspace.name[0]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm font-medium">
|
||||
{truncateText(invitation.workspace.name, 30)}
|
||||
</div>
|
||||
<p className="text-xs text-custom-text-200">{ROLE[invitation.role]}</p>
|
||||
</div>
|
||||
<span
|
||||
className={`flex-shrink-0 ${
|
||||
isSelected ? "text-custom-primary-100" : "text-custom-text-200"
|
||||
}`}
|
||||
>
|
||||
<CheckCircleIcon className="h-5 w-5" />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<PrimaryButton
|
||||
type="submit"
|
||||
size="md"
|
||||
onClick={submitInvitations}
|
||||
disabled={isJoiningWorkspaces || invitationsRespond.length === 0}
|
||||
>
|
||||
Accept & Join
|
||||
</PrimaryButton>
|
||||
<Link href="/">
|
||||
<a>
|
||||
<SecondaryButton size="md" outline>
|
||||
Go Home
|
||||
</SecondaryButton>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="fixed top-0 left-0 h-full w-full grid place-items-center">
|
||||
<EmptyState
|
||||
title="No pending invites"
|
||||
description="You can see here if someone invites you to a workspace."
|
||||
image={emptyInvitation}
|
||||
buttonText="Back to Dashboard"
|
||||
onClick={() => router.push("/")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
) : (
|
||||
<div className="fixed top-0 left-0 h-full w-full grid place-items-center">
|
||||
<EmptyState
|
||||
title="No pending invites"
|
||||
description="You can see here if someone invites you to a workspace."
|
||||
image={emptyInvitation}
|
||||
buttonText="Back to Dashboard"
|
||||
onClick={() => router.push("/")}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
</UserAuthorizationLayout>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<svg width="133" height="30" viewBox="0 0 133 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_372_3489)">
|
||||
<path d="M35.7598 0.00235398H44.7151C46.4036 -0.0301787 48.0819 0.274777 49.6538 0.899752C51.0897 1.48694 52.3232 2.48876 53.2005 3.7804C54.1414 5.27171 54.6058 7.02009 54.5305 8.78788C54.6025 10.5948 54.1394 12.3823 53.2005 13.921C52.338 15.271 51.1056 16.3375 49.6538 16.9901C48.0968 17.6881 46.409 18.0371 44.7062 18.0131H40.3882V30.0024H35.7598V0.00235398ZM40.3882 14.1812H43.4472C44.5022 14.1924 45.5515 14.0225 46.5505 13.6787C47.4425 13.3684 48.2171 12.7851 48.7672 12.0095C49.3447 11.1092 49.6299 10.0487 49.5829 8.97633C49.6547 7.88162 49.368 6.79348 48.7672 5.88031C48.2009 5.1354 47.4202 4.58669 46.5328 4.30986C45.5354 3.98896 44.4937 3.83143 43.4472 3.84322H40.3882V14.1812Z" fill="#e9f4fc"/>
|
||||
<path d="M63.6538 30.0023H58.9102V0.00234985H63.6538V30.0023Z" fill="#e9f4fc"/>
|
||||
<path d="M83.1961 30.0023V26.0717C82.9541 26.623 82.6146 27.1249 82.1941 27.5524C81.5462 28.2456 80.7841 28.8194 79.942 29.2485C78.8993 29.7731 77.7457 30.0319 76.5816 30.0023C74.9896 30.0315 73.4209 29.6134 72.0497 28.7943C70.6785 27.9753 69.5585 26.7874 68.8144 25.3628C68.0116 23.8079 67.6091 22.0734 67.644 20.3194C67.6069 18.5595 68.0094 16.8186 68.8144 15.2581C69.5577 13.8315 70.677 12.6411 72.048 11.819C73.419 10.9969 74.9882 10.5752 76.5816 10.6006C77.7134 10.57 78.8375 10.7969 79.8711 11.2647C80.698 11.6453 81.4539 12.1675 82.1055 12.8082C82.5675 13.2374 82.9256 13.7687 83.1518 14.3607V11.139H87.8333V29.9844L83.1961 30.0023ZM72.299 20.3194C72.2745 21.3974 72.5412 22.4617 73.0704 23.3975C73.548 24.2312 74.2333 24.9235 75.0579 25.4053C75.8825 25.8872 76.8175 26.1417 77.7697 26.1435C78.7342 26.1735 79.6872 25.9254 80.5177 25.4281C81.3482 24.9308 82.0218 24.2047 82.4602 23.3347C82.9374 22.3947 83.1751 21.349 83.1518 20.2925C83.1763 19.233 82.9385 18.1841 82.4602 17.2413C81.9964 16.419 81.3267 15.7349 80.5183 15.2581C79.6888 14.7549 78.7367 14.4969 77.7697 14.5133C76.8353 14.5149 75.9179 14.7656 75.1097 15.2401C74.2725 15.73 73.5757 16.4321 73.0881 17.2772C72.5606 18.2015 72.288 19.2521 72.299 20.3194Z" fill="#e9f4fc"/>
|
||||
<path d="M103.536 10.6096C104.757 10.611 105.964 10.8677 107.082 11.3634C108.238 11.8795 109.223 12.7205 109.92 13.7864C110.714 15.0781 111.104 16.5829 111.037 18.1029V30.0023H106.293V18.9733C106.348 18.3184 106.273 17.6591 106.074 17.0337C105.874 16.4082 105.554 15.8291 105.132 15.3299C104.739 14.9381 104.272 14.6317 103.758 14.4296C103.244 14.2274 102.695 14.1337 102.144 14.1543C101.323 14.1599 100.521 14.3994 99.8295 14.8453C99.0977 15.3167 98.4893 15.9602 98.0562 16.7209C97.5907 17.539 97.3518 18.469 97.3646 19.4131V30.0023H92.6387V11.157H97.3646V14.244C97.5688 13.6234 97.9368 13.0711 98.4286 12.6467C99.081 12.0457 99.8309 11.563 100.645 11.2198C101.557 10.8175 102.541 10.6097 103.536 10.6096Z" fill="#e9f4fc"/>
|
||||
<path d="M118.973 21.4322C118.987 22.346 119.221 23.2426 119.656 24.0436C120.095 24.8189 120.753 25.4438 121.545 25.8384C122.494 26.3044 123.541 26.5293 124.595 26.4935C125.5 26.5093 126.4 26.3635 127.255 26.0628C127.932 25.8094 128.569 25.4556 129.143 25.0128C129.56 24.6821 129.93 24.2957 130.243 23.8642L132.335 26.7448C131.854 27.3909 131.273 27.9545 130.615 28.414C129.834 28.9511 128.963 29.3402 128.044 29.5626C126.808 29.8643 125.538 30.0031 124.267 29.9754C122.476 30.0226 120.705 29.599 119.124 28.746C117.69 27.949 116.513 26.7492 115.737 25.291C114.909 23.68 114.498 21.8834 114.54 20.0682C114.532 18.3948 114.924 16.7444 115.684 15.2581C116.41 13.8364 117.524 12.6558 118.894 11.857C120.412 11.0607 122.093 10.6338 123.803 10.6105C125.513 10.5871 127.205 10.9678 128.744 11.7224C130.081 12.4518 131.174 13.5669 131.883 14.9261C132.667 16.483 133.051 18.2141 133 19.9605C133 20.0771 133 20.3284 133 20.6963C133.005 20.9431 132.984 21.1897 132.938 21.4322H118.973ZM128.567 17.9503C128.53 17.3776 128.37 16.8201 128.097 16.317C127.762 15.6679 127.264 15.12 126.652 14.7287C125.854 14.2334 124.926 13.9955 123.992 14.0466C123.03 14.0014 122.076 14.2282 121.234 14.7017C120.661 15.0464 120.172 15.5175 119.804 16.08C119.435 16.6424 119.197 17.2817 119.106 17.9503H128.567Z" fill="#e9f4fc"/>
|
||||
<path d="M35.7598 0.00235398H44.7151C46.4036 -0.0301787 48.0819 0.274777 49.6538 0.899752C51.0897 1.48694 52.3232 2.48876 53.2005 3.7804C54.1414 5.27171 54.6058 7.02009 54.5305 8.78788C54.6025 10.5948 54.1394 12.3823 53.2005 13.921C52.338 15.271 51.1056 16.3375 49.6538 16.9901C48.0968 17.6881 46.409 18.0371 44.7062 18.0131H40.3882V30.0024H35.7598V0.00235398ZM40.3882 14.1812H43.4472C44.5022 14.1924 45.5515 14.0225 46.5505 13.6787C47.4425 13.3684 48.2171 12.7851 48.7672 12.0095C49.3447 11.1092 49.6299 10.0487 49.5829 8.97633C49.6547 7.88162 49.368 6.79348 48.7672 5.88031C48.2009 5.1354 47.4202 4.58669 46.5328 4.30986C45.5354 3.98896 44.4937 3.83143 43.4472 3.84322H40.3882V14.1812Z" fill="#ffffff"/>
|
||||
<path d="M63.6538 30.0023H58.9102V0.00234985H63.6538V30.0023Z" fill="#ffffff"/>
|
||||
<path d="M83.1961 30.0023V26.0717C82.9541 26.623 82.6146 27.1249 82.1941 27.5524C81.5462 28.2456 80.7841 28.8194 79.942 29.2485C78.8993 29.7731 77.7457 30.0319 76.5816 30.0023C74.9896 30.0315 73.4209 29.6134 72.0497 28.7943C70.6785 27.9753 69.5585 26.7874 68.8144 25.3628C68.0116 23.8079 67.6091 22.0734 67.644 20.3194C67.6069 18.5595 68.0094 16.8186 68.8144 15.2581C69.5577 13.8315 70.677 12.6411 72.048 11.819C73.419 10.9969 74.9882 10.5752 76.5816 10.6006C77.7134 10.57 78.8375 10.7969 79.8711 11.2647C80.698 11.6453 81.4539 12.1675 82.1055 12.8082C82.5675 13.2374 82.9256 13.7687 83.1518 14.3607V11.139H87.8333V29.9844L83.1961 30.0023ZM72.299 20.3194C72.2745 21.3974 72.5412 22.4617 73.0704 23.3975C73.548 24.2312 74.2333 24.9235 75.0579 25.4053C75.8825 25.8872 76.8175 26.1417 77.7697 26.1435C78.7342 26.1735 79.6872 25.9254 80.5177 25.4281C81.3482 24.9308 82.0218 24.2047 82.4602 23.3347C82.9374 22.3947 83.1751 21.349 83.1518 20.2925C83.1763 19.233 82.9385 18.1841 82.4602 17.2413C81.9964 16.419 81.3267 15.7349 80.5183 15.2581C79.6888 14.7549 78.7367 14.4969 77.7697 14.5133C76.8353 14.5149 75.9179 14.7656 75.1097 15.2401C74.2725 15.73 73.5757 16.4321 73.0881 17.2772C72.5606 18.2015 72.288 19.2521 72.299 20.3194Z" fill="#ffffff"/>
|
||||
<path d="M103.536 10.6096C104.757 10.611 105.964 10.8677 107.082 11.3634C108.238 11.8795 109.223 12.7205 109.92 13.7864C110.714 15.0781 111.104 16.5829 111.037 18.1029V30.0023H106.293V18.9733C106.348 18.3184 106.273 17.6591 106.074 17.0337C105.874 16.4082 105.554 15.8291 105.132 15.3299C104.739 14.9381 104.272 14.6317 103.758 14.4296C103.244 14.2274 102.695 14.1337 102.144 14.1543C101.323 14.1599 100.521 14.3994 99.8295 14.8453C99.0977 15.3167 98.4893 15.9602 98.0562 16.7209C97.5907 17.539 97.3518 18.469 97.3646 19.4131V30.0023H92.6387V11.157H97.3646V14.244C97.5688 13.6234 97.9368 13.0711 98.4286 12.6467C99.081 12.0457 99.8309 11.563 100.645 11.2198C101.557 10.8175 102.541 10.6097 103.536 10.6096Z" fill="#ffffff"/>
|
||||
<path d="M118.973 21.4322C118.987 22.346 119.221 23.2426 119.656 24.0436C120.095 24.8189 120.753 25.4438 121.545 25.8384C122.494 26.3044 123.541 26.5293 124.595 26.4935C125.5 26.5093 126.4 26.3635 127.255 26.0628C127.932 25.8094 128.569 25.4556 129.143 25.0128C129.56 24.6821 129.93 24.2957 130.243 23.8642L132.335 26.7448C131.854 27.3909 131.273 27.9545 130.615 28.414C129.834 28.9511 128.963 29.3402 128.044 29.5626C126.808 29.8643 125.538 30.0031 124.267 29.9754C122.476 30.0226 120.705 29.599 119.124 28.746C117.69 27.949 116.513 26.7492 115.737 25.291C114.909 23.68 114.498 21.8834 114.54 20.0682C114.532 18.3948 114.924 16.7444 115.684 15.2581C116.41 13.8364 117.524 12.6558 118.894 11.857C120.412 11.0607 122.093 10.6338 123.803 10.6105C125.513 10.5871 127.205 10.9678 128.744 11.7224C130.081 12.4518 131.174 13.5669 131.883 14.9261C132.667 16.483 133.051 18.2141 133 19.9605C133 20.0771 133 20.3284 133 20.6963C133.005 20.9431 132.984 21.1897 132.938 21.4322H118.973ZM128.567 17.9503C128.53 17.3776 128.37 16.8201 128.097 16.317C127.762 15.6679 127.264 15.12 126.652 14.7287C125.854 14.2334 124.926 13.9955 123.992 14.0466C123.03 14.0014 122.076 14.2282 121.234 14.7017C120.661 15.0464 120.172 15.5175 119.804 16.08C119.435 16.6424 119.197 17.2817 119.106 17.9503H128.567Z" fill="#ffffff"/>
|
||||
<path d="M29.127 0.271576H9.70898V10.0981H19.418V19.9246H29.127V0.271576Z" fill="#3F76FF"/>
|
||||
<path d="M9.709 10.0981H0V19.9066H9.709V10.0981Z" fill="#3F76FF"/>
|
||||
<path d="M19.4266 19.9246H9.73535V29.7511H19.4266V19.9246Z" fill="#3F76FF"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
@@ -111,14 +111,10 @@ class UserNotificationsServices extends APIService {
|
||||
async subscribeToIssueNotifications(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
data: {
|
||||
subscriber: string;
|
||||
}
|
||||
issueId: string
|
||||
): Promise<any> {
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-subscribers/`,
|
||||
data
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/subscribe/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
@@ -159,7 +155,7 @@ class UserNotificationsServices extends APIService {
|
||||
async getUnreadNotificationsCount(workspaceSlug: string): Promise<{
|
||||
created_issues: number;
|
||||
my_issues: number;
|
||||
watching_notifications: number;
|
||||
watching_issues: number;
|
||||
}> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/users/notifications/unread/`)
|
||||
.then((response) => response?.data)
|
||||
|
||||
@@ -295,3 +295,7 @@ body {
|
||||
:-ms-input-placeholder {
|
||||
color: rgb(var(--color-text-400));
|
||||
}
|
||||
|
||||
.bp4-overlay-content {
|
||||
z-index: 555 !important;
|
||||
}
|
||||
|
||||
@@ -82,6 +82,21 @@ services:
|
||||
- plane-db
|
||||
- plane-redis
|
||||
|
||||
plane-beat-worker:
|
||||
container_name: planebeatworker
|
||||
image: makeplane/plane-worker:latest
|
||||
restart: always
|
||||
command: ./bin/beat
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
<<: *api-and-worker-env
|
||||
depends_on:
|
||||
- plane-api
|
||||
- plane-db
|
||||
- plane-redis
|
||||
|
||||
|
||||
plane-db:
|
||||
container_name: plane-db
|
||||
image: postgres:15.2-alpine
|
||||
|
||||
@@ -90,6 +90,22 @@ services:
|
||||
- plane-db
|
||||
- plane-redis
|
||||
|
||||
plane-beat-worker:
|
||||
container_name: planebeatworker
|
||||
build:
|
||||
context: ./apiserver
|
||||
dockerfile: Dockerfile.api
|
||||
restart: always
|
||||
command: ./bin/beat
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
<<: *api-and-worker-env
|
||||
depends_on:
|
||||
- plane-api
|
||||
- plane-db
|
||||
- plane-redis
|
||||
|
||||
plane-db:
|
||||
container_name: plane-db
|
||||
image: postgres:15.2-alpine
|
||||
|
||||
@@ -899,6 +899,13 @@
|
||||
dependencies:
|
||||
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":
|
||||
version "7.20.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8"
|
||||
@@ -1005,6 +1012,17 @@
|
||||
"@emotion/weak-memoize" "^0.3.0"
|
||||
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":
|
||||
version "11.10.8"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/css/-/css-11.10.8.tgz#9dec9996ad9a1cc28ec8d26b1b27ab0b8f6fb053"
|
||||
@@ -1028,11 +1046,23 @@
|
||||
dependencies:
|
||||
"@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":
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f"
|
||||
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":
|
||||
version "11.10.8"
|
||||
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"
|
||||
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":
|
||||
version "11.10.8"
|
||||
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"
|
||||
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":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb"
|
||||
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":
|
||||
version "4.4.0"
|
||||
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"
|
||||
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":
|
||||
version "5.12.3"
|
||||
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.12.3.tgz#3dffe62dccc065ddd7338e97d7be4b917004287e"
|
||||
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":
|
||||
version "5.12.3"
|
||||
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.12.3.tgz#398c1b123fb065763558bc1f9fc47d1f8cb87d0c"
|
||||
@@ -1356,6 +1427,24 @@
|
||||
react-is "^18.2.0"
|
||||
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":
|
||||
version "5.12.3"
|
||||
resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.12.3.tgz#f5e4704e25d9d91b906561cae573cda8f3801e10"
|
||||
@@ -1365,6 +1454,15 @@
|
||||
"@mui/utils" "^5.12.3"
|
||||
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":
|
||||
version "5.12.3"
|
||||
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.12.3.tgz#3307643d52c81947a624cdd0437536cc8109c4f0"
|
||||
@@ -1375,6 +1473,16 @@
|
||||
csstype "^3.1.2"
|
||||
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":
|
||||
version "5.12.3"
|
||||
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.12.3.tgz#306b3cdffa3046067640219c1e5dd7e3dae38ff9"
|
||||
@@ -1389,6 +1497,20 @@
|
||||
csstype "^3.1.2"
|
||||
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":
|
||||
version "7.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.4.tgz#b6fade19323b754c5c6de679a38f068fd50b9328"
|
||||
@@ -1405,6 +1527,17 @@
|
||||
prop-types "^15.8.1"
|
||||
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":
|
||||
version "12.3.2"
|
||||
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"
|
||||
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":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.0.tgz#e1d8ef30b10ea10e69c76e896f608d9276352253"
|
||||
@@ -3230,6 +3368,13 @@
|
||||
dependencies:
|
||||
"@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":
|
||||
version "7.1.25"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.25.tgz#de841631205b24f9dfb4967dd4a7901e048f9a88"
|
||||
@@ -3247,6 +3392,13 @@
|
||||
dependencies:
|
||||
"@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":
|
||||
version "18.2.3"
|
||||
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"
|
||||
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:
|
||||
version "3.32.0"
|
||||
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.32.0.tgz#c4a95e0f1e18b6847127258a75cf360bc568d4a7"
|
||||
|
||||
Reference in New Issue
Block a user