Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
780f847800 | ||
|
|
c31c4e9ccc |
@@ -1,41 +1,33 @@
|
||||
|
||||
name: Branch Build
|
||||
name: Docker Branch Build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
branches:
|
||||
- master
|
||||
- release
|
||||
- qa
|
||||
- develop
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
logLevel:
|
||||
description: 'Log level'
|
||||
required: true
|
||||
default: 'warning'
|
||||
tags:
|
||||
description: 'Dev/QA Builds'
|
||||
|
||||
env:
|
||||
TARGET_BRANCH: ${{ github.event.pull_request.base.ref }}
|
||||
gh_branch: ${{ github.ref_name }}
|
||||
img_tag: latest
|
||||
|
||||
jobs:
|
||||
branch_build_and_push:
|
||||
if: ${{ (github.event_name == 'pull_request' && github.event.action =='closed' && github.event.pull_request.merged == true) }}
|
||||
name: Build-Push Web/Space/API/Proxy Docker Image
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3.3.0
|
||||
|
||||
# - name: Set Target Branch Name on PR close
|
||||
# if: ${{ github.event_name == 'pull_request' && github.event.action =='closed' }}
|
||||
# run: echo "TARGET_BRANCH=${{ github.event.pull_request.base.ref }}" >> $GITHUB_ENV
|
||||
|
||||
# - name: Set Target Branch Name on other than PR close
|
||||
# if: ${{ github.event_name == 'push' }}
|
||||
# run: echo "TARGET_BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
|
||||
- uses: ASzc/change-string-case-action@v2
|
||||
id: gh_branch_upper_lower
|
||||
with:
|
||||
string: ${{env.TARGET_BRANCH}}
|
||||
string: ${{ env.gh_branch }}
|
||||
|
||||
- uses: mad9000/actions-find-and-replace-string@2
|
||||
id: gh_branch_replace_slash
|
||||
@@ -119,7 +111,7 @@ jobs:
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
DOCKET_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
branch_build_push_space:
|
||||
runs-on: ubuntu-20.04
|
||||
@@ -149,7 +141,7 @@ jobs:
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
DOCKET_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
branch_build_push_backend:
|
||||
runs-on: ubuntu-20.04
|
||||
@@ -179,7 +171,7 @@ jobs:
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
DOCKET_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
branch_build_push_proxy:
|
||||
runs-on: ubuntu-20.04
|
||||
@@ -210,4 +202,4 @@ jobs:
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
DOCKET_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -70,6 +70,3 @@ ENABLE_MAGIC_LINK_LOGIN="0"
|
||||
# Email redirections and minio domain settings
|
||||
WEB_URL="http://localhost"
|
||||
|
||||
|
||||
# Gunicorn Workers
|
||||
GUNICORN_WORKERS=2
|
||||
|
||||
@@ -6,4 +6,4 @@ python manage.py migrate
|
||||
# Create a Default User
|
||||
python bin/user_script.py
|
||||
|
||||
exec gunicorn -w $GUNICORN_WORKERS -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile -
|
||||
exec gunicorn -w 8 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile -
|
||||
|
||||
@@ -21,8 +21,8 @@ class ConfigurationEndpoint(BaseAPIView):
|
||||
|
||||
def get(self, request):
|
||||
data = {}
|
||||
data["google_client_id"] = os.environ.get("GOOGLE_CLIENT_ID", None)
|
||||
data["github_client_id"] = os.environ.get("GITHUB_CLIENT_ID", None)
|
||||
data["google"] = os.environ.get("GOOGLE_CLIENT_ID", None)
|
||||
data["github"] = os.environ.get("GITHUB_CLIENT_ID", None)
|
||||
data["github_app_name"] = os.environ.get("GITHUB_APP_NAME", None)
|
||||
data["magic_login"] = (
|
||||
bool(settings.EMAIL_HOST_USER) and bool(settings.EMAIL_HOST_PASSWORD)
|
||||
@@ -30,5 +30,5 @@ class ConfigurationEndpoint(BaseAPIView):
|
||||
data["email_password_login"] = (
|
||||
os.environ.get("ENABLE_EMAIL_PASSWORD", "0") == "1"
|
||||
)
|
||||
data["slack_client_id"] = os.environ.get("SLACK_CLIENT_ID", None)
|
||||
data["slack"] = os.environ.get("SLACK_CLIENT_ID", None)
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
@@ -479,13 +479,13 @@ class CycleViewSet(BaseViewSet):
|
||||
)
|
||||
)
|
||||
cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
|
||||
# Delete the cycle
|
||||
cycle.delete()
|
||||
issue_activity.delay(
|
||||
type="cycle.activity.deleted",
|
||||
requested_data=json.dumps(
|
||||
{
|
||||
"cycle_id": str(pk),
|
||||
"cycle_name": str(cycle.name),
|
||||
"issues": [str(issue_id) for issue_id in cycle_issues],
|
||||
}
|
||||
),
|
||||
@@ -495,8 +495,6 @@ class CycleViewSet(BaseViewSet):
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
# Delete the cycle
|
||||
cycle.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@@ -513,6 +511,12 @@ class CycleIssueViewSet(BaseViewSet):
|
||||
"issue__assignees__id",
|
||||
]
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
cycle_id=self.kwargs.get("cycle_id"),
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.filter_queryset(
|
||||
super()
|
||||
@@ -665,7 +669,7 @@ class CycleIssueViewSet(BaseViewSet):
|
||||
type="cycle.activity.created",
|
||||
requested_data=json.dumps({"cycles_list": issues}),
|
||||
actor_id=str(self.request.user.id),
|
||||
issue_id=None,
|
||||
issue_id=str(self.kwargs.get("pk", None)),
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
current_instance=json.dumps(
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ from django.contrib.auth.hashers import make_password
|
||||
# Third party imports
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from sentry_sdk import capture_exception
|
||||
from sentry_sdk import capture_exception, capture_message
|
||||
|
||||
# Module imports
|
||||
from plane.api.views import BaseViewSet
|
||||
@@ -109,6 +109,7 @@ class WorkspaceIntegrationViewSet(BaseViewSet):
|
||||
access_token = metadata.get("access_token", False)
|
||||
team_id = metadata.get("team", {}).get("id", False)
|
||||
if not metadata or not access_token or not team_id:
|
||||
capture_message(slack_response)
|
||||
return Response(
|
||||
{"error": "Slack could not be installed. Please try again later"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -133,6 +134,7 @@ class WorkspaceIntegrationViewSet(BaseViewSet):
|
||||
user=bot_user,
|
||||
user_type=1, # bot user
|
||||
workspace=workspace,
|
||||
description="",
|
||||
)
|
||||
|
||||
workspace_integration = WorkspaceIntegration.objects.create(
|
||||
|
||||
@@ -59,7 +59,6 @@ class SlackProjectSyncViewSet(BaseViewSet):
|
||||
team_id=slack_response.get("team", {}).get("id"),
|
||||
team_name=slack_response.get("team", {}).get("name"),
|
||||
workspace_integration=workspace_integration,
|
||||
project_id=project_id,
|
||||
)
|
||||
_ = ProjectMember.objects.get_or_create(
|
||||
member=workspace_integration.actor, role=20, project_id=project_id
|
||||
|
||||
@@ -2230,7 +2230,7 @@ class IssueDraftViewSet(BaseViewSet):
|
||||
def destroy(self, request, slug, project_id, pk=None):
|
||||
issue = Issue.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
current_instance = json.dumps(
|
||||
IssueSerializer(issue).data, cls=DjangoJSONEncoder
|
||||
IssueSerializer(current_instance).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
issue.delete()
|
||||
issue_activity.delay(
|
||||
|
||||
@@ -266,12 +266,12 @@ class ModuleViewSet(BaseViewSet):
|
||||
module_issues = list(
|
||||
ModuleIssue.objects.filter(module_id=pk).values_list("issue", flat=True)
|
||||
)
|
||||
module.delete()
|
||||
issue_activity.delay(
|
||||
type="module.activity.deleted",
|
||||
requested_data=json.dumps(
|
||||
{
|
||||
"module_id": str(pk),
|
||||
"module_name": str(module.name),
|
||||
"issues": [str(issue_id) for issue_id in module_issues],
|
||||
}
|
||||
),
|
||||
@@ -281,7 +281,6 @@ class ModuleViewSet(BaseViewSet):
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
module.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@@ -298,6 +297,12 @@ class ModuleIssueViewSet(BaseViewSet):
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
module_id=self.kwargs.get("module_id"),
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.filter_queryset(
|
||||
super()
|
||||
@@ -441,7 +446,7 @@ class ModuleIssueViewSet(BaseViewSet):
|
||||
type="module.activity.created",
|
||||
requested_data=json.dumps({"modules_list": issues}),
|
||||
actor_id=str(self.request.user.id),
|
||||
issue_id=None,
|
||||
issue_id=str(self.kwargs.get("pk", None)),
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
current_instance=json.dumps(
|
||||
{
|
||||
|
||||
@@ -691,10 +691,6 @@ def create_cycle_issue_activity(
|
||||
new_cycle = Cycle.objects.filter(
|
||||
pk=updated_record.get("new_cycle_id", None)
|
||||
).first()
|
||||
issue = Issue.objects.filter(pk=updated_record.get("issue_id")).first()
|
||||
if issue:
|
||||
issue.updated_at = timezone.now()
|
||||
issue.save(update_fields=["updated_at"])
|
||||
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
@@ -717,10 +713,6 @@ def create_cycle_issue_activity(
|
||||
cycle = Cycle.objects.filter(
|
||||
pk=created_record.get("fields").get("cycle")
|
||||
).first()
|
||||
issue = Issue.objects.filter(pk=created_record.get("fields").get("issue")).first()
|
||||
if issue:
|
||||
issue.updated_at = timezone.now()
|
||||
issue.save(update_fields=["updated_at"])
|
||||
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
@@ -755,27 +747,22 @@ def delete_cycle_issue_activity(
|
||||
)
|
||||
|
||||
cycle_id = requested_data.get("cycle_id", "")
|
||||
cycle_name = requested_data.get("cycle_name", "")
|
||||
cycle = Cycle.objects.filter(pk=cycle_id).first()
|
||||
issues = requested_data.get("issues")
|
||||
|
||||
for issue in issues:
|
||||
current_issue = Issue.objects.filter(pk=issue).first()
|
||||
if issue:
|
||||
current_issue.updated_at = timezone.now()
|
||||
current_issue.save(update_fields=["updated_at"])
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=issue,
|
||||
actor_id=actor_id,
|
||||
verb="deleted",
|
||||
old_value=cycle.name if cycle is not None else cycle_name,
|
||||
old_value=cycle.name if cycle is not None else "",
|
||||
new_value="",
|
||||
field="cycles",
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"removed this issue from {cycle.name if cycle is not None else cycle_name}",
|
||||
old_identifier=cycle_id if cycle_id is not None else None,
|
||||
comment=f"removed this issue from {cycle.name if cycle is not None else None}",
|
||||
old_identifier=cycle.id if cycle is not None else None,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
@@ -807,10 +794,6 @@ def create_module_issue_activity(
|
||||
new_module = Module.objects.filter(
|
||||
pk=updated_record.get("new_module_id", None)
|
||||
).first()
|
||||
issue = Issue.objects.filter(pk=updated_record.get("issue_id")).first()
|
||||
if issue:
|
||||
issue.updated_at = timezone.now()
|
||||
issue.save(update_fields=["updated_at"])
|
||||
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
@@ -833,10 +816,6 @@ def create_module_issue_activity(
|
||||
module = Module.objects.filter(
|
||||
pk=created_record.get("fields").get("module")
|
||||
).first()
|
||||
issue = Issue.objects.filter(pk=created_record.get("fields").get("issue")).first()
|
||||
if issue:
|
||||
issue.updated_at = timezone.now()
|
||||
issue.save(update_fields=["updated_at"])
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=created_record.get("fields").get("issue"),
|
||||
@@ -870,27 +849,22 @@ def delete_module_issue_activity(
|
||||
)
|
||||
|
||||
module_id = requested_data.get("module_id", "")
|
||||
module_name = requested_data.get("module_name", "")
|
||||
module = Module.objects.filter(pk=module_id).first()
|
||||
issues = requested_data.get("issues")
|
||||
|
||||
for issue in issues:
|
||||
current_issue = Issue.objects.filter(pk=issue).first()
|
||||
if issue:
|
||||
current_issue.updated_at = timezone.now()
|
||||
current_issue.save(update_fields=["updated_at"])
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=issue,
|
||||
actor_id=actor_id,
|
||||
verb="deleted",
|
||||
old_value=module.name if module is not None else module_name,
|
||||
old_value=module.name if module is not None else "",
|
||||
new_value="",
|
||||
field="modules",
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"removed this issue from {module.name if module is not None else module_name}",
|
||||
old_identifier=module_id if module_id is not None else None,
|
||||
comment=f"removed this issue from ",
|
||||
old_identifier=module.id if module is not None else None,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
@@ -1478,16 +1452,15 @@ def issue_activity(
|
||||
issue_activities = []
|
||||
|
||||
project = Project.objects.get(pk=project_id)
|
||||
issue = Issue.objects.filter(pk=issue_id).first()
|
||||
workspace_id = project.workspace_id
|
||||
|
||||
if issue_id is not None:
|
||||
issue = Issue.objects.filter(pk=issue_id).first()
|
||||
if issue:
|
||||
try:
|
||||
issue.updated_at = timezone.now()
|
||||
issue.save(update_fields=["updated_at"])
|
||||
except Exception as e:
|
||||
pass
|
||||
if issue is not None:
|
||||
try:
|
||||
issue.updated_at = timezone.now()
|
||||
issue.save(update_fields=["updated_at"])
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
ACTIVITY_MAPPER = {
|
||||
"issue.activity.created": create_issue_activity,
|
||||
|
||||
@@ -10,8 +10,6 @@ x-app-env : &app-env
|
||||
- SENTRY_DSN=${SENTRY_DSN:-""}
|
||||
- GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET:-""}
|
||||
- DOCKERIZED=${DOCKERIZED:-1}
|
||||
# Gunicorn Workers
|
||||
- GUNICORN_WORKERS=${GUNICORN_WORKERS:-2}
|
||||
#DB SETTINGS
|
||||
- PGHOST=${PGHOST:-plane-db}
|
||||
- PGDATABASE=${PGDATABASE:-plane}
|
||||
|
||||
@@ -61,5 +61,3 @@ MINIO_ROOT_PASSWORD="secret-key"
|
||||
BUCKET_NAME=uploads
|
||||
FILE_SIZE_LIMIT=5242880
|
||||
|
||||
# Gunicorn Workers
|
||||
GUNICORN_WORKERS=2
|
||||
|
||||
@@ -42,10 +42,6 @@ type Props = {
|
||||
* The source of the avatar image
|
||||
*/
|
||||
src?: string;
|
||||
/**
|
||||
* The custom CSS class name to apply to the component
|
||||
*/
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -98,7 +94,7 @@ export const getBorderRadius = (shape: "circle" | "square") => {
|
||||
case "circle":
|
||||
return "rounded-full";
|
||||
case "square":
|
||||
return "rounded";
|
||||
return "rounded-md";
|
||||
default:
|
||||
return "rounded-full";
|
||||
}
|
||||
@@ -123,7 +119,6 @@ export const Avatar: React.FC<Props> = (props) => {
|
||||
size = "md",
|
||||
shape = "circle",
|
||||
src,
|
||||
className = ""
|
||||
} = props;
|
||||
|
||||
// get size details based on the size prop
|
||||
@@ -150,14 +145,14 @@ export const Avatar: React.FC<Props> = (props) => {
|
||||
{src ? (
|
||||
<img
|
||||
src={src}
|
||||
className={`h-full w-full ${getBorderRadius(shape)} ${className}`}
|
||||
className={`h-full w-full ${getBorderRadius(shape)}`}
|
||||
alt={name}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className={`${
|
||||
sizeInfo.fontSize
|
||||
} grid place-items-center h-full w-full ${getBorderRadius(shape)} ${className}`}
|
||||
} grid place-items-center h-full w-full ${getBorderRadius(shape)}`}
|
||||
style={{
|
||||
backgroundColor:
|
||||
fallbackBackgroundColor ?? "rgba(var(--color-primary-500))",
|
||||
|
||||
@@ -1,23 +1,10 @@
|
||||
import * as React from "react";
|
||||
|
||||
export interface ISpinner extends React.SVGAttributes<SVGElement> {
|
||||
height?: string;
|
||||
width?: string;
|
||||
className?: string | undefined;
|
||||
}
|
||||
|
||||
export const Spinner: React.FC<ISpinner> = ({
|
||||
height = "32px",
|
||||
width = "32px",
|
||||
className = "",
|
||||
...rest
|
||||
}) => (
|
||||
export const Spinner: React.FC = () => (
|
||||
<div role="status">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
height={height}
|
||||
width={width}
|
||||
className={`mr-2 animate-spin fill-blue-600 text-custom-text-200 ${className}`}
|
||||
className="mr-2 h-8 w-8 animate-spin fill-blue-600 text-custom-text-200"
|
||||
viewBox="0 0 100 101"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
@@ -116,9 +116,7 @@ export const SignInView = observer(() => {
|
||||
)}
|
||||
|
||||
<div className="flex flex-col items-center justify-center gap-4 pt-7 sm:w-[360px] mx-auto overflow-hidden">
|
||||
{data?.google_client_id && (
|
||||
<GoogleLoginButton clientId={data.google_client_id} handleSignIn={handleGoogleSignIn} />
|
||||
)}
|
||||
{data?.google && <GoogleLoginButton clientId={data.google} handleSignIn={handleGoogleSignIn} />}
|
||||
</div>
|
||||
|
||||
<p className="pt-16 text-custom-text-200 text-sm text-center">
|
||||
|
||||
@@ -29,7 +29,7 @@ export const RenderIssueState = observer(({ state }: { state: IIssueState }) =>
|
||||
return (
|
||||
<div className={`inline-flex items-center gap-x-1 rounded-full px-2 py-0.5 ${stateGroup.className || ``}`}>
|
||||
<div className="flex-shrink-0 w-3 h-3 flex justify-center items-center overflow-hidden rounded-full">
|
||||
{/* <stateGroup.icon /> */}
|
||||
<stateGroup.icon />
|
||||
</div>
|
||||
<div className="text-xs font-medium whitespace-nowrap">{state?.name}</div>
|
||||
<div
|
||||
|
||||
@@ -77,7 +77,7 @@ export const NavbarIssueFilter = observer(() => {
|
||||
return {
|
||||
display: (
|
||||
<span className="capitalize flex items-center gap-x-2">
|
||||
{/* {stateGroup && <stateGroup.icon />} */}
|
||||
{stateGroup && <stateGroup.icon />}
|
||||
{state.name}
|
||||
</span>
|
||||
),
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
// icons
|
||||
import { Icon } from "components/ui";
|
||||
// helpers
|
||||
@@ -65,7 +63,7 @@ export const PeekOverviewIssueProperties: React.FC<Props> = ({ issueDetails, mod
|
||||
{stateGroup && (
|
||||
<div className="inline-flex bg-custom-background-80 text-sm rounded px-2.5 py-0.5">
|
||||
<div className="flex items-center gap-1.5 text-left text-custom-text-100">
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} />
|
||||
<stateGroup.icon />
|
||||
{addSpaceIfCamelCase(state?.name ?? "")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// interfaces
|
||||
import {
|
||||
IIssueBoardViews,
|
||||
// priority
|
||||
TIssuePriorityKey,
|
||||
// state groups
|
||||
@@ -7,6 +8,14 @@ import {
|
||||
IIssuePriorityFilters,
|
||||
IIssueGroup,
|
||||
} from "types/issue";
|
||||
// icons
|
||||
import {
|
||||
BacklogStateIcon,
|
||||
UnstartedStateIcon,
|
||||
StartedStateIcon,
|
||||
CompletedStateIcon,
|
||||
CancelledStateIcon,
|
||||
} from "components/icons";
|
||||
|
||||
// all issue views
|
||||
export const issueViews: any = {
|
||||
@@ -83,30 +92,35 @@ export const issueGroups: IIssueGroup[] = [
|
||||
title: "Backlog",
|
||||
color: "#d9d9d9",
|
||||
className: `text-[#d9d9d9] bg-[#d9d9d9]/10`,
|
||||
icon: BacklogStateIcon,
|
||||
},
|
||||
{
|
||||
key: "unstarted",
|
||||
title: "Unstarted",
|
||||
color: "#3f76ff",
|
||||
className: `text-[#3f76ff] bg-[#3f76ff]/10`,
|
||||
icon: UnstartedStateIcon,
|
||||
},
|
||||
{
|
||||
key: "started",
|
||||
title: "Started",
|
||||
color: "#f59e0b",
|
||||
className: `text-[#f59e0b] bg-[#f59e0b]/10`,
|
||||
icon: StartedStateIcon,
|
||||
},
|
||||
{
|
||||
key: "completed",
|
||||
title: "Completed",
|
||||
color: "#16a34a",
|
||||
className: `text-[#16a34a] bg-[#16a34a]/10`,
|
||||
icon: CompletedStateIcon,
|
||||
},
|
||||
{
|
||||
key: "cancelled",
|
||||
title: "Cancelled",
|
||||
color: "#dc2626",
|
||||
className: `text-[#dc2626] bg-[#dc2626]/10`,
|
||||
icon: CancelledStateIcon,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -3,13 +3,12 @@ import APIService from "services/api.service";
|
||||
// helper
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
|
||||
export interface IAppConfig {
|
||||
email_password_login: boolean;
|
||||
google_client_id: string | null;
|
||||
export interface IEnvConfig {
|
||||
github: string;
|
||||
google: string;
|
||||
github_app_name: string | null;
|
||||
github_client_id: string | null;
|
||||
email_password_login: boolean;
|
||||
magic_login: boolean;
|
||||
slack_client_id: string | null;
|
||||
}
|
||||
|
||||
export class AppConfigService extends APIService {
|
||||
@@ -17,7 +16,7 @@ export class AppConfigService extends APIService {
|
||||
super(API_BASE_URL);
|
||||
}
|
||||
|
||||
async envConfig(): Promise<IAppConfig> {
|
||||
async envConfig(): Promise<IEnvConfig> {
|
||||
return this.get("/api/configs/", {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface IIssueGroup {
|
||||
title: TIssueGroupTitle;
|
||||
color: string;
|
||||
className: string;
|
||||
icon: React.FC;
|
||||
}
|
||||
|
||||
export interface IIssue {
|
||||
@@ -39,12 +40,7 @@ export interface IIssue {
|
||||
sequence_id: number;
|
||||
start_date: any;
|
||||
state: string;
|
||||
state_detail: {
|
||||
id: string;
|
||||
name: string;
|
||||
group: TIssueGroupKey;
|
||||
color: string;
|
||||
};
|
||||
state_detail: any;
|
||||
target_date: any;
|
||||
votes: IVote[];
|
||||
}
|
||||
|
||||
+3
-1
@@ -5,6 +5,7 @@
|
||||
"NEXT_PUBLIC_DEPLOY_URL",
|
||||
"NEXT_PUBLIC_SENTRY_DSN",
|
||||
"NEXT_PUBLIC_SENTRY_ENVIRONMENT",
|
||||
"NEXT_PUBLIC_GITHUB_APP_NAME",
|
||||
"NEXT_PUBLIC_ENABLE_SENTRY",
|
||||
"NEXT_PUBLIC_ENABLE_OAUTH",
|
||||
"NEXT_PUBLIC_TRACK_EVENTS",
|
||||
@@ -21,7 +22,8 @@
|
||||
"SLACK_CLIENT_SECRET",
|
||||
"JITSU_TRACKER_ACCESS_KEY",
|
||||
"JITSU_TRACKER_HOST",
|
||||
"UNSPLASH_ACCESS_KEY"
|
||||
"UNSPLASH_ACCESS_KEY",
|
||||
"NEXT_PUBLIC_SLACK_CLIENT_ID"
|
||||
],
|
||||
"pipeline": {
|
||||
"build": {
|
||||
|
||||
@@ -29,7 +29,7 @@ export const AutoArchiveAutomation: React.FC<Props> = ({ projectDetails, handleC
|
||||
handleClose={() => setmonthModal(false)}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
<div className="flex flex-col gap-4 border-b border-custom-border-100 px-4 py-6">
|
||||
<div className="flex flex-col gap-4 border-b border-custom-border-200 px-4 py-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex items-center justify-center p-3 rounded bg-custom-background-90">
|
||||
@@ -54,7 +54,7 @@ export const AutoArchiveAutomation: React.FC<Props> = ({ projectDetails, handleC
|
||||
|
||||
{projectDetails?.archive_in !== 0 && (
|
||||
<div className="ml-12">
|
||||
<div className="flex items-center justify-between rounded px-5 py-4 bg-custom-background-90 border-[0.5px] border-custom-border-200 gap-2 w-full">
|
||||
<div className="flex items-center justify-between rounded px-5 py-4 bg-custom-background-90 border border-custom-border-200 gap-2 w-full">
|
||||
<div className="w-1/2 text-sm font-medium">Auto-archive issues that are closed for</div>
|
||||
<div className="w-1/2">
|
||||
<CustomSelect
|
||||
|
||||
@@ -73,7 +73,7 @@ export const AutoCloseAutomation: React.FC<Props> = ({ projectDetails, handleCha
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col gap-4 border-b border-custom-border-100 px-4 py-6">
|
||||
<div className="flex flex-col gap-4 border-b border-custom-border-200 px-4 py-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex items-center justify-center p-3 rounded bg-custom-background-90">
|
||||
@@ -100,7 +100,7 @@ export const AutoCloseAutomation: React.FC<Props> = ({ projectDetails, handleCha
|
||||
|
||||
{projectDetails?.close_in !== 0 && (
|
||||
<div className="ml-12">
|
||||
<div className="flex flex-col rounded bg-custom-background-90 border-[0.5px] border-custom-border-200 p-2">
|
||||
<div className="flex flex-col rounded bg-custom-background-90 border border-custom-border-200 p-2">
|
||||
<div className="flex items-center justify-between px-5 py-4 gap-2 w-full">
|
||||
<div className="w-1/2 text-sm font-medium">Auto-close issues that are inactive for</div>
|
||||
<div className="w-1/2">
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useRouter } from "next/router";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { Command } from "cmdk";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import {
|
||||
FileText,
|
||||
FolderPlus,
|
||||
@@ -17,13 +16,12 @@ import {
|
||||
UserMinus2,
|
||||
UserPlus2,
|
||||
} from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// services
|
||||
import { WorkspaceService } from "services/workspace.service";
|
||||
import { IssueService } from "services/issue";
|
||||
// hooks
|
||||
import useDebounce from "hooks/use-debounce";
|
||||
import useUser from "hooks/use-user";
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import {
|
||||
@@ -63,8 +61,11 @@ type Props = {
|
||||
const workspaceService = new WorkspaceService();
|
||||
const issueService = new IssueService();
|
||||
|
||||
export const CommandModal: React.FC<Props> = observer((props) => {
|
||||
export const CommandModal: React.FC<Props> = (props) => {
|
||||
const { deleteIssue, isPaletteOpen, closePalette } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, issueId } = router.query;
|
||||
// states
|
||||
const [placeholder, setPlaceholder] = useState("Type a command or search...");
|
||||
const [resultsCount, setResultsCount] = useState(0);
|
||||
@@ -85,19 +86,14 @@ export const CommandModal: React.FC<Props> = observer((props) => {
|
||||
const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(false);
|
||||
const [pages, setPages] = useState<string[]>([]);
|
||||
|
||||
const { user: userStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const user = userStore.currentUser ?? undefined;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, issueId } = router.query;
|
||||
|
||||
const page = pages[pages.length - 1];
|
||||
|
||||
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { user } = useUser();
|
||||
|
||||
const { data: issueDetails } = useSWR(
|
||||
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null,
|
||||
workspaceSlug && projectId && issueId
|
||||
@@ -472,7 +468,10 @@ export const CommandModal: React.FC<Props> = observer((props) => {
|
||||
<Command.Item
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
commandPaletteStore.toggleCreateIssueModal(true);
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "c",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
className="focus:bg-custom-background-80"
|
||||
>
|
||||
@@ -489,7 +488,10 @@ export const CommandModal: React.FC<Props> = observer((props) => {
|
||||
<Command.Item
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
commandPaletteStore.toggleCreateProjectModal(true);
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "p",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
>
|
||||
@@ -508,7 +510,10 @@ export const CommandModal: React.FC<Props> = observer((props) => {
|
||||
<Command.Item
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
commandPaletteStore.toggleCreateCycleModal(true);
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "q",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
>
|
||||
@@ -523,7 +528,10 @@ export const CommandModal: React.FC<Props> = observer((props) => {
|
||||
<Command.Item
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
commandPaletteStore.toggleCreateModuleModal(true);
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "m",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
>
|
||||
@@ -538,7 +546,10 @@ export const CommandModal: React.FC<Props> = observer((props) => {
|
||||
<Command.Item
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
commandPaletteStore.toggleCreateViewModal(true);
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "v",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
>
|
||||
@@ -553,7 +564,10 @@ export const CommandModal: React.FC<Props> = observer((props) => {
|
||||
<Command.Item
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
commandPaletteStore.toggleCreatePageModal(true);
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "d",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
>
|
||||
@@ -607,7 +621,10 @@ export const CommandModal: React.FC<Props> = observer((props) => {
|
||||
<Command.Item
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
commandPaletteStore.toggleShortcutModal(true);
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "h",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
>
|
||||
@@ -745,4 +762,4 @@ export const CommandModal: React.FC<Props> = observer((props) => {
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { MouseEvent } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// ui
|
||||
import { SingleProgressStats } from "components/core";
|
||||
import {
|
||||
@@ -27,6 +25,7 @@ import { ActiveCycleProgressStats } from "components/cycles";
|
||||
import { ViewIssueLabel } from "components/issues";
|
||||
// icons
|
||||
import { AlarmClock, AlertTriangle, ArrowRight, CalendarDays, Star, Target } from "lucide-react";
|
||||
|
||||
// helpers
|
||||
import { getDateRangeStatus, renderShortDateWithYearFormat, findHowManyDaysLeft } from "helpers/date-time.helper";
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
@@ -66,12 +65,12 @@ interface IActiveCycleDetails {
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props) => {
|
||||
export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = (props) => {
|
||||
const router = useRouter();
|
||||
|
||||
const { workspaceSlug, projectId } = props;
|
||||
|
||||
const { cycle: cycleStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { cycle: cycleStore } = useMobxStore();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@@ -118,7 +117,12 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
||||
<button
|
||||
type="button"
|
||||
className="text-custom-primary-100 text-sm outline-none"
|
||||
onClick={() => commandPaletteStore.toggleCreateCycleModal(true)}
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "q",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
>
|
||||
Create a new cycle
|
||||
</button>
|
||||
@@ -481,4 +485,4 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -44,7 +44,6 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
||||
const isCompleted = cycleStatus === "completed";
|
||||
const endDate = new Date(cycle.end_date ?? "");
|
||||
const startDate = new Date(cycle.start_date ?? "");
|
||||
const isDateValid = cycle.start_date || cycle.end_date;
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@@ -65,7 +64,9 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
||||
? cycleTotalIssues === 0
|
||||
? "0 Issue"
|
||||
: cycleTotalIssues === cycle.completed_issues
|
||||
? `${cycleTotalIssues} Issue${cycleTotalIssues > 1 ? "s" : ""}`
|
||||
? cycleTotalIssues > 1
|
||||
? `${cycleTotalIssues} Issues`
|
||||
: `${cycleTotalIssues} Issue`
|
||||
: `${cycle.completed_issues}/${cycleTotalIssues} Issues`
|
||||
: "0 Issue";
|
||||
|
||||
@@ -224,14 +225,10 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
||||
</Tooltip>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
{isDateValid ? (
|
||||
<span className="text-xs text-custom-text-300">
|
||||
{areYearsEqual ? renderShortDate(startDate, "_ _") : renderShortMonthDate(startDate, "_ _")} -{" "}
|
||||
{areYearsEqual ? renderShortDate(endDate, "_ _") : renderShortMonthDate(endDate, "_ _")}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-xs text-custom-text-400">No due date</span>
|
||||
)}
|
||||
<span className="text-xs text-custom-text-300">
|
||||
{areYearsEqual ? renderShortDate(startDate, "_ _") : renderShortMonthDate(startDate, "_ _")} -{" "}
|
||||
{areYearsEqual ? renderShortDate(endDate, "_ _") : renderShortMonthDate(endDate, "_ _")}
|
||||
</span>
|
||||
<div className="flex items-center gap-1.5 z-10">
|
||||
{cycle.is_favorite ? (
|
||||
<button type="button" onClick={handleRemoveFromFavorites}>
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { CyclePeekOverview, CyclesBoardCard } from "components/cycles";
|
||||
// types
|
||||
import { ICycle } from "types";
|
||||
// components
|
||||
import { CyclePeekOverview, CyclesBoardCard } from "components/cycles";
|
||||
|
||||
export interface ICyclesBoard {
|
||||
cycles: ICycle[];
|
||||
@@ -15,11 +12,9 @@ export interface ICyclesBoard {
|
||||
peekCycle: string;
|
||||
}
|
||||
|
||||
export const CyclesBoard: FC<ICyclesBoard> = observer((props) => {
|
||||
export const CyclesBoard: FC<ICyclesBoard> = (props) => {
|
||||
const { cycles, filter, workspaceSlug, projectId, peekCycle } = props;
|
||||
|
||||
const { commandPalette: commandPaletteStore } = useMobxStore();
|
||||
|
||||
return (
|
||||
<>
|
||||
{cycles.length > 0 ? (
|
||||
@@ -58,7 +53,12 @@ export const CyclesBoard: FC<ICyclesBoard> = observer((props) => {
|
||||
<button
|
||||
type="button"
|
||||
className="text-custom-primary-100 text-sm outline-none"
|
||||
onClick={() => commandPaletteStore.toggleCreateCycleModal(true)}
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "q",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
>
|
||||
Create a new cycle
|
||||
</button>
|
||||
@@ -67,4 +67,4 @@ export const CyclesBoard: FC<ICyclesBoard> = observer((props) => {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -155,11 +155,9 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
||||
<span className="flex-shrink-0">
|
||||
<CircularProgressIndicator size={38} percentage={progress}>
|
||||
{isCompleted ? (
|
||||
progress === 100 ? (
|
||||
<Check className="h-3 w-3 text-custom-primary-100 stroke-[2]" />
|
||||
) : (
|
||||
<span className="text-sm text-custom-primary-100">{`!`}</span>
|
||||
)
|
||||
<span className="text-sm text-custom-primary-100">{`!`}</span>
|
||||
) : progress === 100 ? (
|
||||
<Check className="h-3 w-3 text-custom-primary-100 stroke-[2]" />
|
||||
) : (
|
||||
<span className="text-xs text-custom-text-300">{`${progress}%`}</span>
|
||||
)}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { CyclePeekOverview, CyclesListItem } from "components/cycles";
|
||||
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
// types
|
||||
@@ -16,11 +14,9 @@ export interface ICyclesList {
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
export const CyclesList: FC<ICyclesList> = observer((props) => {
|
||||
export const CyclesList: FC<ICyclesList> = (props) => {
|
||||
const { cycles, filter, workspaceSlug, projectId } = props;
|
||||
|
||||
const { commandPalette: commandPaletteStore } = useMobxStore();
|
||||
|
||||
return (
|
||||
<>
|
||||
{cycles ? (
|
||||
@@ -57,7 +53,12 @@ export const CyclesList: FC<ICyclesList> = observer((props) => {
|
||||
<button
|
||||
type="button"
|
||||
className="text-custom-primary-100 text-sm outline-none"
|
||||
onClick={() => commandPaletteStore.toggleCreateCycleModal(true)}
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "q",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
>
|
||||
Create a new cycle
|
||||
</button>
|
||||
@@ -74,4 +75,4 @@ export const CyclesList: FC<ICyclesList> = observer((props) => {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -317,11 +317,11 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
<LinkIcon className="h-3 w-3 text-custom-text-300" />
|
||||
</button>
|
||||
{!isCompleted && (
|
||||
<CustomMenu width="lg" placement="bottom-end" ellipsis>
|
||||
<CustomMenu width="lg" ellipsis>
|
||||
<CustomMenu.MenuItem onClick={() => setCycleDeleteModal(true)}>
|
||||
<span className="flex items-center justify-start gap-2">
|
||||
<Trash2 className="h-3 w-3" />
|
||||
<span>Delete cycle</span>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
<span>Delete</span>
|
||||
</span>
|
||||
</CustomMenu.MenuItem>
|
||||
</CustomMenu>
|
||||
|
||||
@@ -54,7 +54,7 @@ export const EstimateListItem: React.FC<Props> = observer((props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="gap-2 p-4 border-b border-custom-border-100">
|
||||
<div className="gap-2 p-4 border-b border-custom-border-200">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h6 className="flex w-[40vw] items-center gap-2 truncate text-sm font-medium">
|
||||
|
||||
@@ -71,7 +71,7 @@ export const EstimatesList: React.FC = observer(() => {
|
||||
data={projectStore.getProjectEstimateById(estimateToDelete!)}
|
||||
/>
|
||||
|
||||
<section className="flex items-center justify-between py-3.5 border-b border-custom-border-100">
|
||||
<section className="flex items-center justify-between py-3.5 border-b border-custom-border-200">
|
||||
<h3 className="text-xl font-medium">Estimates</h3>
|
||||
<div className="col-span-12 space-y-5 sm:col-span-7">
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -53,7 +53,7 @@ const IntegrationGuide = () => {
|
||||
{EXPORTERS_LIST.map((service) => (
|
||||
<div
|
||||
key={service.provider}
|
||||
className="flex items-center justify-between gap-2 border-b border-custom-border-100 bg-custom-background-100 px-4 py-6"
|
||||
className="flex items-center justify-between gap-2 border-b border-custom-border-200 bg-custom-background-100 px-4 py-6"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4 w-full">
|
||||
<div className="flex item-center gap-2.5">
|
||||
@@ -79,7 +79,7 @@ const IntegrationGuide = () => {
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center justify-between pt-7 pb-3.5 border-b border-custom-border-100">
|
||||
<div className="flex items-center justify-between pt-7 pb-3.5 border-b border-custom-border-200">
|
||||
<div className="flex gap-2 items-center">
|
||||
<h3 className="flex gap-2 text-xl font-medium">Previous Exports</h3>
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
cycle: cycleStore,
|
||||
cycleIssueFilter: cycleIssueFilterStore,
|
||||
project: projectStore,
|
||||
commandPalette: commandPaletteStore,
|
||||
} = useMobxStore();
|
||||
const { currentProjectDetails } = projectStore;
|
||||
|
||||
@@ -140,6 +139,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
type="component"
|
||||
component={
|
||||
<CustomMenu
|
||||
placement="bottom-start"
|
||||
label={
|
||||
<>
|
||||
<ContrastIcon className="h-3 w-3" />
|
||||
@@ -148,7 +148,6 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
}
|
||||
className="ml-1.5 flex-shrink-0"
|
||||
width="auto"
|
||||
placement="bottom-start"
|
||||
>
|
||||
{cyclesList?.map((cycle) => (
|
||||
<CustomMenu.MenuItem
|
||||
@@ -195,7 +194,16 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||
Analytics
|
||||
</Button>
|
||||
<Button onClick={() => commandPaletteStore.toggleCreateIssueModal(true)} size="sm" prependIcon={<Plus />}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "c",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
size="sm"
|
||||
prependIcon={<Plus />}
|
||||
>
|
||||
Add Issue
|
||||
</Button>
|
||||
<button
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Plus } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// ui
|
||||
import { Breadcrumbs, Button, ContrastIcon } from "@plane/ui";
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
export const CyclesHeader: FC = observer(() => {
|
||||
export interface ICyclesHeader {}
|
||||
|
||||
export const CyclesHeader: FC<ICyclesHeader> = (props) => {
|
||||
const {} = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
const { project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { project: projectStore } = useMobxStore();
|
||||
const { currentProjectDetails } = projectStore;
|
||||
|
||||
return (
|
||||
@@ -52,11 +54,14 @@ export const CyclesHeader: FC = observer(() => {
|
||||
<Button
|
||||
variant="primary"
|
||||
prependIcon={<Plus />}
|
||||
onClick={() => commandPaletteStore.toggleCreateCycleModal(true)}
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", { key: "q" });
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
>
|
||||
Add Cycle
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -31,7 +31,6 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
module: moduleStore,
|
||||
moduleFilter: moduleFilterStore,
|
||||
project: projectStore,
|
||||
commandPalette: commandPaletteStore,
|
||||
} = useMobxStore();
|
||||
const activeLayout = issueFilterStore.userDisplayFilters.layout;
|
||||
const { currentProjectDetails } = projectStore;
|
||||
@@ -147,7 +146,6 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
}
|
||||
className="ml-1.5 flex-shrink-0"
|
||||
width="auto"
|
||||
placement="bottom-start"
|
||||
>
|
||||
{modulesList?.map((module) => (
|
||||
<CustomMenu.MenuItem
|
||||
@@ -194,7 +192,16 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||
Analytics
|
||||
</Button>
|
||||
<Button onClick={() => commandPaletteStore.toggleCreateIssueModal(true)} size="sm" prependIcon={<Plus />}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "c",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
size="sm"
|
||||
prependIcon={<Plus />}
|
||||
>
|
||||
Add Issue
|
||||
</Button>
|
||||
<button
|
||||
|
||||
@@ -17,7 +17,7 @@ export const ModulesListHeader: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
const { project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { project: projectStore } = useMobxStore();
|
||||
const { currentProjectDetails } = projectStore;
|
||||
|
||||
const { storedValue: modulesView, setValue: setModulesView } = useLocalStorage("modules_view", "grid");
|
||||
@@ -77,7 +77,10 @@ export const ModulesListHeader: React.FC = observer(() => {
|
||||
<Button
|
||||
variant="primary"
|
||||
prependIcon={<Plus />}
|
||||
onClick={() => commandPaletteStore.toggleCreateModuleModal(true)}
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", { key: "m" });
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
>
|
||||
Add Module
|
||||
</Button>
|
||||
|
||||
@@ -24,11 +24,10 @@ const pageService = new PageService();
|
||||
|
||||
export const PageDetailsHeader: FC<IPagesHeaderProps> = observer((props) => {
|
||||
const { showButton = false } = props;
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, pageId } = router.query;
|
||||
|
||||
const { project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { project: projectStore } = useMobxStore();
|
||||
const { currentProjectDetails } = projectStore;
|
||||
|
||||
const { data: pageDetails } = useSWR(
|
||||
@@ -79,7 +78,10 @@ export const PageDetailsHeader: FC<IPagesHeaderProps> = observer((props) => {
|
||||
variant="primary"
|
||||
prependIcon={<Plus />}
|
||||
size="sm"
|
||||
onClick={() => commandPaletteStore.toggleCreatePageModal(true)}
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", { key: "d" });
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
>
|
||||
Create Page
|
||||
</Button>
|
||||
|
||||
@@ -18,7 +18,7 @@ export const PagesHeader: FC<IPagesHeaderProps> = observer((props) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { project: projectStore } = useMobxStore();
|
||||
const { currentProjectDetails } = projectStore;
|
||||
|
||||
return (
|
||||
@@ -56,7 +56,10 @@ export const PagesHeader: FC<IPagesHeaderProps> = observer((props) => {
|
||||
variant="primary"
|
||||
prependIcon={<Plus />}
|
||||
size="sm"
|
||||
onClick={() => commandPaletteStore.toggleCreatePageModal(true)}
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", { key: "d" });
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
>
|
||||
Create Page
|
||||
</Button>
|
||||
|
||||
@@ -23,12 +23,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const {
|
||||
issueFilter: issueFilterStore,
|
||||
project: projectStore,
|
||||
inbox: inboxStore,
|
||||
commandPalette: commandPaletteStore,
|
||||
} = useMobxStore();
|
||||
const { issueFilter: issueFilterStore, project: projectStore, inbox: inboxStore } = useMobxStore();
|
||||
|
||||
const activeLayout = issueFilterStore.userDisplayFilters.layout;
|
||||
|
||||
@@ -203,7 +198,16 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||
Analytics
|
||||
</Button>
|
||||
<Button onClick={() => commandPaletteStore.toggleCreateIssueModal(true)} size="sm" prependIcon={<Plus />}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "c",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
size="sm"
|
||||
prependIcon={<Plus />}
|
||||
>
|
||||
Add Issue
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ export const ProjectsHeader = observer(() => {
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// store
|
||||
const { project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { project: projectStore } = useMobxStore();
|
||||
|
||||
const projectsList = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : [];
|
||||
|
||||
@@ -43,7 +43,14 @@ export const ProjectsHeader = observer(() => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button prependIcon={<Plus />} size="md" onClick={() => commandPaletteStore.toggleCreateProjectModal(true)}>
|
||||
<Button
|
||||
prependIcon={<Plus />}
|
||||
size="md"
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", { key: "p" });
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
>
|
||||
Add Project
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -4,24 +4,14 @@ import useIntegrationPopup from "hooks/use-integration-popup";
|
||||
import { Button } from "@plane/ui";
|
||||
// types
|
||||
import { IWorkspaceIntegration } from "types";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
type Props = {
|
||||
workspaceIntegration: false | IWorkspaceIntegration | undefined;
|
||||
provider: string | undefined;
|
||||
};
|
||||
|
||||
export const GithubAuth: React.FC<Props> = observer(({ workspaceIntegration, provider }) => {
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
// hooks
|
||||
const { startAuth, isConnecting } = useIntegrationPopup({
|
||||
provider,
|
||||
github_app_name: envConfig?.github_app_name || "",
|
||||
slack_client_id: envConfig?.slack_client_id || "",
|
||||
});
|
||||
export const GithubAuth: React.FC<Props> = ({ workspaceIntegration, provider }) => {
|
||||
const { startAuth, isConnecting } = useIntegrationPopup(provider);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -36,4 +26,4 @@ export const GithubAuth: React.FC<Props> = observer(({ workspaceIntegration, pro
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import useSWR, { mutate } from "swr";
|
||||
|
||||
// hooks
|
||||
import useUserAuth from "hooks/use-user-auth";
|
||||
// services
|
||||
@@ -18,7 +21,7 @@ import { IImporterService } from "types";
|
||||
// fetch-keys
|
||||
import { IMPORTER_SERVICES_LIST } from "constants/fetch-keys";
|
||||
// constants
|
||||
import { IMPORTERS_LIST } from "constants/workspace";
|
||||
import { IMPORTERS_EXPORTERS_LIST } from "constants/workspace";
|
||||
|
||||
// services
|
||||
const integrationService = new IntegrationService();
|
||||
@@ -73,10 +76,10 @@ const IntegrationGuide = () => {
|
||||
</div>
|
||||
</a>
|
||||
</div> */}
|
||||
{IMPORTERS_LIST.map((service) => (
|
||||
{IMPORTERS_EXPORTERS_LIST.map((service) => (
|
||||
<div
|
||||
key={service.provider}
|
||||
className="flex items-center justify-between gap-2 border-b border-custom-border-100 bg-custom-background-100 px-4 py-6"
|
||||
className="flex items-center justify-between gap-2 border-b border-custom-border-200 bg-custom-background-100 px-4 py-6"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="relative h-10 w-10 flex-shrink-0">
|
||||
@@ -97,7 +100,7 @@ const IntegrationGuide = () => {
|
||||
</div>
|
||||
))}
|
||||
<div>
|
||||
<div className="flex items-center pt-7 pb-3.5 border-b border-custom-border-100">
|
||||
<div className="flex items-center pt-7 pb-3.5 border-b border-custom-border-200">
|
||||
<h3 className="flex gap-2 text-xl font-medium">
|
||||
Previous Imports
|
||||
<button
|
||||
|
||||
@@ -16,14 +16,15 @@ export const JiraGetImportDetail: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
||||
|
||||
const {
|
||||
control,
|
||||
formState: { errors },
|
||||
} = useFormContext<IJiraImporterForm>();
|
||||
|
||||
const { project: projectStore } = useMobxStore();
|
||||
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
||||
|
||||
return (
|
||||
<div className="h-full w-full space-y-8 overflow-y-auto">
|
||||
<div className="grid grid-cols-1 gap-10 md:grid-cols-2">
|
||||
@@ -189,7 +190,10 @@ export const JiraGetImportDetail: React.FC = observer(() => {
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => commandPaletteStore.toggleCreateProjectModal(true)}
|
||||
onClick={() => {
|
||||
const event = new KeyboardEvent("keydown", { key: "p" });
|
||||
document.dispatchEvent(event);
|
||||
}}
|
||||
className="flex cursor-pointer select-none items-center space-x-2 truncate rounded px-1 py-1.5 text-custom-text-200"
|
||||
>
|
||||
<Plus className="h-4 w-4 text-custom-text-200" />
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
// next
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// swr
|
||||
import { mutate } from "swr";
|
||||
|
||||
// react hook form
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
|
||||
// icons
|
||||
import { ArrowLeft, Check, List, Settings } from "lucide-react";
|
||||
|
||||
// services
|
||||
import { JiraImporterService } from "services/integrations";
|
||||
|
||||
// fetch keys
|
||||
import { IMPORTER_SERVICES_LIST } from "constants/fetch-keys";
|
||||
|
||||
// components
|
||||
import { Button, UserGroupIcon } from "@plane/ui";
|
||||
import {
|
||||
@@ -21,9 +31,9 @@ import {
|
||||
TJiraIntegrationSteps,
|
||||
IJiraIntegrationData,
|
||||
} from ".";
|
||||
// assets
|
||||
import JiraLogo from "public/services/jira.svg";
|
||||
// types
|
||||
|
||||
import JiraLogo from "public/services/jira.png";
|
||||
|
||||
import { IUser, IJiraImporterForm } from "types";
|
||||
|
||||
const integrationWorkflowData: Array<{
|
||||
|
||||
@@ -7,7 +7,7 @@ import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
||||
// types
|
||||
import { IImporterService } from "types";
|
||||
// constants
|
||||
import { IMPORTERS_LIST } from "constants/workspace";
|
||||
import { IMPORTERS_EXPORTERS_LIST } from "constants/workspace";
|
||||
|
||||
type Props = {
|
||||
service: IImporterService;
|
||||
@@ -21,8 +21,10 @@ export const SingleImport: React.FC<Props> = ({ service, refreshing, handleDelet
|
||||
<h4 className="flex items-center gap-2 text-sm">
|
||||
<span>
|
||||
Import from{" "}
|
||||
<span className="font-medium">{IMPORTERS_LIST.find((i) => i.provider === service.service)?.title}</span> to{" "}
|
||||
<span className="font-medium">{service.project_detail.name}</span>
|
||||
<span className="font-medium">
|
||||
{IMPORTERS_EXPORTERS_LIST.find((i) => i.provider === service.service)?.title}
|
||||
</span>{" "}
|
||||
to <span className="font-medium">{service.project_detail.name}</span>
|
||||
</span>
|
||||
<span
|
||||
className={`rounded px-2 py-0.5 text-xs capitalize ${
|
||||
|
||||
@@ -20,8 +20,6 @@ import { CheckCircle } from "lucide-react";
|
||||
import { IAppIntegration, IWorkspaceIntegration } from "types";
|
||||
// fetch-keys
|
||||
import { WORKSPACE_INTEGRATIONS } from "constants/fetch-keys";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
type Props = {
|
||||
integration: IAppIntegration;
|
||||
@@ -43,11 +41,7 @@ const integrationDetails: { [key: string]: any } = {
|
||||
// services
|
||||
const integrationService = new IntegrationService();
|
||||
|
||||
export const SingleIntegrationCard: React.FC<Props> = observer(({ integration }) => {
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
|
||||
export const SingleIntegrationCard: React.FC<Props> = ({ integration }) => {
|
||||
const [deletingIntegration, setDeletingIntegration] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
@@ -55,11 +49,7 @@ export const SingleIntegrationCard: React.FC<Props> = observer(({ integration })
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { startAuth, isConnecting: isInstalling } = useIntegrationPopup({
|
||||
provider: integration.provider,
|
||||
github_app_name: envConfig?.github_app_name || "",
|
||||
slack_client_id: envConfig?.slack_client_id || "",
|
||||
});
|
||||
const { startAuth, isConnecting: isInstalling } = useIntegrationPopup(integration.provider);
|
||||
|
||||
const { data: workspaceIntegrations } = useSWR(
|
||||
workspaceSlug ? WORKSPACE_INTEGRATIONS(workspaceSlug as string) : null,
|
||||
@@ -103,7 +93,7 @@ export const SingleIntegrationCard: React.FC<Props> = observer(({ integration })
|
||||
const isInstalled = workspaceIntegrations?.find((i: any) => i.integration_detail.id === integration.id);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-2 border-b border-custom-border-100 bg-custom-background-100 px-4 py-6">
|
||||
<div className="flex items-center justify-between gap-2 border-b border-custom-border-200 bg-custom-background-100 px-4 py-6">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="h-10 w-10 flex-shrink-0">
|
||||
<Image src={integrationDetails[integration.provider].logo} alt={`${integration.title} Logo`} />
|
||||
@@ -142,4 +132,4 @@ export const SingleIntegrationCard: React.FC<Props> = observer(({ integration })
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// services
|
||||
import { AppInstallationService } from "services/app_installation.service";
|
||||
// ui
|
||||
@@ -12,8 +11,6 @@ import useIntegrationPopup from "hooks/use-integration-popup";
|
||||
import { IWorkspaceIntegration, ISlackIntegration } from "types";
|
||||
// fetch-keys
|
||||
import { SLACK_CHANNEL_INFO } from "constants/fetch-keys";
|
||||
// lib
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
type Props = {
|
||||
integration: IWorkspaceIntegration;
|
||||
@@ -21,24 +18,14 @@ type Props = {
|
||||
|
||||
const appInstallationService = new AppInstallationService();
|
||||
|
||||
export const SelectChannel: React.FC<Props> = observer(({ integration }) => {
|
||||
// store
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
// states
|
||||
export const SelectChannel: React.FC<Props> = ({ integration }) => {
|
||||
const [slackChannelAvailabilityToggle, setSlackChannelAvailabilityToggle] = useState<boolean>(false);
|
||||
const [slackChannel, setSlackChannel] = useState<ISlackIntegration | null>(null);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const { startAuth } = useIntegrationPopup({
|
||||
provider: "slackChannel",
|
||||
stateParams: integration.id,
|
||||
github_app_name: envConfig?.github_client_id || "",
|
||||
slack_client_id: envConfig?.slack_client_id || "",
|
||||
});
|
||||
const { startAuth } = useIntegrationPopup("slackChannel", integration.id);
|
||||
|
||||
const { data: projectIntegration } = useSWR(
|
||||
workspaceSlug && projectId && integration.id
|
||||
@@ -110,4 +97,4 @@ export const SelectChannel: React.FC<Props> = observer(({ integration }) => {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,27 +1,57 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// store
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import useKeypress from "hooks/use-keypress";
|
||||
import useProjectDetails from "hooks/use-project-details";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// helpers
|
||||
import { createIssuePayload } from "helpers/issue.helper";
|
||||
|
||||
// constants
|
||||
import { createIssuePayload } from "constants/issue";
|
||||
|
||||
// icons
|
||||
import { PlusIcon } from "lucide-react";
|
||||
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
|
||||
type Props = {
|
||||
groupId?: string;
|
||||
dependencies?: any[];
|
||||
prePopulatedData?: Partial<IIssue>;
|
||||
onSuccess?: (data: IIssue) => Promise<void> | void;
|
||||
};
|
||||
|
||||
const useCheckIfThereIsSpaceOnRight = (ref: React.RefObject<HTMLDivElement>, deps: any[]) => {
|
||||
const [isThereSpaceOnRight, setIsThereSpaceOnRight] = useState(true);
|
||||
|
||||
const router = useRouter();
|
||||
const { moduleId, cycleId, viewId } = router.query;
|
||||
|
||||
const container = document.getElementById(`calendar-view-${cycleId ?? moduleId ?? viewId}`);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
|
||||
const { right } = ref.current.getBoundingClientRect();
|
||||
|
||||
const width = right;
|
||||
|
||||
const innerWidth = container?.getBoundingClientRect().width ?? window.innerWidth;
|
||||
|
||||
if (width > innerWidth) setIsThereSpaceOnRight(false);
|
||||
else setIsThereSpaceOnRight(true);
|
||||
}, [ref, deps, container]);
|
||||
|
||||
return isThereSpaceOnRight;
|
||||
};
|
||||
|
||||
const defaultValues: Partial<IIssue> = {
|
||||
name: "",
|
||||
};
|
||||
@@ -50,7 +80,7 @@ const Inputs = (props: any) => {
|
||||
};
|
||||
|
||||
export const CalendarInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
||||
const { prePopulatedData, groupId } = props;
|
||||
const { prePopulatedData, dependencies = [], groupId } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
@@ -105,6 +135,8 @@ export const CalendarInlineCreateIssueForm: React.FC<Props> = observer((props) =
|
||||
});
|
||||
}, [errors, setToastAlert]);
|
||||
|
||||
const isSpaceOnRight = useCheckIfThereIsSpaceOnRight(ref, dependencies);
|
||||
|
||||
const onSubmitHandler = async (formData: IIssue) => {
|
||||
if (isSubmitting || !workspaceSlug || !projectId) return;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
|
||||
// states
|
||||
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
|
||||
|
||||
const { cycleIssue: cycleIssueStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { cycleIssue: cycleIssueStore } = useMobxStore();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@@ -62,7 +62,12 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
|
||||
primaryButton={{
|
||||
text: "New issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => commandPaletteStore.toggleCreateIssueModal(true),
|
||||
onClick: () => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "c",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
}}
|
||||
secondaryButton={
|
||||
<Button
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { EmptyState } from "components/common";
|
||||
// assets
|
||||
import emptyIssue from "public/empty-state/issue.svg";
|
||||
|
||||
export const GlobalViewEmptyState: React.FC = observer(() => {
|
||||
const { commandPalette: commandPaletteStore } = useMobxStore();
|
||||
|
||||
return (
|
||||
<div className="h-full w-full grid place-items-center">
|
||||
<EmptyState
|
||||
title="View issues will appear here"
|
||||
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
|
||||
image={emptyIssue}
|
||||
primaryButton={{
|
||||
text: "New issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => commandPaletteStore.toggleCreateIssueModal(true),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
export const GlobalViewEmptyState: React.FC = () => (
|
||||
<div className="h-full w-full grid place-items-center">
|
||||
<EmptyState
|
||||
title="View issues will appear here"
|
||||
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
|
||||
image={emptyIssue}
|
||||
primaryButton={{
|
||||
text: "New issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "c",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -22,7 +22,7 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
|
||||
// states
|
||||
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
|
||||
|
||||
const { moduleIssue: moduleIssueStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { moduleIssue: moduleIssueStore } = useMobxStore();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@@ -58,7 +58,12 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
|
||||
primaryButton={{
|
||||
text: "New issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => commandPaletteStore.toggleCreateIssueModal(true),
|
||||
onClick: () => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "c",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
}}
|
||||
secondaryButton={
|
||||
<Button
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { EmptyState } from "components/common";
|
||||
// assets
|
||||
import emptyIssue from "public/empty-state/issue.svg";
|
||||
|
||||
export const ProjectViewEmptyState: React.FC = observer(() => {
|
||||
const { commandPalette: commandPaletteStore } = useMobxStore();
|
||||
|
||||
return (
|
||||
<div className="h-full w-full grid place-items-center">
|
||||
<EmptyState
|
||||
title="View issues will appear here"
|
||||
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
|
||||
image={emptyIssue}
|
||||
primaryButton={{
|
||||
text: "New issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => commandPaletteStore.toggleCreateIssueModal(true),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
export const ProjectViewEmptyState: React.FC = () => (
|
||||
<div className="h-full w-full grid place-items-center">
|
||||
<EmptyState
|
||||
title="View issues will appear here"
|
||||
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
|
||||
image={emptyIssue}
|
||||
primaryButton={{
|
||||
text: "New issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "c",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { EmptyState } from "components/common";
|
||||
// assets
|
||||
import emptyIssue from "public/empty-state/issue.svg";
|
||||
|
||||
export const ProjectEmptyState: React.FC = observer(() => {
|
||||
const { commandPalette: commandPaletteStore } = useMobxStore();
|
||||
|
||||
return (
|
||||
<div className="h-full w-full grid place-items-center">
|
||||
<EmptyState
|
||||
title="Project issues will appear here"
|
||||
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
|
||||
image={emptyIssue}
|
||||
primaryButton={{
|
||||
text: "New issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => commandPaletteStore.toggleCreateIssueModal(true),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
export const ProjectEmptyState: React.FC = () => (
|
||||
<div className="h-full w-full grid place-items-center">
|
||||
<EmptyState
|
||||
title="Project issues will appear here"
|
||||
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
|
||||
image={emptyIssue}
|
||||
primaryButton={{
|
||||
text: "New issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "c",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,8 +14,8 @@ import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
import { renderDateFormat } from "helpers/date-time.helper";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
// helpers
|
||||
import { createIssuePayload } from "helpers/issue.helper";
|
||||
// constants
|
||||
import { createIssuePayload } from "constants/issue";
|
||||
|
||||
type Props = {
|
||||
prePopulatedData?: Partial<IIssue>;
|
||||
|
||||
@@ -2,16 +2,19 @@ import { useEffect, useState, useRef } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// store
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import useKeypress from "hooks/use-keypress";
|
||||
import useProjectDetails from "hooks/use-project-details";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// helpers
|
||||
import { createIssuePayload } from "helpers/issue.helper";
|
||||
|
||||
// constants
|
||||
import { createIssuePayload } from "constants/issue";
|
||||
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ import { observer } from "mobx-react-lite";
|
||||
import { HeaderGroupByCard } from "./group-by-card";
|
||||
// ui
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
// helpers
|
||||
import { capitalizeFirstLetter } from "helpers/string.helper";
|
||||
|
||||
export interface IStateGroupHeader {
|
||||
column_id: string;
|
||||
@@ -29,7 +27,7 @@ export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => {
|
||||
{stateGroup && (
|
||||
<HeaderGroupByCard
|
||||
icon={<Icon stateGroup={stateGroup?.key} />}
|
||||
title={capitalizeFirstLetter(stateGroup?.key) || ""}
|
||||
title={stateGroup?.key || ""}
|
||||
count={issues_count}
|
||||
issuePayload={{}}
|
||||
/>
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import useKeypress from "hooks/use-keypress";
|
||||
import useProjectDetails from "hooks/use-project-details";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
|
||||
// store
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// helpers
|
||||
import { createIssuePayload } from "helpers/issue.helper";
|
||||
|
||||
// constants
|
||||
import { createIssuePayload } from "constants/issue";
|
||||
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
|
||||
type Props = {
|
||||
groupId?: string;
|
||||
|
||||
@@ -110,7 +110,7 @@ export const IssuePropertyEstimates: React.FC<IIssuePropertyEstimates> = observe
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 w-full text-xs px-2.5 py-1 rounded border-[0.5px] border-custom-border-300 ${
|
||||
className={`flex items-center justify-between gap-1 w-full text-xs px-2.5 py-1 rounded border-[0.5px] border-custom-border-300 duration-300 focus:outline-none ${
|
||||
disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80"
|
||||
} ${buttonClassName}`}
|
||||
>
|
||||
|
||||
@@ -37,8 +37,8 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
||||
disabled,
|
||||
hideDropdownArrow = false,
|
||||
className,
|
||||
buttonClassName = "",
|
||||
optionsClassName = "",
|
||||
buttonClassName,
|
||||
optionsClassName,
|
||||
placement,
|
||||
maxRender = 2,
|
||||
noLabelBorder = false,
|
||||
@@ -93,7 +93,7 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
||||
});
|
||||
|
||||
const label = (
|
||||
<div className="flex items-center gap-2 text-custom-text-200">
|
||||
<div className="flex items-center gap-2 text-custom-text-200 h-full">
|
||||
{value.length > 0 ? (
|
||||
value.length <= maxRender ? (
|
||||
<>
|
||||
|
||||
@@ -66,9 +66,9 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
|
||||
value: state.id,
|
||||
query: state.name,
|
||||
content: (
|
||||
<div className="flex items-center gap-2 w-full overflow-hidden">
|
||||
<div className="flex items-center gap-2">
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} />
|
||||
<div className="truncate inline-block line-clamp-1 w-full">{state.name}</div>
|
||||
{state.name}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
@@ -94,7 +94,7 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
|
||||
<Tooltip tooltipHeading="State" tooltipContent={value?.name ?? ""} position="top">
|
||||
<div className="flex items-center cursor-pointer w-full gap-2 text-custom-text-200">
|
||||
{value && <StateGroupIcon stateGroup={value.group} color={value.color} />}
|
||||
<span className="truncate line-clamp-1 inline-block">{value?.name ?? "State"}</span>
|
||||
<span className="truncate">{value?.name ?? "State"}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
@@ -104,7 +104,7 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
|
||||
{workspaceSlug && projectId && (
|
||||
<Combobox
|
||||
as="div"
|
||||
className={`flex-shrink-0 text-left w-auto max-w-full ${className}`}
|
||||
className={`flex-shrink-0 text-left ${className}`}
|
||||
value={value.id}
|
||||
onChange={(data: string) => {
|
||||
const selectedState = projectStates?.find((state) => state.id === data);
|
||||
@@ -159,11 +159,7 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
|
||||
{({ selected }) => (
|
||||
<>
|
||||
{option.content}
|
||||
{selected && (
|
||||
<div className="flex-shrink-0">
|
||||
<Check className="h-3.5 w-3.5" />
|
||||
</div>
|
||||
)}
|
||||
{selected && <Check className="h-3.5 w-3.5" />}
|
||||
</>
|
||||
)}
|
||||
</Combobox.Option>
|
||||
|
||||
@@ -22,12 +22,19 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
|
||||
|
||||
const { issue: issueStore, issueFilter: issueFilterStore } = useMobxStore();
|
||||
|
||||
useSWR(workspaceSlug && projectId ? `PROJECT_FILTERS_AND_ISSUES_${projectId.toString()}` : null, async () => {
|
||||
if (workspaceSlug && projectId) {
|
||||
await issueFilterStore.fetchUserProjectFilters(workspaceSlug.toString(), projectId.toString());
|
||||
await issueStore.fetchIssues(workspaceSlug.toString(), projectId.toString());
|
||||
const { isLoading } = useSWR(
|
||||
workspaceSlug && projectId ? `PROJECT_FILTERS_AND_ISSUES_${projectId.toString()}` : null,
|
||||
async () => {
|
||||
if (workspaceSlug && projectId) {
|
||||
await issueFilterStore.fetchUserProjectFilters(workspaceSlug.toString(), projectId.toString());
|
||||
await issueStore.fetchIssues(workspaceSlug.toString(), projectId.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
console.log("--");
|
||||
console.log("isLoading -- -->", isLoading);
|
||||
console.log("--");
|
||||
|
||||
const activeLayout = issueFilterStore.userDisplayFilters.layout;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export const SpreadsheetAttachmentColumn: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center px-2.5 py-1 text-xs h-full w-full">
|
||||
<div className="flex items-center justify-center text-xs h-full w-full">
|
||||
{issue.attachment_count} {issue.attachment_count === 1 ? "attachment" : "attachments"}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ export const SpreadsheetDueDateColumn: React.FC<Props> = ({ issue, onChange, exp
|
||||
<ViewDueDateSelect
|
||||
issue={issue}
|
||||
onChange={(val) => onChange({ target_date: val })}
|
||||
className="flex items-center px-2.5 py-1 !h-full !w-full max-w-full"
|
||||
className="!h-full !w-full max-w-full px-2.5 py-1 flex items-center"
|
||||
buttonClassName="!h-full !w-full !shadow-none px-2.5"
|
||||
noBorder
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
@@ -29,7 +29,7 @@ export const SpreadsheetLabelColumn: React.FC<Props> = (props) => {
|
||||
value={issue.labels}
|
||||
onChange={(data) => onChange({ labels: data })}
|
||||
className="h-full w-full"
|
||||
buttonClassName="px-2.5 h-full"
|
||||
buttonClassName="!shadow-none !border-0 h-full w-full px-2.5 py-1 "
|
||||
noLabelBorder
|
||||
hideDropdownArrow
|
||||
maxRender={1}
|
||||
|
||||
@@ -18,7 +18,7 @@ export const SpreadsheetLinkColumn: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center px-2.5 py-1 text-xs h-full w-full">
|
||||
<div className="flex items-center justify-center text-xs h-full w-full">
|
||||
{issue.link_count} {issue.link_count === 1 ? "link" : "links"}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ export const SpreadsheetStartDateColumn: React.FC<Props> = ({ issue, onChange, e
|
||||
<ViewStartDateSelect
|
||||
issue={issue}
|
||||
onChange={(val) => onChange({ start_date: val })}
|
||||
className="flex items-center px-2.5 py-1 !h-full !w-full max-w-full"
|
||||
className="!h-full !w-full max-w-full px-2.5 py-1 flex items-center"
|
||||
buttonClassName="!h-full !w-full !shadow-none px-2.5"
|
||||
noBorder
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
@@ -18,7 +18,7 @@ export const SpreadsheetSubIssueColumn: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center px-2.5 py-1 text-xs h-full w-full">
|
||||
<div className="flex items-center justify-center text-xs h-full w-full">
|
||||
{issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import useKeypress from "hooks/use-keypress";
|
||||
import useProjectDetails from "hooks/use-project-details";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
|
||||
// store
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// helpers
|
||||
import { createIssuePayload } from "helpers/issue.helper";
|
||||
|
||||
// constants
|
||||
import { createIssuePayload } from "constants/issue";
|
||||
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
|
||||
type Props = {
|
||||
groupId?: string;
|
||||
|
||||
@@ -84,7 +84,7 @@ export const SpreadsheetColumn: React.FC<Props> = (props) => {
|
||||
const propertyDetails = SPREADSHEET_PROPERTY_DETAILS[property];
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col h-max w-full max-w-max bg-custom-background-100">
|
||||
<div className="relative flex flex-col h-max w-full bg-custom-background-100">
|
||||
<div className="flex items-center min-w-[8rem] px-4 py-1 text-sm font-medium z-[1] h-11 w-full sticky top-0 bg-custom-background-90 border border-l-0 border-custom-border-100">
|
||||
<CustomMenu
|
||||
customButtonClassName="!w-full"
|
||||
|
||||
@@ -190,7 +190,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
<div className="flex flex-col gap-5 py-5 w-full">
|
||||
{/* state */}
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<div className="flex items-center gap-2 w-40 text-sm flex-shrink-0">
|
||||
<div className="flex items-center gap-2 w-40 text-sm">
|
||||
<DoubleCircleIcon className="h-4 w-4 flex-shrink-0" />
|
||||
<p>State</p>
|
||||
</div>
|
||||
@@ -201,7 +201,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
|
||||
{/* assignee */}
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<div className="flex items-center gap-2 w-40 text-sm flex-shrink-0">
|
||||
<div className="flex items-center gap-2 w-40 text-sm">
|
||||
<UserGroupIcon className="h-4 w-4 flex-shrink-0" />
|
||||
<p>Assignees</p>
|
||||
</div>
|
||||
@@ -216,7 +216,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
|
||||
{/* priority */}
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<div className="flex items-center gap-2 w-40 text-sm flex-shrink-0">
|
||||
<div className="flex items-center gap-2 w-40 text-sm">
|
||||
<Signal className="h-4 w-4 flex-shrink-0" />
|
||||
<p>Priority</p>
|
||||
</div>
|
||||
@@ -231,7 +231,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
|
||||
{/* estimate */}
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<div className="flex items-center gap-2 w-40 text-sm flex-shrink-0">
|
||||
<div className="flex items-center gap-2 w-40 text-sm">
|
||||
<Triangle className="h-4 w-4 flex-shrink-0 " />
|
||||
<p>Estimate</p>
|
||||
</div>
|
||||
@@ -246,7 +246,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
|
||||
{/* start date */}
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<div className="flex items-center gap-2 w-40 text-sm flex-shrink-0">
|
||||
<div className="flex items-center gap-2 w-40 text-sm">
|
||||
<CalendarDays className="h-4 w-4 flex-shrink-0" />
|
||||
<p>Start date</p>
|
||||
</div>
|
||||
@@ -264,7 +264,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
|
||||
{/* due date */}
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<div className="flex items-center gap-2 w-40 text-sm flex-shrink-0">
|
||||
<div className="flex items-center gap-2 w-40 text-sm">
|
||||
<CalendarDays className="h-4 w-4 flex-shrink-0" />
|
||||
<p>Due date</p>
|
||||
</div>
|
||||
@@ -282,7 +282,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
|
||||
{/* parent */}
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<div className="flex items-center gap-2 w-40 text-sm flex-shrink-0">
|
||||
<div className="flex items-center gap-2 w-40 text-sm">
|
||||
<User2 className="h-4 w-4 flex-shrink-0" />
|
||||
<p>Parent</p>
|
||||
</div>
|
||||
@@ -295,8 +295,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
<span className="border-t border-custom-border-200" />
|
||||
|
||||
<div className="flex flex-col gap-5 py-5 w-full">
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<div className="flex items-center gap-2 w-40 text-sm flex-shrink-0">
|
||||
<div className="flex items-center gap-2 w-80">
|
||||
<div className="flex items-center gap-2 w-40 text-sm">
|
||||
<ContrastIcon className="h-4 w-4 flex-shrink-0" />
|
||||
<p>Cycle</p>
|
||||
</div>
|
||||
@@ -309,8 +309,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<div className="flex items-center gap-2 w-40 text-sm flex-shrink-0">
|
||||
<div className="flex items-center gap-2 w-80">
|
||||
<div className="flex items-center gap-2 w-40 text-sm">
|
||||
<DiceIcon className="h-4 w-4 flex-shrink-0" />
|
||||
<p>Module</p>
|
||||
</div>
|
||||
|
||||
@@ -174,11 +174,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||
}}
|
||||
>
|
||||
{/* header */}
|
||||
<div
|
||||
className={`relative flex items-center justify-between p-4 ${
|
||||
currentMode?.key === "full-screen" ? "border-b border-custom-border-200" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="relative flex items-center justify-between p-5 border-b border-custom-border-200">
|
||||
<div className="flex items-center gap-4">
|
||||
<button onClick={removeRoutePeekId}>
|
||||
<MoveRight className="h-4 w-4 text-custom-text-400 hover:text-custom-text-200" />
|
||||
@@ -218,22 +214,20 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
{issue?.created_by !== user?.id &&
|
||||
!issue?.assignees.includes(user?.id ?? "") &&
|
||||
!router.pathname.includes("[archivedIssueId]") && (
|
||||
<Button
|
||||
size="sm"
|
||||
prependIcon={<Bell className="h-3 w-3" />}
|
||||
variant="outline-primary"
|
||||
onClick={() =>
|
||||
issueSubscription && issueSubscription.subscribed
|
||||
? issueSubscriptionRemove()
|
||||
: issueSubscriptionCreate()
|
||||
}
|
||||
>
|
||||
{issueSubscription && issueSubscription.subscribed ? "Unsubscribe" : "Subscribe"}
|
||||
</Button>
|
||||
)}
|
||||
{!isArchived && (
|
||||
<Button
|
||||
size="sm"
|
||||
prependIcon={<Bell className="h-3 w-3" />}
|
||||
variant="outline-primary"
|
||||
onClick={() =>
|
||||
issueSubscription && issueSubscription.subscribed
|
||||
? issueSubscriptionRemove
|
||||
: issueSubscriptionCreate
|
||||
}
|
||||
>
|
||||
{issueSubscription && issueSubscription.subscribed ? "Unsubscribe" : "Subscribe"}
|
||||
</Button>
|
||||
)}
|
||||
<button onClick={handleCopyText}>
|
||||
<Link2 className="h-4 w-4 text-custom-text-400 hover:text-custom-text-200 -rotate-45" />
|
||||
</button>
|
||||
@@ -256,7 +250,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||
issue && (
|
||||
<>
|
||||
{["side-peek", "modal"].includes(peekMode) ? (
|
||||
<div className="flex flex-col gap-3 py-5 px-8">
|
||||
<div className="flex flex-col gap-3 py-6 px-8">
|
||||
<PeekOverviewIssueDetails
|
||||
workspaceSlug={workspaceSlug}
|
||||
issue={issue}
|
||||
|
||||
@@ -79,17 +79,17 @@ export const SidebarCycleSelect: React.FC<Props> = ({ issueDetail, handleCycleCh
|
||||
<Tooltip position="left" tooltipContent={`${issueCycle ? issueCycle.cycle_detail.name : "No cycle"}`}>
|
||||
<button
|
||||
type="button"
|
||||
className={`bg-custom-background-80 text-xs rounded px-2.5 py-0.5 w-full flex items-center ${
|
||||
className={`bg-custom-background-80 text-xs rounded px-2.5 py-0.5 w-full flex ${
|
||||
disabled ? "cursor-not-allowed" : ""
|
||||
} max-w-[10rem]`}
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`flex items-center gap-1.5 truncate ${
|
||||
issueCycle ? "text-custom-text-100" : "text-custom-text-200"
|
||||
}`}
|
||||
>
|
||||
<span className="flex-shrink-0">{issueCycle && <ContrastIcon className="h-3.5 w-3.5" />}</span>
|
||||
<span className="truncate">{issueCycle ? issueCycle.cycle_detail.name : "No cycle"}</span>
|
||||
{issueCycle && <ContrastIcon className="h-3.5 w-3.5" />}
|
||||
{issueCycle ? issueCycle.cycle_detail.name : "No cycle"}
|
||||
</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
@@ -18,7 +18,6 @@ import { Plus, X } from "lucide-react";
|
||||
import { IIssue, IIssueLabels } from "types";
|
||||
// fetch-keys
|
||||
import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
|
||||
import useToast from "hooks/use-toast";
|
||||
|
||||
type Props = {
|
||||
issueDetails: IIssue | undefined;
|
||||
@@ -45,9 +44,6 @@ export const SidebarLabelSelect: React.FC<Props> = ({
|
||||
const [createLabelForm, setCreateLabelForm] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const {
|
||||
@@ -83,14 +79,6 @@ export const SidebarLabelSelect: React.FC<Props> = ({
|
||||
submitChanges({ labels: [...(issueDetails?.labels ?? []), res.id] });
|
||||
|
||||
setCreateLabelForm(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
setToastAlert({
|
||||
title: "Oops!",
|
||||
type: "error",
|
||||
message: error?.error ?? "Error while adding the label",
|
||||
});
|
||||
reset(formData);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -74,19 +74,17 @@ export const SidebarModuleSelect: React.FC<Props> = ({ issueDetail, handleModule
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={`bg-custom-background-80 text-xs rounded px-2.5 py-0.5 w-full flex items-center ${
|
||||
className={`bg-custom-background-80 text-xs rounded px-2.5 py-0.5 w-full flex ${
|
||||
disabled ? "cursor-not-allowed" : ""
|
||||
} max-w-[10rem]`}
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`flex items-center gap-1.5 truncate ${
|
||||
issueModule ? "text-custom-text-100" : "text-custom-text-200"
|
||||
}`}
|
||||
>
|
||||
<span className="flex-shrink-0">{issueModule && <DiceIcon className="h-3.5 w-3.5" />}</span>
|
||||
<span className="truncate">
|
||||
{modules?.find((m) => m.id === issueModule?.module)?.name ?? "No module"}
|
||||
</span>
|
||||
{issueModule && <DiceIcon className="h-3.5 w-3.5" />}
|
||||
{modules?.find((m) => m.id === issueModule?.module)?.name ?? "No module"}
|
||||
</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
// ui
|
||||
import { CustomDatePicker } from "components/ui";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
import { CalendarDays } from "lucide-react";
|
||||
// helpers
|
||||
import {
|
||||
findHowManyDaysLeft,
|
||||
renderShortDate,
|
||||
renderShortDateWithYearFormat,
|
||||
renderShortMonthDate,
|
||||
} from "helpers/date-time.helper";
|
||||
import { findHowManyDaysLeft, renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
|
||||
@@ -19,6 +13,7 @@ type Props = {
|
||||
handleOnClose?: () => void;
|
||||
tooltipPosition?: "top" | "bottom";
|
||||
className?: string;
|
||||
buttonClassName?: string;
|
||||
noBorder?: boolean;
|
||||
disabled: boolean;
|
||||
};
|
||||
@@ -30,16 +25,13 @@ export const ViewDueDateSelect: React.FC<Props> = ({
|
||||
handleOnClose,
|
||||
tooltipPosition = "top",
|
||||
className = "",
|
||||
buttonClassName = "",
|
||||
noBorder = false,
|
||||
disabled,
|
||||
}) => {
|
||||
const minDate = issue.start_date ? new Date(issue.start_date) : null;
|
||||
minDate?.setDate(minDate.getDate());
|
||||
|
||||
const today = new Date();
|
||||
const endDate = new Date(issue.target_date ?? "");
|
||||
const areYearsEqual = endDate.getFullYear() === today.getFullYear();
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
tooltipHeading="Due date"
|
||||
@@ -56,32 +48,10 @@ export const ViewDueDateSelect: React.FC<Props> = ({
|
||||
}`}
|
||||
>
|
||||
<CustomDatePicker
|
||||
placeholder="Due date"
|
||||
value={issue?.target_date}
|
||||
onChange={onChange}
|
||||
className={`bg-transparent ${issue?.target_date ? "w-[6.5rem]" : "w-[5rem] text-center"}`}
|
||||
customInput={
|
||||
<div
|
||||
className={`flex items-center justify-center gap-2 px-2 py-1 text-xs cursor-pointer rounded border border-custom-border-200 shadow-sm duration-200 hover:bg-custom-background-80 ${
|
||||
issue.target_date ? "pr-6 text-custom-text-300" : "text-custom-text-400"
|
||||
}`}
|
||||
>
|
||||
{issue.target_date ? (
|
||||
<>
|
||||
<CalendarDays className="h-3.5 w-3.5 flex-shrink-0" />
|
||||
<span>
|
||||
{areYearsEqual
|
||||
? renderShortDate(issue.target_date ?? "", "_ _")
|
||||
: renderShortMonthDate(issue.target_date ?? "", "_ _")}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CalendarDays className="h-3.5 w-3.5 flex-shrink-0" />
|
||||
<span>Due Date</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
className={`bg-transparent ${issue?.target_date ? "w-[6.5rem]" : "w-[5rem] text-center"} ${buttonClassName}`}
|
||||
minDate={minDate ?? undefined}
|
||||
noBorder={noBorder}
|
||||
handleOnOpen={handleOnOpen}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// ui
|
||||
import { CustomDatePicker } from "components/ui";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
import { CalendarDays } from "lucide-react";
|
||||
// helpers
|
||||
import { renderShortDate, renderShortDateWithYearFormat, renderShortMonthDate } from "helpers/date-time.helper";
|
||||
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
|
||||
@@ -14,6 +13,7 @@ type Props = {
|
||||
handleOnClose?: () => void;
|
||||
tooltipPosition?: "top" | "bottom";
|
||||
className?: string;
|
||||
buttonClassName?: string;
|
||||
noBorder?: boolean;
|
||||
disabled: boolean;
|
||||
};
|
||||
@@ -25,14 +25,12 @@ export const ViewStartDateSelect: React.FC<Props> = ({
|
||||
handleOnClose,
|
||||
tooltipPosition = "top",
|
||||
className = "",
|
||||
buttonClassName = "",
|
||||
noBorder = false,
|
||||
disabled,
|
||||
}) => {
|
||||
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
|
||||
maxDate?.setDate(maxDate.getDate());
|
||||
const today = new Date();
|
||||
const startDate = new Date(issue.start_date ?? "");
|
||||
const areYearsEqual = startDate.getFullYear() === today.getFullYear();
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
@@ -42,34 +40,13 @@ export const ViewStartDateSelect: React.FC<Props> = ({
|
||||
>
|
||||
<div className={`group flex-shrink-0 relative max-w-[6.5rem] ${className}`}>
|
||||
<CustomDatePicker
|
||||
placeholder="Start date"
|
||||
value={issue?.start_date}
|
||||
onChange={onChange}
|
||||
className={`bg-inherit ${issue?.start_date ? "w-[6.5rem]" : "w-[5rem] text-center"} ${buttonClassName}`}
|
||||
maxDate={maxDate ?? undefined}
|
||||
noBorder={noBorder}
|
||||
handleOnOpen={handleOnOpen}
|
||||
customInput={
|
||||
<div
|
||||
className={`flex items-center justify-center gap-2 px-2 py-1 text-xs cursor-pointer rounded border border-custom-border-200 shadow-sm duration-200 hover:bg-custom-background-80 ${
|
||||
issue?.start_date ? "pr-6 text-custom-text-300" : "text-custom-text-400"
|
||||
}`}
|
||||
>
|
||||
{issue?.start_date ? (
|
||||
<>
|
||||
<CalendarDays className="h-3.5 w-3.5 flex-shrink-0" />
|
||||
<span>
|
||||
{areYearsEqual
|
||||
? renderShortDate(issue?.start_date, "_ _")
|
||||
: renderShortMonthDate(issue?.start_date, "_ _")}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CalendarDays className="h-3.5 w-3.5 flex-shrink-0" />
|
||||
<span>Start Date</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
handleOnClose={handleOnClose}
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
@@ -15,7 +15,6 @@ import { ChevronDown } from "lucide-react";
|
||||
import type { IIssueLabels, IState } from "types";
|
||||
// constants
|
||||
import { LABEL_COLOR_OPTIONS, getRandomLabelColor } from "constants/label";
|
||||
import useToast from "hooks/use-toast";
|
||||
|
||||
// types
|
||||
type Props = {
|
||||
@@ -59,8 +58,6 @@ export const CreateLabelModal: React.FC<Props> = observer((props) => {
|
||||
reset(defaultValues);
|
||||
};
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const onSubmit = async (formData: IIssueLabels) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
@@ -71,12 +68,7 @@ export const CreateLabelModal: React.FC<Props> = observer((props) => {
|
||||
if (onSuccess) onSuccess(res);
|
||||
})
|
||||
.catch((error) => {
|
||||
setToastAlert({
|
||||
title: "Oops!",
|
||||
type: "error",
|
||||
message: error?.error ?? "Error while adding the label",
|
||||
});
|
||||
reset(formData);
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import { Button, Input } from "@plane/ui";
|
||||
import { IIssueLabels } from "types";
|
||||
// fetch-keys
|
||||
import { getRandomLabelColor, LABEL_COLOR_OPTIONS } from "constants/label";
|
||||
import useToast from "hooks/use-toast";
|
||||
|
||||
type Props = {
|
||||
labelForm: boolean;
|
||||
@@ -40,8 +39,6 @@ export const CreateUpdateLabelInline = observer(
|
||||
// store
|
||||
const { projectLabel: projectLabelStore } = useMobxStore();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
@@ -63,20 +60,10 @@ export const CreateUpdateLabelInline = observer(
|
||||
const handleLabelCreate: SubmitHandler<IIssueLabels> = async (formData) => {
|
||||
if (!workspaceSlug || !projectId || isSubmitting) return;
|
||||
|
||||
await projectLabelStore
|
||||
.createLabel(workspaceSlug.toString(), projectId.toString(), formData)
|
||||
.then(() => {
|
||||
handleClose();
|
||||
reset(defaultValues);
|
||||
})
|
||||
.catch((error) => {
|
||||
setToastAlert({
|
||||
title: "Oops!",
|
||||
type: "error",
|
||||
message: error?.error ?? "Error while adding the label",
|
||||
});
|
||||
reset(formData);
|
||||
});
|
||||
await projectLabelStore.createLabel(workspaceSlug.toString(), projectId.toString(), formData).then(() => {
|
||||
handleClose();
|
||||
reset(defaultValues);
|
||||
});
|
||||
};
|
||||
|
||||
const handleLabelUpdate: SubmitHandler<IIssueLabels> = async (formData) => {
|
||||
@@ -87,14 +74,6 @@ export const CreateUpdateLabelInline = observer(
|
||||
.then(() => {
|
||||
reset(defaultValues);
|
||||
handleClose();
|
||||
})
|
||||
.catch((error) => {
|
||||
setToastAlert({
|
||||
title: "Oops!",
|
||||
type: "error",
|
||||
message: error?.error ?? "Error while updating the label",
|
||||
});
|
||||
reset(formData);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export const ProjectSettingLabelGroup: React.FC<Props> = observer((props) => {
|
||||
return (
|
||||
<Disclosure
|
||||
as="div"
|
||||
className="rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 px-3.5 py-3 text-custom-text-100"
|
||||
className="rounded border border-custom-border-200 bg-custom-background-100 px-3.5 py-3 text-custom-text-100"
|
||||
defaultOpen
|
||||
>
|
||||
{({ open }) => (
|
||||
@@ -95,7 +95,7 @@ export const ProjectSettingLabelGroup: React.FC<Props> = observer((props) => {
|
||||
{labelChildren.map((child) => (
|
||||
<div
|
||||
key={child.id}
|
||||
className="group flex items-center justify-between border-b-[0.5px] border-custom-border-200 px-4 py-2.5 text-sm last:border-0"
|
||||
className="group flex items-center justify-between border-b border-custom-border-200 px-4 py-2.5 text-sm last:border-0"
|
||||
>
|
||||
<h5 className="flex items-center gap-3">
|
||||
<span
|
||||
|
||||
@@ -25,7 +25,7 @@ export const ProjectSettingLabelItem: React.FC<Props> = (props) => {
|
||||
useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false));
|
||||
|
||||
return (
|
||||
<div className="relative group flex items-center justify-between gap-2 space-y-3 rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 px-4 py-2.5">
|
||||
<div className="relative group flex items-center justify-between gap-2 space-y-3 rounded border border-custom-border-200 bg-custom-background-100 px-4 py-2.5">
|
||||
<div className="flex items-center gap-3">
|
||||
<span
|
||||
className="h-3.5 w-3.5 flex-shrink-0 rounded-full"
|
||||
|
||||
@@ -76,7 +76,7 @@ export const ProjectSettingsLabelList: React.FC = observer(() => {
|
||||
onClose={() => setSelectDeleteLabel(null)}
|
||||
/>
|
||||
|
||||
<div className="flex items-center py-3.5 border-b border-custom-border-100 justify-between">
|
||||
<div className="flex items-center py-3.5 border-b border-custom-border-200 justify-between">
|
||||
<h3 className="text-xl font-medium">Labels</h3>
|
||||
<Button variant="primary" onClick={newLabel} size="sm">
|
||||
Add label
|
||||
|
||||
@@ -49,19 +49,18 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||
const endDate = new Date(module.target_date ?? "");
|
||||
const startDate = new Date(module.start_date ?? "");
|
||||
|
||||
const isDateValid = module.target_date || module.start_date;
|
||||
|
||||
const areYearsEqual = startDate.getFullYear() === endDate.getFullYear();
|
||||
|
||||
const moduleStatus = MODULE_STATUS.find((status) => status.value === module.status);
|
||||
|
||||
const issueCount = module
|
||||
? moduleTotalIssues === 0
|
||||
const issueCount =
|
||||
moduleTotalIssues === 0
|
||||
? "0 Issue"
|
||||
: moduleTotalIssues === module.completed_issues
|
||||
? `${moduleTotalIssues} Issue${moduleTotalIssues > 1 ? "s" : ""}`
|
||||
: `${module.completed_issues}/${moduleTotalIssues} Issues`
|
||||
: "0 Issue";
|
||||
? moduleTotalIssues > 1
|
||||
? `${moduleTotalIssues} Issues`
|
||||
: `${moduleTotalIssues} Issue`
|
||||
: `${module.completed_issues}/${moduleTotalIssues} Issues`;
|
||||
|
||||
const handleAddToFavorites = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
@@ -201,17 +200,10 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||
</Tooltip>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
{isDateValid ? (
|
||||
<>
|
||||
<span className="text-xs text-custom-text-300">
|
||||
{areYearsEqual ? renderShortDate(startDate, "_ _") : renderShortMonthDate(startDate, "_ _")} -{" "}
|
||||
{areYearsEqual ? renderShortDate(endDate, "_ _") : renderShortMonthDate(endDate, "_ _")}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-xs text-custom-text-400">No due date</span>
|
||||
)}
|
||||
|
||||
<span className="text-xs text-custom-text-300">
|
||||
{areYearsEqual ? renderShortDate(startDate, "_ _") : renderShortMonthDate(startDate, "_ _")} -{" "}
|
||||
{areYearsEqual ? renderShortDate(endDate, "_ _") : renderShortMonthDate(endDate, "_ _")}
|
||||
</span>
|
||||
<div className="flex items-center gap-1.5 z-10">
|
||||
{module.is_favorite ? (
|
||||
<button type="button" onClick={handleRemoveFromFavorites}>
|
||||
|
||||
@@ -50,7 +50,7 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
|
||||
|
||||
const progress = isNaN(completionPercentage) ? 0 : Math.floor(completionPercentage);
|
||||
|
||||
const completedModuleCheck = module.status === "completed";
|
||||
const completedModuleCheck = module.status === "completed" && module.total_issues - module.completed_issues;
|
||||
|
||||
const handleAddToFavorites = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
@@ -134,11 +134,9 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
|
||||
<span className="flex-shrink-0">
|
||||
<CircularProgressIndicator size={38} percentage={progress}>
|
||||
{completedModuleCheck ? (
|
||||
progress === 100 ? (
|
||||
<Check className="h-3 w-3 text-custom-primary-100 stroke-[2]" />
|
||||
) : (
|
||||
<span className="text-sm text-custom-primary-100">{`!`}</span>
|
||||
)
|
||||
<span className="text-sm text-custom-primary-100">{`!`}</span>
|
||||
) : progress === 100 ? (
|
||||
<Check className="h-3 w-3 text-custom-primary-100 stroke-[2]" />
|
||||
) : (
|
||||
<span className="text-xs text-custom-text-300">{`${progress}%`}</span>
|
||||
)}
|
||||
|
||||
@@ -17,7 +17,7 @@ export const ModulesListView: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, peekModule } = router.query;
|
||||
|
||||
const { module: moduleStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { module: moduleStore } = useMobxStore();
|
||||
|
||||
const { storedValue: modulesView } = useLocalStorage("modules_view", "grid");
|
||||
|
||||
@@ -85,7 +85,12 @@ export const ModulesListView: React.FC = observer(() => {
|
||||
primaryButton={{
|
||||
icon: <Plus className="h-4 w-4" />,
|
||||
text: "New Module",
|
||||
onClick: () => commandPaletteStore.toggleCreateModuleModal(true),
|
||||
onClick: () => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "m",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -246,11 +246,11 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
<button onClick={handleCopyText}>
|
||||
<LinkIcon className="h-3 w-3 text-custom-text-300" />
|
||||
</button>
|
||||
<CustomMenu width="lg" placement="bottom-end" ellipsis>
|
||||
<CustomMenu width="lg" ellipsis>
|
||||
<CustomMenu.MenuItem onClick={() => setModuleDeleteModal(true)}>
|
||||
<span className="flex items-center justify-start gap-2">
|
||||
<Trash2 className="h-3 w-3" />
|
||||
<span>Delete module</span>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
<span>Delete</span>
|
||||
</span>
|
||||
</CustomMenu.MenuItem>
|
||||
</CustomMenu>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import useSWR from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
@@ -7,6 +8,7 @@ import useToast from "hooks/use-toast";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// services
|
||||
import { AuthService } from "services/auth.service";
|
||||
import { AppConfigService } from "services/app_config.service";
|
||||
// components
|
||||
import {
|
||||
GoogleLoginButton,
|
||||
@@ -22,12 +24,12 @@ import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
|
||||
// types
|
||||
import { IUser, IUserSettings } from "types";
|
||||
|
||||
const appConfigService = new AppConfigService();
|
||||
const authService = new AuthService();
|
||||
|
||||
export const SignInView = observer(() => {
|
||||
const {
|
||||
user: { fetchCurrentUser, fetchCurrentUserSettings },
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
// router
|
||||
const router = useRouter();
|
||||
@@ -36,16 +38,12 @@ export const SignInView = observer(() => {
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// fetch app config
|
||||
const { data, error: appConfigError } = useSWR("APP_CONFIG", () => appConfigService.envConfig());
|
||||
// computed
|
||||
const enableEmailPassword =
|
||||
envConfig &&
|
||||
(envConfig?.email_password_login ||
|
||||
!(
|
||||
envConfig?.email_password_login ||
|
||||
envConfig?.magic_login ||
|
||||
envConfig?.google_client_id ||
|
||||
envConfig?.github_client_id
|
||||
));
|
||||
data &&
|
||||
(data?.email_password_login || !(data?.email_password_login || data?.magic_login || data?.google || data?.github));
|
||||
|
||||
const handleLoginRedirection = useCallback(
|
||||
(user: IUser) => {
|
||||
@@ -116,11 +114,11 @@ export const SignInView = observer(() => {
|
||||
const handleGitHubSignIn = async (credential: string) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
if (envConfig && envConfig.github_client_id && credential) {
|
||||
if (data && data.github && credential) {
|
||||
const socialAuthPayload = {
|
||||
medium: "github",
|
||||
credential,
|
||||
clientId: envConfig.github_client_id,
|
||||
clientId: data.github,
|
||||
};
|
||||
const response = await authService.socialAuth(socialAuthPayload);
|
||||
if (response) {
|
||||
@@ -197,7 +195,7 @@ export const SignInView = observer(() => {
|
||||
Sign in to Plane
|
||||
</h1>
|
||||
|
||||
{!envConfig ? (
|
||||
{!data && !appConfigError ? (
|
||||
<div className="pt-10 w-ful">
|
||||
<Loader className="space-y-4 w-full pb-4">
|
||||
<Loader.Item height="46px" width="360px" />
|
||||
@@ -213,7 +211,7 @@ export const SignInView = observer(() => {
|
||||
<>
|
||||
<>
|
||||
{enableEmailPassword && <EmailPasswordForm onSubmit={handlePasswordSignIn} />}
|
||||
{envConfig?.magic_login && (
|
||||
{data?.magic_login && (
|
||||
<div className="flex flex-col divide-y divide-custom-border-200">
|
||||
<div className="pb-7">
|
||||
<EmailCodeForm handleSignIn={handleEmailCodeSignIn} />
|
||||
@@ -221,12 +219,8 @@ export const SignInView = observer(() => {
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col items-center justify-center gap-4 pt-7 sm:w-[360px] mx-auto overflow-hidden">
|
||||
{envConfig?.google_client_id && (
|
||||
<GoogleLoginButton clientId={envConfig?.google_client_id} handleSignIn={handleGoogleSignIn} />
|
||||
)}
|
||||
{envConfig?.github_client_id && (
|
||||
<GithubLoginButton clientId={envConfig?.github_client_id} handleSignIn={handleGitHubSignIn} />
|
||||
)}
|
||||
{data?.google && <GoogleLoginButton clientId={data?.google} handleSignIn={handleGoogleSignIn} />}
|
||||
{data?.github && <GithubLoginButton clientId={data?.github} handleSignIn={handleGitHubSignIn} />}
|
||||
</div>
|
||||
</>
|
||||
<p className="pt-16 text-custom-text-200 text-sm text-center">
|
||||
|
||||
@@ -18,8 +18,7 @@ export const WorkspaceDashboardView = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
const { user: userStore, project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
|
||||
const { user: userStore, project: projectStore } = useMobxStore();
|
||||
const user = userStore.currentUser;
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
|
||||
const workspaceDashboardInfo = userStore.dashboardInfo;
|
||||
@@ -68,7 +67,16 @@ export const WorkspaceDashboardView = observer(() => {
|
||||
<div className="p-5 md:p-8 pr-0">
|
||||
<h5 className="text-xl font-semibold">Create a project</h5>
|
||||
<p className="mt-2 mb-5">Manage your projects by creating issues, cycles, modules, views and pages.</p>
|
||||
<Button variant="primary" size="sm" onClick={() => commandPaletteStore.toggleCreateProjectModal(true)}>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "p",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
>
|
||||
Create Project
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import React from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import useSWR from "swr";
|
||||
import { Plus } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
// services
|
||||
import { PageService } from "services/page.service";
|
||||
// components
|
||||
import { PagesView } from "components/pages";
|
||||
import { EmptyState } from "components/common";
|
||||
// ui
|
||||
import { EmptyState } from "components/common";
|
||||
import { Loader } from "@plane/ui";
|
||||
// assets
|
||||
// icons
|
||||
import { Plus } from "lucide-react";
|
||||
// images
|
||||
import emptyPage from "public/empty-state/page.svg";
|
||||
// helpers
|
||||
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
||||
@@ -25,11 +26,7 @@ import { RECENT_PAGES_LIST } from "constants/fetch-keys";
|
||||
// services
|
||||
const pageService = new PageService();
|
||||
|
||||
export const RecentPagesList: React.FC<TPagesListProps> = observer((props) => {
|
||||
const { viewType } = props;
|
||||
|
||||
const { commandPalette: commandPaletteStore } = useMobxStore();
|
||||
|
||||
export const RecentPagesList: React.FC<TPagesListProps> = ({ viewType }) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
@@ -49,7 +46,9 @@ export const RecentPagesList: React.FC<TPagesListProps> = observer((props) => {
|
||||
|
||||
return (
|
||||
<div key={key} className="h-full overflow-hidden pb-9">
|
||||
<h2 className="text-xl font-semibold capitalize mb-2">{replaceUnderscoreIfSnakeCase(key)}</h2>
|
||||
<h2 className="text-xl font-semibold capitalize mb-2">
|
||||
{replaceUnderscoreIfSnakeCase(key)}
|
||||
</h2>
|
||||
<PagesView pages={pages[key as keyof RecentPagesResponse]} viewType={viewType} />
|
||||
</div>
|
||||
);
|
||||
@@ -62,7 +61,12 @@ export const RecentPagesList: React.FC<TPagesListProps> = observer((props) => {
|
||||
primaryButton={{
|
||||
icon: <Plus className="h-4 w-4" />,
|
||||
text: "New Page",
|
||||
onClick: () => commandPaletteStore.toggleCreatePageModal(true),
|
||||
onClick: () => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "d",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
@@ -75,4 +79,4 @@ export const RecentPagesList: React.FC<TPagesListProps> = observer((props) => {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { Plus } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// services
|
||||
import { PageService } from "services/page.service";
|
||||
import { ProjectService } from "services/project";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import useUserAuth from "hooks/use-user-auth";
|
||||
// components
|
||||
import { CreateUpdatePageModal, DeletePageModal, SinglePageDetailedItem, SinglePageListItem } from "components/pages";
|
||||
import { EmptyState } from "components/common";
|
||||
// ui
|
||||
import { EmptyState } from "components/common";
|
||||
import { Loader } from "@plane/ui";
|
||||
// icons
|
||||
import { Plus } from "lucide-react";
|
||||
// images
|
||||
import emptyPage from "public/empty-state/page.svg";
|
||||
// types
|
||||
@@ -36,19 +37,17 @@ type Props = {
|
||||
const pageService = new PageService();
|
||||
const projectService = new ProjectService();
|
||||
|
||||
export const PagesView: React.FC<Props> = observer(({ pages, viewType }) => {
|
||||
export const PagesView: React.FC<Props> = ({ pages, viewType }) => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// states
|
||||
const [createUpdatePageModal, setCreateUpdatePageModal] = useState(false);
|
||||
const [selectedPageToUpdate, setSelectedPageToUpdate] = useState<IPage | null>(null);
|
||||
const [deletePageModal, setDeletePageModal] = useState(false);
|
||||
const [selectedPageToDelete, setSelectedPageToDelete] = useState<IPage | null>(null);
|
||||
|
||||
const { user: userStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const user = userStore.currentUser ?? undefined;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
const { user } = useUserAuth();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@@ -164,7 +163,7 @@ export const PagesView: React.FC<Props> = observer(({ pages, viewType }) => {
|
||||
};
|
||||
|
||||
const partialUpdatePage = (page: IPage, formData: Partial<IPage>) => {
|
||||
if (!workspaceSlug || !projectId || !user) return;
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
mutate<IPage[]>(
|
||||
ALL_PAGES_LIST(projectId.toString()),
|
||||
@@ -265,7 +264,12 @@ export const PagesView: React.FC<Props> = observer(({ pages, viewType }) => {
|
||||
primaryButton={{
|
||||
icon: <Plus className="h-4 w-4" />,
|
||||
text: "New Page",
|
||||
onClick: () => commandPaletteStore.toggleCreatePageModal(true),
|
||||
onClick: () => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "d",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -290,4 +294,4 @@ export const PagesView: React.FC<Props> = observer(({ pages, viewType }) => {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ export const ProfileNavbar: React.FC<Props> = (props) => {
|
||||
const tabsList = isAuthorized ? [...viewerTabs, ...adminTabs] : viewerTabs;
|
||||
|
||||
return (
|
||||
<div className="sticky -top-0.5 z-10 md:static px-4 sm:px-5 flex items-center justify-between gap-4 bg-custom-background-100 border-b border-custom-border-300">
|
||||
<div className="sticky -top-0.5 z-[1] md:static px-4 sm:px-5 flex items-center justify-between gap-4 bg-custom-background-100 border-b border-custom-border-300">
|
||||
<div className="flex items-center overflow-x-scroll">
|
||||
{tabsList.map((tab) => (
|
||||
<Link key={tab.route} href={`/${workspaceSlug}/profile/${userId}/${tab.route}`}>
|
||||
|
||||
@@ -18,7 +18,7 @@ export interface IProjectCardList {
|
||||
export const ProjectCardList: FC<IProjectCardList> = observer((props) => {
|
||||
const { workspaceSlug } = props;
|
||||
// store
|
||||
const { project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { project: projectStore } = useMobxStore();
|
||||
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
|
||||
|
||||
@@ -53,7 +53,12 @@ export const ProjectCardList: FC<IProjectCardList> = observer((props) => {
|
||||
primaryButton={{
|
||||
icon: <Plus className="h-4 w-4" />,
|
||||
text: "New Project",
|
||||
onClick: () => commandPaletteStore.toggleCreateProjectModal(true),
|
||||
onClick: () => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "p",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -168,7 +168,7 @@ export const ProjectCard: React.FC<ProjectCardProps> = observer((props) => {
|
||||
</div>
|
||||
|
||||
<div className="h-[104px] w-full flex flex-col justify-between p-4 rounded-b">
|
||||
<p className="text-sm text-custom-text-300 break-words line-clamp-2">{project.description}</p>
|
||||
<p className="text-sm text-custom-text-300 font-medium break-words line-clamp-2">{project.description}</p>
|
||||
<div className="flex item-center justify-between">
|
||||
<Tooltip
|
||||
tooltipHeading="Members"
|
||||
|
||||
@@ -200,7 +200,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
value={value}
|
||||
placeholder="Enter project description"
|
||||
onChange={onChange}
|
||||
className="min-h-[102px] text-sm font-medium"
|
||||
className="min-h-[102px] text-sm"
|
||||
hasError={Boolean(errors?.description)}
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
@@ -236,7 +236,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.identifier)}
|
||||
placeholder="Enter identifier"
|
||||
className="w-full font-medium"
|
||||
className="w-full"
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
)}
|
||||
@@ -253,7 +253,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
label={selectedNetwork?.label ?? "Select network"}
|
||||
className="!border-custom-border-200 !shadow-none font-medium"
|
||||
className="!border-custom-border-200 !shadow-none"
|
||||
input
|
||||
disabled={!isAdmin}
|
||||
optionsClassName="w-full"
|
||||
|
||||
@@ -87,7 +87,7 @@ export const ProjectMemberList: React.FC = observer(() => {
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-between gap-4 py-3.5 border-b border-custom-border-100">
|
||||
<div className="flex items-center justify-between gap-4 py-3.5 border-b border-custom-border-200">
|
||||
<h4 className="text-xl font-medium">Members</h4>
|
||||
<div className="flex gap-1 items-center justify-start ml-auto text-custom-text-400 rounded-md px-2.5 py-1.5 border border-custom-border-200 bg-custom-background-100">
|
||||
<Search className="h-3.5 w-3.5" />
|
||||
@@ -111,7 +111,7 @@ export const ProjectMemberList: React.FC = observer(() => {
|
||||
<Loader.Item height="40px" />
|
||||
</Loader>
|
||||
) : (
|
||||
<div className="divide-y divide-custom-border-100">
|
||||
<div className="divide-y divide-custom-border-200">
|
||||
{members.length > 0
|
||||
? searchedMembers.map((member) => <ProjectMemberListItem key={member.id} member={member} />)
|
||||
: null}
|
||||
|
||||
@@ -83,7 +83,7 @@ export const ProjectSettingsMemberDefaults: React.FC = observer(() => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center py-3.5 border-b border-custom-border-100">
|
||||
<div className="flex items-center py-3.5 border-b border-custom-border-200">
|
||||
<h3 className="text-xl font-medium">Defaults</h3>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export const DeleteProjectSection: React.FC<IDeleteProjectSection> = (props) =>
|
||||
const { projectDetails, handleDelete } = props;
|
||||
|
||||
return (
|
||||
<Disclosure as="div" className="border-t border-custom-border-100">
|
||||
<Disclosure as="div" className="border-t border-custom-border-400">
|
||||
{({ open }) => (
|
||||
<div className="w-full">
|
||||
<Disclosure.Button as="button" type="button" className="flex items-center justify-between w-full py-4">
|
||||
|
||||
@@ -96,7 +96,7 @@ export const ProjectFeaturesList: FC<Props> = observer(() => {
|
||||
{PROJECT_FEATURES_LIST.map((feature) => (
|
||||
<div
|
||||
key={feature.property}
|
||||
className="flex items-center justify-between gap-x-8 gap-y-2 border-b border-custom-border-100 bg-custom-background-100 p-4"
|
||||
className="flex items-center justify-between gap-x-8 gap-y-2 border-b border-custom-border-200 bg-custom-background-100 p-4"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex items-center justify-center p-3 rounded bg-custom-background-90">{feature.icon}</div>
|
||||
|
||||
@@ -19,6 +19,11 @@ import { IProject } from "types";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
export const ProjectSidebarList: FC = observer(() => {
|
||||
const { theme: themeStore, project: projectStore } = useMobxStore();
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// states
|
||||
const [isFavoriteProjectCreate, setIsFavoriteProjectCreate] = useState(false);
|
||||
const [isProjectModalOpen, setIsProjectModalOpen] = useState(false);
|
||||
@@ -26,11 +31,6 @@ export const ProjectSidebarList: FC = observer(() => {
|
||||
// refs
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const { theme: themeStore, project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@@ -254,7 +254,12 @@ export const ProjectSidebarList: FC = observer(() => {
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 px-3 text-sm text-custom-sidebar-text-200"
|
||||
onClick={() => commandPaletteStore.toggleCreateProjectModal(true)}
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "p",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
>
|
||||
<Plus className="h-5 w-5" />
|
||||
{!isCollapsed && "Add Project"}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user