mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'develop' of github.com:makeplane/plane into fix/notifications
This commit is contained in:
commit
2962dda4ac
@ -59,8 +59,9 @@ AWS_S3_BUCKET_NAME="uploads"
|
|||||||
FILE_SIZE_LIMIT=5242880
|
FILE_SIZE_LIMIT=5242880
|
||||||
|
|
||||||
# GPT settings
|
# GPT settings
|
||||||
OPENAI_API_KEY=""
|
OPENAI_API_BASE="https://api.openai.com/v1" # change if using a custom endpoint
|
||||||
GPT_ENGINE=""
|
OPENAI_API_KEY="sk-" # add your openai key here
|
||||||
|
GPT_ENGINE="gpt-3.5-turbo" # use "gpt-4" if you have access
|
||||||
|
|
||||||
# Github
|
# Github
|
||||||
GITHUB_CLIENT_SECRET="" # For fetching release notes
|
GITHUB_CLIENT_SECRET="" # For fetching release notes
|
||||||
|
@ -67,7 +67,7 @@ class GPTIntegrationEndpoint(BaseAPIView):
|
|||||||
|
|
||||||
openai.api_key = settings.OPENAI_API_KEY
|
openai.api_key = settings.OPENAI_API_KEY
|
||||||
response = openai.Completion.create(
|
response = openai.Completion.create(
|
||||||
engine=settings.GPT_ENGINE,
|
model=settings.GPT_ENGINE,
|
||||||
prompt=final_text,
|
prompt=final_text,
|
||||||
temperature=0.7,
|
temperature=0.7,
|
||||||
max_tokens=1024,
|
max_tokens=1024,
|
||||||
|
@ -561,7 +561,6 @@ def track_estimate_points(
|
|||||||
def track_archive_at(
|
def track_archive_at(
|
||||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||||
):
|
):
|
||||||
|
|
||||||
if requested_data.get("archived_at") is None:
|
if requested_data.get("archived_at") is None:
|
||||||
issue_activities.append(
|
issue_activities.append(
|
||||||
IssueActivity(
|
IssueActivity(
|
||||||
@ -572,6 +571,8 @@ def track_archive_at(
|
|||||||
verb="updated",
|
verb="updated",
|
||||||
actor=actor,
|
actor=actor,
|
||||||
field="archvied_at",
|
field="archvied_at",
|
||||||
|
old_value="archive",
|
||||||
|
new_value="restore",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -584,6 +585,8 @@ def track_archive_at(
|
|||||||
verb="updated",
|
verb="updated",
|
||||||
actor=actor,
|
actor=actor,
|
||||||
field="archvied_at",
|
field="archvied_at",
|
||||||
|
old_value=None,
|
||||||
|
new_value="archive",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -982,11 +985,16 @@ def delete_attachment_activity(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Receive message from room group
|
# Receive message from room group
|
||||||
@shared_task
|
@shared_task
|
||||||
def issue_activity(
|
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:
|
try:
|
||||||
issue_activities = []
|
issue_activities = []
|
||||||
@ -999,11 +1007,12 @@ def issue_activity(
|
|||||||
issue.updated_at = timezone.now()
|
issue.updated_at = timezone.now()
|
||||||
issue.save()
|
issue.save()
|
||||||
|
|
||||||
|
|
||||||
if subscriber:
|
if subscriber:
|
||||||
# add the user to issue subscriber
|
# add the user to issue subscriber
|
||||||
try:
|
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:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -10,9 +10,7 @@ from sentry_sdk.integrations.redis import RedisIntegration
|
|||||||
|
|
||||||
from .common import * # noqa
|
from .common import * # noqa
|
||||||
|
|
||||||
DEBUG = int(os.environ.get(
|
DEBUG = int(os.environ.get("DEBUG", 1)) == 1
|
||||||
"DEBUG", 1
|
|
||||||
)) == 1
|
|
||||||
|
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
|
||||||
@ -27,9 +25,7 @@ DATABASES = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DOCKERIZED = int(os.environ.get(
|
DOCKERIZED = int(os.environ.get("DOCKERIZED", 0)) == 1
|
||||||
"DOCKERIZED", 0
|
|
||||||
)) == 1
|
|
||||||
|
|
||||||
USE_MINIO = int(os.environ.get("USE_MINIO", 0)) == 1
|
USE_MINIO = int(os.environ.get("USE_MINIO", 0)) == 1
|
||||||
|
|
||||||
@ -65,6 +61,27 @@ if os.environ.get("SENTRY_DSN", False):
|
|||||||
traces_sample_rate=0.7,
|
traces_sample_rate=0.7,
|
||||||
profiles_sample_rate=1.0,
|
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_HOST = "localhost"
|
||||||
REDIS_PORT = 6379
|
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_SECRET_KEY = os.environ.get("ANALYTICS_SECRET_KEY", False)
|
||||||
ANALYTICS_BASE_API = os.environ.get("ANALYTICS_BASE_API", 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)
|
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)
|
SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN", False)
|
||||||
|
|
||||||
|
@ -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_SECRET_KEY = os.environ.get("ANALYTICS_SECRET_KEY", False)
|
||||||
ANALYTICS_BASE_API = os.environ.get("ANALYTICS_BASE_API", 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)
|
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)
|
SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN", False)
|
||||||
|
|
||||||
|
@ -11,10 +11,9 @@ from sentry_sdk.integrations.django import DjangoIntegration
|
|||||||
from sentry_sdk.integrations.redis import RedisIntegration
|
from sentry_sdk.integrations.redis import RedisIntegration
|
||||||
|
|
||||||
from .common import * # noqa
|
from .common import * # noqa
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
DEBUG = int(os.environ.get(
|
DEBUG = int(os.environ.get("DEBUG", 1)) == 1
|
||||||
"DEBUG", 1
|
|
||||||
)) == 1
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.postgresql_psycopg2",
|
"ENGINE": "django.db.backends.postgresql_psycopg2",
|
||||||
@ -56,9 +55,7 @@ STORAGES = {
|
|||||||
|
|
||||||
|
|
||||||
# Make true if running in a docker environment
|
# Make true if running in a docker environment
|
||||||
DOCKERIZED = int(os.environ.get(
|
DOCKERIZED = int(os.environ.get("DOCKERIZED", 0)) == 1
|
||||||
"DOCKERIZED", 0
|
|
||||||
)) == 1
|
|
||||||
FILE_SIZE_LIMIT = int(os.environ.get("FILE_SIZE_LIMIT", 5242880))
|
FILE_SIZE_LIMIT = int(os.environ.get("FILE_SIZE_LIMIT", 5242880))
|
||||||
USE_MINIO = int(os.environ.get("USE_MINIO", 0)) == 1
|
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_SECRET_KEY = os.environ.get("ANALYTICS_SECRET_KEY", False)
|
||||||
ANALYTICS_BASE_API = os.environ.get("ANALYTICS_BASE_API", 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)
|
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)
|
SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN", False)
|
||||||
|
|
||||||
LOGGER_BASE_URL = os.environ.get("LOGGER_BASE_URL", False)
|
LOGGER_BASE_URL = os.environ.get("LOGGER_BASE_URL", False)
|
||||||
|
|
||||||
redis_url = os.environ.get("REDIS_URL")
|
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_RESULT_BACKEND = broker_url
|
||||||
CELERY_BROKER_URL = broker_url
|
CELERY_BROKER_URL = broker_url
|
||||||
|
@ -238,8 +238,8 @@ export const AnalyticsSidebar: React.FC<Props> = ({
|
|||||||
{project?.name.charAt(0)}
|
{project?.name.charAt(0)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<h5 className="break-words">
|
<h5 className="flex items-center gap-1">
|
||||||
{project.name}
|
<p className="break-words">{project.name}</p>
|
||||||
<span className="text-custom-text-200 text-xs ml-1">
|
<span className="text-custom-text-200 text-xs ml-1">
|
||||||
({project.identifier})
|
({project.identifier})
|
||||||
</span>
|
</span>
|
||||||
|
@ -334,9 +334,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
|||||||
{issue.project_detail.identifier}-{issue.sequence_id}
|
{issue.project_detail.identifier}-{issue.sequence_id}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<h5 className="text-sm group-hover:text-custom-primary break-words line-clamp-3">
|
<h5 className="text-sm break-words line-clamp-3">{issue.name}</h5>
|
||||||
{issue.name}
|
|
||||||
</h5>
|
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="relative mt-2.5 flex flex-wrap items-center gap-2 text-xs">
|
<div className="relative mt-2.5 flex flex-wrap items-center gap-2 text-xs">
|
||||||
|
@ -23,10 +23,33 @@ import {
|
|||||||
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
||||||
import { checkIfArraysHaveSameElements } from "helpers/array.helper";
|
import { checkIfArraysHaveSameElements } from "helpers/array.helper";
|
||||||
// types
|
// types
|
||||||
import { Properties } from "types";
|
import { Properties, TIssueViewOptions } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue";
|
import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue";
|
||||||
|
|
||||||
|
const issueViewOptions: { type: TIssueViewOptions; icon: any }[] = [
|
||||||
|
{
|
||||||
|
type: "list",
|
||||||
|
icon: <ListBulletIcon className="h-4 w-4" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "kanban",
|
||||||
|
icon: <Squares2X2Icon className="h-4 w-4" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "calendar",
|
||||||
|
icon: <CalendarDaysIcon className="h-4 w-4" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "spreadsheet",
|
||||||
|
icon: <Icon iconName="table_chart" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "gantt_chart",
|
||||||
|
icon: <Icon iconName="waterfall_chart" className="rotate-90" />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export const IssuesFilterView: React.FC = () => {
|
export const IssuesFilterView: React.FC = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, viewId } = router.query;
|
const { workspaceSlug, projectId, viewId } = router.query;
|
||||||
@ -56,53 +79,20 @@ export const IssuesFilterView: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-x-1">
|
<div className="flex items-center gap-x-1">
|
||||||
|
{issueViewOptions.map((option) => (
|
||||||
<button
|
<button
|
||||||
|
key={option.type}
|
||||||
type="button"
|
type="button"
|
||||||
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-custom-sidebar-background-80 ${
|
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80 duration-300 ${
|
||||||
issueView === "list" ? "bg-custom-sidebar-background-80" : ""
|
issueView === option.type
|
||||||
|
? "bg-custom-sidebar-background-80"
|
||||||
|
: "text-custom-sidebar-text-200"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setIssueView("list")}
|
onClick={() => setIssueView(option.type)}
|
||||||
>
|
>
|
||||||
<ListBulletIcon className="h-4 w-4 text-custom-sidebar-text-200" />
|
{option.icon}
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-custom-sidebar-background-80 ${
|
|
||||||
issueView === "kanban" ? "bg-custom-sidebar-background-80" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => setIssueView("kanban")}
|
|
||||||
>
|
|
||||||
<Squares2X2Icon className="h-4 w-4 text-custom-sidebar-text-200" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-custom-sidebar-background-80 ${
|
|
||||||
issueView === "calendar" ? "bg-custom-sidebar-background-80" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => setIssueView("calendar")}
|
|
||||||
>
|
|
||||||
<CalendarDaysIcon className="h-4 w-4 text-custom-sidebar-text-200" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-custom-sidebar-background-80 ${
|
|
||||||
issueView === "spreadsheet" ? "bg-custom-sidebar-background-80" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => setIssueView("spreadsheet")}
|
|
||||||
>
|
|
||||||
<Icon iconName="table_chart" className="text-custom-sidebar-text-200" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`grid h-7 w-7 place-items-center rounded outline-none duration-300 hover:bg-custom-sidebar-background-80 ${
|
|
||||||
issueView === "gantt_chart" ? "bg-custom-sidebar-background-80" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => setIssueView("gantt_chart")}
|
|
||||||
>
|
|
||||||
<span className="material-symbols-rounded text-custom-sidebar-text-200 text-[18px] rotate-90">
|
|
||||||
waterfall_chart
|
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<SelectFilters
|
<SelectFilters
|
||||||
filters={filters}
|
filters={filters}
|
||||||
@ -146,7 +136,7 @@ export const IssuesFilterView: React.FC = () => {
|
|||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
className={`group flex items-center gap-2 rounded-md border border-custom-sidebar-border-100 bg-transparent px-3 py-1.5 text-xs hover:bg-custom-sidebar-background-90 hover:text-custom-sidebar-text-100 focus:outline-none ${
|
className={`group flex items-center gap-2 rounded-md border border-custom-sidebar-border-100 bg-transparent px-3 py-1.5 text-xs hover:bg-custom-sidebar-background-90 hover:text-custom-sidebar-text-100 focus:outline-none duration-300 ${
|
||||||
open
|
open
|
||||||
? "bg-custom-sidebar-background-90 text-custom-sidebar-text-100"
|
? "bg-custom-sidebar-background-90 text-custom-sidebar-text-100"
|
||||||
: "text-custom-sidebar-text-200"
|
: "text-custom-sidebar-text-200"
|
||||||
|
@ -279,11 +279,9 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleEditCycle();
|
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"
|
||||||
>
|
>
|
||||||
<span>
|
|
||||||
<PencilIcon className="h-4 w-4" />
|
<PencilIcon className="h-4 w-4" />
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
@ -157,7 +156,7 @@ export const SingleCycleList: React.FC<TSingleStatProps> = ({
|
|||||||
<a className="w-full">
|
<a className="w-full">
|
||||||
<div className="flex h-full flex-col gap-4 rounded-b-[10px] p-4">
|
<div className="flex h-full flex-col gap-4 rounded-b-[10px] p-4">
|
||||||
<div className="flex items-center justify-between gap-1">
|
<div className="flex items-center justify-between gap-1">
|
||||||
<span className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<ContrastIcon
|
<ContrastIcon
|
||||||
className="mt-1 h-5 w-5"
|
className="mt-1 h-5 w-5"
|
||||||
color={`${
|
color={`${
|
||||||
@ -179,15 +178,15 @@ export const SingleCycleList: React.FC<TSingleStatProps> = ({
|
|||||||
position="top-left"
|
position="top-left"
|
||||||
>
|
>
|
||||||
<h3 className="break-words w-full text-base font-semibold">
|
<h3 className="break-words w-full text-base font-semibold">
|
||||||
{truncateText(cycle.name, 70)}
|
{truncateText(cycle.name, 60)}
|
||||||
</h3>
|
</h3>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<p className="mt-2 text-custom-text-200 break-words w-full">
|
<p className="mt-2 text-custom-text-200 break-words w-full">
|
||||||
{cycle.description}
|
{cycle.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</div>
|
||||||
<span className="flex items-center gap-4 capitalize">
|
<div className="flex-shrink-0 flex items-center gap-4">
|
||||||
<span
|
<span
|
||||||
className={`rounded-full px-1.5 py-0.5
|
className={`rounded-full px-1.5 py-0.5
|
||||||
${
|
${
|
||||||
@ -203,14 +202,14 @@ export const SingleCycleList: React.FC<TSingleStatProps> = ({
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{cycleStatus === "current" ? (
|
{cycleStatus === "current" ? (
|
||||||
<span className="flex gap-1">
|
<span className="flex gap-1 whitespace-nowrap">
|
||||||
<PersonRunningIcon className="h-4 w-4" />
|
<PersonRunningIcon className="h-4 w-4" />
|
||||||
{findHowManyDaysLeft(cycle.end_date ?? new Date())} Days Left
|
{findHowManyDaysLeft(cycle.end_date ?? new Date())} days left
|
||||||
</span>
|
</span>
|
||||||
) : cycleStatus === "upcoming" ? (
|
) : cycleStatus === "upcoming" ? (
|
||||||
<span className="flex gap-1">
|
<span className="flex gap-1">
|
||||||
<AlarmClockIcon className="h-4 w-4" />
|
<AlarmClockIcon className="h-4 w-4" />
|
||||||
{findHowManyDaysLeft(cycle.start_date ?? new Date())} Days Left
|
{findHowManyDaysLeft(cycle.start_date ?? new Date())} days left
|
||||||
</span>
|
</span>
|
||||||
) : cycleStatus === "completed" ? (
|
) : cycleStatus === "completed" ? (
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
@ -236,12 +235,12 @@ export const SingleCycleList: React.FC<TSingleStatProps> = ({
|
|||||||
|
|
||||||
{cycleStatus !== "draft" && (
|
{cycleStatus !== "draft" && (
|
||||||
<div className="flex items-center justify-start gap-2 text-custom-text-200">
|
<div className="flex items-center justify-start gap-2 text-custom-text-200">
|
||||||
<div className="flex items-start gap-1 ">
|
<div className="flex items-start gap-1 whitespace-nowrap">
|
||||||
<CalendarDaysIcon className="h-4 w-4" />
|
<CalendarDaysIcon className="h-4 w-4" />
|
||||||
<span>{renderShortDateWithYearFormat(startDate)}</span>
|
<span>{renderShortDateWithYearFormat(startDate)}</span>
|
||||||
</div>
|
</div>
|
||||||
<ArrowRightIcon className="h-4 w-4" />
|
<ArrowRightIcon className="h-4 w-4" />
|
||||||
<div className="flex items-start gap-1 ">
|
<div className="flex items-start gap-1 whitespace-nowrap">
|
||||||
<TargetIcon className="h-4 w-4" />
|
<TargetIcon className="h-4 w-4" />
|
||||||
<span>{renderShortDateWithYearFormat(endDate)}</span>
|
<span>{renderShortDateWithYearFormat(endDate)}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -287,7 +286,7 @@ export const SingleCycleList: React.FC<TSingleStatProps> = ({
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{cycleStatus === "current" ? (
|
{cycleStatus === "current" ? (
|
||||||
<span className="flex gap-1">
|
<span className="flex gap-1 whitespace-nowrap">
|
||||||
{cycle.total_issues > 0 ? (
|
{cycle.total_issues > 0 ? (
|
||||||
<>
|
<>
|
||||||
<RadialProgressBar
|
<RadialProgressBar
|
||||||
@ -380,7 +379,7 @@ export const SingleCycleList: React.FC<TSingleStatProps> = ({
|
|||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -72,7 +72,7 @@ export const SingleEstimate: React.FC<Props> = ({
|
|||||||
<h6 className="flex w-[40vw] items-center gap-2 truncate text-sm font-medium">
|
<h6 className="flex w-[40vw] items-center gap-2 truncate text-sm font-medium">
|
||||||
{estimate.name}
|
{estimate.name}
|
||||||
{projectDetails?.estimate && projectDetails?.estimate === estimate.id && (
|
{projectDetails?.estimate && projectDetails?.estimate === estimate.id && (
|
||||||
<span className="rounded bg-green-500/20 px-2 py-0.5 text-xs capitalize text-green-500">
|
<span className="rounded bg-green-500/20 px-2 py-0.5 text-xs text-green-500">
|
||||||
In use
|
In use
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@ -83,7 +83,10 @@ export const SingleEstimate: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{projectDetails?.estimate !== estimate.id && estimate.points.length > 0 && (
|
{projectDetails?.estimate !== estimate.id && estimate.points.length > 0 && (
|
||||||
<SecondaryButton onClick={handleUseEstimate} className="py-1">
|
<SecondaryButton
|
||||||
|
onClick={handleUseEstimate}
|
||||||
|
className="!py-1 text-custom-text-200 hover:text-custom-text-100"
|
||||||
|
>
|
||||||
Use
|
Use
|
||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
)}
|
)}
|
||||||
|
@ -8,6 +8,7 @@ import { findHowManyDaysLeft, renderShortDateWithYearFormat } from "helpers/date
|
|||||||
import trackEventServices from "services/track-event.service";
|
import trackEventServices from "services/track-event.service";
|
||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IIssue } from "types";
|
import { ICurrentUserResponse, IIssue } from "types";
|
||||||
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
@ -29,6 +30,8 @@ export const ViewDueDateSelect: React.FC<Props> = ({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
|
const { issueView } = useIssuesView();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
tooltipHeading="Due Date"
|
tooltipHeading="Due Date"
|
||||||
@ -71,7 +74,9 @@ export const ViewDueDateSelect: React.FC<Props> = ({
|
|||||||
user
|
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}
|
noBorder={noBorder}
|
||||||
disabled={isNotAllowed}
|
disabled={isNotAllowed}
|
||||||
/>
|
/>
|
||||||
|
@ -71,9 +71,15 @@ export const ViewLabelSelect: React.FC<Props> = ({
|
|||||||
position={tooltipPosition}
|
position={tooltipPosition}
|
||||||
tooltipHeading="Labels"
|
tooltipHeading="Labels"
|
||||||
tooltipContent={
|
tooltipContent={
|
||||||
issue.label_details.length > 0
|
issue.labels.length > 0
|
||||||
? issue.label_details.map((label) => label.name ?? "").join(", ")
|
? issue.labels
|
||||||
: "No Label"
|
.map((labelId) => {
|
||||||
|
const label = issueLabels?.find((l) => l.id === labelId);
|
||||||
|
|
||||||
|
return label?.name ?? "";
|
||||||
|
})
|
||||||
|
.join(", ")
|
||||||
|
: "No label"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -81,20 +87,23 @@ export const ViewLabelSelect: React.FC<Props> = ({
|
|||||||
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
|
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
|
||||||
} items-center gap-2 text-custom-text-200`}
|
} 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.labels.slice(0, 4).map((labelId, index) => {
|
||||||
|
const label = issueLabels?.find((l) => l.id === labelId);
|
||||||
|
|
||||||
|
return (
|
||||||
<div className={`flex h-4 w-4 rounded-full ${index ? "-ml-3.5" : ""}`}>
|
<div className={`flex h-4 w-4 rounded-full ${index ? "-ml-3.5" : ""}`}>
|
||||||
<span
|
<span
|
||||||
className={`h-4 w-4 flex-shrink-0 rounded-full border group-hover:bg-custom-background-80 border-custom-border-100
|
className={`h-4 w-4 flex-shrink-0 rounded-full border group-hover:bg-custom-background-80 border-custom-border-100`}
|
||||||
`}
|
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: label?.color && label.color !== "" ? label.color : "#000000",
|
backgroundColor: label?.color ?? "#000000",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
{issue.label_details.length > 4 ? <span>+{issue.label_details.length - 4}</span> : null}
|
})}
|
||||||
|
{issue.labels.length > 4 ? <span>+{issue.labels.length - 4}</span> : null}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
@ -10,7 +10,6 @@ import { CustomSearchSelect, Tooltip } from "components/ui";
|
|||||||
// icons
|
// icons
|
||||||
import { getStateGroupIcon } from "components/icons";
|
import { getStateGroupIcon } from "components/icons";
|
||||||
// helpers
|
// helpers
|
||||||
import { addSpaceIfCamelCase } from "helpers/string.helper";
|
|
||||||
import { getStatesList } from "helpers/state.helper";
|
import { getStatesList } from "helpers/state.helper";
|
||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IIssue } from "types";
|
import { ICurrentUserResponse, IIssue } from "types";
|
||||||
@ -67,7 +66,7 @@ export const ViewStateSelect: React.FC<Props> = ({
|
|||||||
const stateLabel = (
|
const stateLabel = (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
tooltipHeading="State"
|
tooltipHeading="State"
|
||||||
tooltipContent={addSpaceIfCamelCase(selectedOption?.name ?? "")}
|
tooltipContent={selectedOption?.name ?? ""}
|
||||||
position={tooltipPosition}
|
position={tooltipPosition}
|
||||||
>
|
>
|
||||||
<div className="flex items-center cursor-pointer w-full gap-2 text-custom-text-200">
|
<div className="flex items-center cursor-pointer w-full gap-2 text-custom-text-200">
|
||||||
|
@ -185,12 +185,12 @@ export const SingleModuleCard: React.FC<Props> = ({ module, handleEditModule, us
|
|||||||
<div className="flex items-start gap-1">
|
<div className="flex items-start gap-1">
|
||||||
<CalendarDaysIcon className="h-4 w-4" />
|
<CalendarDaysIcon className="h-4 w-4" />
|
||||||
<span>Start:</span>
|
<span>Start:</span>
|
||||||
<span>{renderShortDateWithYearFormat(startDate)}</span>
|
<span>{renderShortDateWithYearFormat(startDate, "Not set")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-1">
|
<div className="flex items-start gap-1">
|
||||||
<TargetIcon className="h-4 w-4" />
|
<TargetIcon className="h-4 w-4" />
|
||||||
<span>End:</span>
|
<span>End:</span>
|
||||||
<span>{renderShortDateWithYearFormat(endDate)}</span>
|
<span>{renderShortDateWithYearFormat(endDate, "Not set")}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -184,7 +184,7 @@ export const CreateProjectModal: React.FC<Props> = (props) => {
|
|||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="transform rounded-lg bg-custom-background-80 text-left shadow-xl transition-all sm:w-full sm:max-w-2xl">
|
<Dialog.Panel className="transform rounded-lg bg-custom-background-100 text-left shadow-xl transition-all sm:w-full sm:max-w-2xl">
|
||||||
<div className="relative h-36 w-full rounded-t-lg bg-custom-background-80">
|
<div className="relative h-36 w-full rounded-t-lg bg-custom-background-80">
|
||||||
{watch("cover_image") !== null && (
|
{watch("cover_image") !== null && (
|
||||||
<img
|
<img
|
||||||
|
@ -111,9 +111,9 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{!sidebarCollapse && (
|
{!sidebarCollapse && (
|
||||||
<p className="overflow-hidden text-ellipsis text-[0.875rem]">
|
<h5 className={`overflow-hidden text-sm ${open ? "" : "text-custom-text-200"}`}>
|
||||||
{truncateText(project?.name, 20)}
|
{truncateText(project?.name, 20)}
|
||||||
</p>
|
</h5>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!sidebarCollapse && (
|
{!sidebarCollapse && (
|
||||||
|
39
apps/app/components/ui/circular-progress.tsx
Normal file
39
apps/app/components/ui/circular-progress.tsx
Normal file
@ -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 (
|
||||||
|
<div className="relative h-5 w-5">
|
||||||
|
<svg className="absolute top-0 left-0" viewBox="0 0 100 100">
|
||||||
|
<circle
|
||||||
|
className="stroke-current"
|
||||||
|
cx="50"
|
||||||
|
cy="50"
|
||||||
|
r="40"
|
||||||
|
strokeWidth="12"
|
||||||
|
fill="none"
|
||||||
|
strokeDasharray={`${circumference} ${circumference}`}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="fill-current"
|
||||||
|
d={`M50 10
|
||||||
|
A40 40 0 ${progress > 50 ? 1 : 0} 1 ${progressX} ${progressY}
|
||||||
|
L50 50 Z`}
|
||||||
|
strokeWidth="12"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -59,7 +59,7 @@ const CustomMenu = ({
|
|||||||
{ellipsis || verticalEllipsis ? (
|
{ellipsis || verticalEllipsis ? (
|
||||||
<Menu.Button
|
<Menu.Button
|
||||||
type="button"
|
type="button"
|
||||||
className="relative grid place-items-center rounded p-1 text-custom-text-200 hover:bg-custom-background-80 hover:text-custom-text-100 focus:outline-none"
|
className="relative grid place-items-center rounded p-1 text-custom-text-200 hover:bg-custom-background-80 outline-none"
|
||||||
>
|
>
|
||||||
<EllipsisHorizontalIcon
|
<EllipsisHorizontalIcon
|
||||||
className={`h-4 w-4 ${verticalEllipsis ? "rotate-90" : ""}`}
|
className={`h-4 w-4 ${verticalEllipsis ? "rotate-90" : ""}`}
|
||||||
|
@ -148,8 +148,8 @@ export const CustomSearchSelect = ({
|
|||||||
value={option.value}
|
value={option.value}
|
||||||
className={({ active, selected }) =>
|
className={({ active, selected }) =>
|
||||||
`${active || selected ? "bg-custom-background-80" : ""} ${
|
`${active || selected ? "bg-custom-background-80" : ""} ${
|
||||||
selected ? "font-medium" : ""
|
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 text-custom-text-200`
|
} flex cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{({ active, selected }) => (
|
{({ active, selected }) => (
|
||||||
@ -157,7 +157,7 @@ export const CustomSearchSelect = ({
|
|||||||
{option.content}
|
{option.content}
|
||||||
{multiple ? (
|
{multiple ? (
|
||||||
<div
|
<div
|
||||||
className={`flex items-center justify-center rounded border border-gray-500 p-0.5 ${
|
className={`flex items-center justify-center rounded border border-custom-border-400 p-0.5 ${
|
||||||
active || selected ? "opacity-100" : "opacity-0"
|
active || selected ? "opacity-100" : "opacity-0"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
@ -118,8 +118,8 @@ const Option: React.FC<OptionProps> = ({ children, value, className }) => (
|
|||||||
value={value}
|
value={value}
|
||||||
className={({ active, selected }) =>
|
className={({ active, selected }) =>
|
||||||
`${className} ${active || selected ? "bg-custom-background-80" : ""} ${
|
`${className} ${active || selected ? "bg-custom-background-80" : ""} ${
|
||||||
selected ? "font-medium" : ""
|
selected ? "text-custom-text-100" : "text-custom-text-200"
|
||||||
} cursor-pointer select-none truncate rounded px-1 py-1.5 text-custom-text-200`
|
} cursor-pointer select-none truncate rounded px-1 py-1.5`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{({ selected }) => (
|
{({ selected }) => (
|
||||||
|
@ -42,13 +42,13 @@ export const CustomDatePicker: React.FC<Props> = ({
|
|||||||
: renderAs === "button"
|
: renderAs === "button"
|
||||||
? `px-2 py-1 text-xs shadow-sm ${
|
? `px-2 py-1 text-xs shadow-sm ${
|
||||||
disabled ? "" : "hover:bg-custom-background-80"
|
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" : ""} ${
|
} ${error ? "border-red-500 bg-red-100" : ""} ${
|
||||||
disabled ? "cursor-not-allowed" : "cursor-pointer"
|
disabled ? "cursor-not-allowed" : "cursor-pointer"
|
||||||
} ${
|
} ${
|
||||||
noBorder ? "" : "border border-custom-border-100"
|
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"
|
dateFormat="MMM dd, yyyy"
|
||||||
isClearable={isClearable}
|
isClearable={isClearable}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -64,13 +64,13 @@ const EmptySpaceItem: React.FC<EmptySpaceItemProps> = ({ title, description, Ico
|
|||||||
<Icon className="h-6 w-6 text-white" aria-hidden="true" />
|
<Icon className="h-6 w-6 text-white" aria-hidden="true" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1 text-custom-text-200">
|
||||||
<div className="text-sm font-medium text-custom-text-100">{title}</div>
|
<div className="text-sm font-medium group-hover:text-custom-text-100">{title}</div>
|
||||||
{description ? <div className="text-sm text-custom-text-200">{description}</div> : null}
|
{description ? <div className="text-sm">{description}</div> : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-shrink-0 self-center">
|
<div className="flex-shrink-0 self-center">
|
||||||
<ChevronRightIcon
|
<ChevronRightIcon
|
||||||
className="h-5 w-5 text-custom-text-100 group-hover:text-custom-text-200"
|
className="h-5 w-5 text-custom-text-200 group-hover:text-custom-text-100"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,3 +26,4 @@ export * from "./product-updates-modal";
|
|||||||
export * from "./integration-and-import-export-banner";
|
export * from "./integration-and-import-export-banner";
|
||||||
export * from "./range-datepicker";
|
export * from "./range-datepicker";
|
||||||
export * from "./icon";
|
export * from "./icon";
|
||||||
|
export * from "./circular-progress";
|
||||||
|
@ -43,7 +43,7 @@ export const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|||||||
<div>
|
<div>
|
||||||
<Menu.Button
|
<Menu.Button
|
||||||
onClick={() => setOpenChildFor(null)}
|
onClick={() => 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"
|
open ? "bg-custom-background-90 text-custom-text-100" : "text-custom-text-200"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import { useState, useRef, FC } from "react";
|
import { useState, useRef, FC } from "react";
|
||||||
|
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
// headless ui
|
// headless ui
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
// hooks
|
// hooks
|
||||||
@ -12,8 +16,19 @@ import {
|
|||||||
ArrowLongLeftIcon,
|
ArrowLongLeftIcon,
|
||||||
ChatBubbleOvalLeftEllipsisIcon,
|
ChatBubbleOvalLeftEllipsisIcon,
|
||||||
RocketLaunchIcon,
|
RocketLaunchIcon,
|
||||||
|
ArrowUpCircleIcon,
|
||||||
|
XMarkIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { QuestionMarkCircleIcon, DocumentIcon, DiscordIcon, GithubIcon } from "components/icons";
|
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 = [
|
const helpOptions = [
|
||||||
{
|
{
|
||||||
@ -43,7 +58,14 @@ export interface WorkspaceHelpSectionProps {
|
|||||||
setSidebarActive: React.Dispatch<React.SetStateAction<boolean>>;
|
setSidebarActive: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type progress = {
|
||||||
|
progress: number;
|
||||||
|
};
|
||||||
export const WorkspaceHelpSection: FC<WorkspaceHelpSectionProps> = (props) => {
|
export const WorkspaceHelpSection: FC<WorkspaceHelpSectionProps> = (props) => {
|
||||||
|
// router
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
const { setSidebarActive } = props;
|
const { setSidebarActive } = props;
|
||||||
// theme
|
// theme
|
||||||
const { collapsed: sidebarCollapse, toggleCollapsed } = useTheme();
|
const { collapsed: sidebarCollapse, toggleCollapsed } = useTheme();
|
||||||
@ -54,17 +76,103 @@ export const WorkspaceHelpSection: FC<WorkspaceHelpSectionProps> = (props) => {
|
|||||||
// hooks
|
// hooks
|
||||||
useOutsideClickDetector(helpOptionsRef, () => setIsNeedHelpOpen(false));
|
useOutsideClickDetector(helpOptionsRef, () => setIsNeedHelpOpen(false));
|
||||||
|
|
||||||
|
const { user } = useUser();
|
||||||
|
|
||||||
const helpOptionMode = sidebarCollapse ? "left-full" : "left-[-75px]";
|
const helpOptionMode = sidebarCollapse ? "left-full" : "left-[-75px]";
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
|
<>
|
||||||
|
<UpgradeToProModal
|
||||||
|
isOpen={upgradeModal}
|
||||||
|
onClose={() => setUpgradeModal(false)}
|
||||||
|
user={user}
|
||||||
|
issueNumber={issueNumber}
|
||||||
|
/>
|
||||||
|
{!sidebarCollapse && (alert || (issueNumber && issueNumber >= 750)) ? (
|
||||||
|
<>
|
||||||
<div
|
<div
|
||||||
className={`flex w-full items-center justify-between self-baseline border-t border-custom-sidebar-border-100 bg-custom-sidebar-background-100 px-6 py-2 ${
|
className={`border-t p-4 ${
|
||||||
|
issueNumber >= 750
|
||||||
|
? "bg-red-50 text-red-600 border-red-200"
|
||||||
|
: issueNumber >= 500
|
||||||
|
? "bg-yellow-50 text-yellow-600 border-yellow-200"
|
||||||
|
: "text-green-600"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 w-full">
|
||||||
|
<CircularProgress progress={(issueNumber / 1024) * 100} />
|
||||||
|
<div className="">Free Plan</div>
|
||||||
|
{issueNumber < 750 && (
|
||||||
|
<div className="ml-auto text-custom-text-200" onClick={() => setAlert(false)}>
|
||||||
|
<XMarkIcon className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="text-custom-text-200 text-xs mt-2">
|
||||||
|
This workspace has used {issueNumber} of its 1024 issues creation limit (
|
||||||
|
{((issueNumber / 1024) * 100).toFixed(0)}
|
||||||
|
%).
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={`flex w-full items-center justify-between self-baseline border-t border-custom-border-100 bg-custom-sidebar-background-100 px-6 py-2 ${
|
||||||
sidebarCollapse ? "flex-col" : ""
|
sidebarCollapse ? "flex-col" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
{alert || (issueNumber && issueNumber >= 750) ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`flex items-center gap-x-1 rounded-md px-2 py-2 text-xs font-medium text-custom-sidebar-text-200 outline-none hover:bg-custom-sidebar-background-90 hover:text-custom-sidebar-text-100 ${
|
className={`flex items-center gap-x-1 rounded-md px-2 py-2 font-medium outline-none text-sm
|
||||||
|
${
|
||||||
|
issueNumber >= 750
|
||||||
|
? "bg-custom-primary-100 text-white"
|
||||||
|
: "bg-blue-50 text-custom-primary-100"
|
||||||
|
}
|
||||||
|
${sidebarCollapse ? "w-full justify-center" : ""}`}
|
||||||
|
title="Shortcuts"
|
||||||
|
onClick={() => setUpgradeModal(true)}
|
||||||
|
>
|
||||||
|
<ArrowUpCircleIcon className="h-4 w-4 " />
|
||||||
|
{!sidebarCollapse && <span> Learn more</span>}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`flex items-center gap-x-1 rounded-md px-2 py-2 font-medium outline-none text-sm ${
|
||||||
|
issueNumber >= 750
|
||||||
|
? "bg-red-50 text-red-600"
|
||||||
|
: issueNumber >= 500
|
||||||
|
? "bg-yellow-50 text-yellow-600"
|
||||||
|
: "bg-green-50 text-green-600"
|
||||||
|
}
|
||||||
|
${sidebarCollapse ? "w-full justify-center" : ""}`}
|
||||||
|
title="Shortcuts"
|
||||||
|
onClick={() => setAlert(true)}
|
||||||
|
>
|
||||||
|
<CircularProgress
|
||||||
|
progress={(issueNumber / 1024) * 100 > 100 ? 100 : (issueNumber / 1024) * 100}
|
||||||
|
/>
|
||||||
|
{!sidebarCollapse && <span>Free Plan</span>}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`flex items-center gap-x-1 rounded-md px-2 py-2 text-xs font-medium text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 ${
|
||||||
sidebarCollapse ? "w-full justify-center" : ""
|
sidebarCollapse ? "w-full justify-center" : ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -75,36 +183,36 @@ export const WorkspaceHelpSection: FC<WorkspaceHelpSectionProps> = (props) => {
|
|||||||
}}
|
}}
|
||||||
title="Shortcuts"
|
title="Shortcuts"
|
||||||
>
|
>
|
||||||
<RocketLaunchIcon className="h-4 w-4 text-custom-sidebar-text-200" />
|
<RocketLaunchIcon className="h-4 w-4 text-custom-text-200" />
|
||||||
{!sidebarCollapse && <span>Shortcuts</span>}
|
{/* {!sidebarCollapse && <span>Shortcuts</span>} */}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`flex items-center gap-x-1 rounded-md px-2 py-2 text-xs font-medium text-custom-sidebar-text-200 outline-none hover:bg-custom-sidebar-background-90 hover:text-custom-sidebar-text-100 ${
|
className={`flex items-center gap-x-1 rounded-md px-2 py-2 text-xs font-medium text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 ${
|
||||||
sidebarCollapse ? "w-full justify-center" : ""
|
sidebarCollapse ? "w-full justify-center" : ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setIsNeedHelpOpen((prev) => !prev)}
|
onClick={() => setIsNeedHelpOpen((prev) => !prev)}
|
||||||
title="Help"
|
title="Help"
|
||||||
>
|
>
|
||||||
<QuestionMarkCircleIcon className="h-4 w-4 text-custom-sidebar-text-200" />
|
<QuestionMarkCircleIcon className="h-4 w-4 text-custom-text-200" />
|
||||||
{!sidebarCollapse && <span>Help</span>}
|
{/* {!sidebarCollapse && <span>Help</span>} */}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex items-center gap-3 rounded-md px-2 py-2 text-xs font-medium text-custom-sidebar-text-200 outline-none hover:bg-custom-sidebar-background-90 hover:text-custom-sidebar-text-100 md:hidden"
|
className="flex items-center gap-3 rounded-md px-2 py-2 text-xs font-medium text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 md:hidden"
|
||||||
onClick={() => setSidebarActive(false)}
|
onClick={() => setSidebarActive(false)}
|
||||||
>
|
>
|
||||||
<ArrowLongLeftIcon className="h-4 w-4 flex-shrink-0 text-custom-sidebar-text-200 group-hover:text-custom-sidebar-text-100" />
|
<ArrowLongLeftIcon className="h-4 w-4 flex-shrink-0 text-custom-text-200 group-hover:text-custom-text-100" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`hidden items-center gap-3 rounded-md px-2 py-2 text-xs font-medium text-custom-sidebar-text-200 outline-none hover:bg-custom-sidebar-background-90 hover:text-custom-sidebar-text-100 md:flex ${
|
className={`hidden items-center gap-3 rounded-md px-2 py-2 text-xs font-medium text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 md:flex ${
|
||||||
sidebarCollapse ? "w-full justify-center" : ""
|
sidebarCollapse ? "w-full justify-center" : ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() => toggleCollapsed()}
|
onClick={() => toggleCollapsed()}
|
||||||
>
|
>
|
||||||
<ArrowLongLeftIcon
|
<ArrowLongLeftIcon
|
||||||
className={`h-4 w-4 flex-shrink-0 text-custom-sidebar-text-200 duration-300 group-hover:text-custom-sidebar-text-100 ${
|
className={`h-4 w-4 flex-shrink-0 text-custom-text-200 duration-300 group-hover:text-custom-text-100 ${
|
||||||
sidebarCollapse ? "rotate-180" : ""
|
sidebarCollapse ? "rotate-180" : ""
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
@ -121,7 +229,7 @@ export const WorkspaceHelpSection: FC<WorkspaceHelpSectionProps> = (props) => {
|
|||||||
leaveTo="transform opacity-0 scale-95"
|
leaveTo="transform opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`absolute bottom-2 ${helpOptionMode} space-y-2 rounded-sm bg-custom-sidebar-background-80 p-1 shadow-md`}
|
className={`absolute bottom-2 ${helpOptionMode} space-y-2 rounded-sm bg-custom-background-80 p-1 shadow-md`}
|
||||||
ref={helpOptionsRef}
|
ref={helpOptionsRef}
|
||||||
>
|
>
|
||||||
{helpOptions.map(({ name, Icon, href, onClick }) => {
|
{helpOptions.map(({ name, Icon, href, onClick }) => {
|
||||||
@ -130,9 +238,9 @@ export const WorkspaceHelpSection: FC<WorkspaceHelpSectionProps> = (props) => {
|
|||||||
<Link href={href} key={name}>
|
<Link href={href} key={name}>
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="flex items-center gap-x-2 whitespace-nowrap rounded-md px-2 py-1 text-xs hover:bg-custom-sidebar-background-90"
|
className="flex items-center gap-x-2 whitespace-nowrap rounded-md px-2 py-1 text-xs hover:bg-custom-background-90"
|
||||||
>
|
>
|
||||||
<Icon className="h-4 w-4 text-custom-sidebar-text-200" />
|
<Icon className="h-4 w-4 text-custom-text-200" />
|
||||||
<span className="text-sm">{name}</span>
|
<span className="text-sm">{name}</span>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
@ -143,7 +251,7 @@ export const WorkspaceHelpSection: FC<WorkspaceHelpSectionProps> = (props) => {
|
|||||||
key={name}
|
key={name}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onClick ? onClick : undefined}
|
onClick={onClick ? onClick : undefined}
|
||||||
className="flex w-full items-center gap-x-2 whitespace-nowrap rounded-md px-2 py-1 text-xs hover:bg-custom-sidebar-background-90"
|
className="flex w-full items-center gap-x-2 whitespace-nowrap rounded-md px-2 py-1 text-xs hover:bg-custom-background-90"
|
||||||
>
|
>
|
||||||
<Icon className="h-4 w-4 text-custom-sidebar-text-200" />
|
<Icon className="h-4 w-4 text-custom-sidebar-text-200" />
|
||||||
<span className="text-sm">{name}</span>
|
<span className="text-sm">{name}</span>
|
||||||
@ -154,5 +262,6 @@ export const WorkspaceHelpSection: FC<WorkspaceHelpSectionProps> = (props) => {
|
|||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -108,9 +108,9 @@ export const WorkspaceSidebarDropdown = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!sidebarCollapse && (
|
{!sidebarCollapse && (
|
||||||
<p>
|
<h4 className="text-custom-text-100">
|
||||||
{activeWorkspace?.name ? truncateText(activeWorkspace.name, 14) : "Loading..."}
|
{activeWorkspace?.name ? truncateText(activeWorkspace.name, 14) : "Loading..."}
|
||||||
</p>
|
</h4>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Menu.Button>
|
</Menu.Button>
|
||||||
@ -166,7 +166,13 @@ export const WorkspaceSidebarDropdown = () => {
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<h5 className="text-sm">{truncateText(workspace.name, 18)}</h5>
|
<h5
|
||||||
|
className={`text-sm ${
|
||||||
|
workspaceSlug === workspace.slug ? "" : "text-custom-text-200"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{truncateText(workspace.name, 18)}
|
||||||
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<span className="p-1">
|
<span className="p-1">
|
||||||
<CheckIcon
|
<CheckIcon
|
||||||
|
248
apps/app/components/workspace/upgrade-to-pro-modal.tsx
Normal file
248
apps/app/components/workspace/upgrade-to-pro-modal.tsx
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
// headless ui
|
||||||
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
// icons
|
||||||
|
import { XCircleIcon, RocketLaunchIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { CheckCircleIcon } from "@heroicons/react/24/solid";
|
||||||
|
// ui
|
||||||
|
import { CircularProgress } from "components/ui";
|
||||||
|
// types
|
||||||
|
import type { ICurrentUserResponse, IWorkspace } from "types";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
supabase: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
user: ICurrentUserResponse | undefined;
|
||||||
|
issueNumber: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const UpgradeToProModal: React.FC<Props> = ({ isOpen, onClose, user, issueNumber }) => {
|
||||||
|
const [supabaseClient, setSupabaseClient] = useState<any>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Create a Supabase client
|
||||||
|
if (process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {
|
||||||
|
const { createClient } = window.supabase;
|
||||||
|
const supabase = createClient(
|
||||||
|
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||||
|
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||||
|
{
|
||||||
|
auth: {
|
||||||
|
autoRefreshToken: false,
|
||||||
|
persistSession: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (supabase) {
|
||||||
|
setSupabaseClient(supabase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
onClose();
|
||||||
|
setIsLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const proFeatures = [
|
||||||
|
"Everything in free",
|
||||||
|
"Unlimited users",
|
||||||
|
"Unlimited file uploads",
|
||||||
|
"Priority Support",
|
||||||
|
"Custom Theming",
|
||||||
|
"Access to Roadmap",
|
||||||
|
"Plane AI (GPT unlimited)",
|
||||||
|
];
|
||||||
|
|
||||||
|
const [errorMessage, setErrorMessage] = useState<null | { status: String; message: string }>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [loader, setLoader] = useState(false);
|
||||||
|
const submitEmail = async () => {
|
||||||
|
setLoader(true);
|
||||||
|
const payload = { email: user?.email || "" };
|
||||||
|
|
||||||
|
if (supabaseClient) {
|
||||||
|
if (payload?.email) {
|
||||||
|
const emailExists = await supabaseClient
|
||||||
|
.from("web-waitlist")
|
||||||
|
.select("id,email,count")
|
||||||
|
.eq("email", payload?.email);
|
||||||
|
if (emailExists.data.length === 0) {
|
||||||
|
const emailCreation = await supabaseClient
|
||||||
|
.from("web-waitlist")
|
||||||
|
.insert([{ email: payload?.email, count: 1, last_visited: new Date() }])
|
||||||
|
.select("id,email,count");
|
||||||
|
if (emailCreation.status === 201)
|
||||||
|
setErrorMessage({ status: "success", message: "Successfully registered." });
|
||||||
|
else setErrorMessage({ status: "insert_error", message: "Insertion Error." });
|
||||||
|
} else {
|
||||||
|
const emailCountUpdate = await supabaseClient
|
||||||
|
.from("web-waitlist")
|
||||||
|
.upsert({
|
||||||
|
id: emailExists.data[0]?.id,
|
||||||
|
count: emailExists.data[0]?.count + 1,
|
||||||
|
last_visited: new Date(),
|
||||||
|
})
|
||||||
|
.select("id,email,count");
|
||||||
|
if (emailCountUpdate.status === 201)
|
||||||
|
setErrorMessage({
|
||||||
|
status: "email_already_exists",
|
||||||
|
message: "Email already exists.",
|
||||||
|
});
|
||||||
|
else setErrorMessage({ status: "update_error", message: "Update Error." });
|
||||||
|
}
|
||||||
|
} else setErrorMessage({ status: "email_required", message: "Please provide email." });
|
||||||
|
} else
|
||||||
|
setErrorMessage({
|
||||||
|
status: "supabase_error",
|
||||||
|
message: "Network error. Please try again later.",
|
||||||
|
});
|
||||||
|
|
||||||
|
setLoader(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Transition.Root show={isOpen} as={React.Fragment}>
|
||||||
|
<Dialog as="div" className="relative z-20" onClose={handleClose}>
|
||||||
|
<Transition.Child
|
||||||
|
as={React.Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0"
|
||||||
|
enterTo="opacity-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
|
||||||
|
</Transition.Child>
|
||||||
|
|
||||||
|
<div className="fixed inset-0 z-20 overflow-y-auto">
|
||||||
|
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||||
|
<Transition.Child
|
||||||
|
as={React.Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
>
|
||||||
|
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-100 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-4xl">
|
||||||
|
<div className="flex flex-wrap">
|
||||||
|
<div className="w-full md:w-3/5 p-6 flex flex-col gap-y-6">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div
|
||||||
|
className={`font-semibold outline-none text-sm mt-1.5 ${
|
||||||
|
issueNumber >= 750
|
||||||
|
? "text-red-600"
|
||||||
|
: issueNumber >= 500
|
||||||
|
? "text-yellow-600"
|
||||||
|
: "text-green-600"
|
||||||
|
}`}
|
||||||
|
title="Shortcuts"
|
||||||
|
>
|
||||||
|
<CircularProgress
|
||||||
|
progress={
|
||||||
|
(issueNumber / 1024) * 100 > 100 ? 100 : (issueNumber / 1024) * 100
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="">
|
||||||
|
<div className="font-semibold text-lg">Upgrade to pro</div>
|
||||||
|
<div className="text-custom-text-200 text-sm">
|
||||||
|
This workspace has used {issueNumber} of its 1024 issues creation limit (
|
||||||
|
{((issueNumber / 1024) * 100).toFixed(2)}%).
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={handleClose}
|
||||||
|
className="w-5 h-5 text-custom-text-200 cursor-pointer mt-1.5 md:hidden block ml-auto"
|
||||||
|
>
|
||||||
|
<XCircleIcon />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 pt-6">
|
||||||
|
<div
|
||||||
|
className={`font-semibold outline-none text-sm mt-1.5 w-5 h-5 text-[#892FFF] flex-shrink-0`}
|
||||||
|
title="Shortcuts"
|
||||||
|
>
|
||||||
|
<RocketLaunchIcon />
|
||||||
|
</div>
|
||||||
|
<div className="">
|
||||||
|
<div className="font-semibold text-lg">Order summary</div>
|
||||||
|
<div className="text-custom-text-200 text-sm">
|
||||||
|
Priority support, file uploads, and access to premium features.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap my-4">
|
||||||
|
{proFeatures.map((feature, index) => (
|
||||||
|
<div key={index} className="w-1/2 py-2 flex gap-2 my-1.5">
|
||||||
|
<div className="w-5 h-5 mt-0.5 text-green-600">
|
||||||
|
<CheckCircleIcon />
|
||||||
|
</div>
|
||||||
|
<div>{feature}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full md:w-2/5 bg-custom-background-90 p-6 flex flex-col">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div className="font-semibold text-lg">Summary</div>
|
||||||
|
<div
|
||||||
|
onClick={handleClose}
|
||||||
|
className="w-5 h-5 text-custom-text-200 cursor-pointer mt-1.5 hidden md:block"
|
||||||
|
>
|
||||||
|
<XCircleIcon />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-custom-text-200 text-sm mt-4">
|
||||||
|
Plane application is currently in dev-mode. We will soon introduce Pro plans
|
||||||
|
once general availability has been established. Stay tuned for more updates.
|
||||||
|
In the meantime, Plane remains free and unrestricted.
|
||||||
|
<br /> <br />
|
||||||
|
We{"'"}ll ensure a smooth transition from the community version to the Pro
|
||||||
|
plan for you.
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
disabled={loader}
|
||||||
|
onClick={() => submitEmail()}
|
||||||
|
type="button"
|
||||||
|
className="mt-5 md:mt-auto whitespace-nowrap max-w-min items-center gap-x-1 rounded-md px-3 py-2 font-medium outline-none text-sm bg-custom-primary-100 text-white"
|
||||||
|
>
|
||||||
|
{loader ? "Loading.." : " Join waitlist"}
|
||||||
|
</button>
|
||||||
|
{errorMessage && (
|
||||||
|
<div
|
||||||
|
className={`mt-1 text-sm ${
|
||||||
|
errorMessage && errorMessage?.status === "success"
|
||||||
|
? "text-green-500"
|
||||||
|
: " text-red-500"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{errorMessage?.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog.Panel>
|
||||||
|
</Transition.Child>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</Transition.Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpgradeToProModal;
|
@ -73,6 +73,7 @@ export const handleIssuesMutation: THandleIssuesMutation = (
|
|||||||
...prevData[issueIndex],
|
...prevData[issueIndex],
|
||||||
...formData,
|
...formData,
|
||||||
assignees: formData?.assignees_list ?? prevData[issueIndex]?.assignees,
|
assignees: formData?.assignees_list ?? prevData[issueIndex]?.assignees,
|
||||||
|
labels: formData?.labels_list ?? prevData[issueIndex]?.labels,
|
||||||
};
|
};
|
||||||
|
|
||||||
prevData.splice(issueIndex, 1, updatedIssue);
|
prevData.splice(issueIndex, 1, updatedIssue);
|
||||||
@ -90,6 +91,7 @@ export const handleIssuesMutation: THandleIssuesMutation = (
|
|||||||
...oldGroup[issueIndex],
|
...oldGroup[issueIndex],
|
||||||
...formData,
|
...formData,
|
||||||
assignees: formData?.assignees_list ?? oldGroup[issueIndex]?.assignees,
|
assignees: formData?.assignees_list ?? oldGroup[issueIndex]?.assignees,
|
||||||
|
labels: formData?.labels_list ?? oldGroup[issueIndex]?.labels,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (selectedGroupBy !== Object.keys(formData)[0])
|
if (selectedGroupBy !== Object.keys(formData)[0])
|
||||||
|
@ -140,7 +140,7 @@ const SingleCycle: React.FC = () => {
|
|||||||
<IssuesFilterView />
|
<IssuesFilterView />
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
onClick={() => setAnalyticsModal(true)}
|
onClick={() => 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
|
outline
|
||||||
>
|
>
|
||||||
Analytics
|
Analytics
|
||||||
|
@ -63,7 +63,7 @@ const ProjectIssues: NextPage = () => {
|
|||||||
<IssuesFilterView />
|
<IssuesFilterView />
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
onClick={() => setAnalyticsModal(true)}
|
onClick={() => 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
|
outline
|
||||||
>
|
>
|
||||||
Analytics
|
Analytics
|
||||||
@ -72,7 +72,7 @@ const ProjectIssues: NextPage = () => {
|
|||||||
<Link href={`/${workspaceSlug}/projects/${projectId}/inbox/${inboxList?.[0]?.id}`}>
|
<Link href={`/${workspaceSlug}/projects/${projectId}/inbox/${inboxList?.[0]?.id}`}>
|
||||||
<a>
|
<a>
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
className="relative !py-1.5 rounded-md font-normal text-custom-sidebar-text-200 border-custom-sidebar-border-100 hover:bg-custom-sidebar-background-90"
|
className="relative !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
|
outline
|
||||||
>
|
>
|
||||||
<span>Inbox</span>
|
<span>Inbox</span>
|
||||||
|
@ -144,7 +144,7 @@ const SingleModule: React.FC = () => {
|
|||||||
<IssuesFilterView />
|
<IssuesFilterView />
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
onClick={() => setAnalyticsModal(true)}
|
onClick={() => 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
|
outline
|
||||||
>
|
>
|
||||||
Analytics
|
Analytics
|
||||||
|
@ -122,14 +122,14 @@ const EstimatesSettings: NextPage = () => {
|
|||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="p-8">
|
<div className="h-full flex flex-col p-8 overflow-hidden">
|
||||||
<SettingsHeader />
|
<SettingsHeader />
|
||||||
<section className="flex items-center justify-between">
|
<section className="flex items-center justify-between">
|
||||||
<h3 className="text-2xl font-semibold">Estimates</h3>
|
<h3 className="text-2xl font-semibold">Estimates</h3>
|
||||||
<div className="col-span-12 space-y-5 sm:col-span-7">
|
<div className="col-span-12 space-y-5 sm:col-span-7">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span
|
<div
|
||||||
className="flex cursor-pointer items-center gap-2 text-theme"
|
className="flex cursor-pointer items-center gap-2 text-custom-primary-100 hover:text-custom-primary-200"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEstimateToUpdate(undefined);
|
setEstimateToUpdate(undefined);
|
||||||
setEstimateFormOpen(true);
|
setEstimateFormOpen(true);
|
||||||
@ -137,7 +137,7 @@ const EstimatesSettings: NextPage = () => {
|
|||||||
>
|
>
|
||||||
<PlusIcon className="h-4 w-4" />
|
<PlusIcon className="h-4 w-4" />
|
||||||
Create New Estimate
|
Create New Estimate
|
||||||
</span>
|
</div>
|
||||||
{projectDetails?.estimate && (
|
{projectDetails?.estimate && (
|
||||||
<SecondaryButton onClick={disableEstimates}>Disable Estimates</SecondaryButton>
|
<SecondaryButton onClick={disableEstimates}>Disable Estimates</SecondaryButton>
|
||||||
)}
|
)}
|
||||||
@ -146,7 +146,7 @@ const EstimatesSettings: NextPage = () => {
|
|||||||
</section>
|
</section>
|
||||||
{estimatesList ? (
|
{estimatesList ? (
|
||||||
estimatesList.length > 0 ? (
|
estimatesList.length > 0 ? (
|
||||||
<section className="mt-5 divide-y divide-custom-border-100 rounded-xl border border-custom-border-100 bg-custom-background-100 px-6">
|
<section className="h-full mt-5 divide-y divide-custom-border-100 rounded-xl border border-custom-border-100 bg-custom-background-100 px-6 overflow-y-auto">
|
||||||
{estimatesList.map((estimate) => (
|
{estimatesList.map((estimate) => (
|
||||||
<SingleEstimate
|
<SingleEstimate
|
||||||
key={estimate.id}
|
key={estimate.id}
|
||||||
@ -158,7 +158,7 @@ const EstimatesSettings: NextPage = () => {
|
|||||||
))}
|
))}
|
||||||
</section>
|
</section>
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-5">
|
<div className="grid h-full w-full place-items-center">
|
||||||
<EmptyState
|
<EmptyState
|
||||||
type="estimate"
|
type="estimate"
|
||||||
title="Create New Estimate"
|
title="Create New Estimate"
|
||||||
|
@ -188,7 +188,7 @@ const FeaturesSettings: NextPage = () => {
|
|||||||
>
|
>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
{feature.icon}
|
{feature.icon}
|
||||||
<div>
|
<div className="">
|
||||||
<h4 className="text-lg font-semibold">{feature.title}</h4>
|
<h4 className="text-lg font-semibold">{feature.title}</h4>
|
||||||
<p className="text-sm text-custom-text-200">{feature.description}</p>
|
<p className="text-sm text-custom-text-200">{feature.description}</p>
|
||||||
</div>
|
</div>
|
||||||
@ -219,11 +219,21 @@ const FeaturesSettings: NextPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2 text-custom-text-200">
|
||||||
<a href="https://plane.so/" target="_blank" rel="noreferrer">
|
<a
|
||||||
|
href="https://plane.so/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="hover:text-custom-text-100"
|
||||||
|
>
|
||||||
<SecondaryButton outline>Plane is open-source, view Roadmap</SecondaryButton>
|
<SecondaryButton outline>Plane is open-source, view Roadmap</SecondaryButton>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/makeplane/plane" target="_blank" rel="noreferrer">
|
<a
|
||||||
|
href="https://github.com/makeplane/plane"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="hover:text-custom-text-100"
|
||||||
|
>
|
||||||
<SecondaryButton outline>Star us on GitHub</SecondaryButton>
|
<SecondaryButton outline>Star us on GitHub</SecondaryButton>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,11 +58,11 @@ const ProjectIntegrations: NextPage = () => {
|
|||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="p-8">
|
<div className="h-full flex flex-col p-8 overflow-hidden">
|
||||||
<SettingsHeader />
|
<SettingsHeader />
|
||||||
{workspaceIntegrations ? (
|
{workspaceIntegrations ? (
|
||||||
workspaceIntegrations.length > 0 ? (
|
workspaceIntegrations.length > 0 ? (
|
||||||
<section className="space-y-8">
|
<section className="space-y-8 overflow-y-auto">
|
||||||
<IntegrationAndImportExportBanner bannerName="Integrations" />
|
<IntegrationAndImportExportBanner bannerName="Integrations" />
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
{workspaceIntegrations.map((integration) => (
|
{workspaceIntegrations.map((integration) => (
|
||||||
|
@ -85,10 +85,10 @@ const StatesSettings: NextPage = () => {
|
|||||||
return (
|
return (
|
||||||
<div key={key}>
|
<div key={key}>
|
||||||
<div className="mb-2 flex w-full justify-between">
|
<div className="mb-2 flex w-full justify-between">
|
||||||
<h4 className="font-medium capitalize">{key}</h4>
|
<h4 className="text-custom-text-200 capitalize">{key}</h4>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex items-center gap-2 text-custom-primary outline-none"
|
className="flex items-center gap-2 text-custom-primary-100 hover:text-custom-primary-200 outline-none"
|
||||||
onClick={() => setActiveGroup(key as keyof StateGroup)}
|
onClick={() => setActiveGroup(key as keyof StateGroup)}
|
||||||
>
|
>
|
||||||
<PlusIcon className="h-4 w-4" />
|
<PlusIcon className="h-4 w-4" />
|
||||||
|
@ -13,6 +13,7 @@ class MyDocument extends Document {
|
|||||||
<link rel="apple-touch-icon" href="/icon.png" />
|
<link rel="apple-touch-icon" href="/icon.png" />
|
||||||
<meta name="theme-color" content="#fff" />
|
<meta name="theme-color" content="#fff" />
|
||||||
<script defer data-domain="app.plane.so" src="https://plausible.io/js/script.js" />
|
<script defer data-domain="app.plane.so" src="https://plausible.io/js/script.js" />
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2" />
|
||||||
{isSessionRecorderEnabled && process.env.NEXT_PUBLIC_SESSION_RECORDER_KEY && (
|
{isSessionRecorderEnabled && process.env.NEXT_PUBLIC_SESSION_RECORDER_KEY && (
|
||||||
<script
|
<script
|
||||||
defer
|
defer
|
||||||
|
22
apps/app/services/web-waitlist.service.ts
Normal file
22
apps/app/services/web-waitlist.service.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// services
|
||||||
|
import APIService from "services/api.service";
|
||||||
|
|
||||||
|
// types
|
||||||
|
import { IWebWaitListResponse } from "types";
|
||||||
|
|
||||||
|
class WebWailtListServices extends APIService {
|
||||||
|
constructor() {
|
||||||
|
const origin = typeof window !== "undefined" ? window.location.origin || "" : "";
|
||||||
|
super(origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
async create({ email }: { email: string }): Promise<IWebWaitListResponse> {
|
||||||
|
return this.post(`/api/web-waitlist`, { email: email })
|
||||||
|
.then((response) => response?.data)
|
||||||
|
.catch((error) => {
|
||||||
|
throw error?.response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new WebWailtListServices();
|
@ -88,26 +88,26 @@
|
|||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
color-scheme: dark !important;
|
color-scheme: dark !important;
|
||||||
|
|
||||||
--color-background-100: 0, 0, 0; /* primary bg */
|
--color-background-100: 7, 7, 7; /* primary bg */
|
||||||
--color-background-90: 11, 11, 11; /* secondary bg */
|
--color-background-90: 11, 11, 11; /* secondary bg */
|
||||||
--color-background-80: 23, 23, 23; /* tertiary bg */
|
--color-background-80: 23, 23, 23; /* tertiary bg */
|
||||||
|
|
||||||
--color-text-100: 255, 255, 255; /* primary text */
|
--color-text-100: 241, 241, 241; /* primary text */
|
||||||
--color-text-200: 82, 82, 82; /* secondary text */
|
--color-text-200: 115, 115, 115; /* secondary text */
|
||||||
--color-text-300: 115, 115, 115; /* tertiary text */
|
--color-text-300: 163, 163, 163; /* tertiary text */
|
||||||
|
|
||||||
--color-border-100: 34, 34, 34; /* subtle border= 1 */
|
--color-border-100: 34, 34, 34; /* subtle border= 1 */
|
||||||
--color-border-200: 38, 38, 38; /* subtle border- 2 */
|
--color-border-200: 38, 38, 38; /* subtle border- 2 */
|
||||||
--color-border-300: 46, 46, 46; /* strong border- 1 */
|
--color-border-300: 46, 46, 46; /* strong border- 1 */
|
||||||
--color-border-400: 58, 58, 58; /* strong border- 2 */
|
--color-border-400: 58, 58, 58; /* strong border- 2 */
|
||||||
|
|
||||||
--color-sidebar-background-100: 0, 0, 0; /* primary sidebar bg */
|
--color-sidebar-background-100: 7, 7, 7; /* primary sidebar bg */
|
||||||
--color-sidebar-background-90: 11, 11, 11; /* secondary sidebar bg */
|
--color-sidebar-background-90: 11, 11, 11; /* secondary sidebar bg */
|
||||||
--color-sidebar-background-80: 23, 23, 23; /* tertiary sidebar bg */
|
--color-sidebar-background-80: 23, 23, 23; /* tertiary sidebar bg */
|
||||||
|
|
||||||
--color-sidebar-text-100: 255, 255, 255; /* primary sidebar text */
|
--color-sidebar-text-100: 241, 241, 241; /* primary sidebar text */
|
||||||
--color-sidebar-text-200: 82, 82, 82; /* secondary sidebar text */
|
--color-sidebar-text-200: 115, 115, 115; /* secondary sidebar text */
|
||||||
--color-sidebar-text-300: 115, 115, 115; /* tertiary sidebar text */
|
--color-sidebar-text-300: 163, 163, 163; /* tertiary sidebar text */
|
||||||
|
|
||||||
--color-sidebar-border-100: 34, 34, 34; /* subtle sidebar border= 1 */
|
--color-sidebar-border-100: 34, 34, 34; /* subtle sidebar border= 1 */
|
||||||
--color-sidebar-border-200: 38, 38, 38; /* subtle sidebar border- 2 */
|
--color-sidebar-border-200: 38, 38, 38; /* subtle sidebar border- 2 */
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const convertToRGB = (variableName) => `rgb(var(${variableName}))`;
|
const convertToRGB = (variableName) => `rgba(var(${variableName}))`;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
darkMode: "class",
|
darkMode: "class",
|
||||||
|
1
apps/app/types/index.d.ts
vendored
1
apps/app/types/index.d.ts
vendored
@ -15,6 +15,7 @@ export * from "./importer";
|
|||||||
export * from "./inbox";
|
export * from "./inbox";
|
||||||
export * from "./analytics";
|
export * from "./analytics";
|
||||||
export * from "./calendar";
|
export * from "./calendar";
|
||||||
|
export * from "./waitlist";
|
||||||
|
|
||||||
export type NestedKeyOf<ObjectType extends object> = {
|
export type NestedKeyOf<ObjectType extends object> = {
|
||||||
[Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object
|
[Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object
|
||||||
|
3
apps/app/types/waitlist.d.ts
vendored
Normal file
3
apps/app/types/waitlist.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export interface IWebWaitListResponse {
|
||||||
|
status: string;
|
||||||
|
}
|
1
apps/app/types/workspace.d.ts
vendored
1
apps/app/types/workspace.d.ts
vendored
@ -14,6 +14,7 @@ export interface IWorkspace {
|
|||||||
readonly created_by: string;
|
readonly created_by: string;
|
||||||
readonly updated_by: string;
|
readonly updated_by: string;
|
||||||
company_size: number | null;
|
company_size: number | null;
|
||||||
|
total_issues: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkspaceLite {
|
export interface IWorkspaceLite {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
version: "3.8"
|
version: "3.8"
|
||||||
|
|
||||||
x-api-and-worker-env: &api-and-worker-env
|
x-api-and-worker-env:
|
||||||
|
&api-and-worker-env
|
||||||
DEBUG: ${DEBUG}
|
DEBUG: ${DEBUG}
|
||||||
SENTRY_DSN: ${SENTRY_DSN}
|
SENTRY_DSN: ${SENTRY_DSN}
|
||||||
DJANGO_SETTINGS_MODULE: plane.settings.production
|
DJANGO_SETTINGS_MODULE: plane.settings.production
|
||||||
@ -23,6 +24,7 @@ x-api-and-worker-env: &api-and-worker-env
|
|||||||
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
|
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
|
||||||
DISABLE_COLLECTSTATIC: 1
|
DISABLE_COLLECTSTATIC: 1
|
||||||
DOCKERIZED: 1
|
DOCKERIZED: 1
|
||||||
|
OPENAI_API_BASE: ${OPENAI_API_BASE}
|
||||||
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
||||||
GPT_ENGINE: ${GPT_ENGINE}
|
GPT_ENGINE: ${GPT_ENGINE}
|
||||||
SECRET_KEY: ${SECRET_KEY}
|
SECRET_KEY: ${SECRET_KEY}
|
||||||
@ -118,9 +120,7 @@ services:
|
|||||||
createbuckets:
|
createbuckets:
|
||||||
image: minio/mc
|
image: minio/mc
|
||||||
entrypoint: >
|
entrypoint: >
|
||||||
/bin/sh -c " /usr/bin/mc config host add plane-minio http://plane-minio:9000 \$AWS_ACCESS_KEY_ID \$AWS_SECRET_ACCESS_KEY;
|
/bin/sh -c " /usr/bin/mc config host add plane-minio http://plane-minio:9000 \$AWS_ACCESS_KEY_ID \$AWS_SECRET_ACCESS_KEY; /usr/bin/mc mb plane-minio/\$AWS_S3_BUCKET_NAME; /usr/bin/mc anonymous set download plane-minio/\$AWS_S3_BUCKET_NAME; exit 0; "
|
||||||
/usr/bin/mc mb plane-minio/\$AWS_S3_BUCKET_NAME;
|
|
||||||
/usr/bin/mc anonymous set download plane-minio/\$AWS_S3_BUCKET_NAME; exit 0; "
|
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
depends_on:
|
depends_on:
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
version: "3.8"
|
version: "3.8"
|
||||||
|
|
||||||
x-api-and-worker-env: &api-and-worker-env
|
x-api-and-worker-env:
|
||||||
|
&api-and-worker-env
|
||||||
DEBUG: ${DEBUG}
|
DEBUG: ${DEBUG}
|
||||||
SENTRY_DSN: ${SENTRY_DSN}
|
SENTRY_DSN: ${SENTRY_DSN}
|
||||||
DJANGO_SETTINGS_MODULE: plane.settings.production
|
DJANGO_SETTINGS_MODULE: plane.settings.production
|
||||||
@ -23,6 +24,7 @@ x-api-and-worker-env: &api-and-worker-env
|
|||||||
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
|
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
|
||||||
DISABLE_COLLECTSTATIC: 1
|
DISABLE_COLLECTSTATIC: 1
|
||||||
DOCKERIZED: 1
|
DOCKERIZED: 1
|
||||||
|
OPENAI_API_BASE: ${OPENAI_API_BASE}
|
||||||
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
||||||
GPT_ENGINE: ${GPT_ENGINE}
|
GPT_ENGINE: ${GPT_ENGINE}
|
||||||
SECRET_KEY: ${SECRET_KEY}
|
SECRET_KEY: ${SECRET_KEY}
|
||||||
@ -126,9 +128,7 @@ services:
|
|||||||
createbuckets:
|
createbuckets:
|
||||||
image: minio/mc
|
image: minio/mc
|
||||||
entrypoint: >
|
entrypoint: >
|
||||||
/bin/sh -c " /usr/bin/mc config host add plane-minio http://plane-minio:9000 \$AWS_ACCESS_KEY_ID \$AWS_SECRET_ACCESS_KEY;
|
/bin/sh -c " /usr/bin/mc config host add plane-minio http://plane-minio:9000 \$AWS_ACCESS_KEY_ID \$AWS_SECRET_ACCESS_KEY; /usr/bin/mc mb plane-minio/\$AWS_S3_BUCKET_NAME; /usr/bin/mc anonymous set download plane-minio/\$AWS_S3_BUCKET_NAME; exit 0; "
|
||||||
/usr/bin/mc mb plane-minio/\$AWS_S3_BUCKET_NAME;
|
|
||||||
/usr/bin/mc anonymous set download plane-minio/\$AWS_S3_BUCKET_NAME; exit 0; "
|
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
depends_on:
|
depends_on:
|
||||||
|
@ -20,7 +20,9 @@
|
|||||||
"NEXT_PUBLIC_SESSION_RECORDER_KEY",
|
"NEXT_PUBLIC_SESSION_RECORDER_KEY",
|
||||||
"NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS",
|
"NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS",
|
||||||
"NEXT_PUBLIC_SLACK_CLIENT_ID",
|
"NEXT_PUBLIC_SLACK_CLIENT_ID",
|
||||||
"NEXT_PUBLIC_SLACK_CLIENT_SECRET"
|
"NEXT_PUBLIC_SLACK_CLIENT_SECRET",
|
||||||
|
"NEXT_PUBLIC_SUPABASE_URL",
|
||||||
|
"NEXT_PUBLIC_SUPABASE_ANON_KEY"
|
||||||
],
|
],
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"build": {
|
"build": {
|
||||||
|
Loading…
Reference in New Issue
Block a user