diff --git a/.env.example b/.env.example index fc1aef49d..2b6931761 100644 --- a/.env.example +++ b/.env.example @@ -9,11 +9,11 @@ NEXT_PUBLIC_GITHUB_ID="" NEXT_PUBLIC_GITHUB_APP_NAME="" # Sentry DSN for error monitoring NEXT_PUBLIC_SENTRY_DSN="" -# Enable/Disable OAUTH - default 0 for selfhosted instance +# Enable/Disable OAUTH - default 0 for selfhosted instance NEXT_PUBLIC_ENABLE_OAUTH=0 # Enable/Disable sentry NEXT_PUBLIC_ENABLE_SENTRY=0 -# Enable/Disable session recording +# Enable/Disable session recording NEXT_PUBLIC_ENABLE_SESSION_RECORDER=0 # Enable/Disable event tracking NEXT_PUBLIC_TRACK_EVENTS=0 @@ -59,15 +59,16 @@ AWS_S3_BUCKET_NAME="uploads" FILE_SIZE_LIMIT=5242880 # GPT settings -OPENAI_API_KEY="" -GPT_ENGINE="" +OPENAI_API_BASE="https://api.openai.com/v1" # change if using a custom endpoint +OPENAI_API_KEY="sk-" # add your openai key here +GPT_ENGINE="gpt-3.5-turbo" # use "gpt-4" if you have access # Github GITHUB_CLIENT_SECRET="" # For fetching release notes # Settings related to Docker DOCKERIZED=1 -# set to 1 If using the pre-configured minio setup +# set to 1 If using the pre-configured minio setup USE_MINIO=1 # Nginx Configuration @@ -79,4 +80,4 @@ DEFAULT_PASSWORD="password123" # SignUps ENABLE_SIGNUP="1" -# Auto generated and Required that will be generated from setup.sh \ No newline at end of file +# Auto generated and Required that will be generated from setup.sh diff --git a/apiserver/plane/api/views/gpt.py b/apiserver/plane/api/views/gpt.py index a48bea242..8878e99a5 100644 --- a/apiserver/plane/api/views/gpt.py +++ b/apiserver/plane/api/views/gpt.py @@ -67,7 +67,7 @@ class GPTIntegrationEndpoint(BaseAPIView): openai.api_key = settings.OPENAI_API_KEY response = openai.Completion.create( - engine=settings.GPT_ENGINE, + model=settings.GPT_ENGINE, prompt=final_text, temperature=0.7, max_tokens=1024, diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index d7dfa9066..6d7d6c0d7 100644 --- a/apiserver/plane/bgtasks/issue_activites_task.py +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -561,7 +561,6 @@ def track_estimate_points( def track_archive_at( requested_data, current_instance, issue_id, project, actor, issue_activities ): - if requested_data.get("archived_at") is None: issue_activities.append( IssueActivity( @@ -572,6 +571,8 @@ def track_archive_at( verb="updated", actor=actor, field="archvied_at", + old_value="archive", + new_value="restore", ) ) else: @@ -584,6 +585,8 @@ def track_archive_at( verb="updated", actor=actor, field="archvied_at", + old_value=None, + new_value="archive", ) ) @@ -982,11 +985,16 @@ def delete_attachment_activity( ) - # Receive message from room group @shared_task def issue_activity( - type, requested_data, current_instance, issue_id, actor_id, project_id, subscriber=True + type, + requested_data, + current_instance, + issue_id, + actor_id, + project_id, + subscriber=True, ): try: issue_activities = [] @@ -998,12 +1006,13 @@ def issue_activity( if issue is not None: issue.updated_at = timezone.now() issue.save() - if subscriber: # add the user to issue subscriber try: - _ = IssueSubscriber.objects.get_or_create(issue_id=issue_id, subscriber=actor) + _ = IssueSubscriber.objects.get_or_create( + issue_id=issue_id, subscriber=actor + ) except Exception as e: pass diff --git a/apiserver/plane/settings/local.py b/apiserver/plane/settings/local.py index e6f5f8e39..194b2629f 100644 --- a/apiserver/plane/settings/local.py +++ b/apiserver/plane/settings/local.py @@ -10,9 +10,7 @@ from sentry_sdk.integrations.redis import RedisIntegration from .common import * # noqa -DEBUG = int(os.environ.get( - "DEBUG", 1 -)) == 1 +DEBUG = int(os.environ.get("DEBUG", 1)) == 1 EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" @@ -27,13 +25,11 @@ DATABASES = { } } -DOCKERIZED = int(os.environ.get( - "DOCKERIZED", 0 -)) == 1 +DOCKERIZED = int(os.environ.get("DOCKERIZED", 0)) == 1 USE_MINIO = int(os.environ.get("USE_MINIO", 0)) == 1 -FILE_SIZE_LIMIT = int(os.environ.get("FILE_SIZE_LIMIT", 5242880)) +FILE_SIZE_LIMIT = int(os.environ.get("FILE_SIZE_LIMIT", 5242880)) if DOCKERIZED: DATABASES["default"] = dj_database_url.config() @@ -65,6 +61,27 @@ if os.environ.get("SENTRY_DSN", False): traces_sample_rate=0.7, profiles_sample_rate=1.0, ) +else: + LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "handlers": { + "console": { + "class": "logging.StreamHandler", + }, + }, + "root": { + "handlers": ["console"], + "level": "DEBUG", + }, + "loggers": { + "*": { + "handlers": ["console"], + "level": "DEBUG", + "propagate": True, + }, + }, + } REDIS_HOST = "localhost" REDIS_PORT = 6379 @@ -83,8 +100,9 @@ PROXY_BASE_URL = os.environ.get("PROXY_BASE_URL", False) ANALYTICS_SECRET_KEY = os.environ.get("ANALYTICS_SECRET_KEY", False) ANALYTICS_BASE_API = os.environ.get("ANALYTICS_BASE_API", False) +OPENAI_API_BASE = os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1") OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", False) -GPT_ENGINE = os.environ.get("GPT_ENGINE", "text-davinci-003") +GPT_ENGINE = os.environ.get("GPT_ENGINE", "gpt-3.5-turbo") SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN", False) @@ -95,4 +113,4 @@ CELERY_BROKER_URL = os.environ.get("REDIS_URL") GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN", False) -ENABLE_SIGNUP = os.environ.get("ENABLE_SIGNUP", "1") == "1" \ No newline at end of file +ENABLE_SIGNUP = os.environ.get("ENABLE_SIGNUP", "1") == "1" diff --git a/apiserver/plane/settings/production.py b/apiserver/plane/settings/production.py index 2e40c5998..98e22bcbd 100644 --- a/apiserver/plane/settings/production.py +++ b/apiserver/plane/settings/production.py @@ -246,8 +246,9 @@ PROXY_BASE_URL = os.environ.get("PROXY_BASE_URL", False) ANALYTICS_SECRET_KEY = os.environ.get("ANALYTICS_SECRET_KEY", False) ANALYTICS_BASE_API = os.environ.get("ANALYTICS_BASE_API", False) +OPENAI_API_BASE = os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1") OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", False) -GPT_ENGINE = os.environ.get("GPT_ENGINE", "text-davinci-003") +GPT_ENGINE = os.environ.get("GPT_ENGINE", "gpt-3.5-turbo") SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN", False) diff --git a/apiserver/plane/settings/staging.py b/apiserver/plane/settings/staging.py index 076bb3e3c..daf8f974b 100644 --- a/apiserver/plane/settings/staging.py +++ b/apiserver/plane/settings/staging.py @@ -11,10 +11,9 @@ from sentry_sdk.integrations.django import DjangoIntegration from sentry_sdk.integrations.redis import RedisIntegration from .common import * # noqa + # Database -DEBUG = int(os.environ.get( - "DEBUG", 1 -)) == 1 +DEBUG = int(os.environ.get("DEBUG", 1)) == 1 DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql_psycopg2", @@ -56,9 +55,7 @@ STORAGES = { # Make true if running in a docker environment -DOCKERIZED = int(os.environ.get( - "DOCKERIZED", 0 -)) == 1 +DOCKERIZED = int(os.environ.get("DOCKERIZED", 0)) == 1 FILE_SIZE_LIMIT = int(os.environ.get("FILE_SIZE_LIMIT", 5242880)) USE_MINIO = int(os.environ.get("USE_MINIO", 0)) == 1 @@ -201,15 +198,19 @@ PROXY_BASE_URL = os.environ.get("PROXY_BASE_URL", False) ANALYTICS_SECRET_KEY = os.environ.get("ANALYTICS_SECRET_KEY", False) ANALYTICS_BASE_API = os.environ.get("ANALYTICS_BASE_API", False) + +OPENAI_API_BASE = os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1") OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", False) -GPT_ENGINE = os.environ.get("GPT_ENGINE", "text-davinci-003") +GPT_ENGINE = os.environ.get("GPT_ENGINE", "gpt-3.5-turbo") 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()}" +) CELERY_RESULT_BACKEND = broker_url CELERY_BROKER_URL = broker_url diff --git a/apps/app/components/analytics/custom-analytics/sidebar.tsx b/apps/app/components/analytics/custom-analytics/sidebar.tsx index 54428486b..64428a468 100644 --- a/apps/app/components/analytics/custom-analytics/sidebar.tsx +++ b/apps/app/components/analytics/custom-analytics/sidebar.tsx @@ -238,8 +238,8 @@ export const AnalyticsSidebar: React.FC = ({ {project?.name.charAt(0)} )} -
- {project.name} +
+

