From 3913cf571f375b1e51f3f66004a0b32454cf3b9c Mon Sep 17 00:00:00 2001 From: Kyle Lacy Date: Sat, 15 Apr 2023 11:36:23 -0700 Subject: [PATCH 001/120] Make SMTP port and TLS configurable --- apiserver/plane/settings/common.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index c144eeb0b..b3a6883bd 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -174,11 +174,11 @@ EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" # Host for sending e-mail. EMAIL_HOST = os.environ.get("EMAIL_HOST") # Port for sending e-mail. -EMAIL_PORT = 587 +EMAIL_PORT = int(os.environ.get("EMAIL_PORT", 587)) # Optional SMTP authentication information for EMAIL_HOST. EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER") EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD") -EMAIL_USE_TLS = True +EMAIL_USE_TLS = os.environ.get("EMAIL_USE_TLS", "1") == "1" SIMPLE_JWT = { @@ -210,4 +210,4 @@ SIMPLE_JWT = { CELERY_TIMEZONE = TIME_ZONE CELERY_TASK_SERIALIZER = 'json' -CELERY_ACCEPT_CONTENT = ['application/json'] \ No newline at end of file +CELERY_ACCEPT_CONTENT = ['application/json'] From 6af54ebbe75b9491e203e7cd8efb67be56766351 Mon Sep 17 00:00:00 2001 From: Kyle Lacy Date: Sat, 15 Apr 2023 12:32:00 -0700 Subject: [PATCH 002/120] Fix typo in `.env.example --- apiserver/.env.example | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apiserver/.env.example b/apiserver/.env.example index 15056f072..2e1ee6fd1 100644 --- a/apiserver/.env.example +++ b/apiserver/.env.example @@ -3,11 +3,11 @@ DJANGO_SETTINGS_MODULE="plane.settings.production" DATABASE_URL=postgres://plane:xyzzyspoon@db:5432/plane # Cache REDIS_URL=redis://redis:6379/ -# SMPT +# SMTP EMAIL_HOST="" EMAIL_HOST_USER="" EMAIL_HOST_PASSWORD="" -# AWS +# AWS AWS_REGION="" AWS_ACCESS_KEY_ID="" AWS_SECRET_ACCESS_KEY="" @@ -21,4 +21,4 @@ DISABLE_COLLECTSTATIC=1 DOCKERIZED=1 # GPT Envs OPENAI_API_KEY=0 -GPT_ENGINE=0 \ No newline at end of file +GPT_ENGINE=0 From f757d8232b56b5d94ed853a6e6c00763905f8610 Mon Sep 17 00:00:00 2001 From: Kyle Lacy Date: Sat, 15 Apr 2023 12:32:54 -0700 Subject: [PATCH 003/120] Add setting to disable extra TLS config for Celery broker connection --- apiserver/plane/settings/production.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apiserver/plane/settings/production.py b/apiserver/plane/settings/production.py index 8f8453aff..a3f2ea79d 100644 --- a/apiserver/plane/settings/production.py +++ b/apiserver/plane/settings/production.py @@ -239,8 +239,12 @@ SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN", False) LOGGER_BASE_URL = os.environ.get("LOGGER_BASE_URL", False) +broker_ssl = os.environ.get("REDIS_BROKER_SSL", "1") == "1" redis_url = os.environ.get("REDIS_URL") -broker_url = f"{redis_url}?ssl_cert_reqs={ssl.CERT_NONE.name}&ssl_ca_certs={certifi.where()}" +if broker_ssl: + broker_url = f"{redis_url}?ssl_cert_reqs={ssl.CERT_NONE.name}&ssl_ca_certs={certifi.where()}" +else: + broker_url = redis_url CELERY_RESULT_BACKEND = broker_url -CELERY_BROKER_URL = broker_url \ No newline at end of file +CELERY_BROKER_URL = broker_url From e2294f91058e14b2c67918a4f2470d634af77e56 Mon Sep 17 00:00:00 2001 From: Kyle Lacy Date: Sat, 15 Apr 2023 12:51:23 -0700 Subject: [PATCH 004/120] Add `EMAIL_FROM` setting to change sender email for messages --- apiserver/plane/bgtasks/email_verification_task.py | 3 ++- apiserver/plane/bgtasks/forgot_password_task.py | 3 ++- apiserver/plane/bgtasks/magic_link_code_task.py | 3 ++- apiserver/plane/bgtasks/project_invitation_task.py | 3 ++- apiserver/plane/bgtasks/workspace_invitation_task.py | 2 +- apiserver/plane/db/models/user.py | 2 +- apiserver/plane/settings/common.py | 1 + 7 files changed, 11 insertions(+), 6 deletions(-) diff --git a/apiserver/plane/bgtasks/email_verification_task.py b/apiserver/plane/bgtasks/email_verification_task.py index ee4680e53..1da3a7510 100644 --- a/apiserver/plane/bgtasks/email_verification_task.py +++ b/apiserver/plane/bgtasks/email_verification_task.py @@ -2,6 +2,7 @@ from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string from django.utils.html import strip_tags +from django.conf import settings # Third party imports from celery import shared_task @@ -20,7 +21,7 @@ def email_verification(first_name, email, token, current_site): realtivelink = "/request-email-verification/" + "?token=" + str(token) abs_url = "http://" + current_site + realtivelink - from_email_string = f"Team Plane " + from_email_string = settings.EMAIL_FROM subject = f"Verify your Email!" diff --git a/apiserver/plane/bgtasks/forgot_password_task.py b/apiserver/plane/bgtasks/forgot_password_task.py index 4598e5f2f..f13f1b89a 100644 --- a/apiserver/plane/bgtasks/forgot_password_task.py +++ b/apiserver/plane/bgtasks/forgot_password_task.py @@ -2,6 +2,7 @@ from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string from django.utils.html import strip_tags +from django.conf import settings # Third party imports from celery import shared_task @@ -18,7 +19,7 @@ def forgot_password(first_name, email, uidb64, token, current_site): realtivelink = f"/email-verify/?uidb64={uidb64}&token={token}/" abs_url = "http://" + current_site + realtivelink - from_email_string = f"Team Plane " + from_email_string = settings.EMAIL_FROM subject = f"Verify your Email!" diff --git a/apiserver/plane/bgtasks/magic_link_code_task.py b/apiserver/plane/bgtasks/magic_link_code_task.py index 89554dcca..ea97b0fb8 100644 --- a/apiserver/plane/bgtasks/magic_link_code_task.py +++ b/apiserver/plane/bgtasks/magic_link_code_task.py @@ -2,6 +2,7 @@ from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string from django.utils.html import strip_tags +from django.conf import settings # Third party imports from celery import shared_task @@ -14,7 +15,7 @@ def magic_link(email, key, token, current_site): realtivelink = f"/magic-sign-in/?password={token}&key={key}" abs_url = "http://" + current_site + realtivelink - from_email_string = f"Team Plane " + from_email_string = settings.EMAIL_FROM subject = f"Login for Plane" diff --git a/apiserver/plane/bgtasks/project_invitation_task.py b/apiserver/plane/bgtasks/project_invitation_task.py index 18e539970..1c3597120 100644 --- a/apiserver/plane/bgtasks/project_invitation_task.py +++ b/apiserver/plane/bgtasks/project_invitation_task.py @@ -2,6 +2,7 @@ from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string from django.utils.html import strip_tags +from django.conf import settings # Third party imports from celery import shared_task @@ -22,7 +23,7 @@ def project_invitation(email, project_id, token, current_site): relativelink = f"/project-member-invitation/{project_member_invite.id}" abs_url = "http://" + current_site + relativelink - from_email_string = f"Team Plane " + from_email_string = settings.EMAIL_FROM subject = f"{project.created_by.first_name or project.created_by.email} invited you to join {project.name} on Plane" diff --git a/apiserver/plane/bgtasks/workspace_invitation_task.py b/apiserver/plane/bgtasks/workspace_invitation_task.py index c6e69689b..0ce32eee0 100644 --- a/apiserver/plane/bgtasks/workspace_invitation_task.py +++ b/apiserver/plane/bgtasks/workspace_invitation_task.py @@ -27,7 +27,7 @@ def workspace_invitation(email, workspace_id, token, current_site, invitor): ) abs_url = "http://" + current_site + realtivelink - from_email_string = f"Team Plane " + from_email_string = settings.EMAIL_FROM subject = f"{invitor or email} invited you to join {workspace.name} on Plane" diff --git a/apiserver/plane/db/models/user.py b/apiserver/plane/db/models/user.py index 334ec3e13..5a4f487c1 100644 --- a/apiserver/plane/db/models/user.py +++ b/apiserver/plane/db/models/user.py @@ -109,7 +109,7 @@ def send_welcome_email(sender, instance, created, **kwargs): if created and not instance.is_bot: first_name = instance.first_name.capitalize() to_email = instance.email - from_email_string = f"Team Plane " + from_email_string = settings.EMAIL_FROM subject = f"Welcome to Plane ✈️!" diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index b3a6883bd..f5bff248b 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -179,6 +179,7 @@ EMAIL_PORT = int(os.environ.get("EMAIL_PORT", 587)) EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER") EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD") EMAIL_USE_TLS = os.environ.get("EMAIL_USE_TLS", "1") == "1" +EMAIL_FROM = os.environ.get("EMAIL_FROM", "Team Plane ") SIMPLE_JWT = { From 5e5d1a4699599ac9a10b4b4cff89984a93466f97 Mon Sep 17 00:00:00 2001 From: Kyle Lacy Date: Sat, 15 Apr 2023 12:56:43 -0700 Subject: [PATCH 005/120] Update `.env.example` with newly-added env vars --- apiserver/.env.example | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apiserver/.env.example b/apiserver/.env.example index 2e1ee6fd1..a3972379d 100644 --- a/apiserver/.env.example +++ b/apiserver/.env.example @@ -3,10 +3,14 @@ DJANGO_SETTINGS_MODULE="plane.settings.production" DATABASE_URL=postgres://plane:xyzzyspoon@db:5432/plane # Cache REDIS_URL=redis://redis:6379/ +REDIS_BROKER_SSL=0 # SMTP EMAIL_HOST="" EMAIL_HOST_USER="" EMAIL_HOST_PASSWORD="" +EMAIL_PORT="587" +EMAIL_USE_TLS="1" +EMAIL_FROM="Team Plane " # AWS AWS_REGION="" AWS_ACCESS_KEY_ID="" From 396fbc4ebbe6fb1782fdaf91281bdd93139ee1c5 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Mon, 17 Apr 2023 22:38:52 +0530 Subject: [PATCH 006/120] fix: redis url for docker in production settings (#865) --- apiserver/plane/settings/production.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apiserver/plane/settings/production.py b/apiserver/plane/settings/production.py index 8f8453aff..d8f2a8bb7 100644 --- a/apiserver/plane/settings/production.py +++ b/apiserver/plane/settings/production.py @@ -242,5 +242,9 @@ LOGGER_BASE_URL = os.environ.get("LOGGER_BASE_URL", False) redis_url = os.environ.get("REDIS_URL") broker_url = f"{redis_url}?ssl_cert_reqs={ssl.CERT_NONE.name}&ssl_ca_certs={certifi.where()}" -CELERY_RESULT_BACKEND = broker_url -CELERY_BROKER_URL = broker_url \ No newline at end of file +if DOCKERIZED: + CELERY_BROKER_URL = REDIS_URL + CELERY_RESULT_BACKEND = REDIS_URL +else: + CELERY_RESULT_BACKEND = broker_url + CELERY_BROKER_URL = broker_url From 792162ae667c4993a3c85449122582be91086e37 Mon Sep 17 00:00:00 2001 From: Kyle Lacy Date: Mon, 17 Apr 2023 12:37:57 -0700 Subject: [PATCH 007/120] Remove redundant `$REDIS_BROKER_SSL` setting A fix was already pushed upstream that made this setting unnecessary --- apiserver/.env.example | 1 - apiserver/plane/settings/production.py | 1 - 2 files changed, 2 deletions(-) diff --git a/apiserver/.env.example b/apiserver/.env.example index a3972379d..e2625c1ed 100644 --- a/apiserver/.env.example +++ b/apiserver/.env.example @@ -3,7 +3,6 @@ DJANGO_SETTINGS_MODULE="plane.settings.production" DATABASE_URL=postgres://plane:xyzzyspoon@db:5432/plane # Cache REDIS_URL=redis://redis:6379/ -REDIS_BROKER_SSL=0 # SMTP EMAIL_HOST="" EMAIL_HOST_USER="" diff --git a/apiserver/plane/settings/production.py b/apiserver/plane/settings/production.py index 68758135e..d8f2a8bb7 100644 --- a/apiserver/plane/settings/production.py +++ b/apiserver/plane/settings/production.py @@ -239,7 +239,6 @@ SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN", False) LOGGER_BASE_URL = os.environ.get("LOGGER_BASE_URL", False) -broker_ssl = os.environ.get("REDIS_BROKER_SSL", "1") == "1" redis_url = os.environ.get("REDIS_URL") broker_url = f"{redis_url}?ssl_cert_reqs={ssl.CERT_NONE.name}&ssl_ca_certs={certifi.where()}" From 5f20e65ca6952d4d1a500aae953f3b2df51da7be Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 18 Apr 2023 01:15:10 +0530 Subject: [PATCH 008/120] style: list view styling reduced (#867) --- .../core/board-view/board-header.tsx | 10 +- .../components/core/list-view/all-lists.tsx | 2 +- .../core/list-view/single-issue.tsx | 272 +++++++++--------- .../components/core/list-view/single-list.tsx | 16 +- .../issues/view-select/assignee.tsx | 2 +- apps/app/components/ui/datepicker.tsx | 4 +- 6 files changed, 153 insertions(+), 153 deletions(-) diff --git a/apps/app/components/core/board-view/board-header.tsx b/apps/app/components/core/board-view/board-header.tsx index 5eaed3a45..f209ba6e2 100644 --- a/apps/app/components/core/board-view/board-header.tsx +++ b/apps/app/components/core/board-view/board-header.tsx @@ -96,17 +96,17 @@ export const BoardHeader: React.FC = ({ switch (selectedGroup) { case "state": - icon = currentState && getStateGroupIcon(currentState.group, "18", "18", bgColor); + icon = currentState && getStateGroupIcon(currentState.group, "16", "16", bgColor); break; case "priority": - icon = getPriorityIcon(groupTitle, "h-[18px] w-[18px] flex items-center"); + icon = getPriorityIcon(groupTitle, "text-lg"); break; case "labels": const labelColor = issueLabels?.find((label) => label.id === groupTitle)?.color ?? "#000000"; icon = ( ); @@ -143,7 +143,9 @@ export const BoardHeader: React.FC = ({ {getGroupTitle()} {groupedByIssues?.[groupTitle].length ?? 0} diff --git a/apps/app/components/core/list-view/all-lists.tsx b/apps/app/components/core/list-view/all-lists.tsx index 27cd92fa5..2ea40ffe4 100644 --- a/apps/app/components/core/list-view/all-lists.tsx +++ b/apps/app/components/core/list-view/all-lists.tsx @@ -36,7 +36,7 @@ export const AllLists: React.FC = ({ return ( <> {groupedByIssues && ( -
+
{Object.keys(groupedByIssues).map((singleGroup) => { const currentState = selectedGroup === "state" ? states?.find((s) => s.id === singleGroup) : null; diff --git a/apps/app/components/core/list-view/single-issue.tsx b/apps/app/components/core/list-view/single-issue.tsx index df2d1cb3a..4fa518769 100644 --- a/apps/app/components/core/list-view/single-issue.tsx +++ b/apps/app/components/core/list-view/single-issue.tsx @@ -216,149 +216,147 @@ export const SingleListIssue: React.FC = ({ -
-
{ - e.preventDefault(); - setContextMenu(true); - setContextMenuPosition({ x: e.pageX, y: e.pageY }); - }} - > - - - {properties.key && ( - - - {issue.project_detail?.identifier}-{issue.sequence_id} - - - )} - - {truncateText(issue.name, 50)} +
{ + e.preventDefault(); + setContextMenu(true); + setContextMenuPosition({ x: e.pageX, y: e.pageY }); + }} + > + + + {properties.key && ( + + + {issue.project_detail?.identifier}-{issue.sequence_id} + - - + )} + + {truncateText(issue.name, 50)} + + + -
- {properties.priority && ( - - )} - {properties.state && ( - - )} - {properties.due_date && ( - - )} - {properties.sub_issue_count && ( -
- {issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"} -
- )} - {properties.labels && issue.label_details.length > 0 ? ( -
- {issue.label_details.map((label) => ( +
+ {properties.priority && ( + + )} + {properties.state && ( + + )} + {properties.due_date && ( + + )} + {properties.sub_issue_count && ( +
+ {issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"} +
+ )} + {properties.labels && issue.label_details.length > 0 ? ( +
+ {issue.label_details.map((label) => ( + - - {label.name} - - ))} -
- ) : ( - "" - )} - {properties.assignee && ( - - )} - {properties.estimate && ( - - )} - {properties.link && ( -
- -
- - {issue.link_count} -
-
-
- )} - {properties.attachment_count && ( -
- -
- - {issue.attachment_count} -
-
-
- )} - {type && !isNotAllowed && ( - - + className="h-1.5 w-1.5 rounded-full" + style={{ + backgroundColor: label?.color && label.color !== "" ? label.color : "#000", + }} + /> + {label.name} + + ))} +
+ ) : ( + "" + )} + {properties.assignee && ( + + )} + {properties.estimate && ( + + )} + {properties.link && ( +
+ +
+ + {issue.link_count} +
+
+
+ )} + {properties.attachment_count && ( +
+ +
+ + {issue.attachment_count} +
+
+
+ )} + {type && !isNotAllowed && ( + + +
+ + Edit issue +
+
+ {type !== "issue" && removeIssue && ( +
- - Edit issue + + Remove from {type}
- {type !== "issue" && removeIssue && ( - -
- - Remove from {type} -
-
- )} - handleDeleteIssue(issue)}> -
- - Delete issue -
-
- -
- - Copy issue link -
-
-
- )} -
+ )} + handleDeleteIssue(issue)}> +
+ + Delete issue +
+
+ +
+ + Copy issue link +
+
+ + )}
diff --git a/apps/app/components/core/list-view/single-list.tsx b/apps/app/components/core/list-view/single-list.tsx index adf8a6a5f..32880a1a6 100644 --- a/apps/app/components/core/list-view/single-list.tsx +++ b/apps/app/components/core/list-view/single-list.tsx @@ -104,17 +104,17 @@ export const SingleList: React.FC = ({ switch (selectedGroup) { case "state": - icon = currentState && getStateGroupIcon(currentState.group, "18", "18", bgColor); + icon = currentState && getStateGroupIcon(currentState.group, "16", "16", bgColor); break; case "priority": - icon = getPriorityIcon(groupTitle, "h-[18px] w-[18px] flex items-center"); + icon = getPriorityIcon(groupTitle, "text-lg"); break; case "labels": const labelColor = issueLabels?.find((label) => label.id === groupTitle)?.color ?? "#000000"; icon = ( ); @@ -134,24 +134,24 @@ export const SingleList: React.FC = ({ {({ open }) => (
{selectedGroup !== null && ( - {getGroupIcon()} +
{getGroupIcon()}
)} {selectedGroup !== null ? ( -

+

{getGroupTitle()}

) : (

All Issues

)} - - {groupedByIssues[groupTitle as keyof IIssue].length} + + {groupedByIssues?.[groupTitle].length ?? 0}
diff --git a/apps/app/components/issues/view-select/assignee.tsx b/apps/app/components/issues/view-select/assignee.tsx index adfd078a6..1429555d9 100644 --- a/apps/app/components/issues/view-select/assignee.tsx +++ b/apps/app/components/issues/view-select/assignee.tsx @@ -106,7 +106,7 @@ export const ViewAssigneeSelect: React.FC = ({ } items-center gap-2 text-gray-500`} > {issue.assignees && issue.assignees.length > 0 && Array.isArray(issue.assignees) ? ( -
+
{issue.assignees.length} Assignees
diff --git a/apps/app/components/ui/datepicker.tsx b/apps/app/components/ui/datepicker.tsx index b5c992a78..cf99bcc04 100644 --- a/apps/app/components/ui/datepicker.tsx +++ b/apps/app/components/ui/datepicker.tsx @@ -34,7 +34,7 @@ export const CustomDatePicker: React.FC = ({ if (!val) onChange(null); else onChange(renderDateFormat(val)); }} - className={`${className} ${ + className={`${ renderAs === "input" ? "block border-gray-300 bg-transparent px-3 py-2 text-sm focus:outline-none" : renderAs === "button" @@ -44,7 +44,7 @@ export const CustomDatePicker: React.FC = ({ : "" } ${error ? "border-red-500 bg-red-100" : ""} ${ disabled ? "cursor-not-allowed" : "cursor-pointer" - } w-full rounded-md border bg-transparent caret-transparent`} + } w-full rounded-md border bg-transparent caret-transparent ${className}`} dateFormat="dd-MM-yyyy" isClearable={isClearable} disabled={disabled} From fa5c994ddcf0dfb02487083cecd774beb2dc53d2 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 18 Apr 2023 01:15:26 +0530 Subject: [PATCH 009/120] chore: remove redundant console logs (#868) --- apps/app/components/account/email-password-form.tsx | 2 +- apps/app/components/core/issues-view.tsx | 6 ------ apps/app/components/cycles/sidebar.tsx | 9 ++------- .../estimates/create-update-estimate-modal.tsx | 4 +--- apps/app/components/issues/activity.tsx | 5 +---- .../app/components/labels/create-update-label-inline.tsx | 3 +-- apps/app/components/labels/labels-list-modal.tsx | 5 +---- apps/app/components/modules/sidebar.tsx | 9 ++------- apps/app/components/onboarding/invite-members.tsx | 7 ++----- apps/app/components/project/single-integration-card.tsx | 1 - .../rich-text-editor/toolbar/float-tool-tip.tsx | 6 +----- .../workspace/send-workspace-invitation-modal.tsx | 5 ++--- apps/app/helpers/string.helper.ts | 6 +----- .../projects/[projectId]/modules/[moduleId].tsx | 7 ++----- .../projects/[projectId]/settings/labels.tsx | 7 +------ 15 files changed, 18 insertions(+), 64 deletions(-) diff --git a/apps/app/components/account/email-password-form.tsx b/apps/app/components/account/email-password-form.tsx index ca654c81c..91ef2b433 100644 --- a/apps/app/components/account/email-password-form.tsx +++ b/apps/app/components/account/email-password-form.tsx @@ -50,7 +50,7 @@ export const EmailPasswordForm = ({ onSuccess }: any) => { if (!error?.response?.data) return; Object.keys(error.response.data).forEach((key) => { const err = error.response.data[key]; - console.log("err", err); + console.log(err); setError(key as keyof EmailPasswordFormValues, { type: "manual", message: Array.isArray(err) ? err.join(", ") : err, diff --git a/apps/app/components/core/issues-view.tsx b/apps/app/components/core/issues-view.tsx index a964181cd..22261de06 100644 --- a/apps/app/components/core/issues-view.tsx +++ b/apps/app/components/core/issues-view.tsx @@ -315,9 +315,6 @@ export const IssuesView: React.FC = ({ cycleId as string, bridgeId ) - .then((res) => { - console.log(res); - }) .catch((e) => { console.log(e); }); @@ -338,9 +335,6 @@ export const IssuesView: React.FC = ({ moduleId as string, bridgeId ) - .then((res) => { - console.log(res); - }) .catch((e) => { console.log(e); }); diff --git a/apps/app/components/cycles/sidebar.tsx b/apps/app/components/cycles/sidebar.tsx index 6510b53c1..306ec5578 100644 --- a/apps/app/components/cycles/sidebar.tsx +++ b/apps/app/components/cycles/sidebar.tsx @@ -92,13 +92,8 @@ export const CycleDetailsSidebar: React.FC = ({ cyclesService .patchCycle(workspaceSlug as string, projectId as string, cycleId as string, data) - .then((res) => { - console.log(res); - mutate(CYCLE_DETAILS(cycleId as string)); - }) - .catch((e) => { - console.log(e); - }); + .then(() => mutate(CYCLE_DETAILS(cycleId as string))) + .catch((e) => console.log(e)); }; const handleCopyText = () => { diff --git a/apps/app/components/estimates/create-update-estimate-modal.tsx b/apps/app/components/estimates/create-update-estimate-modal.tsx index 2f2ac2c71..a8a191eca 100644 --- a/apps/app/components/estimates/create-update-estimate-modal.tsx +++ b/apps/app/components/estimates/create-update-estimate-modal.tsx @@ -94,9 +94,7 @@ export const CreateUpdateEstimateModal: React.FC = ({ handleClose, data, await estimatesService .patchEstimate(workspaceSlug as string, projectId as string, data?.id as string, payload) - .then(() => { - handleClose(); - }) + .then(() => handleClose()) .catch(() => { setToastAlert({ type: "error", diff --git a/apps/app/components/issues/activity.tsx b/apps/app/components/issues/activity.tsx index dd50f3bca..36fd19b42 100644 --- a/apps/app/components/issues/activity.tsx +++ b/apps/app/components/issues/activity.tsx @@ -157,10 +157,7 @@ export const IssueActivitySection: React.FC = () => { issueId as string, commentId ) - .then((response) => { - mutateIssueActivities(); - console.log(response); - }); + .then(() => mutateIssueActivities()); }; const getLabelColor = (labelId: string) => { diff --git a/apps/app/components/labels/create-update-label-inline.tsx b/apps/app/components/labels/create-update-label-inline.tsx index 24db3ab38..21c70f31c 100644 --- a/apps/app/components/labels/create-update-label-inline.tsx +++ b/apps/app/components/labels/create-update-label-inline.tsx @@ -78,8 +78,7 @@ export const CreateUpdateLabelInline = forwardRef(function CreateUpd labelToUpdate?.id ?? "", formData ) - .then((res) => { - console.log(res); + .then(() => { reset(defaultValues); mutate( PROJECT_ISSUE_LABELS(projectId as string), diff --git a/apps/app/components/labels/labels-list-modal.tsx b/apps/app/components/labels/labels-list-modal.tsx index 55ea3edc8..b2c687380 100644 --- a/apps/app/components/labels/labels-list-modal.tsx +++ b/apps/app/components/labels/labels-list-modal.tsx @@ -61,10 +61,7 @@ export const LabelsListModal: React.FC = ({ isOpen, handleClose, parent } .patchIssueLabel(workspaceSlug as string, projectId as string, label.id, { parent: parent?.id ?? "", }) - .then((res) => { - console.log(res); - mutate(); - }); + .then(() => mutate()); }; return ( diff --git a/apps/app/components/modules/sidebar.tsx b/apps/app/components/modules/sidebar.tsx index 492cdaa00..7811407dd 100644 --- a/apps/app/components/modules/sidebar.tsx +++ b/apps/app/components/modules/sidebar.tsx @@ -87,13 +87,8 @@ export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen, modulesService .patchModule(workspaceSlug as string, projectId as string, moduleId as string, data) - .then((res) => { - console.log(res); - mutate(MODULE_DETAILS(moduleId as string)); - }) - .catch((e) => { - console.log(e); - }); + .then(() => mutate(MODULE_DETAILS(moduleId as string))) + .catch((e) => console.log(e)); }; const handleCreateLink = async (formData: ModuleLink) => { diff --git a/apps/app/components/onboarding/invite-members.tsx b/apps/app/components/onboarding/invite-members.tsx index 8ebce0a07..a731829b4 100644 --- a/apps/app/components/onboarding/invite-members.tsx +++ b/apps/app/components/onboarding/invite-members.tsx @@ -24,17 +24,14 @@ export const InviteMembers: React.FC = ({ setStep, workspace }) => { const onSubmit = async (formData: IUser) => { await workspaceService .inviteWorkspace(workspace.slug, formData) - .then((res) => { - console.log(res); + .then(() => { setToastAlert({ type: "success", title: "Invitations sent!", }); setStep(4); }) - .catch((err) => { - console.log(err); - }); + .catch((err) => console.log(err)); }; return ( diff --git a/apps/app/components/project/single-integration-card.tsx b/apps/app/components/project/single-integration-card.tsx index 4877f12a9..c8de0fe3e 100644 --- a/apps/app/components/project/single-integration-card.tsx +++ b/apps/app/components/project/single-integration-card.tsx @@ -58,7 +58,6 @@ export const SingleIntegration: React.FC = ({ integration }) => { url: html_url, }) .then((res) => { - console.log(res); mutate(PROJECT_GITHUB_REPOSITORY(projectId as string)); setToastAlert({ diff --git a/apps/app/components/rich-text-editor/toolbar/float-tool-tip.tsx b/apps/app/components/rich-text-editor/toolbar/float-tool-tip.tsx index 87f2427d3..034777794 100644 --- a/apps/app/components/rich-text-editor/toolbar/float-tool-tip.tsx +++ b/apps/app/components/rich-text-editor/toolbar/float-tool-tip.tsx @@ -51,11 +51,7 @@ export const CustomFloatingToolbar: React.FC = ({ gptOption, editorState
{gptOption && (
-
diff --git a/apps/app/components/workspace/send-workspace-invitation-modal.tsx b/apps/app/components/workspace/send-workspace-invitation-modal.tsx index c4bcbff4e..b81023c2a 100644 --- a/apps/app/components/workspace/send-workspace-invitation-modal.tsx +++ b/apps/app/components/workspace/send-workspace-invitation-modal.tsx @@ -56,7 +56,6 @@ const SendWorkspaceInvitationModal: React.FC = ({ await workspaceService .inviteWorkspace(workspace_slug, { emails: [formData] }) .then((res) => { - console.log(res); setIsOpen(false); handleClose(); mutate(WORKSPACE_INVITATIONS, (prevData: any) => [ @@ -64,9 +63,9 @@ const SendWorkspaceInvitationModal: React.FC = ({ ...(prevData ?? []), ]); setToastAlert({ - title: "Success", type: "success", - message: "Member invited successfully", + title: "Success!", + message: "Member invited successfully.", }); }) .catch((err) => console.log(err)); diff --git a/apps/app/helpers/string.helper.ts b/apps/app/helpers/string.helper.ts index d3ae3ae26..ebb88f4df 100644 --- a/apps/app/helpers/string.helper.ts +++ b/apps/app/helpers/string.helper.ts @@ -36,11 +36,7 @@ const fallbackCopyTextToClipboard = (text: string) => { // FIXME: Even though we are using this as a fallback, execCommand is deprecated 👎. We should find a better way to do this. // https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand var successful = document.execCommand("copy"); - var msg = successful ? "successful" : "unsuccessful"; - console.log("Fallback: Copying text command was " + msg); - } catch (err) { - console.error("Fallback: Oops, unable to copy", err); - } + } catch (err) {} document.body.removeChild(textArea); }; diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx index 172b049b3..e8b2daf9c 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx @@ -94,11 +94,8 @@ const SingleModule: React.FC = () => { await modulesService .addIssuesToModule(workspaceSlug as string, projectId as string, moduleId as string, data) - .then((res) => { - console.log(res); - mutate(MODULE_ISSUES(moduleId as string)); - }) - .catch((e) => + .then(() => mutate(MODULE_ISSUES(moduleId as string))) + .catch(() => setToastAlert({ type: "error", title: "Error!", diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx index d1f2e37e6..049345b94 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx @@ -81,12 +81,7 @@ const LabelsSettings: NextPage = () => { mutate((prevData) => prevData?.filter((p) => p.id !== labelId), false); issuesService .deleteIssueLabel(workspaceSlug as string, projectDetails.id, labelId) - .then((res) => { - console.log(res); - }) - .catch((e) => { - console.log(e); - }); + .catch((e) => console.log(e)); } }; From acff6396f9b3a5ee01f5ed25a8c3345a74e2390b Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 18 Apr 2023 01:15:39 +0530 Subject: [PATCH 010/120] chore: create/update state duplicate name error (#870) --- .../components/states/create-state-modal.tsx | 22 +++++--- .../states/create-update-state-inline.tsx | 51 ++++++++++++------- .../components/states/delete-state-modal.tsx | 10 ++-- apps/app/services/state.service.ts | 4 +- 4 files changed, 56 insertions(+), 31 deletions(-) diff --git a/apps/app/components/states/create-state-modal.tsx b/apps/app/components/states/create-state-modal.tsx index 4284af6a7..ca19cd752 100644 --- a/apps/app/components/states/create-state-modal.tsx +++ b/apps/app/components/states/create-state-modal.tsx @@ -12,6 +12,8 @@ import { TwitterPicker } from "react-color"; import { Dialog, Popover, Transition } from "@headlessui/react"; // services import stateService from "services/state.service"; +// hooks +import useToast from "hooks/use-toast"; // ui import { CustomSelect, Input, PrimaryButton, SecondaryButton, TextArea } from "components/ui"; // icons @@ -41,6 +43,8 @@ export const CreateStateModal: React.FC = ({ isOpen, projectId, handleClo const router = useRouter(); const { workspaceSlug } = router.query; + const { setToastAlert } = useToast(); + const { register, formState: { errors, isSubmitting }, @@ -48,7 +52,6 @@ export const CreateStateModal: React.FC = ({ isOpen, projectId, handleClo watch, control, reset, - setError, } = useForm({ defaultValues, }); @@ -67,16 +70,23 @@ export const CreateStateModal: React.FC = ({ isOpen, projectId, handleClo await stateService .createState(workspaceSlug as string, projectId, payload) - .then((res) => { + .then(() => { mutate(STATE_LIST(projectId)); onClose(); }) .catch((err) => { - Object.keys(err).map((key) => { - setError(key as keyof IState, { - message: err[key].join(", "), + if (err.status === 400) + setToastAlert({ + type: "error", + title: "Error!", + message: "Another state exists with the same name. Please try again with another name.", + }); + else + setToastAlert({ + type: "error", + title: "Error!", + message: "State could not be created. Please try again.", }); - }); }); }; diff --git a/apps/app/components/states/create-update-state-inline.tsx b/apps/app/components/states/create-update-state-inline.tsx index 0fe766c60..d9e726379 100644 --- a/apps/app/components/states/create-update-state-inline.tsx +++ b/apps/app/components/states/create-update-state-inline.tsx @@ -47,7 +47,6 @@ export const CreateUpdateStateInline: React.FC = ({ data, onClose, select register, handleSubmit, formState: { errors, isSubmitting }, - setError, watch, reset, control, @@ -89,39 +88,55 @@ export const CreateUpdateStateInline: React.FC = ({ data, onClose, select handleClose(); setToastAlert({ - title: "Success", type: "success", - message: "State created successfully", + title: "Success!", + message: "State created successfully.", }); }) .catch((err) => { - Object.keys(err).map((key) => { - setError(key as keyof IState, { - message: err[key].join(", "), + if (err.status === 400) + setToastAlert({ + type: "error", + title: "Error!", + message: + "Another state exists with the same name. Please try again with another name.", + }); + else + setToastAlert({ + type: "error", + title: "Error!", + message: "State could not be created. Please try again.", }); - }); }); } else { await stateService .updateState(workspaceSlug as string, projectId as string, data.id, { ...payload, }) - .then((res) => { + .then(() => { mutate(STATE_LIST(projectId as string)); handleClose(); setToastAlert({ - title: "Success", type: "success", - message: "State updated successfully", + title: "Success!", + message: "State updated successfully.", }); }) .catch((err) => { - Object.keys(err).map((key) => { - setError(key as keyof IState, { - message: err[key].join(", "), + if (err.status === 400) + setToastAlert({ + type: "error", + title: "Error!", + message: + "Another state exists with the same name. Please try again with another name.", + }); + else + setToastAlert({ + type: "error", + title: "Error!", + message: "State could not be updated. Please try again.", }); - }); }); } }; @@ -131,18 +146,18 @@ export const CreateUpdateStateInline: React.FC = ({ data, onClose, select onSubmit={handleSubmit(onSubmit)} className="flex items-center gap-x-2 rounded-[10px] bg-white p-5" > -
- +
+ {({ open }) => ( <> {watch("color") && watch("color") !== "" && ( = ({ isOpen, onClose, data }) =>

- Are you sure you want to delete state- {" "} - {data?.name} - ? All of the data related to the state will be permanently removed. - This action cannot be undone. + Are you sure you want to delete state-{" "} + {data?.name}? All of the data related to + the state will be permanently removed. This action cannot be undone.

@@ -140,7 +139,8 @@ export const DeleteStateModal: React.FC = ({ isOpen, onClose, data }) => Cancel {isDeleteLoading ? "Deleting..." : "Delete"} diff --git a/apps/app/services/state.service.ts b/apps/app/services/state.service.ts index 61cfd4d25..69f30a3dc 100644 --- a/apps/app/services/state.service.ts +++ b/apps/app/services/state.service.ts @@ -22,7 +22,7 @@ class ProjectStateServices extends APIService { return response?.data; }) .catch((error) => { - throw error?.response?.data; + throw error?.response; }); } @@ -66,7 +66,7 @@ class ProjectStateServices extends APIService { return response?.data; }) .catch((error) => { - throw error?.response?.data; + throw error?.response; }); } From ca2366aa9b124bb9f0d6a5152f1b5c7d983d9c36 Mon Sep 17 00:00:00 2001 From: Kyle Lacy Date: Mon, 17 Apr 2023 14:07:00 -0700 Subject: [PATCH 011/120] Use env var to set `AWS_S3_ENDPOINT_URL` setting --- apiserver/plane/settings/production.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/plane/settings/production.py b/apiserver/plane/settings/production.py index d8f2a8bb7..3a590f3b2 100644 --- a/apiserver/plane/settings/production.py +++ b/apiserver/plane/settings/production.py @@ -105,7 +105,7 @@ if ( AWS_S3_ADDRESSING_STYLE = "auto" # The full URL to the S3 endpoint. Leave blank to use the default region URL. - AWS_S3_ENDPOINT_URL = "" + AWS_S3_ENDPOINT_URL = os.environ.get("AWS_S3_ENDPOINT_URL", "") # A prefix to be applied to every stored file. This will be joined to every filename using the "/" separator. AWS_S3_KEY_PREFIX = "" From b8c06b31215c46aa341823bae21db1bbb40d2464 Mon Sep 17 00:00:00 2001 From: Kyle Lacy Date: Mon, 17 Apr 2023 14:08:47 -0700 Subject: [PATCH 012/120] Add `$AWS_S3_ENDPOINT_URL` to `.env.example` --- apiserver/.env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/apiserver/.env.example b/apiserver/.env.example index e2625c1ed..8a7c76ffa 100644 --- a/apiserver/.env.example +++ b/apiserver/.env.example @@ -15,6 +15,7 @@ AWS_REGION="" AWS_ACCESS_KEY_ID="" AWS_SECRET_ACCESS_KEY="" AWS_S3_BUCKET_NAME="" +AWS_S3_ENDPOINT_URL="" # FE WEB_URL="localhost/" # OAUTH From ea87823478b1e2a110357198e356dfc77822f33c Mon Sep 17 00:00:00 2001 From: Kyle Lacy Date: Mon, 17 Apr 2023 16:30:36 -0700 Subject: [PATCH 013/120] Add `$NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS` to add domains for Next Image plugin --- apps/app/.env.example | 3 ++- apps/app/next.config.js | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/app/.env.example b/apps/app/.env.example index 1e2576dfc..cdf30fc72 100644 --- a/apps/app/.env.example +++ b/apps/app/.env.example @@ -1,5 +1,6 @@ # Replace with your instance Public IP # NEXT_PUBLIC_API_BASE_URL = "http://localhost" +NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS= NEXT_PUBLIC_GOOGLE_CLIENTID="" NEXT_PUBLIC_GITHUB_APP_NAME="" NEXT_PUBLIC_GITHUB_ID="" @@ -7,4 +8,4 @@ NEXT_PUBLIC_SENTRY_DSN="" NEXT_PUBLIC_ENABLE_OAUTH=0 NEXT_PUBLIC_ENABLE_SENTRY=0 NEXT_PUBLIC_ENABLE_SESSION_RECORDER=0 -NEXT_PUBLIC_TRACK_EVENTS=0 \ No newline at end of file +NEXT_PUBLIC_TRACK_EVENTS=0 diff --git a/apps/app/next.config.js b/apps/app/next.config.js index d194a6cc7..b3c67eedd 100644 --- a/apps/app/next.config.js +++ b/apps/app/next.config.js @@ -1,5 +1,6 @@ const { withSentryConfig } = require("@sentry/nextjs"); const path = require("path"); +const extraImageDomains = (process.env.NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS ?? "").split(",").filter((domain) => domain.length > 0); const nextConfig = { reactStrictMode: false, @@ -11,6 +12,7 @@ const nextConfig = { "planefs.s3.amazonaws.com", "images.unsplash.com", "avatars.githubusercontent.com", + ...extraImageDomains, ], }, output: "standalone", From 682a1477fb3950ce93fa0e69b36b76fd7f39fe02 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 18 Apr 2023 10:54:19 +0530 Subject: [PATCH 014/120] chore: create/update estimate points validation (#871) --- .../estimates/estimate-points-modal.tsx | 82 ++++++------------- .../components/estimates/single-estimate.tsx | 6 +- 2 files changed, 30 insertions(+), 58 deletions(-) diff --git a/apps/app/components/estimates/estimate-points-modal.tsx b/apps/app/components/estimates/estimate-points-modal.tsx index b14c33c03..e3ce73aee 100644 --- a/apps/app/components/estimates/estimate-points-modal.tsx +++ b/apps/app/components/estimates/estimate-points-modal.tsx @@ -1,23 +1,22 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect } from "react"; import { useRouter } from "next/router"; import { useForm } from "react-hook-form"; import { mutate } from "swr"; + +// services +import estimatesService from "services/estimates.service"; // headless ui import { Dialog, Transition } from "@headlessui/react"; // hooks import useToast from "hooks/use-toast"; // ui import { Input, PrimaryButton, SecondaryButton } from "components/ui"; - -// icons -import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; // types import type { IEstimate, IEstimatePoint } from "types"; - -import estimatesService from "services/estimates.service"; +// fetch-keys import { ESTIMATE_POINTS_LIST } from "constants/fetch-keys"; type Props = { @@ -144,6 +143,21 @@ export const EstimatePointsModal: React.FC = ({ isOpen, data, estimate, o }; const onSubmit = async (formData: FormValues) => { + let c = 0; + + Object.keys(formData).map((key) => { + if (formData[key as keyof FormValues] === "") c++; + }); + + if (c !== 0) { + setToastAlert({ + type: "error", + title: "Error!", + message: "Please fill all the fields.", + }); + return; + } + if (data && data.length !== 0) await updateEstimatePoints(formData); else await createEstimatePoints(formData); @@ -195,7 +209,7 @@ export const EstimatePointsModal: React.FC = ({ isOpen, data, estimate, o

- {data ? "Update" : "Create"} Estimate Points + {data && data.length > 0 ? "Update" : "Create"} Estimate Points

@@ -206,16 +220,9 @@ export const EstimatePointsModal: React.FC = ({ isOpen, data, estimate, o id="name" name="value1" type="name" - placeholder="Value" + placeholder="Point 1" autoComplete="off" register={register} - validations={{ - required: "value is required", - maxLength: { - value: 10, - message: "Name should be less than 10 characters", - }, - }} /> @@ -228,16 +235,9 @@ export const EstimatePointsModal: React.FC = ({ isOpen, data, estimate, o id="name" name="value2" type="name" - placeholder="Value" + placeholder="Point 2" autoComplete="off" register={register} - validations={{ - required: "value is required", - maxLength: { - value: 10, - message: "Name should be less than 10 characters", - }, - }} /> @@ -250,16 +250,9 @@ export const EstimatePointsModal: React.FC = ({ isOpen, data, estimate, o id="name" name="value3" type="name" - placeholder="Value" + placeholder="Point 3" autoComplete="off" register={register} - validations={{ - required: "value is required", - maxLength: { - value: 10, - message: "Name should be less than 10 characters", - }, - }} /> @@ -272,16 +265,9 @@ export const EstimatePointsModal: React.FC = ({ isOpen, data, estimate, o id="name" name="value4" type="name" - placeholder="Value" + placeholder="Point 4" autoComplete="off" register={register} - validations={{ - required: "value is required", - maxLength: { - value: 10, - message: "Name should be less than 10 characters", - }, - }} /> @@ -294,16 +280,9 @@ export const EstimatePointsModal: React.FC = ({ isOpen, data, estimate, o id="name" name="value5" type="name" - placeholder="Value" + placeholder="Point 5" autoComplete="off" register={register} - validations={{ - required: "value is required", - maxLength: { - value: 10, - message: "Name should be less than 10 characters", - }, - }} /> @@ -316,16 +295,9 @@ export const EstimatePointsModal: React.FC = ({ isOpen, data, estimate, o id="name" name="value6" type="name" - placeholder="Value" + placeholder="Point 6" autoComplete="off" register={register} - validations={{ - required: "value is required", - maxLength: { - value: 10, - message: "Name should be less than 10 characters", - }, - }} /> diff --git a/apps/app/components/estimates/single-estimate.tsx b/apps/app/components/estimates/single-estimate.tsx index ea41ae6b7..e0155675b 100644 --- a/apps/app/components/estimates/single-estimate.tsx +++ b/apps/app/components/estimates/single-estimate.tsx @@ -21,11 +21,12 @@ import { SquaresPlusIcon, ListBulletIcon, } from "@heroicons/react/24/outline"; +// helpers +import { orderArrayBy } from "helpers/array.helper"; // types -import { IEstimate, IProject } from "types"; +import { IEstimate } from "types"; // fetch-keys import { ESTIMATE_POINTS_LIST } from "constants/fetch-keys"; -import { orderArrayBy } from "helpers/array.helper"; type Props = { estimate: IEstimate; @@ -41,7 +42,6 @@ export const SingleEstimate: React.FC = ({ const [isEstimatePointsModalOpen, setIsEstimatePointsModalOpen] = useState(false); const [isDeleteEstimateModalOpen, setIsDeleteEstimateModalOpen] = useState(false); - const router = useRouter(); const { workspaceSlug, projectId } = router.query; From 1627a587ee3640395e9b074c78615d3459519985 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 18 Apr 2023 10:54:45 +0530 Subject: [PATCH 015/120] refactor: workspace integrations code (#872) --- .../components/integration/github/auth.tsx | 46 +++-------- .../integration/github/import-configure.tsx | 20 ++--- .../integration/github/import-data.tsx | 4 +- .../integration/github/select-repository.tsx | 4 +- apps/app/components/integration/index.ts | 2 +- .../single-integration-card.tsx} | 72 +++++++++--------- .../project/single-integration-card.tsx | 4 +- apps/app/hooks/use-integration-popup.tsx | 52 +++++++++++++ .../[workspaceSlug]/settings/integrations.tsx | 8 +- apps/app/public/services/slack.png | Bin 0 -> 19970 bytes apps/app/services/integration/index.ts | 6 +- apps/app/types/integration.d.ts | 4 +- 12 files changed, 115 insertions(+), 107 deletions(-) rename apps/app/components/{popup/index.tsx => integration/single-integration-card.tsx} (71%) create mode 100644 apps/app/hooks/use-integration-popup.tsx create mode 100644 apps/app/public/services/slack.png diff --git a/apps/app/components/integration/github/auth.tsx b/apps/app/components/integration/github/auth.tsx index 6055cd903..b530b4a24 100644 --- a/apps/app/components/integration/github/auth.tsx +++ b/apps/app/components/integration/github/auth.tsx @@ -1,51 +1,25 @@ -import { FC, useRef, useState } from "react"; - +// hooks +import useIntegrationPopup from "hooks/use-integration-popup"; // ui import { PrimaryButton } from "components/ui"; +// types +import { IWorkspaceIntegration } from "types"; type Props = { - workspaceSlug: string | undefined; - workspaceIntegration: any; + workspaceIntegration: false | IWorkspaceIntegration | undefined; + provider: string | undefined; }; -export const GithubAuth: FC = ({ workspaceSlug, workspaceIntegration }) => { - const popup = useRef(); - const [authLoader, setAuthLoader] = useState(false); - - const checkPopup = () => { - const check = setInterval(() => { - if (!popup || popup.current.closed || popup.current.closed === undefined) { - clearInterval(check); - setAuthLoader(false); - } - }, 2000); - }; - - const openPopup = () => { - const width = 600, - height = 600; - const left = window.innerWidth / 2 - width / 2; - const top = window.innerHeight / 2 - height / 2; - const url = `https://github.com/apps/${ - process.env.NEXT_PUBLIC_GITHUB_APP_NAME - }/installations/new?state=${workspaceSlug as string}`; - - return window.open(url, "", `width=${width}, height=${height}, top=${top}, left=${left}`); - }; - - const startAuth = () => { - popup.current = openPopup(); - checkPopup(); - setAuthLoader(true); - }; +export const GithubAuth: React.FC = ({ workspaceIntegration, provider }) => { + const { startAuth, isConnecting } = useIntegrationPopup(provider); return (
{workspaceIntegration && workspaceIntegration?.id ? ( Successfully Connected ) : ( - - {authLoader ? "Connecting..." : "Connect"} + + {isConnecting ? "Connecting..." : "Connect"} )}
diff --git a/apps/app/components/integration/github/import-configure.tsx b/apps/app/components/integration/github/import-configure.tsx index ecae21eae..55abe058c 100644 --- a/apps/app/components/integration/github/import-configure.tsx +++ b/apps/app/components/integration/github/import-configure.tsx @@ -1,30 +1,23 @@ -import { FC } from "react"; - -import { useRouter } from "next/router"; - // components import { GithubAuth, TIntegrationSteps } from "components/integration"; // ui import { PrimaryButton } from "components/ui"; // types -import { IAppIntegrations, IWorkspaceIntegrations } from "types"; +import { IAppIntegration, IWorkspaceIntegration } from "types"; type Props = { provider: string | undefined; handleStepChange: (value: TIntegrationSteps) => void; - appIntegrations: IAppIntegrations[] | undefined; - workspaceIntegrations: IWorkspaceIntegrations[] | undefined; + appIntegrations: IAppIntegration[] | undefined; + workspaceIntegrations: IWorkspaceIntegration[] | undefined; }; -export const GithubImportConfigure: FC = ({ +export const GithubImportConfigure: React.FC = ({ handleStepChange, provider, appIntegrations, workspaceIntegrations, }) => { - const router = useRouter(); - const { workspaceSlug } = router.query; - // current integration from all the integrations available const integration = appIntegrations && @@ -46,10 +39,7 @@ export const GithubImportConfigure: FC = ({
Set up your GitHub import.
- +
diff --git a/apps/app/components/integration/github/import-data.tsx b/apps/app/components/integration/github/import-data.tsx index c3fa871e3..2b93017c1 100644 --- a/apps/app/components/integration/github/import-data.tsx +++ b/apps/app/components/integration/github/import-data.tsx @@ -11,11 +11,11 @@ import { CustomSearchSelect, PrimaryButton, SecondaryButton } from "components/u // helpers import { truncateText } from "helpers/string.helper"; // types -import { IWorkspaceIntegrations } from "types"; +import { IWorkspaceIntegration } from "types"; type Props = { handleStepChange: (value: TIntegrationSteps) => void; - integration: IWorkspaceIntegrations | false | undefined; + integration: IWorkspaceIntegration | false | undefined; control: Control; watch: UseFormWatch; }; diff --git a/apps/app/components/integration/github/select-repository.tsx b/apps/app/components/integration/github/select-repository.tsx index a306ff0fc..306b1e90e 100644 --- a/apps/app/components/integration/github/select-repository.tsx +++ b/apps/app/components/integration/github/select-repository.tsx @@ -11,10 +11,10 @@ import { CustomSearchSelect } from "components/ui"; // helpers import { truncateText } from "helpers/string.helper"; // types -import { IWorkspaceIntegrations } from "types"; +import { IWorkspaceIntegration } from "types"; type Props = { - integration: IWorkspaceIntegrations; + integration: IWorkspaceIntegration; value: any; label: string; onChange: (repo: any) => void; diff --git a/apps/app/components/integration/index.ts b/apps/app/components/integration/index.ts index e970bbd84..39ae51e33 100644 --- a/apps/app/components/integration/index.ts +++ b/apps/app/components/integration/index.ts @@ -2,9 +2,9 @@ export * from "./delete-import-modal"; export * from "./guide"; export * from "./single-import"; +export * from "./single-integration-card"; // github export * from "./github"; - // jira export * from "./jira"; diff --git a/apps/app/components/popup/index.tsx b/apps/app/components/integration/single-integration-card.tsx similarity index 71% rename from apps/app/components/popup/index.tsx rename to apps/app/components/integration/single-integration-card.tsx index 5e3971a39..824ea0b9b 100644 --- a/apps/app/components/popup/index.tsx +++ b/apps/app/components/integration/single-integration-card.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState } from "react"; +import { useState } from "react"; import Image from "next/image"; import { useRouter } from "next/router"; @@ -9,49 +9,44 @@ import useSWR, { mutate } from "swr"; import IntegrationService from "services/integration"; // hooks import useToast from "hooks/use-toast"; +import useIntegrationPopup from "hooks/use-integration-popup"; // ui import { DangerButton, Loader, SecondaryButton } from "components/ui"; // icons -import GithubLogo from "public/logos/github-square.png"; +import GithubLogo from "public/services/github.png"; +import SlackLogo from "public/services/slack.png"; // types -import { IWorkspaceIntegrations } from "types"; +import { IAppIntegration, IWorkspaceIntegration } from "types"; // fetch-keys import { WORKSPACE_INTEGRATIONS } from "constants/fetch-keys"; -const OAuthPopUp = ({ integration }: any) => { - const [deletingIntegration, setDeletingIntegration] = useState(false); +type Props = { + integration: IAppIntegration; +}; - const popup = useRef(); +const integrationDetails: { [key: string]: any } = { + github: { + logo: GithubLogo, + installed: + "Activate GitHub integrations on individual projects to sync with specific repositories.", + notInstalled: "Connect with GitHub with your Plane workspace to sync project issues.", + }, + slack: { + logo: SlackLogo, + installed: "Activate Slack integrations on individual projects to sync with specific cahnnels.", + notInstalled: "Connect with Slack with your Plane workspace to sync project issues.", + }, +}; + +export const SingleIntegrationCard: React.FC = ({ integration }) => { + const [deletingIntegration, setDeletingIntegration] = useState(false); const router = useRouter(); const { workspaceSlug } = router.query; const { setToastAlert } = useToast(); - const checkPopup = () => { - const check = setInterval(() => { - if (!popup || popup.current.closed || popup.current.closed === undefined) { - clearInterval(check); - } - }, 1000); - }; - - const openPopup = () => { - const width = 600, - height = 600; - const left = window.innerWidth / 2 - width / 2; - const top = window.innerHeight / 2 - height / 2; - const url = `https://github.com/apps/${ - process.env.NEXT_PUBLIC_GITHUB_APP_NAME - }/installations/new?state=${workspaceSlug as string}`; - - return window.open(url, "", `width=${width}, height=${height}, top=${top}, left=${left}`); - }; - - const startAuth = () => { - popup.current = openPopup(); - checkPopup(); - }; + const { startAuth, isConnecting: isInstalling } = useIntegrationPopup(integration.provider); const { data: workspaceIntegrations } = useSWR( workspaceSlug ? WORKSPACE_INTEGRATIONS(workspaceSlug as string) : null, @@ -75,7 +70,7 @@ const OAuthPopUp = ({ integration }: any) => { workspaceIntegrationId ?? "" ) .then(() => { - mutate( + mutate( WORKSPACE_INTEGRATIONS(workspaceSlug as string), (prevData) => prevData?.filter((i) => i.id !== workspaceIntegrationId), false @@ -107,7 +102,10 @@ const OAuthPopUp = ({ integration }: any) => {
- GithubLogo + {`${integration.title}

@@ -128,8 +126,8 @@ const OAuthPopUp = ({ integration }: any) => {

{workspaceIntegrations ? isInstalled - ? "Activate GitHub integrations on individual projects to sync with specific repositories." - : "Connect with GitHub with your Plane workspace to sync project issues." + ? integrationDetails[integration.provider].installed + : integrationDetails[integration.provider].notInstalled : "Loading..."}

@@ -141,8 +139,8 @@ const OAuthPopUp = ({ integration }: any) => { {deletingIntegration ? "Removing..." : "Remove installation"} ) : ( - - Add installation + + {isInstalling ? "Installing..." : "Add installation"} ) ) : ( @@ -153,5 +151,3 @@ const OAuthPopUp = ({ integration }: any) => {
); }; - -export default OAuthPopUp; diff --git a/apps/app/components/project/single-integration-card.tsx b/apps/app/components/project/single-integration-card.tsx index c8de0fe3e..bbf21f438 100644 --- a/apps/app/components/project/single-integration-card.tsx +++ b/apps/app/components/project/single-integration-card.tsx @@ -14,12 +14,12 @@ import { SelectRepository } from "components/integration"; // icons import GithubLogo from "public/logos/github-square.png"; // types -import { IWorkspaceIntegrations } from "types"; +import { IWorkspaceIntegration } from "types"; // fetch-keys import { PROJECT_GITHUB_REPOSITORY } from "constants/fetch-keys"; type Props = { - integration: IWorkspaceIntegrations; + integration: IWorkspaceIntegration; }; export const SingleIntegration: React.FC = ({ integration }) => { diff --git a/apps/app/hooks/use-integration-popup.tsx b/apps/app/hooks/use-integration-popup.tsx new file mode 100644 index 000000000..03137a195 --- /dev/null +++ b/apps/app/hooks/use-integration-popup.tsx @@ -0,0 +1,52 @@ +import { useRef, useState } from "react"; + +import { useRouter } from "next/router"; + +const useIntegrationPopup = (provider: string | undefined) => { + const [authLoader, setAuthLoader] = useState(false); + + const router = useRouter(); + const { workspaceSlug } = router.query; + + const providerUrls: { [key: string]: string } = { + github: `https://github.com/apps/${ + process.env.NEXT_PUBLIC_GITHUB_APP_NAME + }/installations/new?state=${workspaceSlug as string}`, + slack: "", + }; + const popup = useRef(); + + const checkPopup = () => { + const check = setInterval(() => { + if (!popup || popup.current.closed || popup.current.closed === undefined) { + clearInterval(check); + setAuthLoader(false); + } + }, 1000); + }; + + const openPopup = () => { + if (!provider) return; + + const width = 600, + height = 600; + const left = window.innerWidth / 2 - width / 2; + const top = window.innerHeight / 2 - height / 2; + const url = providerUrls[provider]; + + return window.open(url, "", `width=${width}, height=${height}, top=${top}, left=${left}`); + }; + + const startAuth = () => { + popup.current = openPopup(); + checkPopup(); + setAuthLoader(true); + }; + + return { + startAuth, + isConnecting: authLoader, + }; +}; + +export default useIntegrationPopup; diff --git a/apps/app/pages/[workspaceSlug]/settings/integrations.tsx b/apps/app/pages/[workspaceSlug]/settings/integrations.tsx index 71908bc5d..44717f67d 100644 --- a/apps/app/pages/[workspaceSlug]/settings/integrations.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/integrations.tsx @@ -10,7 +10,7 @@ import IntegrationService from "services/integration"; // layouts import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; // components -import OAuthPopUp from "components/popup"; +import { SingleIntegrationCard } from "components/integration"; // ui import { Loader } from "components/ui"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; @@ -49,11 +49,7 @@ const WorkspaceIntegrations: NextPage = () => {
{appIntegrations ? ( appIntegrations.map((integration) => ( - + )) ) : ( diff --git a/apps/app/public/services/slack.png b/apps/app/public/services/slack.png new file mode 100644 index 0000000000000000000000000000000000000000..fc918001f6748644a9ce8bd278ad5d4f638f54a8 GIT binary patch literal 19970 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 zUscU^>!+>6)&T$j00000000000000000000000000000000000z)KtX&s4h4(6rV7 O0000 { + async getAppIntegrationsList(): Promise { return this.get(`/api/integrations/`) .then((response) => response?.data) .catch((error) => { @@ -17,7 +17,7 @@ class IntegrationService extends APIService { }); } - async getWorkspaceIntegrationsList(workspaceSlug: string): Promise { + async getWorkspaceIntegrationsList(workspaceSlug: string): Promise { return this.get(`/api/workspaces/${workspaceSlug}/workspace-integrations/`) .then((response) => response?.data) .catch((error) => { diff --git a/apps/app/types/integration.d.ts b/apps/app/types/integration.d.ts index 77ea9d35d..b499d743f 100644 --- a/apps/app/types/integration.d.ts +++ b/apps/app/types/integration.d.ts @@ -1,5 +1,5 @@ // All the app integrations that are available -export interface IAppIntegrations { +export interface IAppIntegration { author: string; author: ""; avatar_url: string | null; @@ -19,7 +19,7 @@ export interface IAppIntegrations { webhook_url: string; } -export interface IWorkspaceIntegrations { +export interface IWorkspaceIntegration { actor: string; api_token: string; config: any; From be5ef6142873c1681e72f115fc9483f91de24ae7 Mon Sep 17 00:00:00 2001 From: Kunal Vishwakarma <116634168+kunalv17@users.noreply.github.com> Date: Tue, 18 Apr 2023 10:55:32 +0530 Subject: [PATCH 016/120] fix: join project button (#873) --- apps/app/components/auth-screens/project/join-project.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/components/auth-screens/project/join-project.tsx b/apps/app/components/auth-screens/project/join-project.tsx index 402fff42b..cf8fabd41 100644 --- a/apps/app/components/auth-screens/project/join-project.tsx +++ b/apps/app/components/auth-screens/project/join-project.tsx @@ -31,7 +31,7 @@ export const JoinProject: React.FC = () => { project_ids: [projectId as string], }) .then(async () => { - await mutate(USER_PROJECT_VIEW(workspaceSlug.toString())); + await mutate(USER_PROJECT_VIEW(projectId.toString())); setIsJoiningProject(false); }) .catch((err) => { From 2b280935a1756e87f64c5c088aa7baa9b97c567e Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Tue, 18 Apr 2023 12:25:22 +0530 Subject: [PATCH 017/120] chore: update state create endpoint to send error response on integrity error (#869) * chore: update state create endpoint to send error response on integrity error * dev: update status code for general exception --- apiserver/plane/api/views/state.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apiserver/plane/api/views/state.py b/apiserver/plane/api/views/state.py index 4616fcee7..048ac4a6a 100644 --- a/apiserver/plane/api/views/state.py +++ b/apiserver/plane/api/views/state.py @@ -1,6 +1,9 @@ # Python imports from itertools import groupby +# Django imports +from django.db import IntegrityError + # Third party imports from rest_framework.response import Response from rest_framework import status @@ -36,6 +39,22 @@ class StateViewSet(BaseViewSet): .distinct() ) + def create(self, request, slug, project_id): + try: + serializer = StateSerializer(data=request.data) + if serializer.is_valid(): + serializer.save(project_id=project_id) + return Response(serializer.data, status=status.HTTP_200_OK) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + except IntegrityError: + return Response( + {"error": "State with the name already exists"}, + status=status.HTTP_400_BAD_REQUEST, + ) + except Exception as e: + capture_exception(e) + return Response({"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST) + def list(self, request, slug, project_id): try: state_dict = dict() From 63a36fb25d915fd71eeaba9f3e25a44d06511961 Mon Sep 17 00:00:00 2001 From: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Date: Wed, 19 Apr 2023 15:40:31 +0530 Subject: [PATCH 018/120] feat: jira importer (#879) * feat: jira importer * fix: yarn lock * fix: displaying correct count of users that are been imported * fix: showing workspace member in import users --- apps/app/components/integration/guide.tsx | 2 +- .../integration/jira/confirm-import.tsx | 49 ++++ .../integration/jira/give-details.tsx | 178 ++++++++++++++ .../integration/jira/import-users.tsx | 145 +++++++++++ apps/app/components/integration/jira/index.ts | 38 +++ .../integration/jira/jira-project-detail.tsx | 168 +++++++++++++ apps/app/components/integration/jira/root.tsx | 225 +++++++++++++++++- .../components/ui/custom-search-select.tsx | 6 +- apps/app/components/ui/index.ts | 1 + apps/app/components/ui/toggle-switch.tsx | 32 +++ apps/app/constants/fetch-keys.ts | 11 +- apps/app/constants/workspace.ts | 14 +- apps/app/hooks/use-workspace-members.tsx | 43 ++++ apps/app/package.json | 2 + apps/app/services/integration/jira.service.ts | 34 +++ apps/app/types/importer/index.ts | 1 + apps/app/types/importer/jira-importer.d.ts | 58 +++++ yarn.lock | 10 + 18 files changed, 1006 insertions(+), 11 deletions(-) create mode 100644 apps/app/components/integration/jira/confirm-import.tsx create mode 100644 apps/app/components/integration/jira/give-details.tsx create mode 100644 apps/app/components/integration/jira/import-users.tsx create mode 100644 apps/app/components/integration/jira/jira-project-detail.tsx create mode 100644 apps/app/components/ui/toggle-switch.tsx create mode 100644 apps/app/hooks/use-workspace-members.tsx create mode 100644 apps/app/services/integration/jira.service.ts create mode 100644 apps/app/types/importer/jira-importer.d.ts diff --git a/apps/app/components/integration/guide.tsx b/apps/app/components/integration/guide.tsx index ce33f16d2..ad9ffe559 100644 --- a/apps/app/components/integration/guide.tsx +++ b/apps/app/components/integration/guide.tsx @@ -52,7 +52,7 @@ const IntegrationGuide = () => { handleClose={() => setDeleteImportModal(false)} data={importToDelete} /> -
+
{!provider && ( <>
diff --git a/apps/app/components/integration/jira/confirm-import.tsx b/apps/app/components/integration/jira/confirm-import.tsx new file mode 100644 index 000000000..79c56bf4b --- /dev/null +++ b/apps/app/components/integration/jira/confirm-import.tsx @@ -0,0 +1,49 @@ +import React from "react"; + +// react hook form +import { useFormContext } from "react-hook-form"; + +// types +import { IJiraImporterForm } from "types"; + +export const JiraConfirmImport: React.FC = () => { + const { watch } = useFormContext(); + + return ( +
+
+
+

Confirm

+
+ +
+

Migrating

+
+
+
+

{watch("data.total_issues")}

+

Issues

+
+
+

{watch("data.total_states")}

+

States

+
+
+

{watch("data.total_modules")}

+

Modules

+
+
+

{watch("data.total_labels")}

+

Labels

+
+
+

+ {watch("data.users").filter((user) => user.import).length} +

+

User

+
+
+
+
+ ); +}; diff --git a/apps/app/components/integration/jira/give-details.tsx b/apps/app/components/integration/jira/give-details.tsx new file mode 100644 index 000000000..fb4032b68 --- /dev/null +++ b/apps/app/components/integration/jira/give-details.tsx @@ -0,0 +1,178 @@ +import React from "react"; + +// next +import Link from "next/link"; + +// react hook form +import { useFormContext, Controller } from "react-hook-form"; + +// icons +import { PlusIcon } from "@heroicons/react/20/solid"; + +// hooks +import useProjects from "hooks/use-projects"; + +// components +import { Input, CustomSelect } from "components/ui"; + +import { IJiraImporterForm } from "types"; + +export const JiraGetImportDetail: React.FC = () => { + const { + register, + control, + formState: { errors }, + } = useFormContext(); + + const { projects } = useProjects(); + + return ( +
+
+
+

Jira Personal Access Token

+

+ Get to know your access token by navigating to{" "} + + + Atlassian Settings + + +

+
+ +
+ +
+
+ +
+
+

Jira Project Key

+

If XXX-123 is your issue, then enter XXX

+
+
+ +
+
+ +
+
+

Jira Email Address

+

+ Enter the Gmail account that you use in Jira account +

+
+
+ +
+
+ +
+
+

Jira Installation or Cloud Host Name

+

Enter your companies cloud host name

+
+
+ +
+
+ +
+
+

Import to project

+

Select which project you want to import to.

+
+
+ ( + + {value && value !== "" + ? projects.find((p) => p.id === value)?.name + : "Select Project"} + + } + > + {projects.length > 0 ? ( + projects.map((project) => ( + + {project.name} + + )) + ) : ( +
+

You don{"'"}t have any project. Please create a project first.

+
+ )} +
+ +
+
+ )} + /> +
+
+
+ ); +}; diff --git a/apps/app/components/integration/jira/import-users.tsx b/apps/app/components/integration/jira/import-users.tsx new file mode 100644 index 000000000..fa48bc772 --- /dev/null +++ b/apps/app/components/integration/jira/import-users.tsx @@ -0,0 +1,145 @@ +import { FC } from "react"; + +// next +import { useRouter } from "next/router"; + +// react-hook-form +import { useFormContext, useFieldArray, Controller } from "react-hook-form"; + +// hooks +import useWorkspaceMembers from "hooks/use-workspace-members"; + +// components +import { ToggleSwitch, Input, CustomSelect, CustomSearchSelect, Avatar } from "components/ui"; + +import { IJiraImporterForm } from "types"; + +export const JiraImportUsers: FC = () => { + const { + control, + watch, + register, + formState: { errors }, + } = useFormContext(); + + const { fields } = useFieldArray({ + control, + name: "data.users", + }); + + const router = useRouter(); + const { workspaceSlug } = router.query; + + const { workspaceMembers: members } = useWorkspaceMembers(workspaceSlug?.toString()); + + const options = + members?.map((member) => ({ + value: member.member.email, + query: + (member.member.first_name && member.member.first_name !== "" + ? member.member.first_name + : member.member.email) + + " " + + member.member.last_name ?? "", + content: ( +
+ + {member.member.first_name && member.member.first_name !== "" + ? member.member.first_name + " (" + member.member.email + ")" + : member.member.email} +
+ ), + })) ?? []; + + return ( +
+
+
+

Users

+

Update, invite or choose not to invite assignee

+
+
+ ( + + )} + /> +
+
+ + {watch("data.invite_users") && ( +
+
+
Name
+
Import as
+
+ +
+ {fields.map((user, index) => ( +
+
+

{user.username}

+
+
+ ( + + {Boolean(value) ? value : ("Ignore" as any)} + + } + > + Invite by email + Map to existing + Do not import + + )} + /> +
+
+ {watch(`data.users.${index}.import`) === "invite" && ( + + )} + {watch(`data.users.${index}.import`) === "map" && ( + ( + + )} + /> + )} +
+
+ ))} +
+
+ )} +
+ ); +}; diff --git a/apps/app/components/integration/jira/index.ts b/apps/app/components/integration/jira/index.ts index 1efe34c51..321e4f313 100644 --- a/apps/app/components/integration/jira/index.ts +++ b/apps/app/components/integration/jira/index.ts @@ -1 +1,39 @@ export * from "./root"; +export * from "./give-details"; +export * from "./jira-project-detail"; +export * from "./import-users"; +export * from "./confirm-import"; + +import { IJiraImporterForm } from "types"; + +export type TJiraIntegrationSteps = + | "import-configure" + | "display-import-data" + | "select-import-data" + | "import-users" + | "import-confirmation"; + +export interface IJiraIntegrationData { + state: TJiraIntegrationSteps; +} + +export const jiraFormDefaultValues: IJiraImporterForm = { + metadata: { + cloud_hostname: "", + api_token: "", + project_key: "", + email: "", + }, + config: { + epics_to_modules: false, + }, + data: { + users: [], + invite_users: true, + total_issues: 0, + total_labels: 0, + total_modules: 0, + total_states: 0, + }, + project_id: "", +}; diff --git a/apps/app/components/integration/jira/jira-project-detail.tsx b/apps/app/components/integration/jira/jira-project-detail.tsx new file mode 100644 index 000000000..48220a8c1 --- /dev/null +++ b/apps/app/components/integration/jira/jira-project-detail.tsx @@ -0,0 +1,168 @@ +import React, { useEffect } from "react"; + +// next +import { useRouter } from "next/router"; + +// swr +import useSWR from "swr"; + +// react hook form +import { useFormContext, Controller } from "react-hook-form"; + +// services +import jiraImporterService from "services/integration/jira.service"; + +// fetch keys +import { JIRA_IMPORTER_DETAIL } from "constants/fetch-keys"; + +import { IJiraImporterForm, IJiraMetadata } from "types"; + +// components +import { Spinner, ToggleSwitch } from "components/ui"; + +import type { IJiraIntegrationData, TJiraIntegrationSteps } from "./"; + +type Props = { + setCurrentStep: React.Dispatch>; + setDisableTopBarAfter: React.Dispatch>; +}; + +export const JiraProjectDetail: React.FC = (props) => { + const { setCurrentStep, setDisableTopBarAfter } = props; + + const { + watch, + setValue, + control, + formState: { errors }, + } = useFormContext(); + + const router = useRouter(); + const { workspaceSlug } = router.query; + + const params: IJiraMetadata = { + api_token: watch("metadata.api_token"), + project_key: watch("metadata.project_key"), + email: watch("metadata.email"), + cloud_hostname: watch("metadata.cloud_hostname"), + }; + + const { data: projectInfo, error } = useSWR( + workspaceSlug && + !errors.metadata?.api_token && + !errors.metadata?.project_key && + !errors.metadata?.email && + !errors.metadata?.cloud_hostname + ? JIRA_IMPORTER_DETAIL(workspaceSlug.toString(), params) + : null, + workspaceSlug && + !errors.metadata?.api_token && + !errors.metadata?.project_key && + !errors.metadata?.email && + !errors.metadata?.cloud_hostname + ? () => jiraImporterService.getJiraProjectInfo(workspaceSlug.toString(), params) + : null + ); + + useEffect(() => { + if (!projectInfo) return; + + setValue("data.total_issues", projectInfo.issues); + setValue("data.total_labels", projectInfo.labels); + setValue( + "data.users", + projectInfo.users?.map((user) => ({ + email: user.emailAddress, + import: false, + username: user.displayName, + })) + ); + setValue("data.total_states", projectInfo.states); + setValue("data.total_modules", projectInfo.modules); + }, [projectInfo, setValue]); + + useEffect(() => { + if (error) setDisableTopBarAfter("display-import-data"); + else setDisableTopBarAfter(null); + }, [error, setDisableTopBarAfter]); + + useEffect(() => { + if (!projectInfo && !error) setDisableTopBarAfter("display-import-data"); + else if (!error) setDisableTopBarAfter(null); + }, [projectInfo, error, setDisableTopBarAfter]); + + if (!projectInfo && !error) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+

+ Something went wrong. Please{" "} + {" "} + and check your Jira project details. +

+
+ ); + } + + return ( +
+
+
+

Import Data

+

Import Completed. We have found:

+
+
+
+

{projectInfo?.issues}

+

Issues

+
+
+

{projectInfo?.states}

+

States

+
+
+

{projectInfo?.modules}

+

Modules

+
+
+

{projectInfo?.labels}

+

Labels

+
+
+

{projectInfo?.users?.length}

+

Users

+
+
+
+ +
+
+

Import Epics

+

Import epics as modules

+
+
+ ( + + )} + /> +
+
+
+ ); +}; diff --git a/apps/app/components/integration/jira/root.tsx b/apps/app/components/integration/jira/root.tsx index 15a89c3e4..b9bd732c0 100644 --- a/apps/app/components/integration/jira/root.tsx +++ b/apps/app/components/integration/jira/root.tsx @@ -1 +1,224 @@ -export const JiraImporterRoot = () => <>; +import React, { useState } from "react"; + +// next +import Link from "next/link"; +import Image from "next/image"; +import { useRouter } from "next/router"; + +// swr +import { mutate } from "swr"; + +// react hook form +import { FormProvider, useForm } from "react-hook-form"; + +// icons +import { ArrowLeftIcon, ListBulletIcon } from "@heroicons/react/24/outline"; +import { CogIcon, CloudUploadIcon, UsersIcon, CheckIcon } from "components/icons"; + +// services +import jiraImporterService from "services/integration/jira.service"; + +// fetch keys +import { IMPORTER_SERVICES_LIST } from "constants/fetch-keys"; + +// components +import { PrimaryButton, SecondaryButton } from "components/ui"; +import { + JiraGetImportDetail, + JiraProjectDetail, + JiraImportUsers, + JiraConfirmImport, + jiraFormDefaultValues, + TJiraIntegrationSteps, + IJiraIntegrationData, +} from "./"; + +import JiraLogo from "public/services/jira.png"; + +import { IJiraImporterForm } from "types"; + +const integrationWorkflowData: Array<{ + title: string; + key: TJiraIntegrationSteps; + icon: React.FC>; +}> = [ + { + title: "Configure", + key: "import-configure", + icon: CogIcon, + }, + { + title: "Import Data", + key: "display-import-data", + icon: ListBulletIcon, + }, + { + title: "Users", + key: "import-users", + icon: UsersIcon, + }, + { + title: "Confirm", + key: "import-confirmation", + icon: CheckIcon, + }, +]; + +export const JiraImporterRoot = () => { + const [currentStep, setCurrentStep] = useState({ + state: "import-configure", + }); + const [disableTopBarAfter, setDisableTopBarAfter] = useState(null); + + const router = useRouter(); + const { workspaceSlug } = router.query; + + const methods = useForm({ + defaultValues: jiraFormDefaultValues, + mode: "all", + reValidateMode: "onChange", + }); + + const isValid = methods.formState.isValid; + + const onSubmit = async (data: IJiraImporterForm) => { + if (!workspaceSlug) return; + + await jiraImporterService + .createJiraImporter(workspaceSlug.toString(), data) + .then(() => { + mutate(IMPORTER_SERVICES_LIST(workspaceSlug.toString())); + router.push(`/${workspaceSlug}/settings/import-export`); + }) + .catch((err) => { + console.log(err); + }); + }; + + const activeIntegrationState = () => { + const currentElementIndex = integrationWorkflowData.findIndex( + (i) => i?.key === currentStep?.state + ); + + return currentElementIndex; + }; + + return ( +
+ +
+
+ +
+
Cancel import & go back
+
+ + +
+
+
+ jira logo +
+
+ {integrationWorkflowData.map((integration, index) => ( + + + {index < integrationWorkflowData.length - 1 && ( +
+ {" "} +
+ )} +
+ ))} +
+
+ +
+ +
+
+ {currentStep.state === "import-configure" && } + {currentStep.state === "display-import-data" && ( + + )} + {currentStep?.state === "import-users" && } + {currentStep?.state === "import-confirmation" && } +
+ +
+ {currentStep?.state !== "import-configure" && ( + { + const currentElementIndex = integrationWorkflowData.findIndex( + (i) => i?.key === currentStep?.state + ); + setCurrentStep({ + state: integrationWorkflowData[currentElementIndex - 1]?.key, + }); + }} + > + Back + + )} + { + const currentElementIndex = integrationWorkflowData.findIndex( + (i) => i?.key === currentStep?.state + ); + + if (currentElementIndex === integrationWorkflowData.length - 1) { + methods.handleSubmit(onSubmit)(); + } else { + setCurrentStep({ + state: integrationWorkflowData[currentElementIndex + 1]?.key, + }); + } + }} + > + {currentStep?.state === "import-confirmation" ? "Confirm & Import" : "Next"} + +
+
+
+
+
+
+ ); +}; diff --git a/apps/app/components/ui/custom-search-select.tsx b/apps/app/components/ui/custom-search-select.tsx index c8cea10be..fd7188db6 100644 --- a/apps/app/components/ui/custom-search-select.tsx +++ b/apps/app/components/ui/custom-search-select.tsx @@ -18,6 +18,7 @@ type CustomSearchSelectProps = { noChevron?: boolean; customButton?: JSX.Element; optionsClassName?: string; + input?: boolean; disabled?: boolean; selfPositioned?: boolean; multiple?: boolean; @@ -34,6 +35,7 @@ export const CustomSearchSelect = ({ noChevron = false, customButton, optionsClassName = "", + input = false, disabled = false, selfPositioned = false, multiple = false, @@ -68,7 +70,9 @@ export const CustomSearchSelect = ({ void; + label?: string; + disabled?: boolean; + className?: string; +}; + +export const ToggleSwitch: React.FC = (props) => { + const { value, onChange, label, disabled, className } = props; + + return ( + + {label} + + ); +}; diff --git a/apps/app/constants/fetch-keys.ts b/apps/app/constants/fetch-keys.ts index aff1d99f4..7090d1f18 100644 --- a/apps/app/constants/fetch-keys.ts +++ b/apps/app/constants/fetch-keys.ts @@ -1,3 +1,5 @@ +import { IJiraMetadata } from "types"; + const paramsToKey = (params: any) => { const { state, priority, assignees, created_by, labels } = params; @@ -122,6 +124,12 @@ export const APP_INTEGRATIONS = "APP_INTEGRATIONS"; export const WORKSPACE_INTEGRATIONS = (workspaceSlug: string) => `WORKSPACE_INTEGRATIONS_${workspaceSlug.toUpperCase()}`; +export const JIRA_IMPORTER_DETAIL = (workspaceSlug: string, params: IJiraMetadata) => { + const { api_token, cloud_hostname, email, project_key } = params; + + return `JIRA_IMPORTER_DETAIL_${workspaceSlug.toUpperCase()}_${api_token}_${cloud_hostname}_${email}_${project_key}`; +}; + //import-export export const IMPORTER_SERVICES_LIST = (workspaceSlug: string) => `IMPORTER_SERVICES_LIST_${workspaceSlug.toUpperCase()}`; @@ -153,6 +161,7 @@ export const PAGE_BLOCK_DETAILS = (pageId: string) => `PAGE_BLOCK_DETAILS_${page // estimates export const ESTIMATES_LIST = (projectId: string) => `ESTIMATES_LIST_${projectId.toUpperCase()}`; -export const ESTIMATE_DETAILS = (estimateId: string) => `ESTIMATE_DETAILS_${estimateId.toUpperCase()}`; +export const ESTIMATE_DETAILS = (estimateId: string) => + `ESTIMATE_DETAILS_${estimateId.toUpperCase()}`; export const ESTIMATE_POINTS_LIST = (estimateId: string) => `ESTIMATES_POINTS_LIST_${estimateId.toUpperCase()}`; diff --git a/apps/app/constants/workspace.ts b/apps/app/constants/workspace.ts index 2864855d3..24f886af9 100644 --- a/apps/app/constants/workspace.ts +++ b/apps/app/constants/workspace.ts @@ -75,11 +75,11 @@ export const IMPORTERS_EXPORTERS_LIST = [ description: "Import issues from GitHub repositories and sync them.", logo: GithubLogo, }, - // { - // provider: "jira", - // type: "import", - // title: "Jira", - // description: "Import issues and epics from Jira projects and epics.", - // logo: JiraLogo, - // }, + { + provider: "jira", + type: "import", + title: "Jira", + description: "Import issues and epics from Jira projects and epics.", + logo: JiraLogo, + }, ]; diff --git a/apps/app/hooks/use-workspace-members.tsx b/apps/app/hooks/use-workspace-members.tsx new file mode 100644 index 000000000..b41f4535d --- /dev/null +++ b/apps/app/hooks/use-workspace-members.tsx @@ -0,0 +1,43 @@ +import useSWR from "swr"; +// services +import workspaceService from "services/workspace.service"; +// fetch-keys +import { WORKSPACE_MEMBERS } from "constants/fetch-keys"; +// hooks +import useUser from "./use-user"; + +const useWorkspaceMembers = (workspaceSlug?: string) => { + const { user } = useUser(); + + const { data: workspaceMembers, error: workspaceMemberErrors } = useSWR( + workspaceSlug ? WORKSPACE_MEMBERS(workspaceSlug) : null, + workspaceSlug ? () => workspaceService.workspaceMembers(workspaceSlug) : null + ); + + const hasJoined = workspaceMembers?.some((item: any) => item.member.id === (user as any)?.id); + + const isOwner = workspaceMembers?.some( + (item) => item.member.id === (user as any)?.id && item.role === 20 + ); + const isMember = workspaceMembers?.some( + (item) => item.member.id === (user as any)?.id && item.role === 15 + ); + const isViewer = workspaceMembers?.some( + (item) => item.member.id === (user as any)?.id && item.role === 10 + ); + const isGuest = workspaceMembers?.some( + (item) => item.member.id === (user as any)?.id && item.role === 5 + ); + + return { + workspaceMembers, + workspaceMemberErrors, + hasJoined, + isOwner, + isMember, + isViewer, + isGuest, + }; +}; + +export default useWorkspaceMembers; diff --git a/apps/app/package.json b/apps/app/package.json index 8279ecb2d..5374c6363 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -56,6 +56,8 @@ "eslint-config-custom": "*", "eslint-config-next": "12.2.2", "postcss": "^8.4.14", + "prettier": "^2.8.7", + "prettier-plugin-tailwindcss": "^0.2.7", "tailwindcss": "^3.1.6", "tsconfig": "*", "typescript": "4.7.4" diff --git a/apps/app/services/integration/jira.service.ts b/apps/app/services/integration/jira.service.ts new file mode 100644 index 000000000..20ad8166a --- /dev/null +++ b/apps/app/services/integration/jira.service.ts @@ -0,0 +1,34 @@ +import APIService from "services/api.service"; + +// types +import { IJiraMetadata, IJiraResponse, IJiraImporterForm } from "types"; + +const { NEXT_PUBLIC_API_BASE_URL } = process.env; + +class JiraImportedService extends APIService { + constructor() { + super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + } + + async getJiraProjectInfo(workspaceSlug: string, params: IJiraMetadata): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/importers/jira`, { + params, + }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async createJiraImporter(workspaceSlug: string, data: IJiraImporterForm): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/projects/importers/jira/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} + +const jiraImporterService = new JiraImportedService(); + +export default jiraImporterService; diff --git a/apps/app/types/importer/index.ts b/apps/app/types/importer/index.ts index 1e3f8c2ad..134248a71 100644 --- a/apps/app/types/importer/index.ts +++ b/apps/app/types/importer/index.ts @@ -1,4 +1,5 @@ export * from "./github-importer"; +export * from "./jira-importer"; import { IProjectLite } from "types/projects"; // types diff --git a/apps/app/types/importer/jira-importer.d.ts b/apps/app/types/importer/jira-importer.d.ts new file mode 100644 index 000000000..cd9e31ad2 --- /dev/null +++ b/apps/app/types/importer/jira-importer.d.ts @@ -0,0 +1,58 @@ +export interface IJiraImporterForm { + metadata: IJiraMetadata; + config: IJiraConfig; + data: IJiraData; + project_id: string; +} + +export interface IJiraConfig { + epics_to_modules: boolean; +} + +export interface IJiraData { + users: User[]; + invite_users: boolean; + total_issues: number; + total_labels: number; + total_states: number; + total_modules: number; +} + +export interface User { + username: string; + import: "invite" | "map" | false; + email: string; +} + +export interface IJiraMetadata { + cloud_hostname: string; + api_token: string; + project_key: string; + email: string; +} + +export interface IJiraResponse { + issues: number; + modules: number; + labels: number; + states: number; + users: IJiraResponseUser[]; +} + +export interface IJiraResponseUser { + self: string; + accountId: string; + accountType: string; + emailAddress: string; + avatarUrls: AvatarUrls; + displayName: string; + active: boolean; + locale: string; +} + +export interface IJiraResponseAvatarUrls { + "48x48": string; + "24x24": string; + "16x16": string; + "32x32": string; +} diff --git a/yarn.lock b/yarn.lock index ae33bba9a..99ac4cf56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6485,6 +6485,16 @@ prelude-ls@^1.2.1: resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prettier-plugin-tailwindcss@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.2.7.tgz#2314d728cce9c9699ced41a01253eb48b4218da5" + integrity sha512-jQopIOgjLpX+y8HeD56XZw7onupRTC0cw7eKKUimI7vhjkPF5/1ltW5LyqaPtSyc8HvEpvNZsvvsGFa2qpa59w== + +prettier@^2.8.7: + version "2.8.7" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.7.tgz#bb79fc8729308549d28fe3a98fce73d2c0656450" + integrity sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw== + prettier@latest: version "2.8.4" resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz" From ac125965eb4bbe74a5c9b6757b0d631b3f9d7f43 Mon Sep 17 00:00:00 2001 From: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Date: Wed, 19 Apr 2023 15:40:47 +0530 Subject: [PATCH 019/120] fix: add tlds (#851) --- apps/app/components/rich-text-editor/index.tsx | 7 ++++++- apps/app/package.json | 1 + yarn.lock | 5 +++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/app/components/rich-text-editor/index.tsx b/apps/app/components/rich-text-editor/index.tsx index 32b4931f1..701a96562 100644 --- a/apps/app/components/rich-text-editor/index.tsx +++ b/apps/app/components/rich-text-editor/index.tsx @@ -29,6 +29,8 @@ import { OnChangeHTML, } from "@remirror/react"; import { TableExtension } from "@remirror/extension-react-tables"; +// tlds +import tlds from "tlds"; // services import fileService from "services/file.service"; // ui @@ -136,7 +138,10 @@ const RemirrorRichTextEditor: FC = (props) => { new CodeExtension(), new PlaceholderExtension({ placeholder: placeholder || "Enter text..." }), new HistoryExtension(), - new LinkExtension({ autoLink: true }), + new LinkExtension({ + autoLink: true, + autoLinkAllowedTLDs: tlds, + }), new ImageExtension({ enableResizing: true, uploadHandler: uploadImageHandler, diff --git a/apps/app/package.json b/apps/app/package.json index 5374c6363..823541cb2 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -38,6 +38,7 @@ "recharts": "^2.3.2", "remirror": "^2.0.23", "swr": "^1.3.0", + "tlds": "^1.238.0", "uuid": "^9.0.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 99ac4cf56..1e8bb41f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7763,6 +7763,11 @@ tinycolor2@^1.4.1: resolved "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz" integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw== +tlds@^1.238.0: + version "1.238.0" + resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.238.0.tgz#ffe7c19c8940c35b497cda187a6927f9450325a4" + integrity sha512-lFPF9pZFhLrPodaJ0wt9QIN0l8jOxqmUezGZnm7BfkDSVd9q667oVIJukLVzhF+4oW7uDlrLlfJrL5yu9RWwew== + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" From fb1932e3097ad290954a57d5e20966d7eaa067c9 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Wed, 19 Apr 2023 15:41:17 +0530 Subject: [PATCH 020/120] fix : create issue modal (#875) * fix: label list bug fix * fix: assignee and label count removed * fix: assignee and label fix --- apps/app/components/issues/select/assignee.tsx | 3 +-- apps/app/components/issues/select/label.tsx | 3 +-- apps/app/components/ui/labels-list.tsx | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/app/components/issues/select/assignee.tsx b/apps/app/components/issues/select/assignee.tsx index d47d4b178..1d06f3f0e 100644 --- a/apps/app/components/issues/select/assignee.tsx +++ b/apps/app/components/issues/select/assignee.tsx @@ -57,8 +57,7 @@ export const IssueAssigneeSelect: React.FC = ({ projectId, value = [], on
{value && value.length > 0 && Array.isArray(value) ? (
- - {value.length} Assignees +
) : (
diff --git a/apps/app/components/issues/select/label.tsx b/apps/app/components/issues/select/label.tsx index c05c8a8c8..2adeaa222 100644 --- a/apps/app/components/issues/select/label.tsx +++ b/apps/app/components/issues/select/label.tsx @@ -73,9 +73,8 @@ export const IssueLabelSelect: React.FC = ({ setIsOpen, value, onChange, issueLabels?.find((l) => l.id === v)?.color) ?? []} length={3} - showLength + showLength={true} /> - {value.length} Labels ) : ( diff --git a/apps/app/components/ui/labels-list.tsx b/apps/app/components/ui/labels-list.tsx index 2b296d624..ceb8d7e3a 100644 --- a/apps/app/components/ui/labels-list.tsx +++ b/apps/app/components/ui/labels-list.tsx @@ -14,7 +14,7 @@ export const IssueLabelsList: React.FC = ({ <> {labels && ( <> - {labels.map((color, index) => ( + {labels.slice(0, length).map((color, index) => (
Date: Wed, 19 Apr 2023 15:41:46 +0530 Subject: [PATCH 021/120] fix: invite button validation , style: workspace screen (#877) * fix: invite button validation * style: workspace screen tab height --- apps/app/components/onboarding/invite-members.tsx | 3 ++- apps/app/components/onboarding/workspace.tsx | 12 ++++++------ .../components/workspace/create-workspace-form.tsx | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/app/components/onboarding/invite-members.tsx b/apps/app/components/onboarding/invite-members.tsx index a731829b4..8c257ad9d 100644 --- a/apps/app/components/onboarding/invite-members.tsx +++ b/apps/app/components/onboarding/invite-members.tsx @@ -34,6 +34,7 @@ export const InviteMembers: React.FC = ({ setStep, workspace }) => { .catch((err) => console.log(err)); }; + const checkEmail = watch("emails") && watch("emails").length > 0 ; return (
= ({ setStep, workspace }) => { {isSubmitting ? "Inviting..." : "Continue"} diff --git a/apps/app/components/onboarding/workspace.tsx b/apps/app/components/onboarding/workspace.tsx index c4c5de4f7..7432fd7a7 100644 --- a/apps/app/components/onboarding/workspace.tsx +++ b/apps/app/components/onboarding/workspace.tsx @@ -65,10 +65,10 @@ export const Workspace: React.FC = ({ setStep, setWorkspace }) => { }; return ( -
+
= ({ setStep, setWorkspace }) => { Invited Workspace - + { @@ -108,9 +108,9 @@ export const Workspace: React.FC = ({ setStep, setWorkspace }) => { setDefaultValues={setDefaultValues} /> - -
-
+ +
+
{invitations && invitations.length > 0 ? ( invitations.map((invitation) => (
diff --git a/apps/app/components/workspace/create-workspace-form.tsx b/apps/app/components/workspace/create-workspace-form.tsx index b663c6fb8..10d9ed919 100644 --- a/apps/app/components/workspace/create-workspace-form.tsx +++ b/apps/app/components/workspace/create-workspace-form.tsx @@ -100,7 +100,7 @@ export const CreateWorkspaceForm: React.FC = ({ onSubmit={handleSubmit(handleCreateWorkspace)} >
-
+
Workspace name From c5206a7792c4994de46bfe1bee6580fb3326e372 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Wed, 19 Apr 2023 17:25:31 +0530 Subject: [PATCH 022/120] feat: separate filters for cycles and modules (#892) --- apps/app/contexts/issue-view.context.tsx | 152 ++++++++++++++++++++--- apps/app/services/cycles.service.ts | 2 +- apps/app/services/modules.service.ts | 6 +- apps/app/types/cycles.d.ts | 13 +- apps/app/types/modules.d.ts | 4 + 5 files changed, 156 insertions(+), 21 deletions(-) diff --git a/apps/app/contexts/issue-view.context.tsx b/apps/app/contexts/issue-view.context.tsx index 8205d9277..ff17007ab 100644 --- a/apps/app/contexts/issue-view.context.tsx +++ b/apps/app/contexts/issue-view.context.tsx @@ -8,6 +8,8 @@ import useSWR, { mutate } from "swr"; import ToastAlert from "components/toast-alert"; // services import projectService from "services/project.service"; +import cyclesService from "services/cycles.service"; +import modulesService from "services/modules.service"; import viewsService from "services/views.service"; // types import { @@ -18,7 +20,12 @@ import { TIssueOrderByOptions, } from "types"; // fetch-keys -import { USER_PROJECT_VIEW, VIEW_DETAILS } from "constants/fetch-keys"; +import { + CYCLE_DETAILS, + MODULE_DETAILS, + USER_PROJECT_VIEW, + VIEW_DETAILS, +} from "constants/fetch-keys"; export const issueViewContext = createContext({} as ContextType); @@ -171,7 +178,29 @@ const saveDataToServer = async (workspaceSlug: string, projectID: string, state: }); }; -const sendFilterDataToServer = async ( +const saveCycleFilters = async ( + workspaceSlug: string, + projectId: string, + cycleId: string, + state: any +) => { + await cyclesService.patchCycle(workspaceSlug, projectId, cycleId, { + ...state, + }); +}; + +const saveModuleFilters = async ( + workspaceSlug: string, + projectId: string, + moduleId: string, + state: any +) => { + await modulesService.patchModule(workspaceSlug, projectId, moduleId, { + ...state, + }); +}; + +const saveViewFilters = async ( workspaceSlug: string, projectId: string, viewId: string, @@ -206,7 +235,7 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = const [state, dispatch] = useReducer(reducer, initialState); const router = useRouter(); - const { workspaceSlug, projectId, viewId } = router.query; + const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; const { data: myViewProps, mutate: mutateMyViewProps } = useSWR( workspaceSlug && projectId ? USER_PROJECT_VIEW(projectId as string) : null, @@ -227,6 +256,30 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = : null ); + const { data: cycleDetails, mutate: mutateCycleDetails } = useSWR( + workspaceSlug && projectId && cycleId ? CYCLE_DETAILS(cycleId as string) : null, + workspaceSlug && projectId && cycleId + ? () => + cyclesService.getCycleDetails( + workspaceSlug.toString(), + projectId.toString(), + cycleId.toString() + ) + : null + ); + + const { data: moduleDetails, mutate: mutateModuleDetails } = useSWR( + workspaceSlug && projectId && moduleId ? MODULE_DETAILS(moduleId.toString()) : null, + workspaceSlug && projectId && moduleId + ? () => + modulesService.getModuleDetails( + workspaceSlug.toString(), + projectId.toString(), + moduleId.toString() + ) + : null + ); + const setIssueView = useCallback( (property: TIssueViewOptions) => { dispatch({ @@ -352,9 +405,8 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = const setFilters = useCallback( (property: Partial, saveToServer = true) => { Object.keys(property).forEach((key) => { - if (property[key as keyof typeof property]?.length === 0) { + if (property[key as keyof typeof property]?.length === 0) property[key as keyof typeof property] = null; - } }); dispatch({ @@ -369,22 +421,53 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = if (!workspaceSlug || !projectId) return; - mutateMyViewProps((prevData) => { - if (!prevData) return prevData; + if (cycleId) { + mutateCycleDetails((prevData: any) => { + if (!prevData) return prevData; - return { - ...prevData, + return { + ...prevData, + view_props: { + filters: { + ...state.filters, + ...property, + }, + }, + }; + }, false); + + saveCycleFilters(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), { view_props: { - ...state, filters: { ...state.filters, ...property, }, }, - }; - }, false); + }); + } else if (moduleId) { + mutateModuleDetails((prevData: any) => { + if (!prevData) return prevData; - if (viewId) { + return { + ...prevData, + view_props: { + filters: { + ...state.filters, + ...property, + }, + }, + }; + }, false); + + saveModuleFilters(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), { + view_props: { + filters: { + ...state.filters, + ...property, + }, + }, + }); + } else if (viewId) { mutateViewDetails((prevData: any) => { if (!prevData) return prevData; return { @@ -396,13 +479,28 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = }; }, false); if (saveToServer) - sendFilterDataToServer(workspaceSlug as string, projectId as string, viewId as string, { + saveViewFilters(workspaceSlug as string, projectId as string, viewId as string, { query_data: { ...state.filters, ...property, }, }); - } else if (saveToServer) + } else { + mutateMyViewProps((prevData) => { + if (!prevData) return prevData; + + return { + ...prevData, + view_props: { + ...state, + filters: { + ...state.filters, + ...property, + }, + }, + }; + }, false); + saveDataToServer(workspaceSlug as string, projectId as string, { ...state, filters: { @@ -410,8 +508,20 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = ...property, }, }); + } }, - [projectId, workspaceSlug, state, mutateMyViewProps, viewId, mutateViewDetails] + [ + projectId, + workspaceSlug, + state, + mutateMyViewProps, + cycleId, + mutateCycleDetails, + moduleId, + mutateModuleDetails, + viewId, + mutateViewDetails, + ] ); const setNewDefaultView = useCallback(() => { @@ -439,11 +549,17 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = payload: { ...myViewProps?.view_props, filters: { - ...(viewId ? viewDetails?.query_data : myViewProps?.view_props?.filters), + ...(cycleId + ? cycleDetails?.view_props.filters + : moduleId + ? moduleDetails?.view_props.filters + : viewId + ? viewDetails?.query_data + : myViewProps?.view_props?.filters), } as any, }, }); - }, [myViewProps, viewDetails, viewId]); + }, [myViewProps, cycleId, cycleDetails, moduleId, moduleDetails, viewId, viewDetails]); return ( ): Promise { return this.patch( `/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/`, diff --git a/apps/app/services/modules.service.ts b/apps/app/services/modules.service.ts index 5b7e2623a..89227982c 100644 --- a/apps/app/services/modules.service.ts +++ b/apps/app/services/modules.service.ts @@ -53,7 +53,11 @@ class ProjectIssuesServices extends APIService { }); } - async getModuleDetails(workspaceSlug: string, projectId: string, moduleId: string): Promise { + async getModuleDetails( + workspaceSlug: string, + projectId: string, + moduleId: string + ): Promise { return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`) .then((response) => response?.data) .catch((error) => { diff --git a/apps/app/types/cycles.d.ts b/apps/app/types/cycles.d.ts index d39f0b140..760ba03d5 100644 --- a/apps/app/types/cycles.d.ts +++ b/apps/app/types/cycles.d.ts @@ -1,4 +1,12 @@ -import type { IUser, IIssue, IProject, IProjectLite, IWorkspace, IWorkspaceLite } from "types"; +import type { + IUser, + IIssue, + IProject, + IProjectLite, + IWorkspace, + IWorkspaceLite, + IIssueFilterOptions, +} from "types"; export interface ICycle { backlog_issues: number; @@ -21,6 +29,9 @@ export interface ICycle { unstarted_issues: number; updated_at: Date; updated_by: string; + view_props: { + filters: IIssueFilterOptions; + }; workspace: string; workspace_detail: IWorkspaceLite; } diff --git a/apps/app/types/modules.d.ts b/apps/app/types/modules.d.ts index 92f2ae199..3e2a326c4 100644 --- a/apps/app/types/modules.d.ts +++ b/apps/app/types/modules.d.ts @@ -6,6 +6,7 @@ import type { IWorkspace, IWorkspaceLite, IProjectLite, + IIssueFilterOptions, } from "types"; export interface IModule { @@ -45,6 +46,9 @@ export interface IModule { unstarted_issues: number; updated_at: Date; updated_by: string; + view_props: { + filters: IIssueFilterOptions; + }; workspace: string; workspace_detail: IWorkspaceLite; } From ebce364104c3f36925fb8d0ddf6e944037514881 Mon Sep 17 00:00:00 2001 From: Rhea Jain <65884341+rhea0110@users.noreply.github.com> Date: Wed, 19 Apr 2023 20:52:52 +0530 Subject: [PATCH 023/120] chore: changed loading text (#898) --- apps/app/components/onboarding/invite-members.tsx | 2 +- apps/app/layouts/auth-layout/project-authorization-wrapper.tsx | 2 +- .../app/layouts/auth-layout/workspace-authorization-wrapper.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/app/components/onboarding/invite-members.tsx b/apps/app/components/onboarding/invite-members.tsx index 8c257ad9d..e075caf73 100644 --- a/apps/app/components/onboarding/invite-members.tsx +++ b/apps/app/components/onboarding/invite-members.tsx @@ -51,7 +51,7 @@ export const InviteMembers: React.FC = ({ setStep, workspace }) => {
= ({ {loading ? (
-

Setting up your project...

+

Loading your project...

diff --git a/apps/app/layouts/auth-layout/workspace-authorization-wrapper.tsx b/apps/app/layouts/auth-layout/workspace-authorization-wrapper.tsx index 8803653e5..8f9ec7f8b 100644 --- a/apps/app/layouts/auth-layout/workspace-authorization-wrapper.tsx +++ b/apps/app/layouts/auth-layout/workspace-authorization-wrapper.tsx @@ -72,7 +72,7 @@ export const WorkspaceAuthorizationLayout: React.FC = ({ return (
-

Setting up your workspace...

+

Loading your workspace...

From d30a88832af99d51ff601cdce187e99d4960dab3 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Thu, 20 Apr 2023 12:09:35 +0530 Subject: [PATCH 024/120] fix: reset estimates modal form after create/update (#893) --- .../estimates/create-update-estimate-modal.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/app/components/estimates/create-update-estimate-modal.tsx b/apps/app/components/estimates/create-update-estimate-modal.tsx index a8a191eca..92c32a1c2 100644 --- a/apps/app/components/estimates/create-update-estimate-modal.tsx +++ b/apps/app/components/estimates/create-update-estimate-modal.tsx @@ -41,6 +41,11 @@ export const CreateUpdateEstimateModal: React.FC = ({ handleClose, data, defaultValues, }); + const onClose = () => { + handleClose(); + reset(); + }; + const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -62,7 +67,6 @@ export const CreateUpdateEstimateModal: React.FC = ({ handleClose, data, (prevData) => [res, ...(prevData ?? [])], false ); - handleClose(); }) .catch(() => { setToastAlert({ @@ -71,6 +75,8 @@ export const CreateUpdateEstimateModal: React.FC = ({ handleClose, data, message: "Error: Estimate could not be created", }); }); + + onClose(); }; const updateEstimate = async (formData: IEstimate) => { @@ -102,7 +108,8 @@ export const CreateUpdateEstimateModal: React.FC = ({ handleClose, data, message: "Error: Estimate could not be updated", }); }); - handleClose(); + + onClose(); }; useEffect(() => { From 7149d206015a2fcbce3b6421cbd0a52c5e65415b Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Thu, 20 Apr 2023 12:09:48 +0530 Subject: [PATCH 025/120] chore: change pages icon (#894) --- .../components/pages/single-page-detailed-item.tsx | 4 ++-- apps/app/components/pages/single-page-list-item.tsx | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/app/components/pages/single-page-detailed-item.tsx b/apps/app/components/pages/single-page-detailed-item.tsx index 0c47e4317..c7ebec25d 100644 --- a/apps/app/components/pages/single-page-detailed-item.tsx +++ b/apps/app/components/pages/single-page-detailed-item.tsx @@ -49,10 +49,10 @@ export const SinglePageDetailedItem: React.FC = ({ return (
- +
-

{truncateText(page.name, 75)}

+

{truncateText(page.name, 75)}

{page.label_details.length > 0 && page.label_details.map((label) => (
= ({
-
- -

+

+ +

{truncateText(page.name, 75)}

{page.label_details.length > 0 && From 4f61c5d552b3f140ddfa8c287139ba79be2044d6 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Thu, 20 Apr 2023 12:09:55 +0530 Subject: [PATCH 026/120] fix: pages access mutation (#896) --- apps/app/components/pages/pages-view.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/app/components/pages/pages-view.tsx b/apps/app/components/pages/pages-view.tsx index c6d600307..7e923b0fe 100644 --- a/apps/app/components/pages/pages-view.tsx +++ b/apps/app/components/pages/pages-view.tsx @@ -166,17 +166,17 @@ export const PagesView: React.FC = ({ pages, viewType }) => { mutate( ALL_PAGES_LIST(projectId as string), - (prevData) => (prevData ?? []).map((p) => ({ ...p, ...formData })), + (prevData) => (prevData ?? []).map((p) => ({ ...p, ...(p.id === page.id ? formData : {}) })), false ); mutate( MY_PAGES_LIST(projectId as string), - (prevData) => (prevData ?? []).map((p) => ({ ...p, ...formData })), + (prevData) => (prevData ?? []).map((p) => ({ ...p, ...(p.id === page.id ? formData : {}) })), false ); mutate( FAVORITE_PAGES_LIST(projectId as string), - (prevData) => (prevData ?? []).map((p) => ({ ...p, ...formData })), + (prevData) => (prevData ?? []).map((p) => ({ ...p, ...(p.id === page.id ? formData : {}) })), false ); @@ -217,7 +217,7 @@ export const PagesView: React.FC = ({ pages, viewType }) => { ))} ) : viewType === "detailed" ? ( -
+
{pages.map((page) => ( Date: Thu, 20 Apr 2023 12:11:33 +0530 Subject: [PATCH 027/120] feat: cycle list tab context (#900) --- .../projects/[projectId]/cycles/index.tsx | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx index bfc8ef002..cd53e80b6 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx @@ -7,6 +7,8 @@ import useSWR from "swr"; // headless ui import { Tab } from "@headlessui/react"; +// hooks +import useLocalStorage from "hooks/use-local-storage"; // services import cycleService from "services/cycles.service"; import projectService from "services/project.service"; @@ -45,6 +47,8 @@ const ProjectCycles: NextPage = () => { const [selectedCycle, setSelectedCycle] = useState(); const [createUpdateCycleModal, setCreateUpdateCycleModal] = useState(false); + const { storedValue: cycleTab, setValue: setCycleTab } = useLocalStorage("cycleTab", "Upcoming"); + const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -77,6 +81,20 @@ const ProjectCycles: NextPage = () => { }, 500); }, [createUpdateCycleModal]); + const currentTabValue = (tab: string | null) => { + switch (tab) { + case "Upcoming": + return 0; + case "Completed": + return 1; + case "Drafts": + return 2; + + default: + return 0; + } + }; + return ( { document.dispatchEvent(e); }} > - + Add Cycle } @@ -123,7 +141,22 @@ const ProjectCycles: NextPage = () => {

Other Cycles

- + { + switch (i) { + case 0: + return setCycleTab("Upcoming"); + case 1: + return setCycleTab("Completed"); + case 2: + return setCycleTab("Drafts"); + + default: + return setCycleTab("Upcoming"); + } + }} + > Date: Thu, 20 Apr 2023 12:16:13 +0530 Subject: [PATCH 028/120] chore: upload button and github banner text update (#899) * chore: upload button and github banner text update * chore: attachments button text fix --- apps/app/components/issues/attachment-upload.tsx | 4 ++-- apps/app/pages/[workspaceSlug]/index.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/app/components/issues/attachment-upload.tsx b/apps/app/components/issues/attachment-upload.tsx index 6819807ba..2c6f384ad 100644 --- a/apps/app/components/issues/attachment-upload.tsx +++ b/apps/app/components/issues/attachment-upload.tsx @@ -96,9 +96,9 @@ export const IssueAttachmentUpload = () => { ) : fileError ? (

{fileError}

) : isLoading ? ( -

Uploading....

+

Uploading...

) : ( -

Drag and drop/Click to add

+

Drag and drop here

)}
diff --git a/apps/app/pages/[workspaceSlug]/index.tsx b/apps/app/pages/[workspaceSlug]/index.tsx index 24a6d6a20..0546985f5 100644 --- a/apps/app/pages/[workspaceSlug]/index.tsx +++ b/apps/app/pages/[workspaceSlug]/index.tsx @@ -46,7 +46,7 @@ const WorkspacePage: NextPage = () => { // style={{ background: "linear-gradient(90deg, #8e2de2 0%, #4a00e0 100%)" }} >

- Plane is open source, support us by staring us on GitHub. + Plane is open source, support us by starring us on GitHub.

{/* From 3c2f5d12ed3548d63336c5f05462f0d1cc3c1648 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Thu, 20 Apr 2023 13:41:24 +0530 Subject: [PATCH 029/120] feat: themes (#902) * chore: add next theme and initial setup * chore: add dark mode colors to layouts * chore: user general setting page theming * chore: dashboard theming * chore: project page theming * chore: workspace setting page theming * chore: my issue page theming * chore: cmdk theming * chore: change hardcode bg and text color to theme * chore: change color name according to the design * style: fix card in the dashboard * style: fix merge conflict design issues * style: add light high contrast and dark high contrast * style: fix cmd k menu color and selection * feat: change theme from cmdk menu * chore: add multiple theme field to custom theme * chore: removed custom theming * fix: build error --------- Co-authored-by: Saheb Giri --- .../components/account/email-code-form.tsx | 2 +- .../account/email-password-form.tsx | 2 +- .../account/github-login-button.tsx | 2 +- .../auth-screens/not-authorized-view.tsx | 8 +- apps/app/components/breadcrumbs/index.tsx | 4 +- .../change-interface-theme.tsx | 45 +++ .../command-palette/command-pallette.tsx | 121 ++++---- apps/app/components/command-palette/index.ts | 1 + .../command-palette/shortcuts-modal.tsx | 30 +- .../components/core/board-view/all-boards.tsx | 4 +- .../core/board-view/board-header.tsx | 8 +- .../core/board-view/single-board.tsx | 12 +- .../core/board-view/single-issue.tsx | 10 +- .../core/bulk-delete-issues-modal.tsx | 16 +- .../core/calendar-view/calendar.tsx | 22 +- .../app/components/core/custom-theme-form.tsx | 267 ++++++++++++++++++ .../components/core/custom-theme-modal.tsx | 65 +++++ .../core/existing-issues-list-modal.tsx | 16 +- apps/app/components/core/feeds.tsx | 30 +- apps/app/components/core/filter-list.tsx | 8 +- .../components/core/gpt-assistant-modal.tsx | 2 +- .../components/core/image-picker-popover.tsx | 10 +- .../components/core/image-upload-modal.tsx | 12 +- apps/app/components/core/index.ts | 1 + .../components/core/issues-view-filter.tsx | 45 ++- apps/app/components/core/issues-view.tsx | 4 +- apps/app/components/core/link-modal.tsx | 6 +- .../core/list-view/single-issue.tsx | 2 +- .../components/core/list-view/single-list.tsx | 16 +- .../components/core/sidebar/links-list.tsx | 8 +- .../core/sidebar/sidebar-progress-stats.tsx | 16 +- .../core/sidebar/single-progress-stats.tsx | 4 +- apps/app/components/core/theme-switch.tsx | 60 ++++ .../cycles/completed-cycles-list.tsx | 2 +- apps/app/components/cycles/cycles-list.tsx | 4 +- .../components/cycles/delete-cycle-modal.tsx | 10 +- apps/app/components/cycles/empty-cycle.tsx | 22 +- apps/app/components/cycles/form.tsx | 4 +- apps/app/components/cycles/modal.tsx | 4 +- apps/app/components/cycles/select.tsx | 14 +- apps/app/components/cycles/sidebar.tsx | 42 +-- .../components/cycles/single-cycle-card.tsx | 8 +- .../cycles/transfer-issues-modal.tsx | 14 +- .../app/components/cycles/transfer-issues.tsx | 2 +- .../components/emoji-icon-picker/index.tsx | 12 +- .../integration/single-integration-card.tsx | 2 +- apps/app/components/issues/activity.tsx | 26 +- .../components/issues/comment/add-comment.tsx | 14 +- .../issues/comment/comment-card.tsx | 6 +- .../components/issues/delete-issue-modal.tsx | 6 +- .../components/issues/description-form.tsx | 4 +- apps/app/components/issues/form.tsx | 18 +- apps/app/components/issues/modal.tsx | 4 +- .../components/issues/my-issues-list-item.tsx | 8 +- .../issues/parent-issues-list-modal.tsx | 30 +- .../app/components/issues/select/assignee.tsx | 6 +- apps/app/components/issues/select/date.tsx | 6 +- apps/app/components/issues/select/label.tsx | 32 +-- .../app/components/issues/select/priority.tsx | 4 +- apps/app/components/issues/select/project.tsx | 2 +- apps/app/components/issues/select/state.tsx | 4 +- .../issues/sidebar-select/assignee.tsx | 4 +- .../issues/sidebar-select/blocked.tsx | 22 +- .../issues/sidebar-select/blocker.tsx | 22 +- .../issues/sidebar-select/cycle.tsx | 2 +- .../issues/sidebar-select/module.tsx | 2 +- .../issues/sidebar-select/parent.tsx | 4 +- .../issues/sidebar-select/priority.tsx | 2 +- .../issues/sidebar-select/state.tsx | 2 +- apps/app/components/issues/sidebar.tsx | 38 +-- .../app/components/issues/sub-issues-list.tsx | 8 +- .../issues/view-select/assignee.tsx | 8 +- .../components/issues/view-select/state.tsx | 2 +- .../components/labels/create-label-modal.tsx | 12 +- .../labels/create-update-label-inline.tsx | 8 +- .../components/labels/labels-list-modal.tsx | 14 +- .../components/labels/single-label-group.tsx | 10 +- apps/app/components/labels/single-label.tsx | 4 +- .../modules/delete-module-modal.tsx | 10 +- apps/app/components/modules/form.tsx | 4 +- apps/app/components/modules/modal.tsx | 4 +- .../components/modules/select/select-lead.tsx | 4 +- .../modules/select/select-members.tsx | 8 +- .../modules/sidebar-select/select-lead.tsx | 4 +- .../modules/sidebar-select/select-members.tsx | 4 +- .../modules/sidebar-select/select-status.tsx | 2 +- apps/app/components/modules/sidebar.tsx | 46 +-- .../components/modules/single-module-card.tsx | 16 +- .../components/onboarding/invite-members.tsx | 2 +- .../components/onboarding/user-details.tsx | 4 +- apps/app/components/onboarding/workspace.tsx | 20 +- .../pages/create-update-page-modal.tsx | 4 +- .../components/pages/delete-page-modal.tsx | 10 +- apps/app/components/pages/page-form.tsx | 2 +- apps/app/components/pages/pages-view.tsx | 4 +- .../pages/single-page-detailed-item.tsx | 2 +- .../pages/single-page-list-item.tsx | 4 +- .../project/confirm-project-member-remove.tsx | 10 +- .../project/create-project-modal.tsx | 4 +- .../project/delete-project-modal.tsx | 6 +- .../components/project/join-project-modal.tsx | 6 +- .../project/send-project-invitation-modal.tsx | 10 +- .../project/settings/single-label.tsx | 10 +- apps/app/components/project/sidebar-list.tsx | 4 +- .../project/single-integration-card.tsx | 2 +- .../project/single-project-card.tsx | 4 +- .../project/single-sidebar-project.tsx | 10 +- .../app/components/rich-text-editor/index.tsx | 12 +- .../toolbar/float-tool-tip.tsx | 8 +- .../rich-text-editor/toolbar/link.tsx | 4 +- .../toolbar/table-controls.tsx | 4 +- apps/app/components/search-listbox/index.tsx | 12 +- .../components/states/create-state-modal.tsx | 12 +- .../states/create-update-state-inline.tsx | 2 +- .../components/states/delete-state-modal.tsx | 13 +- apps/app/components/states/single-state.tsx | 10 +- apps/app/components/ui/avatar.tsx | 4 +- .../components/ui/buttons/primary-button.tsx | 6 +- .../ui/buttons/secondary-button.tsx | 8 +- apps/app/components/ui/context-menu.tsx | 6 +- apps/app/components/ui/custom-menu.tsx | 16 +- .../components/ui/custom-search-select.tsx | 22 +- apps/app/components/ui/custom-select.tsx | 12 +- apps/app/components/ui/date.tsx | 10 +- apps/app/components/ui/datepicker.tsx | 6 +- apps/app/components/ui/empty-space.tsx | 16 +- apps/app/components/ui/empty-state.tsx | 6 +- apps/app/components/ui/input/index.tsx | 6 +- apps/app/components/ui/labels-list.tsx | 2 +- apps/app/components/ui/loader.tsx | 2 +- apps/app/components/ui/multi-input.tsx | 4 +- .../components/ui/multi-level-dropdown.tsx | 14 +- apps/app/components/ui/multi-level-select.tsx | 12 +- apps/app/components/ui/outline-button.tsx | 4 +- apps/app/components/ui/text-area/index.tsx | 4 +- apps/app/components/ui/tooltip.tsx | 2 +- .../components/views/delete-view-modal.tsx | 17 +- apps/app/components/views/form.tsx | 2 +- apps/app/components/views/modal.tsx | 4 +- .../components/workspace/activity-graph.tsx | 14 +- .../workspace/completed-issues-graph.tsx | 6 +- .../confirm-workspace-member-remove.tsx | 10 +- .../workspace/create-workspace-form.tsx | 8 +- .../workspace/delete-workspace-modal.tsx | 6 +- .../app/components/workspace/help-section.tsx | 28 +- apps/app/components/workspace/issues-list.tsx | 10 +- .../components/workspace/issues-pie-chart.tsx | 2 +- .../app/components/workspace/issues-stats.tsx | 8 +- .../send-workspace-invitation-modal.tsx | 8 +- .../components/workspace/sidebar-dropdown.tsx | 24 +- .../app/components/workspace/sidebar-menu.tsx | 8 +- .../workspace/single-invitation.tsx | 6 +- apps/app/constants/themes.ts | 35 +++ apps/app/helpers/theme.helper.ts | 35 +++ apps/app/layouts/app-layout/app-header.tsx | 4 +- apps/app/layouts/app-layout/app-sidebar.tsx | 4 +- apps/app/layouts/app-layout/index.tsx | 12 +- .../project-authorization-wrapper.tsx | 2 +- .../workspace-authorization-wrapper.tsx | 6 +- apps/app/layouts/default-layout/index.tsx | 2 +- apps/app/layouts/settings-navbar.tsx | 6 +- apps/app/package.json | 1 + apps/app/pages/404.tsx | 2 +- apps/app/pages/[workspaceSlug]/index.tsx | 6 +- .../pages/[workspaceSlug]/me/my-issues.tsx | 14 +- .../[workspaceSlug]/me/profile/index.tsx | 31 +- .../projects/[projectId]/cycles/[cycleId].tsx | 2 +- .../projects/[projectId]/cycles/index.tsx | 16 +- .../projects/[projectId]/issues/[issueId].tsx | 12 +- .../[projectId]/modules/[moduleId].tsx | 2 +- .../projects/[projectId]/modules/index.tsx | 2 +- .../projects/[projectId]/pages/[pageId].tsx | 12 +- .../projects/[projectId]/pages/index.tsx | 16 +- .../projects/[projectId]/settings/control.tsx | 4 +- .../[projectId]/settings/features.tsx | 32 +-- .../projects/[projectId]/settings/index.tsx | 12 +- .../projects/[projectId]/settings/labels.tsx | 2 +- .../projects/[projectId]/settings/members.tsx | 6 +- .../projects/[projectId]/settings/states.tsx | 8 +- .../projects/[projectId]/views/index.tsx | 8 +- .../pages/[workspaceSlug]/settings/index.tsx | 10 +- .../[workspaceSlug]/settings/members.tsx | 8 +- apps/app/pages/_app.tsx | 23 +- apps/app/pages/_error.js | 6 +- .../pages/installations/[provider]/index.tsx | 4 +- apps/app/pages/signin.tsx | 6 +- .../[invitationId].tsx | 2 +- apps/app/styles/command-pallette.css | 8 +- apps/app/styles/globals.css | 79 +++++- apps/app/tailwind.config.js | 32 +++ yarn.lock | 5 + 191 files changed, 1550 insertions(+), 880 deletions(-) create mode 100644 apps/app/components/command-palette/change-interface-theme.tsx create mode 100644 apps/app/components/core/custom-theme-form.tsx create mode 100644 apps/app/components/core/custom-theme-modal.tsx create mode 100644 apps/app/components/core/theme-switch.tsx create mode 100644 apps/app/constants/themes.ts create mode 100644 apps/app/helpers/theme.helper.ts diff --git a/apps/app/components/account/email-code-form.tsx b/apps/app/components/account/email-code-form.tsx index 5e4c49b1a..389153d60 100644 --- a/apps/app/components/account/email-code-form.tsx +++ b/apps/app/components/account/email-code-form.tsx @@ -141,7 +141,7 @@ export const EmailCodeForm = ({ onSuccess }: any) => { diff --git a/apps/app/components/auth-screens/not-authorized-view.tsx b/apps/app/components/auth-screens/not-authorized-view.tsx index 37c07e8de..054e5bfa3 100644 --- a/apps/app/components/auth-screens/not-authorized-view.tsx +++ b/apps/app/components/auth-screens/not-authorized-view.tsx @@ -36,16 +36,16 @@ export const NotAuthorizedView: React.FC = ({ actionButton, type }) => { alt="ProjectSettingImg" />
-

+

Oops! You are not authorized to view this page

-
+
{user ? (

You have signed in as {user.email}.
- Sign in + Sign in {" "} with different account that has access to this page.

@@ -53,7 +53,7 @@ export const NotAuthorizedView: React.FC = ({ actionButton, type }) => {

You need to{" "} - Sign in + Sign in {" "} with an account that has access to this page.

diff --git a/apps/app/components/breadcrumbs/index.tsx b/apps/app/components/breadcrumbs/index.tsx index 0a2d36e1f..98944fe37 100644 --- a/apps/app/components/breadcrumbs/index.tsx +++ b/apps/app/components/breadcrumbs/index.tsx @@ -16,7 +16,7 @@ const Breadcrumbs = ({ children }: BreadcrumbsProps) => {
-
- +
+ = ({ isOpen, setIsOpen }) => {
-

{shortcut.description}

+

{shortcut.description}

{shortcut.keys.split(",").map((key, index) => ( {key === "Ctrl" ? ( - + ) : ( - + {key === "Ctrl" ? : key} )} @@ -145,7 +145,7 @@ export const ShortcutsModal: React.FC = ({ isOpen, setIsOpen }) => { )) ) : (
-

+

No shortcuts found for{" "} {`"`} @@ -162,16 +162,16 @@ export const ShortcutsModal: React.FC = ({ isOpen, setIsOpen }) => {

{shortcuts.map(({ keys, description }, index) => (
-

{description}

+

{description}

{keys.split(",").map((key, index) => ( {key === "Ctrl" ? ( - + ) : ( - + {key === "Ctrl" ? : key} )} diff --git a/apps/app/components/core/board-view/all-boards.tsx b/apps/app/components/core/board-view/all-boards.tsx index f476b76a5..19d0e7636 100644 --- a/apps/app/components/core/board-view/all-boards.tsx +++ b/apps/app/components/core/board-view/all-boards.tsx @@ -81,7 +81,7 @@ export const AllBoards: React.FC = ({ return (
{currentState && @@ -92,7 +92,7 @@ export const AllBoards: React.FC = ({ : addSpaceIfCamelCase(singleGroup)}
- 0 + 0
); })} diff --git a/apps/app/components/core/board-view/board-header.tsx b/apps/app/components/core/board-view/board-header.tsx index f209ba6e2..93443d99c 100644 --- a/apps/app/components/core/board-view/board-header.tsx +++ b/apps/app/components/core/board-view/board-header.tsx @@ -124,7 +124,7 @@ export const BoardHeader: React.FC = ({ return (
@@ -145,7 +145,7 @@ export const BoardHeader: React.FC = ({ {groupedByIssues?.[groupTitle].length ?? 0} @@ -155,7 +155,7 @@ export const BoardHeader: React.FC = ({
))}
-
+
{monthOptions.map((month) => (
-
+

Show weekends

+
+
+
+
+ Cancel + + {status + ? isSubmitting + ? "Updating Theme..." + : "Update Theme" + : isSubmitting + ? "Creating Theme..." + : "Set Theme"} + +
+ + ); +}; diff --git a/apps/app/components/core/custom-theme-modal.tsx b/apps/app/components/core/custom-theme-modal.tsx new file mode 100644 index 000000000..d46d17d28 --- /dev/null +++ b/apps/app/components/core/custom-theme-modal.tsx @@ -0,0 +1,65 @@ +import React from "react"; + +// headless ui +import { Dialog, Transition } from "@headlessui/react"; +// components +import { ThemeForm } from "./custom-theme-form"; +// helpers +import { applyTheme } from "helpers/theme.helper"; +// fetch-keys + +type Props = { + isOpen: boolean; + handleClose: () => void; +}; + +export const CustomThemeModal: React.FC = ({ isOpen, handleClose }) => { + const onClose = () => { + handleClose(); + }; + + const handleFormSubmit = async (formData: any) => { + applyTheme(formData.palette, formData.darkPalette); + onClose(); + }; + + return ( + + + +
+ + +
+
+ + + + + +
+
+
+
+ ); +}; diff --git a/apps/app/components/core/existing-issues-list-modal.tsx b/apps/app/components/core/existing-issues-list-modal.tsx index 2e255ae19..2d5ac37dc 100644 --- a/apps/app/components/core/existing-issues-list-modal.tsx +++ b/apps/app/components/core/existing-issues-list-modal.tsx @@ -130,7 +130,7 @@ export const ExistingIssuesListModal: React.FC = ({ leaveFrom="opacity-100 scale-100" leaveTo="opacity-0 scale-95" > - +
= ({
)} - +
-

+

Commented {timeAgo(activity.created_at)}

@@ -244,7 +244,7 @@ export const Feeds: React.FC = ({ activities }) => ( editable={false} onBlur={() => ({})} noBorder - customClassName="text-xs bg-gray-100" + customClassName="text-xs bg-brand-surface-1" />
@@ -259,7 +259,7 @@ export const Feeds: React.FC = ({ activities }) => (
{activities.length > 1 && activityIdx !== activities.length - 1 ? (
-
+
{activity.field ? ( activityDetails[activity.field as keyof typeof activityDetails]?.icon ) : activity.actor_detail.avatar && @@ -292,7 +292,7 @@ export const Feeds: React.FC = ({ activities }) => (
-
+
{activity.actor_detail.first_name} {activity.actor_detail.is_bot @@ -300,7 +300,7 @@ export const Feeds: React.FC = ({ activities }) => ( : " " + activity.actor_detail.last_name} {action} - {value} + {value} {timeAgo(activity.created_at)}
diff --git a/apps/app/components/core/filter-list.tsx b/apps/app/components/core/filter-list.tsx index 08d1e8062..c42b7501b 100644 --- a/apps/app/components/core/filter-list.tsx +++ b/apps/app/components/core/filter-list.tsx @@ -57,9 +57,9 @@ export const FilterList: React.FC = ({ filters, setFilters }) => { return (
- + {replaceUnderscoreIfSnakeCase(key)}: {filters[key as keyof IIssueFilterOptions] === null || @@ -131,7 +131,7 @@ export const FilterList: React.FC = ({ filters, setFilters }) => { ? "bg-yellow-100 text-yellow-500 hover:bg-yellow-100" : priority === "low" ? "bg-green-100 text-green-500 hover:bg-green-100" - : "bg-gray-100 text-gray-700 hover:bg-gray-100" + : "bg-brand-surface-1 text-gray-700 hover:bg-brand-surface-1" }`} > {getPriorityIcon(priority)} @@ -339,7 +339,7 @@ export const FilterList: React.FC = ({ filters, setFilters }) => { created_by: null, }) } - className="flex items-center gap-x-1 rounded-full border bg-white px-3 py-1.5 text-xs" + className="flex items-center gap-x-1 rounded-full border border-brand-base bg-brand-surface-2 px-3 py-1.5 text-xs" > Clear all filters diff --git a/apps/app/components/core/gpt-assistant-modal.tsx b/apps/app/components/core/gpt-assistant-modal.tsx index b50b1e0f8..37104e30f 100644 --- a/apps/app/components/core/gpt-assistant-modal.tsx +++ b/apps/app/components/core/gpt-assistant-modal.tsx @@ -121,7 +121,7 @@ export const GptAssistantModal: React.FC = ({ return (
diff --git a/apps/app/components/core/image-picker-popover.tsx b/apps/app/components/core/image-picker-popover.tsx index a6f2efb72..9a3d5ff2f 100644 --- a/apps/app/components/core/image-picker-popover.tsx +++ b/apps/app/components/core/image-picker-popover.tsx @@ -65,7 +65,7 @@ export const ImagePickerPopover: React.FC = ({ label, value, onChange }) return ( setIsOpen((prev) => !prev)} > {label} @@ -79,16 +79,16 @@ export const ImagePickerPopover: React.FC = ({ label, value, onChange }) leaveFrom="transform opacity-100 scale-100" leaveTo="transform opacity-0 scale-95" > - -
+ +
- + {tabOptions.map((tab) => ( `rounded py-1 px-4 text-center text-sm outline-none transition-colors ${ - selected ? "bg-theme text-white" : "text-black" + selected ? "bg-brand-accent text-white" : "text-brand-base" }` } > diff --git a/apps/app/components/core/image-upload-modal.tsx b/apps/app/components/core/image-upload-modal.tsx index 28ff260d4..ba41f3efa 100644 --- a/apps/app/components/core/image-upload-modal.tsx +++ b/apps/app/components/core/image-upload-modal.tsx @@ -110,7 +110,7 @@ export const ImageUploadModal: React.FC = ({ leaveFrom="opacity-100" leaveTo="opacity-0" > -
+
@@ -124,9 +124,9 @@ export const ImageUploadModal: React.FC = ({ leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - +
- + Upload Image
@@ -135,7 +135,7 @@ export const ImageUploadModal: React.FC = ({ {...getRootProps()} className={`relative block h-80 w-full rounded-lg p-12 text-center focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 ${ (image === null && isDragActive) || !value - ? "border-2 border-dashed border-gray-300 hover:border-gray-400" + ? "border-2 border-dashed border-brand-base hover:border-gray-400" : "" }`} > @@ -143,7 +143,7 @@ export const ImageUploadModal: React.FC = ({ <> @@ -157,7 +157,7 @@ export const ImageUploadModal: React.FC = ({ ) : ( <> - + {isDragActive ? "Drop image here to upload" : "Drag & drop image here"} diff --git a/apps/app/components/core/index.ts b/apps/app/components/core/index.ts index 41a71d965..eb547578c 100644 --- a/apps/app/components/core/index.ts +++ b/apps/app/components/core/index.ts @@ -11,3 +11,4 @@ export * from "./link-modal"; export * from "./image-picker-popover"; export * from "./filter-list"; export * from "./feeds"; +export * from "./theme-switch"; diff --git a/apps/app/components/core/issues-view-filter.tsx b/apps/app/components/core/issues-view-filter.tsx index 4b4d15e09..0df8e8f1b 100644 --- a/apps/app/components/core/issues-view-filter.tsx +++ b/apps/app/components/core/issues-view-filter.tsx @@ -12,8 +12,7 @@ import { SelectFilters } from "components/views"; // ui import { CustomMenu } from "components/ui"; // icons -import { ChevronDownIcon, ListBulletIcon, CalendarDaysIcon } from "@heroicons/react/24/outline"; -import { Squares2X2Icon } from "@heroicons/react/20/solid"; +import { ChevronDownIcon, ListBulletIcon, Squares2X2Icon, CalendarDaysIcon } from "@heroicons/react/24/outline"; // helpers import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; // types @@ -53,30 +52,30 @@ export const IssuesFilterView: React.FC = () => {
{ {({ open }) => ( <> View @@ -130,11 +129,11 @@ export const IssuesFilterView: React.FC = () => { leaveFrom="opacity-100 translate-y-0" leaveTo="opacity-0 translate-y-1" > - -
+ +
-

Group by

+

Group by

option.key === groupByProperty)?.name ?? @@ -155,7 +154,7 @@ export const IssuesFilterView: React.FC = () => {
-

Order by

+

Order by

option.key === orderBy)?.name ?? @@ -178,7 +177,7 @@ export const IssuesFilterView: React.FC = () => {
-

Issue type

+

Issue type

option.key === filters.type) @@ -201,11 +200,11 @@ export const IssuesFilterView: React.FC = () => {
-

Show empty states

+

Show empty states

-

Display Properties

+

Display Properties

{Object.keys(properties).map((key) => { if (key === "estimate" && !isEstimateActive) return null; diff --git a/apps/app/components/core/issues-view.tsx b/apps/app/components/core/issues-view.tsx index 22261de06..18e685866 100644 --- a/apps/app/components/core/issues-view.tsx +++ b/apps/app/components/core/issues-view.tsx @@ -502,8 +502,8 @@ export const IssuesView: React.FC = ({ title="Create a new issue" description={ - Use
C
shortcut to - create a new issue + Use
C
{" "} + shortcut to create a new issue
} Icon={PlusIcon} diff --git a/apps/app/components/core/link-modal.tsx b/apps/app/components/core/link-modal.tsx index 7fd19dc8a..3c22c2b1b 100644 --- a/apps/app/components/core/link-modal.tsx +++ b/apps/app/components/core/link-modal.tsx @@ -56,7 +56,7 @@ export const LinkModal: React.FC = ({ isOpen, handleClose, onFormSubmit } leaveFrom="opacity-100" leaveTo="opacity-0" > -
+
@@ -70,11 +70,11 @@ export const LinkModal: React.FC = ({ isOpen, handleClose, onFormSubmit } leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - +
- + Add Link
diff --git a/apps/app/components/core/list-view/single-issue.tsx b/apps/app/components/core/list-view/single-issue.tsx index 4fa518769..a728c3a75 100644 --- a/apps/app/components/core/list-view/single-issue.tsx +++ b/apps/app/components/core/list-view/single-issue.tsx @@ -217,7 +217,7 @@ export const SingleListIssue: React.FC = ({
{ e.preventDefault(); setContextMenu(true); diff --git a/apps/app/components/core/list-view/single-list.tsx b/apps/app/components/core/list-view/single-list.tsx index 32880a1a6..bfd8acdab 100644 --- a/apps/app/components/core/list-view/single-list.tsx +++ b/apps/app/components/core/list-view/single-list.tsx @@ -132,10 +132,10 @@ export const SingleList: React.FC = ({ return ( {({ open }) => ( -
+
@@ -144,21 +144,21 @@ export const SingleList: React.FC = ({
{getGroupIcon()}
)} {selectedGroup !== null ? ( -

+

{getGroupTitle()}

) : (

All Issues

)} - - {groupedByIssues?.[groupTitle].length ?? 0} + + {groupedByIssues[groupTitle as keyof IIssue].length}
{type === "issue" ? (
)} - +
{link.title}
-

+

Added {timeAgo(link.created_at)}
by{" "} diff --git a/apps/app/components/core/sidebar/sidebar-progress-stats.tsx b/apps/app/components/core/sidebar/sidebar-progress-stats.tsx index fad29ddaa..84b79e4df 100644 --- a/apps/app/components/core/sidebar/sidebar-progress-stats.tsx +++ b/apps/app/components/core/sidebar/sidebar-progress-stats.tsx @@ -100,13 +100,13 @@ export const SidebarProgressStats: React.FC = ({ > - `w-full rounded px-3 py-1 text-gray-900 ${ - selected ? " bg-theme text-white" : " hover:bg-hover-gray" + `w-full rounded px-3 py-1 text-brand-base ${ + selected ? " bg-brand-accent text-white" : " hover:bg-brand-surface-2" }` } > @@ -114,8 +114,8 @@ export const SidebarProgressStats: React.FC = ({ - `w-full rounded px-3 py-1 text-gray-900 ${ - selected ? " bg-theme text-white" : " hover:bg-hover-gray" + `w-full rounded px-3 py-1 text-brand-base ${ + selected ? " bg-brand-accent text-white" : " hover:bg-brand-surface-2" }` } > @@ -123,8 +123,8 @@ export const SidebarProgressStats: React.FC = ({ - `w-full rounded px-3 py-1 text-gray-900 ${ - selected ? " bg-theme text-white" : " hover:bg-hover-gray" + `w-full rounded px-3 py-1 text-brand-base ${ + selected ? " bg-brand-accent text-white" : " hover:bg-brand-surface-2" }` } > @@ -166,7 +166,7 @@ export const SidebarProgressStats: React.FC = ({ -

+
= ({ }) => (
{title}
diff --git a/apps/app/components/core/theme-switch.tsx b/apps/app/components/core/theme-switch.tsx new file mode 100644 index 000000000..f93b1998e --- /dev/null +++ b/apps/app/components/core/theme-switch.tsx @@ -0,0 +1,60 @@ +import { useState, useEffect, ChangeEvent } from "react"; +import { useTheme } from "next-themes"; +import { THEMES_OBJ } from "constants/themes"; +import { CustomSelect } from "components/ui"; +import { CustomThemeModal } from "./custom-theme-modal"; + +export const ThemeSwitch = () => { + const [mounted, setMounted] = useState(false); + const [customThemeModal, setCustomThemeModal] = useState(false); + const { theme, setTheme } = useTheme(); + + // useEffect only runs on the client, so now we can safely show the UI + useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) { + return null; + } + + return ( + <> + t.value === theme)?.label : "Select your theme"} + onChange={({ value, type }: { value: string; type: string }) => { + if (value === "custom") { + if (!customThemeModal) setCustomThemeModal(true); + } else { + const cssVars = [ + "--color-bg-base", + "--color-bg-surface-1", + "--color-bg-surface-2", + + "--color-border", + "--color-bg-sidebar", + "--color-accent", + + "--color-text-base", + "--color-text-secondary", + ]; + cssVars.forEach((cssVar) => document.documentElement.style.removeProperty(cssVar)); + } + document.documentElement.style.setProperty("color-scheme", type); + setTheme(value); + }} + input + width="w-full" + position="right" + > + {THEMES_OBJ.map(({ value, label, type }) => ( + + {label} + + ))} + + {/* setCustomThemeModal(false)} /> */} + + ); +}; diff --git a/apps/app/components/cycles/completed-cycles-list.tsx b/apps/app/components/cycles/completed-cycles-list.tsx index cb8bb43d7..bf1971368 100644 --- a/apps/app/components/cycles/completed-cycles-list.tsx +++ b/apps/app/components/cycles/completed-cycles-list.tsx @@ -64,7 +64,7 @@ export const CompletedCyclesList: React.FC = ({ {completedCycles ? ( completedCycles.completed_cycles.length > 0 ? (
-
+
Completed cycles are not editable.
diff --git a/apps/app/components/cycles/cycles-list.tsx b/apps/app/components/cycles/cycles-list.tsx index 28cd171d0..68592b051 100644 --- a/apps/app/components/cycles/cycles-list.tsx +++ b/apps/app/components/cycles/cycles-list.tsx @@ -62,8 +62,8 @@ export const CyclesList: React.FC = ({
) : type === "current" ? ( showNoCurrentCycleMessage && ( -
-

No current cycle is present.

+
+

No current cycle is present.

diff --git a/apps/app/components/cycles/delete-cycle-modal.tsx b/apps/app/components/cycles/delete-cycle-modal.tsx index c43d34d68..b69537cdc 100644 --- a/apps/app/components/cycles/delete-cycle-modal.tsx +++ b/apps/app/components/cycles/delete-cycle-modal.tsx @@ -139,7 +139,7 @@ export const DeleteCycleModal: React.FC = ({ leaveFrom="opacity-100" leaveTo="opacity-0" > -
+
@@ -153,8 +153,8 @@ export const DeleteCycleModal: React.FC = ({ leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - -
+ +
= ({ />
- + Delete Cycle
-

+

Are you sure you want to delete cycle-{" "} {data?.name}? All of the data related to the cycle will be permanently removed. This action cannot be undone. diff --git a/apps/app/components/cycles/empty-cycle.tsx b/apps/app/components/cycles/empty-cycle.tsx index dfefa4011..af2f12a11 100644 --- a/apps/app/components/cycles/empty-cycle.tsx +++ b/apps/app/components/cycles/empty-cycle.tsx @@ -37,30 +37,30 @@ export const EmptyCycle = () => { return (

-
+
- Cycle Name + Cycle Name
- - + +
-
+
-
+
- Cycle Name + Cycle Name
- - + +
-
+
@@ -68,7 +68,7 @@ export const EmptyCycle = () => {

Create New Cycle

-

+

Sprint more effectively with Cycles by confining your project
to a fixed amount of time. Create new cycle now.

diff --git a/apps/app/components/cycles/form.tsx b/apps/app/components/cycles/form.tsx index 99e55ce7a..168f4f2cd 100644 --- a/apps/app/components/cycles/form.tsx +++ b/apps/app/components/cycles/form.tsx @@ -94,7 +94,7 @@ export const CycleForm: React.FC = ({ handleFormSubmit, handleClose, stat return (
-

+

{status ? "Update" : "Create"} Cycle

@@ -196,7 +196,7 @@ export const CycleForm: React.FC = ({ handleFormSubmit, handleClose, stat
-
+
Cancel = ({ leaveFrom="opacity-100" leaveTo="opacity-0" > -
+
@@ -164,7 +164,7 @@ export const CreateUpdateCycleModal: React.FC = ({ leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - + = ({ {({ open }) => ( <> - +
{cycles?.find((c) => c.id === value)?.name ?? "Cycles"}
@@ -75,7 +75,7 @@ export const CycleSelect: React.FC = ({ leaveTo="opacity-0" >
{options ? ( @@ -93,7 +93,7 @@ export const CycleSelect: React.FC = ({ : "" } ${ active ? "bg-indigo-50" : "" - } relative cursor-pointer select-none p-2 text-gray-900` + } relative cursor-pointer select-none p-2 text-brand-base` } value={option.value} > @@ -103,14 +103,14 @@ export const CycleSelect: React.FC = ({ )) ) : ( -

No options

+

No options

) ) : ( -

Loading...

+

Loading...

)}
-
- +
+ = ({ isOpen, handleClose }) => filteredOptions.map((option: ICycle) => (
diff --git a/apps/app/components/cycles/transfer-issues.tsx b/apps/app/components/cycles/transfer-issues.tsx index 979e87b93..59a10a4d8 100644 --- a/apps/app/components/cycles/transfer-issues.tsx +++ b/apps/app/components/cycles/transfer-issues.tsx @@ -38,7 +38,7 @@ export const TransferIssues: React.FC = ({ handleClick }) => { : 0; return (
-
+
Completed cycles are not editable.
diff --git a/apps/app/components/emoji-icon-picker/index.tsx b/apps/app/components/emoji-icon-picker/index.tsx index 6c3d3842b..6cb3b84f9 100644 --- a/apps/app/components/emoji-icon-picker/index.tsx +++ b/apps/app/components/emoji-icon-picker/index.tsx @@ -55,7 +55,7 @@ const EmojiIconPicker: React.FC = ({ return ( setIsOpen((prev) => !prev)} > {label} @@ -69,8 +69,8 @@ const EmojiIconPicker: React.FC = ({ leaveFrom="transform opacity-100 scale-100" leaveTo="transform opacity-0 scale-95" > - -
+ +
{tabOptions.map((tab) => ( @@ -100,7 +100,7 @@ const EmojiIconPicker: React.FC = ({ {recentEmojis.map((emoji) => ( +
diff --git a/apps/app/components/issues/comment/comment-card.tsx b/apps/app/components/issues/comment/comment-card.tsx index 9cab65531..4691ad705 100644 --- a/apps/app/components/issues/comment/comment-card.tsx +++ b/apps/app/components/issues/comment/comment-card.tsx @@ -67,7 +67,7 @@ export const CommentCard: React.FC = ({ comment, onSubmit, handleCommentD
)} - +
@@ -77,7 +77,7 @@ export const CommentCard: React.FC = ({ comment, onSubmit, handleCommentD {comment.actor_detail.first_name} {comment.actor_detail.is_bot ? "Bot" : " " + comment.actor_detail.last_name}
-

Commented {timeAgo(comment.created_at)}

+

Commented {timeAgo(comment.created_at)}

{isEditing ? ( @@ -117,7 +117,7 @@ export const CommentCard: React.FC = ({ comment, onSubmit, handleCommentD editable={false} onBlur={() => ({})} noBorder - customClassName="text-xs bg-gray-100" + customClassName="text-xs bg-brand-surface-1" /> )}
diff --git a/apps/app/components/issues/delete-issue-modal.tsx b/apps/app/components/issues/delete-issue-modal.tsx index e161e52a1..4fb5f92ea 100644 --- a/apps/app/components/issues/delete-issue-modal.tsx +++ b/apps/app/components/issues/delete-issue-modal.tsx @@ -88,7 +88,7 @@ export const DeleteIssueModal: React.FC = ({ isOpen, handleClose, data }) leaveFrom="opacity-100" leaveTo="opacity-0" > -
+
@@ -102,7 +102,7 @@ export const DeleteIssueModal: React.FC = ({ isOpen, handleClose, data }) leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - +
@@ -116,7 +116,7 @@ export const DeleteIssueModal: React.FC = ({ isOpen, handleClose, data })
-

+

Are you sure you want to delete issue{" "} {data?.project_detail.identifier}-{data?.sequence_id} diff --git a/apps/app/components/issues/description-form.tsx b/apps/app/components/issues/description-form.tsx index 95962b929..b568fb016 100644 --- a/apps/app/components/issues/description-form.tsx +++ b/apps/app/components/issues/description-form.tsx @@ -115,7 +115,7 @@ export const IssueDescriptionForm: FC = ({ issue, handleFormS role="textbox" /> {characterLimit && ( -

+
255 ? "text-red-500" : "" @@ -158,7 +158,7 @@ export const IssueDescriptionForm: FC = ({ issue, handleFormS )} />
diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index 257a6a4e1..8556c2551 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -216,12 +216,12 @@ export const IssueForm: FC = ({ /> )} /> -

+

{status ? "Update" : "Create"} Issue

{watch("parent") && watch("parent") !== "" ? ( -
+
= ({ /> {mostSimilarIssue && (
-

+

@@ -283,7 +283,7 @@ export const IssueForm: FC = ({

-
+
setCreateMore((prevData) => !prevData)} @@ -453,7 +453,7 @@ export const IssueForm: FC = ({
diff --git a/apps/app/components/issues/modal.tsx b/apps/app/components/issues/modal.tsx index 47fe6e871..1b74664b1 100644 --- a/apps/app/components/issues/modal.tsx +++ b/apps/app/components/issues/modal.tsx @@ -219,7 +219,7 @@ export const CreateUpdateIssueModal: React.FC = ({ leaveFrom="opacity-100" leaveTo="opacity-0" > -
+
@@ -233,7 +233,7 @@ export const CreateUpdateIssueModal: React.FC = ({ leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - + = ({ issue, properties, projectId const isNotAllowed = false; return ( -
+
@@ -97,7 +97,7 @@ export const MyIssuesListItem: React.FC = ({ issue, properties, projectId )} - + {truncateText(issue.name, 50)} @@ -127,7 +127,7 @@ export const MyIssuesListItem: React.FC = ({ issue, properties, projectId /> )} {properties.sub_issue_count && ( -
+
{issue?.sub_issues_count} {issue?.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
)} @@ -136,7 +136,7 @@ export const MyIssuesListItem: React.FC = ({ issue, properties, projectId {issue.label_details.map((label) => ( = ({ leaveFrom="opacity-100 scale-100" leaveTo="opacity-0 scale-95" > - + {multiple ? ( <> ({})} multiple>
  • {query === "" && ( -

    +

    {title}

    )} @@ -182,7 +182,7 @@ export const ParentIssuesListModal: React.FC = ({ value={issue.id} className={({ active }) => `flex cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 ${ - active ? "bg-gray-900 bg-opacity-5 text-gray-900" : "" + active ? "bg-gray-900 bg-opacity-5 text-brand-base" : "" }` } onClick={() => handleClose()} @@ -194,7 +194,7 @@ export const ParentIssuesListModal: React.FC = ({ backgroundColor: issue.state_detail.color, }} /> - + {issue.project_detail?.identifier}-{issue.sequence_id} {" "} {issue.name} @@ -206,9 +206,9 @@ export const ParentIssuesListModal: React.FC = ({ ) : (
    -

    +

    No issues found. Create a new issue with{" "} -
    C
    . +
    C
    .

    )} diff --git a/apps/app/components/issues/select/assignee.tsx b/apps/app/components/issues/select/assignee.tsx index 1d06f3f0e..5c1d7c1b4 100644 --- a/apps/app/components/issues/select/assignee.tsx +++ b/apps/app/components/issues/select/assignee.tsx @@ -54,15 +54,15 @@ export const IssueAssigneeSelect: React.FC = ({ projectId, value = [], on onChange={onChange} options={options} label={ -
    +
    {value && value.length > 0 && Array.isArray(value) ? (
    ) : (
    - - Assignee + + Assignee
    )}
    diff --git a/apps/app/components/issues/select/date.tsx b/apps/app/components/issues/select/date.tsx index 438291a2d..3bb8c1bed 100644 --- a/apps/app/components/issues/select/date.tsx +++ b/apps/app/components/issues/select/date.tsx @@ -18,11 +18,11 @@ export const IssueDateSelect: React.FC = ({ value, onChange }) => ( <> - `flex cursor-pointer items-center rounded-md border text-xs shadow-sm duration-200 + `flex cursor-pointer items-center rounded-md border border-brand-base text-xs shadow-sm duration-200 ${ open - ? "border-theme bg-theme/5 outline-none ring-1 ring-theme " - : "hover:bg-theme/5 " + ? "border-brand-accent bg-brand-accent/5 outline-none ring-1 ring-brand-accent " + : "hover:bg-brand-accent/5 " }` } > diff --git a/apps/app/components/issues/select/label.tsx b/apps/app/components/issues/select/label.tsx index 2adeaa222..f5b023280 100644 --- a/apps/app/components/issues/select/label.tsx +++ b/apps/app/components/issues/select/label.tsx @@ -60,11 +60,11 @@ export const IssueLabelSelect: React.FC = ({ setIsOpen, value, onChange, <> - `flex cursor-pointer items-center rounded-md border text-xs shadow-sm duration-200 + `flex cursor-pointer items-center rounded-md border border-brand-base text-xs shadow-sm duration-200 ${ open - ? "border-theme bg-theme/5 outline-none ring-1 ring-theme " - : "hover:bg-theme/5 " + ? "border-brand-accent bg-brand-accent/5 outline-none ring-1 ring-brand-accent " + : "hover:bg-brand-accent/5 " }` } > @@ -78,8 +78,8 @@ export const IssueLabelSelect: React.FC = ({ setIsOpen, value, onChange, ) : ( - - Label + + Label )} @@ -96,12 +96,12 @@ export const IssueLabelSelect: React.FC = ({ setIsOpen, value, onChange, > -
    - +
    + setQuery(event.target.value)} placeholder="Search for label..." displayValue={(assigned: any) => assigned?.name} @@ -120,7 +120,7 @@ export const IssueLabelSelect: React.FC = ({ setIsOpen, value, onChange, key={label.id} className={({ active }) => `${ - active ? "bg-gray-200" : "" + active ? "bg-brand-surface-2" : "" } group flex min-w-[14rem] cursor-pointer select-none items-center gap-2 truncate rounded px-1 py-1.5 text-gray-600` } value={label.id} @@ -150,8 +150,8 @@ export const IssueLabelSelect: React.FC = ({ setIsOpen, value, onChange, ); } else return ( -
    -
    +
    +
    {label.name}
    @@ -160,7 +160,7 @@ export const IssueLabelSelect: React.FC = ({ setIsOpen, value, onChange, key={child.id} className={({ active }) => `${ - active ? "bg-gray-200" : "" + active ? "bg-brand-surface-2" : "" } group flex min-w-[14rem] cursor-pointer select-none items-center gap-2 truncate rounded px-1 py-1.5 text-gray-600` } value={child.id} @@ -192,14 +192,14 @@ export const IssueLabelSelect: React.FC = ({ setIsOpen, value, onChange, ); }) ) : ( -

    No labels found

    +

    No labels found

    ) ) : ( -

    Loading...

    +

    Loading...

    )}
    -
    +
    = ({ issueDetail?.parent_detail ? ( ) : ( -
    +
    No parent selected
    ) @@ -395,7 +395,7 @@ export const IssueDetailsSidebar: React.FC = ({ return ( { const updatedLabels = watchIssue("labels_list")?.filter( (l) => l !== labelId @@ -435,8 +435,8 @@ export const IssueDetailsSidebar: React.FC = ({ className={`flex ${ isNotAllowed ? "cursor-not-allowed" - : "cursor-pointer hover:bg-gray-100" - } items-center gap-2 rounded-2xl border px-2 py-0.5 text-xs`} + : "cursor-pointer hover:bg-brand-surface-1" + } items-center gap-2 rounded-2xl border border-brand-base px-2 py-0.5 text-xs`} > Select Label @@ -448,7 +448,7 @@ export const IssueDetailsSidebar: React.FC = ({ leaveFrom="opacity-100" leaveTo="opacity-0" > - +
    {issueLabels ? ( issueLabels.length > 0 ? ( @@ -463,9 +463,9 @@ export const IssueDetailsSidebar: React.FC = ({ - `${active || selected ? "bg-indigo-50" : ""} ${ + `${active || selected ? "bg-brand-surface-1" : ""} ${ selected ? "font-medium" : "" - } flex cursor-pointer select-none items-center gap-2 truncate p-2 text-gray-900` + } flex cursor-pointer select-none items-center gap-2 truncate p-2 text-brand-base` } value={label.id} > @@ -483,8 +483,8 @@ export const IssueDetailsSidebar: React.FC = ({ ); } else return ( -
    -
    +
    +
    {" "} {label.name}
    @@ -495,7 +495,7 @@ export const IssueDetailsSidebar: React.FC = ({ className={({ active, selected }) => `${active || selected ? "bg-indigo-50" : ""} ${ selected ? "font-medium" : "" - } flex cursor-pointer select-none items-center gap-2 truncate p-2 text-gray-900` + } flex cursor-pointer select-none items-center gap-2 truncate p-2 text-brand-base` } value={child.id} > @@ -530,8 +530,8 @@ export const IssueDetailsSidebar: React.FC = ({ )} diff --git a/apps/app/components/issues/view-select/assignee.tsx b/apps/app/components/issues/view-select/assignee.tsx index 1429555d9..64199b901 100644 --- a/apps/app/components/issues/view-select/assignee.tsx +++ b/apps/app/components/issues/view-select/assignee.tsx @@ -103,17 +103,17 @@ export const ViewAssigneeSelect: React.FC = ({
    {issue.assignees && issue.assignees.length > 0 && Array.isArray(issue.assignees) ? (
    - {issue.assignees.length} Assignees + {issue.assignees.length} Assignees
    ) : (
    - - Assignee + + Assignee
    )}
    diff --git a/apps/app/components/issues/view-select/state.tsx b/apps/app/components/issues/view-select/state.tsx index 34ce8c82c..4a6482cde 100644 --- a/apps/app/components/issues/view-select/state.tsx +++ b/apps/app/components/issues/view-select/state.tsx @@ -97,7 +97,7 @@ export const ViewStateSelect: React.FC = ({ tooltipHeading="State" tooltipContent={addSpaceIfCamelCase(selectedOption?.name ?? "")} > -
    +
    {selectedOption && getStateGroupIcon(selectedOption.group, "16", "16", selectedOption.color)} {selectedOption?.name ?? "State"} diff --git a/apps/app/components/labels/create-label-modal.tsx b/apps/app/components/labels/create-label-modal.tsx index e07de48f5..c186cbd7c 100644 --- a/apps/app/components/labels/create-label-modal.tsx +++ b/apps/app/components/labels/create-label-modal.tsx @@ -84,7 +84,7 @@ export const CreateLabelModal: React.FC = ({ isOpen, projectId, handleClo leaveFrom="opacity-100" leaveTo="opacity-0" > -
    +
    @@ -98,10 +98,10 @@ export const CreateLabelModal: React.FC = ({ isOpen, projectId, handleClo leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - +
    - + Create Label
    @@ -109,8 +109,8 @@ export const CreateLabelModal: React.FC = ({ isOpen, projectId, handleClo {({ open, close }) => ( <> {watch("color") && watch("color") !== "" && ( @@ -122,7 +122,7 @@ export const CreateLabelModal: React.FC = ({ isOpen, projectId, handleClo /> )}
  • {query === "" && ( -

    +

    Labels

    )} @@ -132,7 +132,7 @@ export const LabelsListModal: React.FC = ({ isOpen, handleClose, parent } }} className={({ active }) => `flex cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 ${ - active ? "bg-gray-900 bg-opacity-5 text-gray-900" : "" + active ? "bg-gray-900 bg-opacity-5 text-brand-base" : "" }` } onClick={() => { @@ -159,10 +159,10 @@ export const LabelsListModal: React.FC = ({ isOpen, handleClose, parent } {query !== "" && filteredLabels.length === 0 && (
    diff --git a/apps/app/components/labels/single-label-group.tsx b/apps/app/components/labels/single-label-group.tsx index d41b8508e..c5e5f91db 100644 --- a/apps/app/components/labels/single-label-group.tsx +++ b/apps/app/components/labels/single-label-group.tsx @@ -66,7 +66,7 @@ export const SingleLabelGroup: React.FC = ({ }; return ( - + {({ open }) => ( <>
    @@ -74,7 +74,7 @@ export const SingleLabelGroup: React.FC = ({ -
    {label.name}
    +
    {label.name}
    @@ -100,7 +100,7 @@ export const SingleLabelGroup: React.FC = ({ @@ -120,9 +120,9 @@ export const SingleLabelGroup: React.FC = ({ {labelChildren.map((child) => (
    -
    +
    = ({ editLabel, handleLabelDelete, }) => ( -
    +
    = ({ backgroundColor: label.color && label.color !== "" ? label.color : "#000", }} /> -
    {label.name}
    +
    {label.name}
    addLabelToGroup(label)}> diff --git a/apps/app/components/modules/delete-module-modal.tsx b/apps/app/components/modules/delete-module-modal.tsx index 70fca1916..77ed8dd4c 100644 --- a/apps/app/components/modules/delete-module-modal.tsx +++ b/apps/app/components/modules/delete-module-modal.tsx @@ -77,7 +77,7 @@ export const DeleteModuleModal: React.FC = ({ isOpen, setIsOpen, data }) leaveFrom="opacity-100" leaveTo="opacity-0" > -
    +
    @@ -91,8 +91,8 @@ export const DeleteModuleModal: React.FC = ({ isOpen, setIsOpen, data }) leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - -
    + +
    = ({ isOpen, setIsOpen, data }) />
    - + Delete Module
    -

    +

    Are you sure you want to delete module-{" "} {data?.name}? All of the data related to the module will be permanently removed. This action cannot be undone. diff --git a/apps/app/components/modules/form.tsx b/apps/app/components/modules/form.tsx index b1e56ce90..fe1e557b7 100644 --- a/apps/app/components/modules/form.tsx +++ b/apps/app/components/modules/form.tsx @@ -66,7 +66,7 @@ export const ModuleForm: React.FC = ({ handleFormSubmit, handleClose, sta return (

    -

    +

    {status ? "Update" : "Create"} Module

    @@ -170,7 +170,7 @@ export const ModuleForm: React.FC = ({ handleFormSubmit, handleClose, sta
    -
    +
    Cancel {status diff --git a/apps/app/components/modules/modal.tsx b/apps/app/components/modules/modal.tsx index b611f415b..d42a09ee7 100644 --- a/apps/app/components/modules/modal.tsx +++ b/apps/app/components/modules/modal.tsx @@ -125,7 +125,7 @@ export const CreateUpdateModuleModal: React.FC = ({ isOpen, setIsOpen, da leaveFrom="opacity-100" leaveTo="opacity-0" > -
    +
    @@ -139,7 +139,7 @@ export const CreateUpdateModuleModal: React.FC = ({ isOpen, setIsOpen, da leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - + = ({ value, onChange }) => { options={options} value={value} label={ -
    +
    {selectedOption ? ( ) : ( -
    +
    No user
    )} diff --git a/apps/app/components/modules/select/select-members.tsx b/apps/app/components/modules/select/select-members.tsx index 439753a91..8d407f36a 100644 --- a/apps/app/components/modules/select/select-members.tsx +++ b/apps/app/components/modules/select/select-members.tsx @@ -51,16 +51,16 @@ export const ModuleMembersSelect: React.FC = ({ value, onChange }) => { +
    {value && value.length > 0 && Array.isArray(value) ? (
    - {value.length} Assignees + {value.length} Assignees
    ) : (
    - - Assignee + + Assignee
    )}
    diff --git a/apps/app/components/modules/sidebar-select/select-lead.tsx b/apps/app/components/modules/sidebar-select/select-lead.tsx index bf45f614e..795d0f75c 100644 --- a/apps/app/components/modules/sidebar-select/select-lead.tsx +++ b/apps/app/components/modules/sidebar-select/select-lead.tsx @@ -62,11 +62,11 @@ export const SidebarLeadSelect: React.FC = ({ value, onChange }) => { +
    {selectedOption ? ( ) : ( -
    +
    = ({ value, onChange }) => { +
    {value && value.length > 0 && Array.isArray(value) ? (
    - {value.length} Assignees + {value.length} Assignees
    ) : ( "No members" diff --git a/apps/app/components/modules/sidebar-select/select-status.tsx b/apps/app/components/modules/sidebar-select/select-status.tsx index 2156b4357..5d5ac9464 100644 --- a/apps/app/components/modules/sidebar-select/select-status.tsx +++ b/apps/app/components/modules/sidebar-select/select-status.tsx @@ -33,7 +33,7 @@ export const SidebarStatusSelect: React.FC = ({ control, submitChanges, w label={ = ({ issues, module, isOpen,
    {module ? ( <> @@ -199,7 +199,7 @@ export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen, {capitalizeFirstLetter(`${watch("status")}`)} @@ -218,13 +218,13 @@ export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen, )} />
    -
    +
    {({ open }) => ( <> @@ -271,8 +271,8 @@ export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen, {({ open }) => ( <> @@ -319,7 +319,7 @@ export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen,
    -

    {module.name}

    +

    {module.name}

    setModuleDeleteModal(true)}> @@ -336,7 +336,7 @@ export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen,
    - + {module.description}
    @@ -373,7 +373,7 @@ export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen, Progress
    -
    +
    = ({ issues, module, isOpen,
    -
    - +
    + {({ open }) => (
    - Progress + Progress {!open && moduleIssues && progressPercentage ? ( {progressPercentage ? `${progressPercentage}%` : ""} @@ -415,7 +415,7 @@ export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen, ) : (
    - + Invalid date. Please enter valid date.
    @@ -428,7 +428,7 @@ export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen,
    - + Pending Issues -{" "} @@ -437,7 +437,7 @@ export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen,
    -
    +
    Ideal @@ -466,15 +466,15 @@ export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen,
    -
    - +
    + {({ open }) => (
    - Other Information + Other Information
    {module.total_issues > 0 ? ( @@ -487,7 +487,7 @@ export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen, ) : (
    - + No issues found. Please add issue.
    @@ -522,11 +522,11 @@ export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen,
    -
    +
    -

    Links

    +

    Links

    diff --git a/apps/app/components/rich-text-editor/toolbar/link.tsx b/apps/app/components/rich-text-editor/toolbar/link.tsx index 458a50171..a47c45c1a 100644 --- a/apps/app/components/rich-text-editor/toolbar/link.tsx +++ b/apps/app/components/rich-text-editor/toolbar/link.tsx @@ -173,12 +173,12 @@ export const FloatingLinkToolbar = () => { return ( <> {!isEditing && ( - + {linkEditButtons} )} {!isEditing && empty && ( - + {linkEditButtons} )} diff --git a/apps/app/components/rich-text-editor/toolbar/table-controls.tsx b/apps/app/components/rich-text-editor/toolbar/table-controls.tsx index 3ad285245..d8f8e24e2 100644 --- a/apps/app/components/rich-text-editor/toolbar/table-controls.tsx +++ b/apps/app/components/rich-text-editor/toolbar/table-controls.tsx @@ -8,7 +8,7 @@ export const TableControls = () => {
    diff --git a/apps/app/components/states/create-state-modal.tsx b/apps/app/components/states/create-state-modal.tsx index ca19cd752..769ca6821 100644 --- a/apps/app/components/states/create-state-modal.tsx +++ b/apps/app/components/states/create-state-modal.tsx @@ -102,7 +102,7 @@ export const CreateStateModal: React.FC = ({ isOpen, projectId, handleClo leaveFrom="opacity-100" leaveTo="opacity-0" > -
    +
    @@ -116,10 +116,10 @@ export const CreateStateModal: React.FC = ({ isOpen, projectId, handleClo leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - +
    - + Create State
    @@ -165,8 +165,8 @@ export const CreateStateModal: React.FC = ({ isOpen, projectId, handleClo {({ open }) => ( <> Color @@ -179,7 +179,7 @@ export const CreateStateModal: React.FC = ({ isOpen, projectId, handleClo /> )}
    diff --git a/apps/app/components/states/delete-state-modal.tsx b/apps/app/components/states/delete-state-modal.tsx index d1eca3a93..362ff9353 100644 --- a/apps/app/components/states/delete-state-modal.tsx +++ b/apps/app/components/states/delete-state-modal.tsx @@ -90,7 +90,7 @@ export const DeleteStateModal: React.FC = ({ isOpen, onClose, data }) => leaveFrom="opacity-100" leaveTo="opacity-0" > -
    +
    @@ -104,8 +104,8 @@ export const DeleteStateModal: React.FC = ({ isOpen, onClose, data }) => leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - -
    + +
    = ({ isOpen, onClose, data }) => />
    - + Delete State
    -

    +

    Are you sure you want to delete state-{" "} {data?.name}? All of the data related to the state will be permanently removed. This action cannot be undone. diff --git a/apps/app/components/states/single-state.tsx b/apps/app/components/states/single-state.tsx index f4383b376..2a65a1674 100644 --- a/apps/app/components/states/single-state.tsx +++ b/apps/app/components/states/single-state.tsx @@ -135,12 +135,12 @@ export const SingleState: React.FC = ({ return (

    {getStateGroupIcon(state.group, "20", "20", state.color)}
    -
    {addSpaceIfCamelCase(state.name)}
    +
    {addSpaceIfCamelCase(state.name)}

    {state.description}

    @@ -148,7 +148,7 @@ export const SingleState: React.FC = ({ {index !== 0 && ( diff --git a/apps/app/components/ui/multi-level-select.tsx b/apps/app/components/ui/multi-level-select.tsx index 1662c58a8..a063ee45f 100644 --- a/apps/app/components/ui/multi-level-select.tsx +++ b/apps/app/components/ui/multi-level-select.tsx @@ -48,7 +48,7 @@ export const MultiLevelSelect: React.FC = ({
    setOpenChildFor(null)} - className="relative w-full cursor-default rounded-lg bg-white py-2 pl-3 pr-10 text-left shadow-md sm:text-sm" + className="relative w-full cursor-default rounded-lg bg-brand-surface-2 py-2 pl-3 pr-10 text-left shadow-md sm:text-sm" > {selected?.label ?? label} @@ -64,13 +64,13 @@ export const MultiLevelSelect: React.FC = ({ > {options.map((option) => ( { if (option.children !== null) { @@ -88,7 +88,7 @@ export const MultiLevelSelect: React.FC = ({ <> {openChildFor?.id === option.id && (
    = ({ = ({ ))}
    ( theme === "primary" ? `${ disabled ? "opacity-70" : "" - } border border-theme text-white shadow-sm hover:bg-theme focus:outline-none` + } border border-brand-accent text-white shadow-sm hover:bg-brand-accent focus:outline-none` : theme === "secondary" - ? "border bg-transparent hover:bg-gray-100" + ? "border bg-transparent hover:bg-brand-surface-1" : theme === "success" ? `${ disabled ? "opacity-70" : "" diff --git a/apps/app/components/ui/text-area/index.tsx b/apps/app/components/ui/text-area/index.tsx index 8cda29077..be4938699 100644 --- a/apps/app/components/ui/text-area/index.tsx +++ b/apps/app/components/ui/text-area/index.tsx @@ -44,7 +44,7 @@ export const TextArea: React.FC = ({ return ( <> {label && ( -
    -
    +

    Pending issues

    {data ? ( @@ -52,7 +52,7 @@ export const IssuesStats: React.FC = ({ data }) => ( )}
    -
    +

    Issues due by this week

    {data ? ( diff --git a/apps/app/components/workspace/send-workspace-invitation-modal.tsx b/apps/app/components/workspace/send-workspace-invitation-modal.tsx index b81023c2a..f58269b89 100644 --- a/apps/app/components/workspace/send-workspace-invitation-modal.tsx +++ b/apps/app/components/workspace/send-workspace-invitation-modal.tsx @@ -83,7 +83,7 @@ const SendWorkspaceInvitationModal: React.FC = ({ leaveFrom="opacity-100" leaveTo="opacity-0" > -
    +
    @@ -97,14 +97,14 @@ const SendWorkspaceInvitationModal: React.FC = ({ leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - +
    - + Members -

    +

    Invite members to work on your workspace.

    diff --git a/apps/app/components/workspace/sidebar-dropdown.tsx b/apps/app/components/workspace/sidebar-dropdown.tsx index 93be576fe..ac5ee7f7a 100644 --- a/apps/app/components/workspace/sidebar-dropdown.tsx +++ b/apps/app/components/workspace/sidebar-dropdown.tsx @@ -84,13 +84,13 @@ export const WorkspaceSidebarDropdown = () => {
    - +
    -
    +
    {activeWorkspace?.logo && activeWorkspace.logo !== "" ? ( { >
    -
    {user?.email}
    - Workspace +
    {user?.email}
    + Workspace {workspaces ? (
    {workspaces.length > 0 ? ( @@ -148,7 +148,7 @@ export const WorkspaceSidebarDropdown = () => {
    )}
    -
    +
    {userLinks(workspaceSlug as string).map((link, index) => ( {link.name} @@ -217,11 +217,11 @@ export const WorkspaceSidebarDropdown = () => { ))}
    -
    diff --git a/apps/app/constants/themes.ts b/apps/app/constants/themes.ts new file mode 100644 index 000000000..8ac745b82 --- /dev/null +++ b/apps/app/constants/themes.ts @@ -0,0 +1,35 @@ +export const THEMES = [ + "light", + "dark", + "light-contrast", + "dark-contrast", + // "custom" +]; + +export const THEMES_OBJ = [ + { + value: "light", + label: "Light", + type: "light", + }, + { + value: "dark", + label: "Dark", + type: "dark", + }, + { + value: "light-contrast", + label: "Light High Contrast", + type: "light", + }, + { + value: "dark-contrast", + label: "Dark High Contrast", + type: "dark", + }, + // { + // value: "custom", + // label: "Custom", + // type: "light", + // }, +]; diff --git a/apps/app/helpers/theme.helper.ts b/apps/app/helpers/theme.helper.ts new file mode 100644 index 000000000..d3d76f3e5 --- /dev/null +++ b/apps/app/helpers/theme.helper.ts @@ -0,0 +1,35 @@ +export const hexToRgb = (hex: string) => { + hex = hex.toLowerCase(); + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result + ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)].join(",") + : null; +}; + +export const applyTheme = (palette: string, isDarkPalette: boolean) => { + const values: string[] = []; + palette.split(",").map((color: string) => { + const cssVarColor = hexToRgb(color); + if (cssVarColor) values.push(cssVarColor); + }); + + const cssVars = [ + "--color-bg-base", + "--color-bg-surface-1", + "--color-bg-surface-2", + "--color-border", + "--color-bg-sidebar", + "--color-accent", + "--color-text-base", + "--color-text-secondary", + "color-scheme", + ]; + + values.push(isDarkPalette ? "dark" : "light"); + + cssVars.forEach((cssVar, i) => + document + .querySelector("[data-theme='custom']") + ?.style.setProperty(cssVar, values[i]) + ); +}; diff --git a/apps/app/layouts/app-layout/app-header.tsx b/apps/app/layouts/app-layout/app-header.tsx index 13106f480..81ec5dbf0 100644 --- a/apps/app/layouts/app-layout/app-header.tsx +++ b/apps/app/layouts/app-layout/app-header.tsx @@ -9,12 +9,12 @@ type Props = { }; const Header: React.FC = ({ breadcrumbs, left, right, setToggleSidebar }) => ( -
    +
    @@ -375,7 +375,7 @@ const SinglePage: NextPage = () => { customButton={ {/*
    -
    +

    Modules

    -

    +

    Modules are enabled for all the projects in this workspace. Access it from the navigation bar.

    @@ -146,7 +146,7 @@ const FeaturesSettings: NextPage = () => {
    -
    +

    Views

    -

    +

    Views are enabled for all the projects in this workspace. Access it from the navigation bar.

    @@ -187,7 +187,7 @@ const FeaturesSettings: NextPage = () => {
    -
    +

    Pages

    -

    +

    Pages are enabled for all the projects in this workspace. Access them from the navigation bar.

    @@ -228,7 +228,7 @@ const FeaturesSettings: NextPage = () => {
    -
    +
    {key === activeGroup && ( { diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx index 7c8f7ff64..e6a7eb10a 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx @@ -79,7 +79,7 @@ const ProjectViews: NextPage = () => { document.dispatchEvent(e); }} > - + Create View
    @@ -98,8 +98,8 @@ const ProjectViews: NextPage = () => { {views ? ( views.length > 0 ? (
    -

    Views

    -
      +

      Views

      +
      {views.map((view) => ( { handleDeleteView={() => handleDeleteView(view)} /> ))} -
    +
    ) : ( {

    Logo

    -

    +

    Max file size is 5MB. Supported file types are .jpg and .png.

    @@ -228,7 +228,7 @@ const WorkspaceSettings: NextPage = () => {

    URL

    -

    Your workspace URL.

    +

    Your workspace URL.

    {

    Name

    -

    Give a name to your workspace.

    +

    Give a name to your workspace.

    {

    Company Size

    -

    How big is your company?

    +

    How big is your company?

    {

    Danger Zone

    -

    +

    The danger zone of the workspace delete page is a critical area that requires careful consideration and attention. When deleting a workspace, all of the data and resources within that workspace will be permanently removed and cannot be recovered. diff --git a/apps/app/pages/[workspaceSlug]/settings/members.tsx b/apps/app/pages/[workspaceSlug]/settings/members.tsx index a85c00cf3..f993c2ff9 100644 --- a/apps/app/pages/[workspaceSlug]/settings/members.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/members.tsx @@ -142,7 +142,7 @@ const MembersSettings: NextPage = () => {

    Members

    @@ -424,7 +429,7 @@ export const CalendarView = () => { key={index} ref={provided.innerRef} {...provided.droppableProps} - className={`flex flex-col gap-1.5 border-t border-brand-base p-2.5 text-left text-sm font-medium hover:bg-brand-surface-1 ${ + className={`group flex flex-col gap-1.5 border-t border-brand-base p-2.5 text-left text-sm font-medium hover:bg-brand-surface-1 ${ showWeekEnds ? (index + 1) % 7 === 0 ? "" @@ -458,6 +463,15 @@ export const CalendarView = () => { )} ))} +
    + +
    {provided.placeholder}
    )} diff --git a/apps/app/components/core/issues-view.tsx b/apps/app/components/core/issues-view.tsx index 18e685866..6c80945c9 100644 --- a/apps/app/components/core/issues-view.tsx +++ b/apps/app/components/core/issues-view.tsx @@ -280,6 +280,17 @@ export const IssuesView: React.FC = ({ [setCreateIssueModal, setPreloadedData, selectedGroup] ); + const addIssueToDate = useCallback( + (date: string) => { + setCreateIssueModal(true); + setPreloadedData({ + target_date: date, + actionType: "createIssue", + }); + }, + [setCreateIssueModal, setPreloadedData] + ); + const makeIssueCopy = useCallback( (issue: IIssue) => { setCreateIssueModal(true); @@ -389,7 +400,7 @@ export const IssuesView: React.FC = ({ <>
    @@ -481,7 +492,7 @@ export const IssuesView: React.FC = ({ userAuth={memberRole} /> ) : ( - + )} ) : type === "issue" ? ( From 170b3d6eecc745adee28ab2891de018c48f803f4 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Thu, 20 Apr 2023 18:09:01 +0530 Subject: [PATCH 033/120] fix: cycle and module sidebar fix (#905) --- apps/app/components/cycles/sidebar.tsx | 4 ++-- apps/app/components/modules/sidebar.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/app/components/cycles/sidebar.tsx b/apps/app/components/cycles/sidebar.tsx index 1b9a3de34..bbbd170e4 100644 --- a/apps/app/components/cycles/sidebar.tsx +++ b/apps/app/components/cycles/sidebar.tsx @@ -345,7 +345,7 @@ export const CycleDetailsSidebar: React.FC = ({
    - + {({ open }) => (
    = ({
    - + {({ open }) => (
    = ({ issues, module, isOpen,
    - + {({ open }) => (
    = ({ issues, module, isOpen,
    - + {({ open }) => (
    Date: Thu, 20 Apr 2023 18:13:21 +0530 Subject: [PATCH 034/120] style: list and kanban view theming (#906) * fix: sidebar workspace dropdown logo * style: list and kanban view theming --- .../core/board-view/board-header.tsx | 6 +- .../core/board-view/single-issue.tsx | 8 +- .../components/core/issues-view-filter.tsx | 9 +- .../core/list-view/single-issue.tsx | 20 +-- .../components/core/list-view/single-list.tsx | 18 +-- .../issues/view-select/assignee.tsx | 2 +- .../issues/view-select/priority.tsx | 16 +- apps/app/components/ui/avatar.tsx | 36 +++-- apps/app/components/ui/custom-menu.tsx | 142 +++++++++--------- .../components/ui/custom-search-select.tsx | 10 +- apps/app/components/ui/custom-select.tsx | 4 +- apps/app/components/ui/datepicker.tsx | 4 +- .../components/ui/multi-level-dropdown.tsx | 10 +- apps/app/components/views/select-filters.tsx | 2 +- .../project-authorization-wrapper.tsx | 13 +- apps/app/styles/globals.css | 6 +- apps/app/tailwind.config.js | 8 +- 17 files changed, 170 insertions(+), 144 deletions(-) diff --git a/apps/app/components/core/board-view/board-header.tsx b/apps/app/components/core/board-view/board-header.tsx index 93443d99c..6fa8f68f3 100644 --- a/apps/app/components/core/board-view/board-header.tsx +++ b/apps/app/components/core/board-view/board-header.tsx @@ -123,8 +123,8 @@ export const BoardHeader: React.FC = ({ return (
    @@ -145,7 +145,7 @@ export const BoardHeader: React.FC = ({ {groupedByIssues?.[groupTitle].length ?? 0} diff --git a/apps/app/components/core/board-view/single-issue.tsx b/apps/app/components/core/board-view/single-issue.tsx index 76ae934d7..453a3bc19 100644 --- a/apps/app/components/core/board-view/single-issue.tsx +++ b/apps/app/components/core/board-view/single-issue.tsx @@ -358,7 +358,7 @@ export const SingleBoardIssue: React.FC = ({ {issue.label_details.map((label) => (
    = ({ /> )} {properties.link && ( -
    +
    @@ -399,10 +399,10 @@ export const SingleBoardIssue: React.FC = ({
    )} {properties.attachment_count && ( -
    +
    - + {issue.attachment_count}
    diff --git a/apps/app/components/core/issues-view-filter.tsx b/apps/app/components/core/issues-view-filter.tsx index 0df8e8f1b..cbabb23a1 100644 --- a/apps/app/components/core/issues-view-filter.tsx +++ b/apps/app/components/core/issues-view-filter.tsx @@ -12,7 +12,12 @@ import { SelectFilters } from "components/views"; // ui import { CustomMenu } from "components/ui"; // icons -import { ChevronDownIcon, ListBulletIcon, Squares2X2Icon, CalendarDaysIcon } from "@heroicons/react/24/outline"; +import { + ChevronDownIcon, + ListBulletIcon, + Squares2X2Icon, + CalendarDaysIcon, +} from "@heroicons/react/24/outline"; // helpers import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; // types @@ -113,7 +118,7 @@ export const IssuesFilterView: React.FC = () => { <> View diff --git a/apps/app/components/core/list-view/single-issue.tsx b/apps/app/components/core/list-view/single-issue.tsx index a728c3a75..529adeeaf 100644 --- a/apps/app/components/core/list-view/single-issue.tsx +++ b/apps/app/components/core/list-view/single-issue.tsx @@ -217,7 +217,7 @@ export const SingleListIssue: React.FC = ({
    { e.preventDefault(); setContextMenu(true); @@ -231,13 +231,15 @@ export const SingleListIssue: React.FC = ({ tooltipHeading="Issue ID" tooltipContent={`${issue.project_detail?.identifier}-${issue.sequence_id}`} > - + {issue.project_detail?.identifier}-{issue.sequence_id} )} - {truncateText(issue.name, 50)} + + {truncateText(issue.name, 50)} + @@ -267,7 +269,7 @@ export const SingleListIssue: React.FC = ({ /> )} {properties.sub_issue_count && ( -
    +
    {issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
    )} @@ -276,7 +278,7 @@ export const SingleListIssue: React.FC = ({ {issue.label_details.map((label) => ( = ({ /> )} {properties.link && ( -
    - +
    +
    {issue.link_count} @@ -318,8 +320,8 @@ export const SingleListIssue: React.FC = ({
    )} {properties.attachment_count && ( -
    - +
    +
    {issue.attachment_count} diff --git a/apps/app/components/core/list-view/single-list.tsx b/apps/app/components/core/list-view/single-list.tsx index bfd8acdab..5e71063a3 100644 --- a/apps/app/components/core/list-view/single-list.tsx +++ b/apps/app/components/core/list-view/single-list.tsx @@ -130,14 +130,10 @@ export const SingleList: React.FC = ({ }; return ( - + {({ open }) => ( -
    -
    +
    +
    {selectedGroup !== null && ( @@ -150,7 +146,7 @@ export const SingleList: React.FC = ({ ) : (

    All Issues

    )} - + {groupedByIssues[groupTitle as keyof IIssue].length}
    @@ -168,7 +164,7 @@ export const SingleList: React.FC = ({ ) : ( +
    } @@ -215,7 +211,9 @@ export const SingleList: React.FC = ({ /> )) ) : ( -

    No issues.

    +

    + No issues. +

    ) ) : (
    Loading...
    diff --git a/apps/app/components/issues/view-select/assignee.tsx b/apps/app/components/issues/view-select/assignee.tsx index 64199b901..188543185 100644 --- a/apps/app/components/issues/view-select/assignee.tsx +++ b/apps/app/components/issues/view-select/assignee.tsx @@ -106,7 +106,7 @@ export const ViewAssigneeSelect: React.FC = ({ } items-center gap-2 text-brand-secondary`} > {issue.assignees && issue.assignees.length > 0 && Array.isArray(issue.assignees) ? ( -
    +
    {issue.assignees.length} Assignees
    diff --git a/apps/app/components/issues/view-select/priority.tsx b/apps/app/components/issues/view-select/priority.tsx index 11cd4e698..fe6fb189e 100644 --- a/apps/app/components/issues/view-select/priority.tsx +++ b/apps/app/components/issues/view-select/priority.tsx @@ -52,19 +52,19 @@ export const ViewPrioritySelect: React.FC = ({ customButton={
    ); @@ -141,7 +147,7 @@ const MenuItem: React.FC = ({ {children} @@ -152,7 +158,7 @@ const MenuItem: React.FC = ({ type="button" className={`${className} ${ active ? "bg-brand-surface-1" : "" - } w-full select-none gap-2 truncate rounded px-1 py-1.5 text-left text-brand-secondary hover:bg-brand-surface-1 hover:text-brand-muted-1`} + } hover:text-brand-muted-1 w-full select-none gap-2 truncate rounded px-1 py-1.5 text-left text-brand-secondary hover:bg-brand-surface-1`} onClick={onClick} > {children} diff --git a/apps/app/components/ui/custom-search-select.tsx b/apps/app/components/ui/custom-search-select.tsx index d6e3d3396..8daeed336 100644 --- a/apps/app/components/ui/custom-search-select.tsx +++ b/apps/app/components/ui/custom-search-select.tsx @@ -68,11 +68,11 @@ export const CustomSearchSelect = ({ {customButton} ) : ( `${active || selected ? "bg-brand-surface-1" : ""} ${ selected ? "font-medium" : "" - } text-brand-muted-1 flex cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5` + } flex cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5 text-brand-secondary` } > {({ active, selected }) => ( diff --git a/apps/app/components/ui/custom-select.tsx b/apps/app/components/ui/custom-select.tsx index 62a87ec45..fd2a3b8b1 100644 --- a/apps/app/components/ui/custom-select.tsx +++ b/apps/app/components/ui/custom-select.tsx @@ -51,8 +51,8 @@ const CustomSelect = ({ ) : ( = ({ }} className={`${ renderAs === "input" - ? "block border-brand-base bg-transparent px-3 py-2 text-sm focus:outline-none" + ? "block px-3 py-2 text-sm focus:outline-none" : renderAs === "button" ? `px-3 py-1 text-xs shadow-sm ${ disabled ? "" : "hover:bg-brand-surface-1" @@ -44,7 +44,7 @@ export const CustomDatePicker: React.FC = ({ : "" } ${error ? "border-red-500 bg-red-100" : ""} ${ disabled ? "cursor-not-allowed" : "cursor-pointer" - } w-full rounded-md border bg-transparent caret-transparent ${className}`} + } w-full rounded-md border border-brand-base bg-transparent caret-transparent ${className}`} dateFormat="dd-MM-yyyy" isClearable={isClearable} disabled={disabled} diff --git a/apps/app/components/ui/multi-level-dropdown.tsx b/apps/app/components/ui/multi-level-dropdown.tsx index 2b2a9719b..8309b4441 100644 --- a/apps/app/components/ui/multi-level-dropdown.tsx +++ b/apps/app/components/ui/multi-level-dropdown.tsx @@ -41,7 +41,7 @@ export const MultiLevelDropdown: React.FC = ({
    setOpenChildFor(null)} - className={`group flex items-center justify-between gap-2 rounded-md border border-brand-base px-3 py-1.5 text-xs shadow-sm duration-300 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${ + className={`group flex items-center justify-between gap-2 rounded-md border border-brand-base px-3 py-1.5 text-xs shadow-sm duration-300 focus:outline-none ${ open ? "bg-brand-surface-1 text-brand-base" : "text-brand-secondary" }`} > @@ -83,8 +83,8 @@ export const MultiLevelDropdown: React.FC = ({ <>
    @@ -126,8 +126,8 @@ export const MultiLevelDropdown: React.FC = ({ onSelect(child.value); }} className={`${ - child.selected ? "bg-brand-surface-1" : "text-brand-base" - } flex w-full items-center break-all rounded px-1 py-1.5 text-left capitalize hover:bg-brand-surface-1`} + child.selected ? "bg-brand-surface-1" : "" + } flex w-full items-center break-all rounded px-1 py-1.5 text-left capitalize text-brand-secondary hover:bg-brand-surface-1`} > {child.label} diff --git a/apps/app/components/views/select-filters.tsx b/apps/app/components/views/select-filters.tsx index 732b115c2..2d25983c7 100644 --- a/apps/app/components/views/select-filters.tsx +++ b/apps/app/components/views/select-filters.tsx @@ -160,7 +160,7 @@ export const SelectFilters: React.FC = ({ label: (
    = ({
    {loading ? ( -
    +

    Loading your project...

    @@ -81,8 +80,8 @@ const ProjectAuthorizationWrapped: React.FC = ({ ) : error?.status === 401 || error?.status === 403 ? ( ) : error?.status === 404 ? ( -
    -
    +
    +

    No such project exist. Create one?

    { @@ -121,7 +120,11 @@ const ProjectAuthorizationWrapped: React.FC = ({ className={`flex w-full flex-grow flex-col ${ noPadding || issueView === "list" ? "" : settingsLayout ? "p-8 lg:px-28" : "p-8" } ${ - bg === "primary" ? "bg-brand-base" : bg === "secondary" ? "bg-brand-surface-1" : "bg-brand-base" + bg === "primary" + ? "bg-brand-base" + : bg === "secondary" + ? "bg-brand-surface-1" + : "bg-brand-base" }`} > {settingsLayout && ( diff --git a/apps/app/styles/globals.css b/apps/app/styles/globals.css index afd3d59b9..7e4137273 100644 --- a/apps/app/styles/globals.css +++ b/apps/app/styles/globals.css @@ -113,12 +113,11 @@ .horizontal-scroll-enable::-webkit-scrollbar-track { height: 7px; - background-color: #f5f5f5; } .horizontal-scroll-enable::-webkit-scrollbar-thumb { border-radius: 5px; - background-color: #9ca3af; + background-color: rgba(var(--color-bg-surface-1)); } .vertical-scroll-enable::-webkit-scrollbar { @@ -128,12 +127,11 @@ .vertical-scroll-enable::-webkit-scrollbar-track { width: 5px; - background-color: #f5f5f5; } .vertical-scroll-enable::-webkit-scrollbar-thumb { border-radius: 5px; - background-color: #9ca3af; + background-color: rgba(var(--color-bg-surface-1)); } /* end scrollbar style */ diff --git a/apps/app/tailwind.config.js b/apps/app/tailwind.config.js index 85c18cda1..ddb9fb4ad 100644 --- a/apps/app/tailwind.config.js +++ b/apps/app/tailwind.config.js @@ -1,8 +1,7 @@ function withOpacity(variableName) { return ({ opacityValue }) => { - if (opacityValue !== undefined) { - return `rgba(var(${variableName}), ${opacityValue})`; - } + if (opacityValue !== undefined) return `rgba(var(${variableName}), ${opacityValue})`; + return `rgb(var(${variableName}))`; }; } @@ -19,11 +18,14 @@ module.exports = { secondary: "white", brand: { accent: withOpacity("--color-accent"), + base: withOpacity("--color-border"), }, }, borderColor: { brand: { base: withOpacity("--color-border"), + "surface-1": withOpacity("--color-bg-surface-1"), + "surface-2": withOpacity("--color-bg-surface-2"), }, }, backgroundColor: { From 73a8bbb31f1a5288c5551100b43481bbd9a609c9 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Thu, 20 Apr 2023 18:13:49 +0530 Subject: [PATCH 035/120] feat: release information endpoint (#876) * dev: init release notes * feat: API endpoint to fetch get last 5 release information --- apiserver/plane/api/urls.py | 10 +++++++++ apiserver/plane/api/views/__init__.py | 3 +++ apiserver/plane/api/views/release.py | 21 +++++++++++++++++++ apiserver/plane/settings/local.py | 3 +++ apiserver/plane/settings/production.py | 6 +++++- apiserver/plane/settings/staging.py | 4 +++- apiserver/plane/utils/integrations/github.py | 22 ++++++++++++++++++++ 7 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 apiserver/plane/api/views/release.py diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index 0e27ce665..029358c98 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -146,6 +146,9 @@ from plane.api.views import ( # Gpt GPTIntegrationEndpoint, ## End Gpt + # Release Notes + ReleaseNotesEndpoint, + ## End Release Notes ) @@ -1284,4 +1287,11 @@ urlpatterns = [ name="importer", ), ## End Gpt + # Release Notes + path( + "release-notes/", + ReleaseNotesEndpoint.as_view(), + name="release-notes", + ), + ## End Release Notes ] diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 82eb49e44..adeb285f0 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -138,3 +138,6 @@ from .estimate import ( ProjectEstimatePointEndpoint, BulkEstimatePointEndpoint, ) + + +from .release import ReleaseNotesEndpoint diff --git a/apiserver/plane/api/views/release.py b/apiserver/plane/api/views/release.py new file mode 100644 index 000000000..de827c896 --- /dev/null +++ b/apiserver/plane/api/views/release.py @@ -0,0 +1,21 @@ +# Third party imports +from rest_framework.response import Response +from rest_framework import status +from sentry_sdk import capture_exception + +# Module imports +from .base import BaseAPIView +from plane.utils.integrations.github import get_release_notes + + +class ReleaseNotesEndpoint(BaseAPIView): + def get(self, request): + try: + release_notes = get_release_notes() + return Response(release_notes, status=status.HTTP_200_OK) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) diff --git a/apiserver/plane/settings/local.py b/apiserver/plane/settings/local.py index c3bf65588..e03a0b822 100644 --- a/apiserver/plane/settings/local.py +++ b/apiserver/plane/settings/local.py @@ -83,3 +83,6 @@ LOGGER_BASE_URL = os.environ.get("LOGGER_BASE_URL", False) CELERY_RESULT_BACKEND = os.environ.get("REDIS_URL") CELERY_BROKER_URL = os.environ.get("REDIS_URL") + + +GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN", False) \ No newline at end of file diff --git a/apiserver/plane/settings/production.py b/apiserver/plane/settings/production.py index d8f2a8bb7..6598a7c29 100644 --- a/apiserver/plane/settings/production.py +++ b/apiserver/plane/settings/production.py @@ -240,7 +240,9 @@ SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN", False) LOGGER_BASE_URL = os.environ.get("LOGGER_BASE_URL", False) redis_url = os.environ.get("REDIS_URL") -broker_url = f"{redis_url}?ssl_cert_reqs={ssl.CERT_NONE.name}&ssl_ca_certs={certifi.where()}" +broker_url = ( + f"{redis_url}?ssl_cert_reqs={ssl.CERT_NONE.name}&ssl_ca_certs={certifi.where()}" +) if DOCKERIZED: CELERY_BROKER_URL = REDIS_URL @@ -248,3 +250,5 @@ if DOCKERIZED: else: CELERY_RESULT_BACKEND = broker_url CELERY_BROKER_URL = broker_url + +GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN", False) diff --git a/apiserver/plane/settings/staging.py b/apiserver/plane/settings/staging.py index 384116ba3..b43327c09 100644 --- a/apiserver/plane/settings/staging.py +++ b/apiserver/plane/settings/staging.py @@ -203,4 +203,6 @@ redis_url = os.environ.get("REDIS_URL") broker_url = f"{redis_url}?ssl_cert_reqs={ssl.CERT_NONE.name}&ssl_ca_certs={certifi.where()}" CELERY_RESULT_BACKEND = broker_url -CELERY_BROKER_URL = broker_url \ No newline at end of file +CELERY_BROKER_URL = broker_url + +GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN", False) diff --git a/apiserver/plane/utils/integrations/github.py b/apiserver/plane/utils/integrations/github.py index d9185cb10..d9aecece1 100644 --- a/apiserver/plane/utils/integrations/github.py +++ b/apiserver/plane/utils/integrations/github.py @@ -5,6 +5,7 @@ from urllib.parse import urlparse, parse_qs from datetime import datetime, timedelta from cryptography.hazmat.primitives.serialization import load_pem_private_key from cryptography.hazmat.backends import default_backend +from django.conf import settings def get_jwt_token(): @@ -128,3 +129,24 @@ def get_github_repo_details(access_tokens_url, owner, repo): ).json() return open_issues, total_labels, collaborators + + +def get_release_notes(): + token = settings.GITHUB_ACCESS_TOKEN + + if token: + headers = { + "Authorization": "Bearer " + str(token), + "Accept": "application/vnd.github.v3+json", + } + else: + headers = { + "Accept": "application/vnd.github.v3+json", + } + url = "https://api.github.com/repos/makeplane/plane/releases?per_page=5&page=1" + response = requests.get(url, headers=headers) + + if response.status_code != 200: + return {"error": "Unable to render information from Github Repository"} + + return response.json() From 5b6caadd6fa1f320bdd60c0197e4b50e6220353e Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Thu, 20 Apr 2023 18:14:05 +0530 Subject: [PATCH 036/120] chore: state delete validations endpoint (#880) --- apiserver/plane/api/urls.py | 6 +++++ apiserver/plane/api/views/__init__.py | 2 +- apiserver/plane/api/views/state.py | 39 ++++++++++++++++++++++++--- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index 029358c98..f064d370a 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -79,6 +79,7 @@ from plane.api.views import ( ## End Issues # States StateViewSet, + StateDeleteIssueCheckEndpoint, ## End States # Estimates EstimateViewSet, @@ -509,6 +510,11 @@ urlpatterns = [ ), name="project-state", ), + path( + "workspaces//projects//states//", + StateDeleteIssueCheckEndpoint.as_view(), + name="state-delete-check", + ), # End States ## # States path( diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index adeb285f0..18809cd9d 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -42,7 +42,7 @@ from .workspace import ( UserWorkspaceDashboardEndpoint, WorkspaceThemeViewSet, ) -from .state import StateViewSet +from .state import StateViewSet, StateDeleteIssueCheckEndpoint from .shortcut import ShortCutViewSet from .view import IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet from .cycle import ( diff --git a/apiserver/plane/api/views/state.py b/apiserver/plane/api/views/state.py index 048ac4a6a..f1e409e14 100644 --- a/apiserver/plane/api/views/state.py +++ b/apiserver/plane/api/views/state.py @@ -11,10 +11,10 @@ from sentry_sdk import capture_exception # Module imports -from . import BaseViewSet +from . import BaseViewSet, BaseAPIView from plane.api.serializers import StateSerializer from plane.api.permissions import ProjectEntityPermission -from plane.db.models import State +from plane.db.models import State, Issue class StateViewSet(BaseViewSet): @@ -53,7 +53,10 @@ class StateViewSet(BaseViewSet): ) except Exception as e: capture_exception(e) - return Response({"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) def list(self, request, slug, project_id): try: @@ -85,7 +88,37 @@ class StateViewSet(BaseViewSet): {"error": "Default state cannot be deleted"}, status=False ) + # Check for any issues in the state + issue_exist = Issue.objects.filter(state=pk).exists() + + if issue_exist: + return Response( + { + "error": "The state is not empty, only empty states can be deleted" + }, + status=status.HTTP_400_BAD_REQUEST, + ) + state.delete() return Response(status=status.HTTP_204_NO_CONTENT) except State.DoesNotExist: return Response({"error": "State does not exists"}, status=status.HTTP_404) + + +class StateDeleteIssueCheckEndpoint(BaseAPIView): + permission_classes = [ + ProjectEntityPermission, + ] + + def get(self, request, slug, project_id, pk): + try: + issue_count = Issue.objects.filter( + state=pk, workspace__slug=slug, project_id=project_id + ).count() + return Response({"issue_count": issue_count}, status=status.HTTP_200_OK) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) From e68a5382f9224791961f3304475eb130b604ffdd Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Thu, 20 Apr 2023 18:14:24 +0530 Subject: [PATCH 037/120] feat: sub issue state distribution (#885) * feat: sub issue state distribution * dev: update the response structure to match consistency * dev: update the query for sub issue distribution to include 0 issue groups as well --- apiserver/plane/api/views/issue.py | 35 +++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 1f604d271..c0f54bcdb 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -1,13 +1,14 @@ # Python imports import json import random -from itertools import groupby, chain +from itertools import chain # Django imports -from django.db.models import Prefetch, OuterRef, Func, F, Q +from django.db.models import Prefetch, OuterRef, Func, F, Q, Count from django.core.serializers.json import DjangoJSONEncoder from django.utils.decorators import method_decorator from django.views.decorators.gzip import gzip_page +from django.db.models.functions import Coalesce # Third Party imports from rest_framework.response import Response @@ -46,6 +47,7 @@ from plane.db.models import ( Label, IssueLink, IssueAttachment, + State, ) from plane.bgtasks.issue_activites_task import issue_activity from plane.utils.grouper import group_results @@ -590,10 +592,33 @@ class SubIssuesEndpoint(BaseAPIView): .prefetch_related("labels") ) - serializer = IssueLiteSerializer(sub_issues, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) + state_distribution = ( + State.objects.filter(workspace__slug=slug, project_id=project_id) + .annotate( + state_count=Count( + "state_issue", + filter=Q(state_issue__parent_id=issue_id), + ) + ) + .order_by("group") + .values("group", "state_count") + ) + + result = {item["group"]: item["state_count"] for item in state_distribution} + + serializer = IssueLiteSerializer( + sub_issues, + many=True, + ) + return Response( + { + "sub_issues": serializer.data, + "state_distribution": result, + }, + status=status.HTTP_200_OK, + ) except Exception as e: - capture_exception(e) + print(e) return Response( {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, From 4dda4ec6101f2b9fdac16c9c82fde7b1e92ddedd Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Thu, 20 Apr 2023 18:14:38 +0530 Subject: [PATCH 038/120] chore: single endpoint to create estimate and estimate points (#897) --- apiserver/plane/api/urls.py | 5 ++++ apiserver/plane/api/views/estimate.py | 37 +++++++++++++++++++++------ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index f064d370a..2c202a1c0 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -566,6 +566,11 @@ urlpatterns = [ ProjectEstimatePointEndpoint.as_view(), name="project-estimate-points", ), + path( + "workspaces//projects//estimates/bulk-estimate-points/", + BulkEstimatePointEndpoint.as_view(), + name="bulk-create-estimate-points", + ), path( "workspaces//projects//estimates//bulk-estimate-points/", BulkEstimatePointEndpoint.as_view(), diff --git a/apiserver/plane/api/views/estimate.py b/apiserver/plane/api/views/estimate.py index 96d0ed1a4..99374282d 100644 --- a/apiserver/plane/api/views/estimate.py +++ b/apiserver/plane/api/views/estimate.py @@ -146,11 +146,13 @@ class BulkEstimatePointEndpoint(BaseAPIView): ProjectEntityPermission, ] - def post(self, request, slug, project_id, estimate_id): + def post(self, request, slug, project_id): try: - estimate = Estimate.objects.get( - pk=estimate_id, workspace__slug=slug, project=project_id - ) + if not request.data.get("estimate", False): + return Response( + {"error": "Estimate is required"}, + status=status.HTTP_400_BAD_REQUEST, + ) estimate_points = request.data.get("estimate_points", []) @@ -160,6 +162,18 @@ class BulkEstimatePointEndpoint(BaseAPIView): status=status.HTTP_400_BAD_REQUEST, ) + estimate_serializer = EstimateSerializer(data=request.data.get("estimate")) + if not estimate_serializer.is_valid(): + return Response( + estimate_serializer.errors, status=status.HTTP_400_BAD_REQUEST + ) + try: + estimate = estimate_serializer.save(project_id=project_id) + except IntegrityError: + return Response( + {"errror": "Estimate with the name already exists"}, + status=status.HTTP_400_BAD_REQUEST, + ) estimate_points = EstimatePoint.objects.bulk_create( [ EstimatePoint( @@ -178,9 +192,17 @@ class BulkEstimatePointEndpoint(BaseAPIView): ignore_conflicts=True, ) - serializer = EstimatePointSerializer(estimate_points, many=True) + estimate_point_serializer = EstimatePointSerializer( + estimate_points, many=True + ) - return Response(serializer.data, status=status.HTTP_200_OK) + return Response( + { + "estimate": estimate_serializer.data, + "estimate_points": estimate_point_serializer.data, + }, + status=status.HTTP_200_OK, + ) except Estimate.DoesNotExist: return Response( {"error": "Estimate does not exist"}, @@ -212,7 +234,6 @@ class BulkEstimatePointEndpoint(BaseAPIView): estimate_id=estimate_id, ) - print(estimate_points) updated_estimate_points = [] for estimate_point in estimate_points: # Find the data for that estimate point @@ -238,7 +259,7 @@ class BulkEstimatePointEndpoint(BaseAPIView): {"error": "Estimate does not exist"}, status=status.HTTP_400_BAD_REQUEST ) except Exception as e: - print(e) + capture_exception(e) return Response( {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, From 73388195ef3af2beb68b4b9e7a6756663ea57171 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Thu, 20 Apr 2023 19:04:10 +0530 Subject: [PATCH 039/120] chore: page block input (#907) * fix: sidebar workspace dropdown logo * chore: changed textarea to input for page block --- .../pages/create-update-block-inline.tsx | 21 +++++++++---------- .../components/pages/single-page-block.tsx | 21 ++++++++++--------- .../app/components/rich-text-editor/index.tsx | 4 +--- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/apps/app/components/pages/create-update-block-inline.tsx b/apps/app/components/pages/create-update-block-inline.tsx index d3e3dd92e..089025a23 100644 --- a/apps/app/components/pages/create-update-block-inline.tsx +++ b/apps/app/components/pages/create-update-block-inline.tsx @@ -16,7 +16,7 @@ import aiService from "services/ai.service"; // hooks import useToast from "hooks/use-toast"; // ui -import { Loader, PrimaryButton, SecondaryButton, TextArea } from "components/ui"; +import { Input, Loader, PrimaryButton, SecondaryButton, TextArea } from "components/ui"; // types import { IPageBlock } from "types"; // fetch-keys @@ -232,23 +232,22 @@ export const CreateUpdateBlockInline: React.FC = ({ return (
    -
    +
    -