Compare commits

..
Author SHA1 Message Date
NarayanBavisetti 780f847800 fix: added capture message 2023-11-07 14:00:45 +05:30
NarayanBavisetti c31c4e9ccc fix: added desription in API Token 2023-11-07 13:22:08 +05:30
133 changed files with 815 additions and 1012 deletions
+16 -24
View File
@@ -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 }}
-3
View File
@@ -70,6 +70,3 @@ ENABLE_MAGIC_LINK_LOGIN="0"
# Email redirections and minio domain settings
WEB_URL="http://localhost"
# Gunicorn Workers
GUNICORN_WORKERS=2
+1 -1
View File
@@ -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 -
+3 -3
View File
@@ -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)
+9 -5
View File
@@ -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
+1 -1
View File
@@ -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(
+8 -3
View File
@@ -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(
{
+13 -40
View File
@@ -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,
-2
View File
@@ -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}
-2
View File
@@ -61,5 +61,3 @@ MINIO_ROOT_PASSWORD="secret-key"
BUCKET_NAME=uploads
FILE_SIZE_LIMIT=5242880
# Gunicorn Workers
GUNICORN_WORKERS=2
+3 -8
View File
@@ -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))",
+2 -15
View File
@@ -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"
+1 -3
View File
@@ -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>
+14
View File
@@ -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,
},
];
+5 -6
View File
@@ -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",
+2 -6
View File
@@ -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
View File
@@ -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>
);
});
};
+11 -7
View File
@@ -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>
);
});
};
+7 -10
View File
@@ -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}>
+10 -10
View File
@@ -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) => {
)}
</>
);
});
};
+3 -5
View File
@@ -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>
)}
+9 -8
View File
@@ -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) => {
)}
</>
);
});
};
+3 -3
View File
@@ -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">
+1 -1
View File
@@ -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">
+2 -2
View File
@@ -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>
+11 -3
View File
@@ -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
+12 -7
View File
@@ -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>
);
});
};
+10 -3
View File
@@ -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
+5 -2
View File
@@ -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>
+5 -3
View File
@@ -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>
+5 -2
View File
@@ -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>
+11 -7
View File
@@ -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>
+9 -2
View File
@@ -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>
+3 -13
View File
@@ -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>
);
});
};
+7 -4
View File
@@ -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" />
+13 -3
View File
@@ -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<{
+5 -3
View File
@@ -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>
+5 -35
View File
@@ -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}
/>
+1 -9
View File
@@ -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
+10 -18
View File
@@ -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}>
+4 -6
View File
@@ -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>
)}
+7 -2
View File
@@ -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);
},
}}
/>
)}
+3 -3
View File
@@ -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>
+13 -19
View File
@@ -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) => {
)}
</>
);
});
};
+20 -16
View File
@@ -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 }) => {
)}
</>
);
});
};
+1 -1
View File
@@ -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}`}>
+7 -2
View File
@@ -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);
},
}}
/>
)}
+1 -1
View File
@@ -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"
+3 -3
View File
@@ -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"
+2 -2
View File
@@ -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>
+11 -6
View File
@@ -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