{project.name}

({project.identifier}) diff --git a/apps/app/components/core/board-view/single-issue.tsx b/apps/app/components/core/board-view/single-issue.tsx index 27a95d29e..e019d3a9e 100644 --- a/apps/app/components/core/board-view/single-issue.tsx +++ b/apps/app/components/core/board-view/single-issue.tsx @@ -334,9 +334,7 @@ export const SingleBoardIssue: React.FC = ({ {issue.project_detail.identifier}-{issue.sequence_id} )} -
- {issue.name} -
+
{issue.name}
diff --git a/apps/app/components/core/filters/issues-view-filter.tsx b/apps/app/components/core/filters/issues-view-filter.tsx index 2958b966d..8b625fbcf 100644 --- a/apps/app/components/core/filters/issues-view-filter.tsx +++ b/apps/app/components/core/filters/issues-view-filter.tsx @@ -23,10 +23,33 @@ import { import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; import { checkIfArraysHaveSameElements } from "helpers/array.helper"; // types -import { Properties } from "types"; +import { Properties, TIssueViewOptions } from "types"; // constants import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue"; +const issueViewOptions: { type: TIssueViewOptions; icon: any }[] = [ + { + type: "list", + icon: , + }, + { + type: "kanban", + icon: , + }, + { + type: "calendar", + icon: , + }, + { + type: "spreadsheet", + icon: , + }, + { + type: "gantt_chart", + icon: , + }, +]; + export const IssuesFilterView: React.FC = () => { const router = useRouter(); const { workspaceSlug, projectId, viewId } = router.query; @@ -56,53 +79,20 @@ export const IssuesFilterView: React.FC = () => { return (
- - - - - + {issueViewOptions.map((option) => ( + + ))}
{ {({ open }) => ( <> = ({ e.preventDefault(); handleEditCycle(); }} - className="flex cursor-pointer items-center rounded p-1 text-custom-text-200 duration-300 hover:bg-custom-background-90" + className="cursor-pointer rounded p-1 text-custom-text-200 duration-300 hover:bg-custom-background-80" > - - - + )} diff --git a/apps/app/components/cycles/single-cycle-list.tsx b/apps/app/components/cycles/single-cycle-list.tsx index 33cc5e3b6..32bd18539 100644 --- a/apps/app/components/cycles/single-cycle-list.tsx +++ b/apps/app/components/cycles/single-cycle-list.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from "react"; import Link from "next/link"; -import Image from "next/image"; import { useRouter } from "next/router"; // hooks @@ -157,7 +156,7 @@ export const SingleCycleList: React.FC = ({
- +
= ({ position="top-left" >

- {truncateText(cycle.name, 70)} + {truncateText(cycle.name, 60)}

{cycle.description}

-
- +
+
= ({ }`} > {cycleStatus === "current" ? ( - + - {findHowManyDaysLeft(cycle.end_date ?? new Date())} Days Left + {findHowManyDaysLeft(cycle.end_date ?? new Date())} days left ) : cycleStatus === "upcoming" ? ( - {findHowManyDaysLeft(cycle.start_date ?? new Date())} Days Left + {findHowManyDaysLeft(cycle.start_date ?? new Date())} days left ) : cycleStatus === "completed" ? ( @@ -236,12 +235,12 @@ export const SingleCycleList: React.FC = ({ {cycleStatus !== "draft" && (
-
+
{renderShortDateWithYearFormat(startDate)}
-
+
{renderShortDateWithYearFormat(endDate)}
@@ -287,7 +286,7 @@ export const SingleCycleList: React.FC = ({ }`} > {cycleStatus === "current" ? ( - + {cycle.total_issues > 0 ? ( <> = ({
- +
diff --git a/apps/app/components/estimates/single-estimate.tsx b/apps/app/components/estimates/single-estimate.tsx index 21ffe4aff..17c111559 100644 --- a/apps/app/components/estimates/single-estimate.tsx +++ b/apps/app/components/estimates/single-estimate.tsx @@ -72,7 +72,7 @@ export const SingleEstimate: React.FC = ({
{estimate.name} {projectDetails?.estimate && projectDetails?.estimate === estimate.id && ( - + In use )} @@ -83,7 +83,10 @@ export const SingleEstimate: React.FC = ({
{projectDetails?.estimate !== estimate.id && estimate.points.length > 0 && ( - + Use )} diff --git a/apps/app/components/issues/view-select/due-date.tsx b/apps/app/components/issues/view-select/due-date.tsx index efd568c30..f45440cfc 100644 --- a/apps/app/components/issues/view-select/due-date.tsx +++ b/apps/app/components/issues/view-select/due-date.tsx @@ -8,6 +8,7 @@ import { findHowManyDaysLeft, renderShortDateWithYearFormat } from "helpers/date import trackEventServices from "services/track-event.service"; // types import { ICurrentUserResponse, IIssue } from "types"; +import useIssuesView from "hooks/use-issues-view"; type Props = { issue: IIssue; @@ -29,6 +30,8 @@ export const ViewDueDateSelect: React.FC = ({ const router = useRouter(); const { workspaceSlug } = router.query; + const { issueView } = useIssuesView(); + return ( = ({ user ); }} - className={issue?.target_date ? "w-[6.5rem]" : "w-[5rem] text-center"} + className={`${issue?.target_date ? "w-[6.5rem]" : "w-[5rem] text-center"} ${ + issueView === "kanban" ? "bg-custom-background-90" : "bg-custom-background-100" + }`} noBorder={noBorder} disabled={isNotAllowed} /> diff --git a/apps/app/components/issues/view-select/label.tsx b/apps/app/components/issues/view-select/label.tsx index 826ba6560..098576dd7 100644 --- a/apps/app/components/issues/view-select/label.tsx +++ b/apps/app/components/issues/view-select/label.tsx @@ -71,9 +71,15 @@ export const ViewLabelSelect: React.FC = ({ position={tooltipPosition} tooltipHeading="Labels" tooltipContent={ - issue.label_details.length > 0 - ? issue.label_details.map((label) => label.name ?? "").join(", ") - : "No Label" + issue.labels.length > 0 + ? issue.labels + .map((labelId) => { + const label = issueLabels?.find((l) => l.id === labelId); + + return label?.name ?? ""; + }) + .join(", ") + : "No label" } >
= ({ isNotAllowed ? "cursor-not-allowed" : "cursor-pointer" } items-center gap-2 text-custom-text-200`} > - {issue.label_details.length > 0 ? ( + {issue.labels.length > 0 ? ( <> - {issue.label_details.slice(0, 4).map((label, index) => ( -
- -
- ))} - {issue.label_details.length > 4 ? +{issue.label_details.length - 4} : null} + {issue.labels.slice(0, 4).map((labelId, index) => { + const label = issueLabels?.find((l) => l.id === labelId); + + return ( +
+ +
+ ); + })} + {issue.labels.length > 4 ? +{issue.labels.length - 4} : null} ) : ( <> diff --git a/apps/app/components/issues/view-select/state.tsx b/apps/app/components/issues/view-select/state.tsx index 75d158faf..6f679fe7c 100644 --- a/apps/app/components/issues/view-select/state.tsx +++ b/apps/app/components/issues/view-select/state.tsx @@ -10,7 +10,6 @@ import { CustomSearchSelect, Tooltip } from "components/ui"; // icons import { getStateGroupIcon } from "components/icons"; // helpers -import { addSpaceIfCamelCase } from "helpers/string.helper"; import { getStatesList } from "helpers/state.helper"; // types import { ICurrentUserResponse, IIssue } from "types"; @@ -67,7 +66,7 @@ export const ViewStateSelect: React.FC = ({ const stateLabel = (
diff --git a/apps/app/components/modules/single-module-card.tsx b/apps/app/components/modules/single-module-card.tsx index a91c29763..16535e77f 100644 --- a/apps/app/components/modules/single-module-card.tsx +++ b/apps/app/components/modules/single-module-card.tsx @@ -185,12 +185,12 @@ export const SingleModuleCard: React.FC = ({ module, handleEditModule, us
Start: - {renderShortDateWithYearFormat(startDate)} + {renderShortDateWithYearFormat(startDate, "Not set")}
End: - {renderShortDateWithYearFormat(endDate)} + {renderShortDateWithYearFormat(endDate, "Not set")}
diff --git a/apps/app/components/project/create-project-modal.tsx b/apps/app/components/project/create-project-modal.tsx index 06a3ff29b..5f289d7c5 100644 --- a/apps/app/components/project/create-project-modal.tsx +++ b/apps/app/components/project/create-project-modal.tsx @@ -184,7 +184,7 @@ export const CreateProjectModal: React.FC = (props) => { leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - +
{watch("cover_image") !== null && ( = ({ )} {!sidebarCollapse && ( -

+

{truncateText(project?.name, 20)} -

+
)}
{!sidebarCollapse && ( diff --git a/apps/app/components/ui/circular-progress.tsx b/apps/app/components/ui/circular-progress.tsx new file mode 100644 index 000000000..af808ee8a --- /dev/null +++ b/apps/app/components/ui/circular-progress.tsx @@ -0,0 +1,39 @@ +import React, { useEffect, useState } from "react"; + +export const CircularProgress = ({ progress }: { progress: number }) => { + const [circumference, setCircumference] = useState(0); + + useEffect(() => { + const radius = 40; + const calcCircumference = 2 * Math.PI * radius; + setCircumference(calcCircumference); + }, []); + + const progressAngle = (progress / 100) * 360 >= 360 ? 359.9 : (progress / 100) * 360; + const progressX = 50 + Math.cos((progressAngle - 90) * (Math.PI / 180)) * 40; + const progressY = 50 + Math.sin((progressAngle - 90) * (Math.PI / 180)) * 40; + + return ( +
+ + + 50 ? 1 : 0} 1 ${progressX} ${progressY} + L50 50 Z`} + strokeWidth="12" + strokeLinecap="round" + /> + +
+ ); +}; diff --git a/apps/app/components/ui/custom-menu.tsx b/apps/app/components/ui/custom-menu.tsx index a7014b633..e42e49d21 100644 --- a/apps/app/components/ui/custom-menu.tsx +++ b/apps/app/components/ui/custom-menu.tsx @@ -59,7 +59,7 @@ const CustomMenu = ({ {ellipsis || verticalEllipsis ? ( `${active || selected ? "bg-custom-background-80" : ""} ${ - selected ? "font-medium" : "" - } flex cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5 text-custom-text-200` + selected ? "text-custom-text-100" : "text-custom-text-200" + } flex cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5` } > {({ active, selected }) => ( @@ -157,7 +157,7 @@ export const CustomSearchSelect = ({ {option.content} {multiple ? (
diff --git a/apps/app/components/ui/custom-select.tsx b/apps/app/components/ui/custom-select.tsx index 1b5513666..be26ec5be 100644 --- a/apps/app/components/ui/custom-select.tsx +++ b/apps/app/components/ui/custom-select.tsx @@ -118,8 +118,8 @@ const Option: React.FC = ({ children, value, className }) => ( value={value} className={({ active, selected }) => `${className} ${active || selected ? "bg-custom-background-80" : ""} ${ - selected ? "font-medium" : "" - } cursor-pointer select-none truncate rounded px-1 py-1.5 text-custom-text-200` + selected ? "text-custom-text-100" : "text-custom-text-200" + } cursor-pointer select-none truncate rounded px-1 py-1.5` } > {({ selected }) => ( diff --git a/apps/app/components/ui/datepicker.tsx b/apps/app/components/ui/datepicker.tsx index 106fe9fad..a13220ee6 100644 --- a/apps/app/components/ui/datepicker.tsx +++ b/apps/app/components/ui/datepicker.tsx @@ -42,13 +42,13 @@ export const CustomDatePicker: React.FC = ({ : renderAs === "button" ? `px-2 py-1 text-xs shadow-sm ${ disabled ? "" : "hover:bg-custom-background-80" - } duration-300 focus:border-custom-primary focus:outline-none focus:ring-1 focus:ring-custom-primary` + } duration-300` : "" } ${error ? "border-red-500 bg-red-100" : ""} ${ disabled ? "cursor-not-allowed" : "cursor-pointer" } ${ noBorder ? "" : "border border-custom-border-100" - } w-full rounded-md bg-transparent caret-transparent ${className}`} + } w-full rounded-md caret-transparent outline-none ${className}`} dateFormat="MMM dd, yyyy" isClearable={isClearable} disabled={disabled} diff --git a/apps/app/components/ui/empty-space.tsx b/apps/app/components/ui/empty-space.tsx index 8c09d4c16..f31280aeb 100644 --- a/apps/app/components/ui/empty-space.tsx +++ b/apps/app/components/ui/empty-space.tsx @@ -64,13 +64,13 @@ const EmptySpaceItem: React.FC = ({ title, description, Ico
-
-
{title}
- {description ?
{description}
: null} +
+
{title}
+ {description ?
{description}
: null}
diff --git a/apps/app/components/ui/index.ts b/apps/app/components/ui/index.ts index 6eb273c4a..e66f369df 100644 --- a/apps/app/components/ui/index.ts +++ b/apps/app/components/ui/index.ts @@ -26,3 +26,4 @@ export * from "./product-updates-modal"; export * from "./integration-and-import-export-banner"; export * from "./range-datepicker"; export * from "./icon"; +export * from "./circular-progress"; diff --git a/apps/app/components/ui/multi-level-dropdown.tsx b/apps/app/components/ui/multi-level-dropdown.tsx index cf2371471..14f2a8106 100644 --- a/apps/app/components/ui/multi-level-dropdown.tsx +++ b/apps/app/components/ui/multi-level-dropdown.tsx @@ -43,7 +43,7 @@ export const MultiLevelDropdown: React.FC = ({
setOpenChildFor(null)} - className={`group flex items-center justify-between gap-2 rounded-md border border-custom-border-100 px-3 py-1.5 text-xs shadow-sm duration-300 focus:outline-none ${ + className={`group flex items-center justify-between gap-2 rounded-md border border-custom-border-100 px-3 py-1.5 text-xs shadow-sm duration-300 focus:outline-none hover:text-custom-text-100 hover:bg-custom-background-90 ${ open ? "bg-custom-background-90 text-custom-text-100" : "text-custom-text-200" }`} > diff --git a/apps/app/components/workspace/help-section.tsx b/apps/app/components/workspace/help-section.tsx index 54659edd6..bbb50c21f 100644 --- a/apps/app/components/workspace/help-section.tsx +++ b/apps/app/components/workspace/help-section.tsx @@ -1,7 +1,11 @@ import { useState, useRef, FC } from "react"; +import { useRouter } from "next/router"; + import Link from "next/link"; +import useSWR from "swr"; + // headless ui import { Transition } from "@headlessui/react"; // hooks @@ -12,8 +16,19 @@ import { ArrowLongLeftIcon, ChatBubbleOvalLeftEllipsisIcon, RocketLaunchIcon, + ArrowUpCircleIcon, + XMarkIcon, } from "@heroicons/react/24/outline"; import { QuestionMarkCircleIcon, DocumentIcon, DiscordIcon, GithubIcon } from "components/icons"; +// services +import workspaceService from "services/workspace.service"; +// fetch-keys +import { WORKSPACE_DETAILS } from "constants/fetch-keys"; +// ui +import { CircularProgress } from "components/ui"; +// components +import UpgradeToProModal from "./upgrade-to-pro-modal"; +import useUser from "hooks/use-user"; const helpOptions = [ { @@ -43,7 +58,14 @@ export interface WorkspaceHelpSectionProps { setSidebarActive: React.Dispatch>; } +type progress = { + progress: number; +}; export const WorkspaceHelpSection: FC = (props) => { + // router + const router = useRouter(); + const { workspaceSlug } = router.query; + const { setSidebarActive } = props; // theme const { collapsed: sidebarCollapse, toggleCollapsed } = useTheme(); @@ -54,105 +76,192 @@ export const WorkspaceHelpSection: FC = (props) => { // hooks useOutsideClickDetector(helpOptionsRef, () => setIsNeedHelpOpen(false)); + const { user } = useUser(); + const helpOptionMode = sidebarCollapse ? "left-full" : "left-[-75px]"; - return ( -
- - - - + const [alert, setAlert] = useState(false); -
- + const [upgradeModal, setUpgradeModal] = useState(false); + + const { data: workspaceDetails } = useSWR( + workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, + workspaceSlug ? () => workspaceService.getWorkspace(workspaceSlug as string) : null + ); + const issueNumber = workspaceDetails?.total_issues || 0; + + return ( + <> + setUpgradeModal(false)} + user={user} + issueNumber={issueNumber} + /> + {!sidebarCollapse && (alert || (issueNumber && issueNumber >= 750)) ? ( + <>
= 750 + ? "bg-red-50 text-red-600 border-red-200" + : issueNumber >= 500 + ? "bg-yellow-50 text-yellow-600 border-yellow-200" + : "text-green-600" + }`} > - {helpOptions.map(({ name, Icon, href, onClick }) => { - if (href) - return ( - - + +
Free Plan
+ {issueNumber < 750 && ( +
setAlert(false)}> + +
+ )} +
+
+ This workspace has used {issueNumber} of its 1024 issues creation limit ( + {((issueNumber / 1024) * 100).toFixed(0)} + %). +
+
+ + ) : ( + "" + )} +
+ {alert || (issueNumber && issueNumber >= 750) ? ( + + ) : ( + + )} + + + + + + +
+ +
+ {helpOptions.map(({ name, Icon, href, onClick }) => { + if (href) + return ( + + + + {name} + + + ); + else + return ( + - ); - })} -
- + + ); + })} +
+ +
-
+ ); }; diff --git a/apps/app/components/workspace/sidebar-dropdown.tsx b/apps/app/components/workspace/sidebar-dropdown.tsx index b44df4832..5629b782b 100644 --- a/apps/app/components/workspace/sidebar-dropdown.tsx +++ b/apps/app/components/workspace/sidebar-dropdown.tsx @@ -108,9 +108,9 @@ export const WorkspaceSidebarDropdown = () => {
{!sidebarCollapse && ( -

+

{activeWorkspace?.name ? truncateText(activeWorkspace.name, 14) : "Loading..."} -

+

)}
@@ -166,7 +166,13 @@ export const WorkspaceSidebarDropdown = () => { )} -
{truncateText(workspace.name, 18)}
+
+ {truncateText(workspace.name, 18)} +
void; + user: ICurrentUserResponse | undefined; + issueNumber: number; +}; + +const UpgradeToProModal: React.FC = ({ isOpen, onClose, user, issueNumber }) => { + const [supabaseClient, setSupabaseClient] = useState(null); + + useEffect(() => { + // Create a Supabase client + if (process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) { + const { createClient } = window.supabase; + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY, + { + auth: { + autoRefreshToken: false, + persistSession: false, + }, + } + ); + + if (supabase) { + setSupabaseClient(supabase); + } + } + }, []); + + const [isLoading, setIsLoading] = useState(false); + + const handleClose = () => { + onClose(); + setIsLoading(false); + }; + + const proFeatures = [ + "Everything in free", + "Unlimited users", + "Unlimited file uploads", + "Priority Support", + "Custom Theming", + "Access to Roadmap", + "Plane AI (GPT unlimited)", + ]; + + const [errorMessage, setErrorMessage] = useState( + null + ); + const [loader, setLoader] = useState(false); + const submitEmail = async () => { + setLoader(true); + const payload = { email: user?.email || "" }; + + if (supabaseClient) { + if (payload?.email) { + const emailExists = await supabaseClient + .from("web-waitlist") + .select("id,email,count") + .eq("email", payload?.email); + if (emailExists.data.length === 0) { + const emailCreation = await supabaseClient + .from("web-waitlist") + .insert([{ email: payload?.email, count: 1, last_visited: new Date() }]) + .select("id,email,count"); + if (emailCreation.status === 201) + setErrorMessage({ status: "success", message: "Successfully registered." }); + else setErrorMessage({ status: "insert_error", message: "Insertion Error." }); + } else { + const emailCountUpdate = await supabaseClient + .from("web-waitlist") + .upsert({ + id: emailExists.data[0]?.id, + count: emailExists.data[0]?.count + 1, + last_visited: new Date(), + }) + .select("id,email,count"); + if (emailCountUpdate.status === 201) + setErrorMessage({ + status: "email_already_exists", + message: "Email already exists.", + }); + else setErrorMessage({ status: "update_error", message: "Update Error." }); + } + } else setErrorMessage({ status: "email_required", message: "Please provide email." }); + } else + setErrorMessage({ + status: "supabase_error", + message: "Network error. Please try again later.", + }); + + setLoader(false); + }; + + return ( + + + +
+ + +
+
+ + +
+
+
+
= 750 + ? "text-red-600" + : issueNumber >= 500 + ? "text-yellow-600" + : "text-green-600" + }`} + title="Shortcuts" + > + 100 ? 100 : (issueNumber / 1024) * 100 + } + /> +
+
+
Upgrade to pro
+
+ This workspace has used {issueNumber} of its 1024 issues creation limit ( + {((issueNumber / 1024) * 100).toFixed(2)}%). +
+
+
+ +
+
+
+
+ +
+
+
Order summary
+
+ Priority support, file uploads, and access to premium features. +
+ +
+ {proFeatures.map((feature, index) => ( +
+
+ +
+
{feature}
+
+ ))} +
+
+
+
+
+
+
Summary
+
+ +
+
+
+ Plane application is currently in dev-mode. We will soon introduce Pro plans + once general availability has been established. Stay tuned for more updates. + In the meantime, Plane remains free and unrestricted. +

+ We{"'"}ll ensure a smooth transition from the community version to the Pro + plan for you. +
+ + {errorMessage && ( +
+ {errorMessage?.message} +
+ )} +
+
+
+
+
+
+
+
+ ); +}; + +export default UpgradeToProModal; diff --git a/apps/app/constants/issue.ts b/apps/app/constants/issue.ts index 0a3d37809..0bf3f4ffb 100644 --- a/apps/app/constants/issue.ts +++ b/apps/app/constants/issue.ts @@ -73,6 +73,7 @@ export const handleIssuesMutation: THandleIssuesMutation = ( ...prevData[issueIndex], ...formData, assignees: formData?.assignees_list ?? prevData[issueIndex]?.assignees, + labels: formData?.labels_list ?? prevData[issueIndex]?.labels, }; prevData.splice(issueIndex, 1, updatedIssue); @@ -90,6 +91,7 @@ export const handleIssuesMutation: THandleIssuesMutation = ( ...oldGroup[issueIndex], ...formData, assignees: formData?.assignees_list ?? oldGroup[issueIndex]?.assignees, + labels: formData?.labels_list ?? oldGroup[issueIndex]?.labels, }; if (selectedGroupBy !== Object.keys(formData)[0]) diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx index bcb779d78..690300358 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx @@ -140,7 +140,7 @@ const SingleCycle: React.FC = () => { setAnalyticsModal(true)} - className="!py-1.5 font-normal rounded-md text-custom-text-200" + className="!py-1.5 font-normal rounded-md text-custom-text-200 hover:text-custom-text-100" outline > Analytics diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx index 90075fe81..6f87366f8 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx @@ -63,7 +63,7 @@ const ProjectIssues: NextPage = () => { setAnalyticsModal(true)} - className="!py-1.5 rounded-md font-normal text-custom-sidebar-text-200 border-custom-sidebar-border-100 hover:bg-custom-sidebar-background-90" + className="!py-1.5 rounded-md font-normal text-custom-sidebar-text-200 border-custom-sidebar-border-100 hover:text-custom-text-100 hover:bg-custom-sidebar-background-90" outline > Analytics @@ -72,7 +72,7 @@ const ProjectIssues: NextPage = () => { Inbox diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx index bbfb9c2b9..98d2c7985 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx @@ -144,7 +144,7 @@ const SingleModule: React.FC = () => { setAnalyticsModal(true)} - className="!py-1.5 font-normal rounded-md text-custom-text-200" + className="!py-1.5 font-normal rounded-md text-custom-text-200 hover:text-custom-text-100" outline > Analytics diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx index 545b4f276..1415382e9 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx @@ -122,14 +122,14 @@ const EstimatesSettings: NextPage = () => { } > -
+

Estimates

- { setEstimateToUpdate(undefined); setEstimateFormOpen(true); @@ -137,7 +137,7 @@ const EstimatesSettings: NextPage = () => { > Create New Estimate - +
{projectDetails?.estimate && ( Disable Estimates )} @@ -146,7 +146,7 @@ const EstimatesSettings: NextPage = () => {
{estimatesList ? ( estimatesList.length > 0 ? ( -
+
{estimatesList.map((estimate) => ( { ))}
) : ( -
+
{ >
{feature.icon} -
+

{feature.title}

{feature.description}

@@ -219,11 +219,21 @@ const FeaturesSettings: NextPage = () => {
))}
-
- + diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx index d2acc3a5f..988c82198 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx @@ -58,11 +58,11 @@ const ProjectIntegrations: NextPage = () => { } > -
+
{workspaceIntegrations ? ( workspaceIntegrations.length > 0 ? ( -
+
{workspaceIntegrations.map((integration) => ( diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx index b6f61b95c..835b395f5 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx @@ -85,10 +85,10 @@ const StatesSettings: NextPage = () => { return (
-

{key}

+

{key}