From e3e57df4a207d3c1c131c8ab96f76281e1f95e53 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Thu, 9 Mar 2023 16:05:25 +0530 Subject: [PATCH 001/256] style: list view (#409) * style: list view * style: list board header spacing fix --- .../components/core/list-view/all-lists.tsx | 10 + .../core/list-view/single-issue.tsx | 171 +++++++++--------- .../components/core/list-view/single-list.tsx | 122 +++++++------ 3 files changed, 157 insertions(+), 146 deletions(-) diff --git a/apps/app/components/core/list-view/all-lists.tsx b/apps/app/components/core/list-view/all-lists.tsx index d7c93cb78..0e067513d 100644 --- a/apps/app/components/core/list-view/all-lists.tsx +++ b/apps/app/components/core/list-view/all-lists.tsx @@ -38,15 +38,25 @@ export const AllLists: React.FC = ({ return (
{Object.keys(groupedByIssues).map((singleGroup) => { + const currentState = + selectedGroup === "state_detail.name" + ? states?.find((s) => s.name === singleGroup) + : null; const stateId = selectedGroup === "state_detail.name" ? states?.find((s) => s.name === singleGroup)?.id ?? null : null; + const bgColor = + selectedGroup === "state_detail.name" + ? states?.find((s) => s.name === singleGroup)?.color + : "#000000"; return ( = ({ Copy issue link -
{ - e.preventDefault(); - setContextMenu(true); - setContextMenuPosition({ x: e.pageX, y: e.pageY }); - }} - > -
- +
+ -
- {properties.priority && ( - - )} - {properties.state && ( - - )} - {properties.due_date && ( - - )} - {properties.sub_issue_count && ( -
- {issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"} -
- )} - {properties.labels && ( -
- {issue.label_details.map((label) => ( - + +
+ {properties.priority && ( + + )} + {properties.state && ( + + )} + {properties.due_date && ( + + )} + {properties.sub_issue_count && ( +
+ {issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"} +
+ )} + {properties.labels && issue.label_details.length > 0 ? ( +
+ {issue.label_details.map((label) => ( - {label.name} - - ))} -
- )} - {properties.assignee && ( - - )} - {type && !isNotAllowed && ( - - Edit issue - {type !== "issue" && removeIssue && ( - - <>Remove from {type} + key={label.id} + className="group flex items-center gap-1 rounded-2xl border px-2 py-0.5 text-xs" + > + + {label.name} + + ))} +
+ ) : ( + "" + )} + {properties.assignee && ( + + )} + {type && !isNotAllowed && ( + + Edit issue + {type !== "issue" && removeIssue && ( + + <>Remove from {type} + + )} + handleDeleteIssue(issue)}> + Delete issue - )} - handleDeleteIssue(issue)}> - Delete issue - - Copy issue link - - )} + Copy issue link + + )} +
diff --git a/apps/app/components/core/list-view/single-list.tsx b/apps/app/components/core/list-view/single-list.tsx index 478b27380..1ca09ff99 100644 --- a/apps/app/components/core/list-view/single-list.tsx +++ b/apps/app/components/core/list-view/single-list.tsx @@ -7,15 +7,18 @@ import useIssuesProperties from "hooks/use-issue-properties"; // components import { SingleListIssue } from "components/core"; // icons -import { ChevronDownIcon, PlusIcon } from "@heroicons/react/24/outline"; +import { PlusIcon } from "@heroicons/react/24/outline"; +import { getStateGroupIcon } from "components/icons"; // helpers import { addSpaceIfCamelCase } from "helpers/string.helper"; // types -import { IIssue, IProjectMember, NestedKeyOf, UserAuth } from "types"; +import { IIssue, IProjectMember, IState, NestedKeyOf, UserAuth } from "types"; import { CustomMenu } from "components/ui"; type Props = { type?: "issue" | "cycle" | "module"; + currentState?: IState | null; + bgColor?: string; groupTitle: string; groupedByIssues: { [key: string]: IIssue[]; @@ -33,6 +36,8 @@ type Props = { export const SingleList: React.FC = ({ type, + currentState, + bgColor, groupTitle, groupedByIssues, selectedGroup, @@ -69,17 +74,23 @@ export const SingleList: React.FC = ({ return ( {({ open }) => ( -
-
+
+
-
- - - +
+ {selectedGroup !== null && selectedGroup === "state_detail.name" ? ( + + {currentState && getStateGroupIcon(currentState.group, "20", "20", bgColor)} + + ) : ( + "" + )} {selectedGroup !== null ? ( -

+

{selectedGroup === "created_by" ? createdBy : selectedGroup === "assignees" @@ -89,65 +100,25 @@ export const SingleList: React.FC = ({ ) : (

All Issues

)} -

+ {groupedByIssues[groupTitle as keyof IIssue].length} -

+

-
- - -
- {groupedByIssues[groupTitle] ? ( - groupedByIssues[groupTitle].length > 0 ? ( - groupedByIssues[groupTitle].map((issue: IIssue) => ( - handleEditIssue(issue)} - makeIssueCopy={() => makeIssueCopy(issue)} - handleDeleteIssue={handleDeleteIssue} - removeIssue={() => { - removeIssue && removeIssue(issue.bridge); - }} - userAuth={userAuth} - /> - )) - ) : ( -

No issues.

- ) - ) : ( -
Loading...
- )} -
-
-
-
+ {type === "issue" ? ( ) : ( - - Add issue + + } optionsPosition="left" @@ -162,6 +133,41 @@ export const SingleList: React.FC = ({ )}
+ + + {groupedByIssues[groupTitle] ? ( + groupedByIssues[groupTitle].length > 0 ? ( + groupedByIssues[groupTitle].map((issue: IIssue) => ( + handleEditIssue(issue)} + makeIssueCopy={() => makeIssueCopy(issue)} + handleDeleteIssue={handleDeleteIssue} + removeIssue={() => { + removeIssue && removeIssue(issue.bridge); + }} + userAuth={userAuth} + /> + )) + ) : ( +

No issues.

+ ) + ) : ( +
Loading...
+ )} +
+
)} From 0416e07f46ba9b498977027b1226bd87887e239c Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Thu, 9 Mar 2023 20:49:12 +0530 Subject: [PATCH 002/256] refactor: self hosting setup (#411) * merge-commit: self hosted updates * dev: updates in self hosting setup * dev: update script to get the instance IP * dev: update script to generate backend secret key --- apiserver/.env.example | 19 +++++++-------- apiserver/plane/settings/production.py | 33 +++++++++++++++++++------- apps/app/.env.example | 11 +++++---- docker-compose.yml | 7 +++--- nginx/Dockerfile | 2 +- nginx/nginx.conf | 21 +++++++--------- setup.sh | 7 ++++-- 7 files changed, 57 insertions(+), 43 deletions(-) mode change 100644 => 100755 setup.sh diff --git a/apiserver/.env.example b/apiserver/.env.example index 3d502fadb..2241e2217 100644 --- a/apiserver/.env.example +++ b/apiserver/.env.example @@ -1,22 +1,21 @@ -SECRET_KEY="<-- django secret -->" DJANGO_SETTINGS_MODULE="plane.settings.production" # Database -DATABASE_URL=postgres://plane:plane@db:5432/plane +DATABASE_URL=postgres://plane:xyzzyspoon@db:5432/plane # Cache REDIS_URL=redis://redis:6379/ # SMPT -EMAIL_HOST="<-- email smtp -->" -EMAIL_HOST_USER="<-- email host user -->" -EMAIL_HOST_PASSWORD="<-- email host password -->" +EMAIL_HOST="" +EMAIL_HOST_USER="" +EMAIL_HOST_PASSWORD="" # AWS -AWS_REGION="<-- aws region -->" -AWS_ACCESS_KEY_ID="<-- aws access key -->" -AWS_SECRET_ACCESS_KEY="<-- aws secret acess key -->" -AWS_S3_BUCKET_NAME="<-- aws s3 bucket name -->" +AWS_REGION="" +AWS_ACCESS_KEY_ID="" +AWS_SECRET_ACCESS_KEY="" +AWS_S3_BUCKET_NAME="" # FE WEB_URL="localhost/" # OAUTH -GITHUB_CLIENT_SECRET="<-- github secret -->" +GITHUB_CLIENT_SECRET="" # Flags DISABLE_COLLECTSTATIC=1 DOCKERIZED=1 diff --git a/apiserver/plane/settings/production.py b/apiserver/plane/settings/production.py index 1b6ac2cf7..c1cde24a0 100644 --- a/apiserver/plane/settings/production.py +++ b/apiserver/plane/settings/production.py @@ -22,13 +22,7 @@ DATABASES = { } } -# CORS WHITELIST ON PROD -CORS_ORIGIN_WHITELIST = [ - # "https://example.com", - # "https://sub.example.com", - # "http://localhost:8080", - # "http://127.0.0.1:9000" -] + # Parse database configuration from $DATABASE_URL DATABASES["default"] = dj_database_url.config() SITE_ID = 1 @@ -43,12 +37,33 @@ DOCKERIZED = os.environ.get( # Honor the 'X-Forwarded-Proto' header for request.is_secure() SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") -# Allow all host headers -ALLOWED_HOSTS = ["*"] # TODO: Make it FALSE and LIST DOMAINS IN FULL PROD. CORS_ALLOW_ALL_ORIGINS = True + +CORS_ALLOW_METHODS = [ + "DELETE", + "GET", + "OPTIONS", + "PATCH", + "POST", + "PUT", +] + +CORS_ALLOW_HEADERS = [ + "accept", + "accept-encoding", + "authorization", + "content-type", + "dnt", + "origin", + "user-agent", + "x-csrftoken", + "x-requested-with", +] + +CORS_ALLOW_CREDENTIALS = True # Simplified static file serving. STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" diff --git a/apps/app/.env.example b/apps/app/.env.example index 50747dcc6..371a64c80 100644 --- a/apps/app/.env.example +++ b/apps/app/.env.example @@ -1,7 +1,8 @@ -NEXT_PUBLIC_API_BASE_URL = "http://localhost" -NEXT_PUBLIC_GOOGLE_CLIENTID="<-- google client id -->" -NEXT_PUBLIC_GITHUB_APP_NAME="<-- github app name -->" -NEXT_PUBLIC_GITHUB_ID="<-- github client id -->" -NEXT_PUBLIC_SENTRY_DSN="<-- sentry dns -->" +# Replace with your instance Public IP +# NEXT_PUBLIC_API_BASE_URL = "http://localhost" +NEXT_PUBLIC_GOOGLE_CLIENTID="" +NEXT_PUBLIC_GITHUB_APP_NAME="" +NEXT_PUBLIC_GITHUB_ID="" +NEXT_PUBLIC_SENTRY_DSN="" NEXT_PUBLIC_ENABLE_OAUTH=0 NEXT_PUBLIC_ENABLE_SENTRY=0 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 2ac5ed9dc..2fe1a945f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,10 +9,10 @@ services: ports: - 80:80 depends_on: - # - plane-web + - plane-web - plane-api db: - image: postgres:12-alpine + image: postgres:15.2-alpine container_name: db restart: always volumes: @@ -20,7 +20,8 @@ services: environment: POSTGRES_USER: plane POSTGRES_DB: plane - POSTGRES_PASSWORD: plane + POSTGRES_PASSWORD: xyzzyspoon + PGDATA : /var/lib/postgresql/data command: postgres -c 'max_connections=1000' ports: - 5432:5432 diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 29ffd3c93..4948a0109 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,4 +1,4 @@ FROM nginx:1.21-alpine RUN rm /etc/nginx/conf.d/default.conf -COPY nginx.conf /etc/nginx/conf.d \ No newline at end of file +COPY nginx.conf /etc/nginx/nginx.conf \ No newline at end of file diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 6b00daa8e..4ea689700 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -1,25 +1,20 @@ -upstream plane { - server localhost:80; -} +events { } -error_log /var/log/nginx/error.log; + +http { + sendfile on; server { listen 80; root /www/data/; access_log /var/log/nginx/access.log; location / { - proxy_pass http://planefrontend:3000/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; + proxy_pass http://planefrontend:3000/; } + location /api/ { proxy_pass http://planebackend:8000/api/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; } + } +} \ No newline at end of file diff --git a/setup.sh b/setup.sh old mode 100644 new mode 100755 index 4370cc44a..da76e64a7 --- a/setup.sh +++ b/setup.sh @@ -1,4 +1,7 @@ -# Generating API Server environmental variables +#!/bin/bash cp ./apiserver/.env.example ./apiserver/.env # Generating App environmental variables -cp ./apps/app/.env.example ./apps/app/.env \ No newline at end of file +cp ./apps/app/.env.example ./apps/app/.env + +echo -e "\nNEXT_PUBLIC_API_BASE_URL=http://$1" >> ./apps/app/.env +echo -e "\nSECRET_KEY=\"$(tr -dc 'a-z0-9!@#$%^&*(-_=+)' < /dev/urandom | head -c50)\"" >> ./apiserver/.env \ No newline at end of file From 4e9149a27cf46491e08056420bad61cc42d34ea3 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Thu, 9 Mar 2023 22:49:03 +0530 Subject: [PATCH 003/256] style: empty cycle state (#410) --- apps/app/components/cycles/cycles-list.tsx | 20 +----- apps/app/components/cycles/empty-cycle.tsx | 78 ++++++++++++++++++++++ apps/app/components/cycles/index.ts | 1 + 3 files changed, 82 insertions(+), 17 deletions(-) create mode 100644 apps/app/components/cycles/empty-cycle.tsx diff --git a/apps/app/components/cycles/cycles-list.tsx b/apps/app/components/cycles/cycles-list.tsx index 97701202c..d26292a66 100644 --- a/apps/app/components/cycles/cycles-list.tsx +++ b/apps/app/components/cycles/cycles-list.tsx @@ -1,12 +1,10 @@ import { useState } from "react"; // components -import { DeleteCycleModal, SingleCycleCard } from "components/cycles"; -// icons -import { CompletedCycleIcon, CurrentCycleIcon, UpcomingCycleIcon } from "components/icons"; +import { DeleteCycleModal, EmptyCycle, SingleCycleCard } from "components/cycles"; +import { Loader } from "components/ui"; // types import { ICycle, SelectCycleType } from "types"; -import { Loader } from "components/ui"; type TCycleStatsViewProps = { cycles: ICycle[] | undefined; @@ -58,19 +56,7 @@ export const CyclesList: React.FC = ({ ))}
) : ( -
- {type === "upcoming" ? ( - - ) : type === "draft" ? ( - - ) : ( - - )} -

- No {type} {type === "current" ? "cycle" : "cycles"} yet. Create with{" "} -
Q
. -

-
+ ) ) : ( diff --git a/apps/app/components/cycles/empty-cycle.tsx b/apps/app/components/cycles/empty-cycle.tsx new file mode 100644 index 000000000..dfefa4011 --- /dev/null +++ b/apps/app/components/cycles/empty-cycle.tsx @@ -0,0 +1,78 @@ +import React from "react"; +import { LinearProgressIndicator } from "components/ui"; + +export const EmptyCycle = () => { + const emptyCycleData = [ + { + id: 1, + name: "backlog", + value: 20, + color: "#DEE2E6", + }, + { + id: 2, + name: "unstarted", + value: 14, + color: "#26B5CE", + }, + { + id: 3, + name: "started", + value: 27, + color: "#F7AE59", + }, + { + id: 4, + name: "cancelled", + value: 15, + color: "#D687FF", + }, + { + id: 5, + name: "completed", + value: 14, + color: "#09A953", + }, + ]; + return ( +
+
+
+
+ Cycle Name +
+ + +
+
+ +
+ +
+
+ +
+
+ Cycle Name +
+ + +
+
+ +
+ +
+
+
+ +
+

Create New Cycle

+

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

+
+
+ ); +}; diff --git a/apps/app/components/cycles/index.ts b/apps/app/components/cycles/index.ts index d1fd0d6b6..082070eb0 100644 --- a/apps/app/components/cycles/index.ts +++ b/apps/app/components/cycles/index.ts @@ -6,3 +6,4 @@ export * from "./modal"; export * from "./select"; export * from "./sidebar"; export * from "./single-cycle-card"; +export * from "./empty-cycle"; From 704b7d02ef3e643f9043b70ace8dd1a7db3ae23b Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Thu, 9 Mar 2023 22:50:34 +0530 Subject: [PATCH 004/256] style : ui fixes (#412) * fix: kanban view vertical scroll fix * fix: delete option remove from my issue page * fix: my issue filter key renamed with id * fix: sidebar ellipsis alignment * fix: cycle card favorite icon alignment * style: icon added in card options * fix: progress icon alignment * style: my issue page list view --- .../components/core/board-view/all-boards.tsx | 2 +- apps/app/components/cycles/sidebar.tsx | 2 +- .../components/cycles/single-cycle-card.tsx | 34 +++- .../components/issues/my-issues-list-item.tsx | 164 ++++++++---------- apps/app/components/modules/sidebar.tsx | 2 +- .../components/modules/single-module-card.tsx | 20 ++- .../pages/[workspaceSlug]/me/my-issues.tsx | 50 +++--- 7 files changed, 142 insertions(+), 132 deletions(-) diff --git a/apps/app/components/core/board-view/all-boards.tsx b/apps/app/components/core/board-view/all-boards.tsx index b81c3238f..88d303dc7 100644 --- a/apps/app/components/core/board-view/all-boards.tsx +++ b/apps/app/components/core/board-view/all-boards.tsx @@ -39,7 +39,7 @@ export const AllBoards: React.FC = ({ return ( <> {groupedByIssues ? ( -
+
{Object.keys(groupedByIssues).map((singleGroup, index) => { const currentState = diff --git a/apps/app/components/cycles/sidebar.tsx b/apps/app/components/cycles/sidebar.tsx index 51b3e6c71..8b5dc000d 100644 --- a/apps/app/components/cycles/sidebar.tsx +++ b/apps/app/components/cycles/sidebar.tsx @@ -246,7 +246,7 @@ export const CycleDetailsSidebar: React.FC = ({
-
+

{cycle.name}

diff --git a/apps/app/components/cycles/single-cycle-card.tsx b/apps/app/components/cycles/single-cycle-card.tsx index edbaa35c4..01f4f1d00 100644 --- a/apps/app/components/cycles/single-cycle-card.tsx +++ b/apps/app/components/cycles/single-cycle-card.tsx @@ -15,7 +15,13 @@ import { CustomMenu, LinearProgressIndicator, Tooltip } from "components/ui"; import { Disclosure, Transition } from "@headlessui/react"; // icons import { CalendarDaysIcon } from "@heroicons/react/20/solid"; -import { ChevronDownIcon, PencilIcon, StarIcon } from "@heroicons/react/24/outline"; +import { + ChevronDownIcon, + DocumentDuplicateIcon, + PencilIcon, + StarIcon, + TrashIcon, +} from "@heroicons/react/24/outline"; // helpers import { getDateRangeStatus, renderShortDateWithYearFormat } from "helpers/date-time.helper"; import { groupBy } from "helpers/array.helper"; @@ -232,7 +238,7 @@ export const SingleCycleCard: React.FC = (props) => {
diff --git a/apps/app/components/issues/my-issues-list-item.tsx b/apps/app/components/issues/my-issues-list-item.tsx index 740da34ba..be28022a0 100644 --- a/apps/app/components/issues/my-issues-list-item.tsx +++ b/apps/app/components/issues/my-issues-list-item.tsx @@ -20,22 +20,16 @@ import { CustomMenu, Tooltip } from "components/ui"; import { IIssue, Properties } from "types"; // fetch-keys import { USER_ISSUE } from "constants/fetch-keys"; -import { copyTextToClipboard } from "helpers/string.helper"; +import { copyTextToClipboard, truncateText } from "helpers/string.helper"; import useToast from "hooks/use-toast"; type Props = { issue: IIssue; properties: Properties; projectId: string; - handleDeleteIssue: () => void; }; -export const MyIssuesListItem: React.FC = ({ - issue, - properties, - projectId, - handleDeleteIssue, -}) => { +export const MyIssuesListItem: React.FC = ({ issue, properties, projectId }) => { const router = useRouter(); const { workspaceSlug } = router.query; const { setToastAlert } = useToast(); @@ -84,14 +78,8 @@ export const MyIssuesListItem: React.FC = ({ const isNotAllowed = false; return ( -
-
- +
+ -
- {properties.priority && ( - - )} - {properties.state && ( - - )} - {properties.due_date && ( - - )} - {properties.sub_issue_count && ( -
- {issue?.sub_issues_count} {issue?.sub_issues_count === 1 ? "sub-issue" : "sub-issues"} -
- )} - {properties.labels && ( -
- {issue.label_details.map((label) => ( - - - {label.name} - - ))} -
- )} - {properties.assignee && ( - 0 - ? issue.assignee_details - .map((assignee) => - assignee?.first_name !== "" ? assignee?.first_name : assignee?.email - ) - .join(", ") - : "No Assignee" - } - > -
- + +
+ {properties.priority && ( + + )} + {properties.state && ( + + )} + {properties.due_date && ( + + )} + {properties.sub_issue_count && ( +
+ {issue?.sub_issues_count} {issue?.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
- - )} - - Delete issue - Copy issue link - + )} + {properties.labels && issue.label_details.length > 0 ? ( +
+ {issue.label_details.map((label) => ( + + + {label.name} + + ))} +
+ ) : ( + "" + )} + {properties.assignee && ( + 0 + ? issue.assignee_details + .map((assignee) => + assignee?.first_name !== "" ? assignee?.first_name : assignee?.email + ) + .join(", ") + : "No Assignee" + } + > +
+ +
+
+ )} + + Copy issue link + +
); diff --git a/apps/app/components/modules/sidebar.tsx b/apps/app/components/modules/sidebar.tsx index 07e102ce0..72d705907 100644 --- a/apps/app/components/modules/sidebar.tsx +++ b/apps/app/components/modules/sidebar.tsx @@ -327,7 +327,7 @@ export const ModuleDetailsSidebar: React.FC = ({
-
+

{module.name}

diff --git a/apps/app/components/modules/single-module-card.tsx b/apps/app/components/modules/single-module-card.tsx index 2a48baac5..5a4785aae 100644 --- a/apps/app/components/modules/single-module-card.tsx +++ b/apps/app/components/modules/single-module-card.tsx @@ -15,7 +15,10 @@ import { AssigneesList, Avatar, CustomMenu, Tooltip } from "components/ui"; import User from "public/user.png"; import { CalendarDaysIcon, + DocumentDuplicateIcon, + PencilIcon, StarIcon, + TrashIcon, UserCircleIcon, UserGroupIcon, } from "@heroicons/react/24/outline"; @@ -255,12 +258,23 @@ export const SingleModuleCard: React.FC = ({ module, handleEditModule }) )} - Edit module + + + + Edit Module + + - Delete module + + + Delete Module + - Copy module link + + + Copy Module Link +
diff --git a/apps/app/pages/[workspaceSlug]/me/my-issues.tsx b/apps/app/pages/[workspaceSlug]/me/my-issues.tsx index d5b8bbfaf..ccc5ea7df 100644 --- a/apps/app/pages/[workspaceSlug]/me/my-issues.tsx +++ b/apps/app/pages/[workspaceSlug]/me/my-issues.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React from "react"; import { useRouter } from "next/router"; // headless ui @@ -17,16 +17,13 @@ import useIssuesProperties from "hooks/use-issue-properties"; // types import { IIssue, Properties } from "types"; // components -import { DeleteIssueModal, MyIssuesListItem } from "components/issues"; +import { MyIssuesListItem } from "components/issues"; // helpers import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; // types import type { NextPage } from "next"; const MyIssuesPage: NextPage = () => { - const [deleteIssueModal, setDeleteIssueModal] = useState(false); - const [issueToDelete, setIssueToDelete] = useState(null); - const router = useRouter(); const { workspaceSlug } = router.query; @@ -38,18 +35,8 @@ const MyIssuesPage: NextPage = () => { undefined ); - const handleDeleteIssue = (issue: IIssue) => { - setDeleteIssueModal(true); - setIssueToDelete(issue); - }; - return ( <> - setDeleteIssueModal(false)} - isOpen={deleteIssueModal} - data={issueToDelete} - /> @@ -96,7 +83,7 @@ const MyIssuesPage: NextPage = () => { }`} onClick={() => setProperties(key as keyof Properties)} > - {replaceUnderscoreIfSnakeCase(key)} + {key === "key" ? "ID" : replaceUnderscoreIfSnakeCase(key)} ))}
@@ -129,8 +116,12 @@ const MyIssuesPage: NextPage = () => {
{({ open }) => ( -
-
+
+
@@ -141,7 +132,9 @@ const MyIssuesPage: NextPage = () => { />

My Issues

-

{myIssues.length}

+ + {myIssues.length} +
@@ -155,17 +148,14 @@ const MyIssuesPage: NextPage = () => { leaveTo="transform opacity-0" > -
- {myIssues.map((issue: IIssue) => ( - handleDeleteIssue(issue)} - /> - ))} -
+ {myIssues.map((issue: IIssue) => ( + + ))}
From 4fad685ec81c2715ee82c84e12990acfb9e83153 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 10 Mar 2023 12:32:29 +0530 Subject: [PATCH 005/256] style: workspace dropdown (#416) * style: workspace dropdown * style: workspace dropdown hover fix --- .../components/workspace/sidebar-dropdown.tsx | 164 ++++++++++-------- 1 file changed, 88 insertions(+), 76 deletions(-) diff --git a/apps/app/components/workspace/sidebar-dropdown.tsx b/apps/app/components/workspace/sidebar-dropdown.tsx index f7392f1d2..5a98ad12e 100644 --- a/apps/app/components/workspace/sidebar-dropdown.tsx +++ b/apps/app/components/workspace/sidebar-dropdown.tsx @@ -3,7 +3,7 @@ import { Menu, Transition } from "@headlessui/react"; import { useRouter } from "next/router"; import Image from "next/image"; import Link from "next/link"; -import { ChevronDownIcon, PlusIcon } from "@heroicons/react/24/outline"; +import { CheckIcon, ChevronDownIcon, PlusIcon } from "@heroicons/react/24/outline"; // hooks import useUser from "hooks/use-user"; import useTheme from "hooks/use-theme"; @@ -20,13 +20,17 @@ import { IWorkspace } from "types"; // Static Data const userLinks = (workspaceSlug: string) => [ { - name: "My Profile", - href: `/${workspaceSlug}/me/profile`, + name: "Workspace Settings", + href: `/${workspaceSlug}/settings`, }, { name: "Workspace Invites", href: "/invitations", }, + { + name: "My Profile", + href: `/${workspaceSlug}/me/profile`, + }, ]; export const WorkspaceSidebarDropdown = () => { @@ -116,28 +120,29 @@ export const WorkspaceSidebarDropdown = () => { leaveFrom="transform opacity-100 scale-100" leaveTo="transform opacity-0 scale-95" > - -
-
- - {user?.email} - -
-
- {workspaces ? ( - <> - {workspaces.length > 0 ? ( - workspaces.map((workspace) => ( - - {({ active }) => ( - - )} - - )) - ) : ( -

No workspace found!

- )} - { - router.push("/create-workspace"); - }} - className="flex w-full items-center gap-2 rounded px-2 py-1 text-left text-xs hover:bg-gray-100" - > - - Create Workspace - - - ) : ( -
- - - - -
- )} -
-
- {userLinks(workspaceSlug as string).map((link, index) => ( - - - - {link.name} - - + +
{workspace.name}
+
+ + + + + )} + + )) + ) : ( +

No workspace found!

+ )} + { + router.push("/create-workspace"); + }} + className="flex w-full items-center gap-1 text-sm text-gray-600" + > + + Create Workspace - ))} +
+ ) : ( +
+ + + + +
+ )} +
+
+ {userLinks(workspaceSlug as string).map((link, index) => ( - Sign out + + {link.name} + -
+ ))} +
+
+ + Sign out +
From c7923f6d445201aad761d8db104ce5d0bb492f01 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 10 Mar 2023 16:03:49 +0530 Subject: [PATCH 006/256] feat: added load more button to github repos dropdown (#414) --- .../project/single-integration-card.tsx | 157 ++++++++++++++---- apps/app/services/api.service.ts | 9 + apps/app/services/project.service.ts | 9 +- apps/app/styles/globals.css | 56 ++++--- 4 files changed, 170 insertions(+), 61 deletions(-) diff --git a/apps/app/components/project/single-integration-card.tsx b/apps/app/components/project/single-integration-card.tsx index 42220647b..dc2630a2e 100644 --- a/apps/app/components/project/single-integration-card.tsx +++ b/apps/app/components/project/single-integration-card.tsx @@ -1,16 +1,22 @@ +import React, { useState } from "react"; + import Image from "next/image"; import useSWR, { mutate } from "swr"; +import useSWRInfinite from "swr/infinite"; +// headless ui +import { Combobox, Transition } from "@headlessui/react"; // services import projectService from "services/project.service"; // hooks import { useRouter } from "next/router"; import useToast from "hooks/use-toast"; -// ui -import { CustomSelect } from "components/ui"; // icons +import { CheckIcon, ChevronDownIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; import GithubLogo from "public/logos/github-square.png"; +// helpers +import { truncateText } from "helpers/string.helper"; // types import { IWorkspaceIntegrations } from "types"; // fetch-keys @@ -21,6 +27,8 @@ type Props = { }; export const SingleIntegration: React.FC = ({ integration }) => { + const [query, setQuery] = useState(""); + const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -38,11 +46,28 @@ export const SingleIntegration: React.FC = ({ integration }) => { : null ); - const { data: userRepositories } = useSWR("USER_REPOSITORIES", () => - workspaceSlug && integration - ? projectService.getGithubRepositories(workspaceSlug as any, integration.id) - : null - ); + const getKey = (pageIndex: number) => { + if (!workspaceSlug || !integration) return; + + return `${ + process.env.NEXT_PUBLIC_API_BASE_URL + }/api/workspaces/${workspaceSlug}/workspace-integrations/${ + integration.id + }/github-repositories/?page=${++pageIndex}`; + }; + + const fetchGithubRepos = async (url: string) => { + const data = await projectService.getGithubRepositories(url); + + return data; + }; + + const { + data: paginatedData, + size, + setSize, + isValidating, + } = useSWRInfinite(getKey, fetchGithubRepos); const handleChange = (repo: any) => { if (!workspaceSlug || !projectId || !integration) return; @@ -82,6 +107,21 @@ export const SingleIntegration: React.FC = ({ integration }) => { }); }; + const userRepositories = (paginatedData ?? []).map((data) => data.repositories).flat(); + const totalCount = paginatedData && paginatedData.length > 0 ? paginatedData[0].total_count : 0; + + const options = + userRepositories.map((repo) => ({ + value: repo.id, + query: repo.full_name, + content:

{truncateText(repo.full_name, 25)}

, + })) ?? []; + + const filteredOptions = + query === "" + ? options + : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase())); + return ( <> {integration && ( @@ -97,42 +137,95 @@ export const SingleIntegration: React.FC = ({ integration }) => {

Select GitHub repository to enable sync.

- 0 ? `${syncedGithubRepository[0].repo_detail.owner}/${syncedGithubRepository[0].repo_detail.name}` : null } onChange={(val: string) => { - const repo = userRepositories?.repositories.find((repo) => repo.full_name === val); + const repo = userRepositories.find((repo) => repo.id === val); handleChange(repo); }} - label={ - syncedGithubRepository && syncedGithubRepository.length > 0 - ? `${syncedGithubRepository[0].repo_detail.owner}/${syncedGithubRepository[0].repo_detail.name}` - : "Select Repository" - } - input + className="relative flex-shrink-0 text-left" > - {userRepositories ? ( - userRepositories.repositories.length > 0 ? ( - userRepositories.repositories.map((repo) => ( - - <>{repo.full_name} - - )) - ) : ( -

No repositories found

- ) - ) : ( -

Loading repositories...

+ {({ open }: any) => ( + <> + + {syncedGithubRepository && syncedGithubRepository.length > 0 + ? `${syncedGithubRepository[0].repo_detail.owner}/${syncedGithubRepository[0].repo_detail.name}` + : "Select Repository"} + + + + +
+ + setQuery(e.target.value)} + placeholder="Type to search..." + displayValue={(assigned: any) => assigned?.name} + /> +
+
+

+ {options.length} of {totalCount} repositories +

+ {paginatedData ? ( + filteredOptions.length > 0 ? ( + filteredOptions.map((option) => ( + + `${active || selected ? "bg-hover-gray" : ""} ${ + selected ? "font-medium" : "" + } flex cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5 text-gray-500` + } + > + {({ selected }) => ( + <> + {option.content} + {selected && } + + )} + + )) + ) : ( +

No matching results

+ ) + ) : ( +

Loading...

+ )} + {userRepositories && options.length < totalCount && ( + + )} +
+
+
+ )} -
+
)} diff --git a/apps/app/services/api.service.ts b/apps/app/services/api.service.ts index 112a87f9e..a625c0b37 100644 --- a/apps/app/services/api.service.ts +++ b/apps/app/services/api.service.ts @@ -39,6 +39,15 @@ abstract class APIService { }; } + getWithoutBase(url: string, config = {}): Promise { + return axios({ + method: "get", + url: url, + headers: this.getAccessToken() ? this.getHeaders() : {}, + ...config, + }); + } + get(url: string, config = {}): Promise { return axios({ method: "get", diff --git a/apps/app/services/project.service.ts b/apps/app/services/project.service.ts index cb280596e..ec07b7aa4 100644 --- a/apps/app/services/project.service.ts +++ b/apps/app/services/project.service.ts @@ -209,13 +209,8 @@ class ProjectServices extends APIService { }); } - async getGithubRepositories( - slug: string, - workspaceIntegrationId: string - ): Promise { - return this.get( - `/api/workspaces/${slug}/workspace-integrations/${workspaceIntegrationId}/github-repositories/` - ) + async getGithubRepositories(url: string): Promise { + return this.getWithoutBase(url) .then((response) => response?.data) .catch((error) => { throw error?.response?.data; diff --git a/apps/app/styles/globals.css b/apps/app/styles/globals.css index e6490a822..f4dd63e48 100644 --- a/apps/app/styles/globals.css +++ b/apps/app/styles/globals.css @@ -30,34 +30,46 @@ -webkit-font-smoothing: antialiased; } -/* Horizontal Scrollbar style */ -.horizontal-scroll-enable{ - overflow-x: scroll; -} +/* scrollbar style */ -.horizontal-scroll-enable::-webkit-scrollbar{ - display: block; - height: 10px; -} - -.horizontal-scroll-enable::-webkit-scrollbar-thumb{ - border-radius: 5px; - background-color: #9ca3af; -} -/* end Horizontal Scrollbar style */ -.scrollbar-enable::-webkit-scrollbar { - display: block; -} - -/* Scrollbar style */ ::-webkit-scrollbar { display: none; } -.no-scrollbar::-webkit-scrollbar { - display: none; +.horizontal-scroll-enable { + overflow-x: scroll; } -/* End scrollbar style */ + +.horizontal-scroll-enable::-webkit-scrollbar { + display: block; + height: 7px; +} + +.horizontal-scroll-enable::-webkit-scrollbar-track { + height: 7px; + background-color: #f5f5f5; +} + +.horizontal-scroll-enable::-webkit-scrollbar-thumb { + border-radius: 5px; + background-color: #9ca3af; +} + +.vertical-scroll-enable::-webkit-scrollbar { + display: block; + width: 5px; +} + +.vertical-scroll-enable::-webkit-scrollbar-track { + width: 5px; + background-color: #f5f5f5; +} + +.vertical-scroll-enable::-webkit-scrollbar-thumb { + border-radius: 5px; + background-color: #9ca3af; +} +/* end scrollbar style */ .tags-input-container { border: 2px solid #000; From 4a7f80712bb8729c7e32eb361cb41a8da6ac0a1d Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 10 Mar 2023 16:05:10 +0530 Subject: [PATCH 007/256] style: workspace sidebar (#417) --- apps/app/components/ui/avatar.tsx | 22 +++++- .../components/workspace/sidebar-dropdown.tsx | 74 ++++++++++--------- .../app/components/workspace/sidebar-menu.tsx | 44 ++++++----- apps/app/layouts/app-layout/app-sidebar.tsx | 6 +- 4 files changed, 80 insertions(+), 66 deletions(-) diff --git a/apps/app/components/ui/avatar.tsx b/apps/app/components/ui/avatar.tsx index 93ef49252..5bc94bfcf 100644 --- a/apps/app/components/ui/avatar.tsx +++ b/apps/app/components/ui/avatar.tsx @@ -15,15 +15,25 @@ import { WORKSPACE_MEMBERS } from "constants/fetch-keys"; type AvatarProps = { user?: Partial | Partial | IUser | IUserLite | undefined | null; index?: number; + height?: string; + width?: string; + fontSize?: string }; -export const Avatar: React.FC = ({ user, index }) => ( -
+export const Avatar: React.FC = ({ user, index, height="20px", width="20px", fontSize="12px" }) => ( +
{user && user.avatar && user.avatar !== "" ? (
= ({ user, index }) => ( />
) : ( -
+
{user?.first_name && user.first_name !== "" ? user.first_name.charAt(0) : user?.email?.charAt(0)} diff --git a/apps/app/components/workspace/sidebar-dropdown.tsx b/apps/app/components/workspace/sidebar-dropdown.tsx index 5a98ad12e..c50ecd258 100644 --- a/apps/app/components/workspace/sidebar-dropdown.tsx +++ b/apps/app/components/workspace/sidebar-dropdown.tsx @@ -12,7 +12,7 @@ import useWorkspaces from "hooks/use-workspaces"; import userService from "services/user.service"; import authenticationService from "services/authentication.service"; // components -import { Loader } from "components/ui"; +import { Avatar, Loader } from "components/ui"; // types import { IWorkspace } from "types"; @@ -72,44 +72,48 @@ export const WorkspaceSidebarDropdown = () => { return (
- - -
-
- {activeWorkspace?.logo && activeWorkspace.logo !== "" ? ( - Workspace Logo - ) : ( - activeWorkspace?.name?.charAt(0) ?? "..." + +
+ +
+
+ {activeWorkspace?.logo && activeWorkspace.logo !== "" ? ( + Workspace Logo + ) : ( + activeWorkspace?.name?.charAt(0) ?? "..." + )} +
+ + {!sidebarCollapse && ( +

+ {activeWorkspace?.name + ? activeWorkspace.name.length > 17 + ? `${activeWorkspace.name.substring(0, 17)}...` + : activeWorkspace.name + : "Loading..."} +

)}
- {!sidebarCollapse && ( -

- {activeWorkspace?.name - ? activeWorkspace.name.length > 17 - ? `${activeWorkspace.name.substring(0, 17)}...` - : activeWorkspace.name - : "Loading..."} -

- )} -
+ + {!sidebarCollapse && ( -
-
+ + +
+ +
+
+ )} - +
{ const { collapsed: sidebarCollapse } = useTheme(); return ( -
-
- {workspaceLinks(workspaceSlug as string).map((link, index) => ( - - + {workspaceLinks(workspaceSlug as string).map((link, index) => ( + + + - - - ))} -
+ link.href === router.asPath ? "text-gray-900" : "text-gray-600" + } h-5 w-5 flex-shrink-0 group-hover:text-gray-900`} + aria-hidden="true" + /> + {!sidebarCollapse && link.name} + + + ))}
); }; diff --git a/apps/app/layouts/app-layout/app-sidebar.tsx b/apps/app/layouts/app-layout/app-sidebar.tsx index ff64ea3ba..1c5f0b2b0 100644 --- a/apps/app/layouts/app-layout/app-sidebar.tsx +++ b/apps/app/layouts/app-layout/app-sidebar.tsx @@ -25,10 +25,8 @@ const Sidebar: React.FC = ({ toggleSidebar, setToggleSidebar }) => } flex h-full flex-col bg-white duration-300 md:relative`} >
-
-
- -
+
+ From 441cf39d2ce25af1c407341951a7efc937f2aba1 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Sat, 11 Mar 2023 17:23:23 +0530 Subject: [PATCH 008/256] refactor: global workspace form (#421) --- apps/app/components/onboarding/workspace.tsx | 145 ++--------------- .../workspace/create-workspace-form.tsx | 154 ++++++++++++++++++ ...eletion.tsx => delete-workspace-modal.tsx} | 4 +- apps/app/components/workspace/index.ts | 2 + .../pages/[workspaceSlug]/settings/index.tsx | 4 +- apps/app/pages/create-workspace.tsx | 151 +---------------- .../index.tsx => magic-sign-in.tsx} | 0 .../{onboarding/index.tsx => onboarding.tsx} | 0 8 files changed, 173 insertions(+), 287 deletions(-) create mode 100644 apps/app/components/workspace/create-workspace-form.tsx rename apps/app/components/workspace/{confirm-workspace-deletion.tsx => delete-workspace-modal.tsx} (98%) rename apps/app/pages/{magic-sign-in/index.tsx => magic-sign-in.tsx} (100%) rename apps/app/pages/{onboarding/index.tsx => onboarding.tsx} (100%) diff --git a/apps/app/components/onboarding/workspace.tsx b/apps/app/components/onboarding/workspace.tsx index b8011a2c0..cacb9966e 100644 --- a/apps/app/components/onboarding/workspace.tsx +++ b/apps/app/components/onboarding/workspace.tsx @@ -1,85 +1,33 @@ -import { useEffect, useState } from "react"; +import { useState } from "react"; import Image from "next/image"; import useSWR from "swr"; -import { Controller, useForm } from "react-hook-form"; - +// headless ui import { Tab } from "@headlessui/react"; - -// hooks -import useToast from "hooks/use-toast"; // services import workspaceService from "services/workspace.service"; -// ui -import { CustomSelect, Input } from "components/ui"; // types -import { IWorkspace, IWorkspaceMemberInvitation } from "types"; +import { IWorkspaceMemberInvitation } from "types"; // fetch-keys import { USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys"; // constants -import { COMPANY_SIZE } from "constants/workspace"; +import { CreateWorkspaceForm } from "components/workspace"; type Props = { setStep: React.Dispatch>; setWorkspace: React.Dispatch>; }; -const defaultValues: Partial = { - name: "", - slug: "", - company_size: null, -}; - const Workspace: React.FC = ({ setStep, setWorkspace }) => { - const [slugError, setSlugError] = useState(false); const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false); const [invitationsRespond, setInvitationsRespond] = useState([]); - const { setToastAlert } = useToast(); - const { data: invitations, mutate } = useSWR(USER_WORKSPACE_INVITATIONS, () => workspaceService.userWorkspaceInvitations() ); - const { - register, - handleSubmit, - control, - setValue, - reset, - formState: { errors, isSubmitting }, - } = useForm({ defaultValues }); - - const handleCreateWorkspace = async (formData: IWorkspace) => { - await workspaceService - .workspaceSlugCheck(formData.slug) - .then(async (res) => { - if (res.status === true) { - setSlugError(false); - await workspaceService - .createWorkspace(formData) - .then((res) => { - console.log(res); - setToastAlert({ - type: "success", - title: "Success!", - message: "Workspace created successfully.", - }); - setWorkspace(res); - setStep(3); - }) - .catch((err) => { - console.error(err); - }); - } else setSlugError(true); - }) - .catch((err) => { - console.error(err); - }); - }; - const handleInvitation = ( workspace_invitation: IWorkspaceMemberInvitation, action: "accepted" | "withdraw" @@ -109,10 +57,6 @@ const Workspace: React.FC = ({ setStep, setWorkspace }) => { }); }; - useEffect(() => { - reset(defaultValues); - }, [reset]); - return (
@@ -136,80 +80,13 @@ const Workspace: React.FC = ({ setStep, setWorkspace }) => { - -
-
-
-
- - setValue("slug", e.target.value.toLocaleLowerCase().replace(/ /g, "-")) - } - validations={{ - required: "Workspace name is required", - }} - error={errors.name} - /> -
-
-
Workspace slug
-
- {"https://app.plane.so/"} - -
- {slugError && ( - - Workspace URL is already taken! - - )} -
-
- ( - - {COMPANY_SIZE?.map((item) => ( - - {item.label} - - ))} - - )} - /> - {errors.company_size && ( - {errors.company_size.message} - )} -
-
-
-
- -
-
+ + { + setWorkspace(res); + setStep(3); + }} + />
diff --git a/apps/app/components/workspace/create-workspace-form.tsx b/apps/app/components/workspace/create-workspace-form.tsx new file mode 100644 index 000000000..4e6dc7511 --- /dev/null +++ b/apps/app/components/workspace/create-workspace-form.tsx @@ -0,0 +1,154 @@ +import { useEffect, useState } from "react"; + +import { mutate } from "swr"; + +// react-hook-form +import { Controller, useForm } from "react-hook-form"; +// services +import workspaceService from "services/workspace.service"; +// hooks +import useToast from "hooks/use-toast"; +// ui +import { CustomSelect, Input } from "components/ui"; +// types +import { IWorkspace } from "types"; +// fetch-keys +import { USER_WORKSPACES } from "constants/fetch-keys"; +// constants +import { COMPANY_SIZE } from "constants/workspace"; + +type Props = { + onSubmit: (res: IWorkspace) => void; +}; + +const defaultValues = { + name: "", + slug: "", + company_size: null, +}; + +export const CreateWorkspaceForm: React.FC = ({ onSubmit }) => { + const [slugError, setSlugError] = useState(false); + + const { setToastAlert } = useToast(); + + const { + register, + handleSubmit, + control, + setValue, + reset, + formState: { errors, isSubmitting }, + } = useForm({ defaultValues }); + + const handleCreateWorkspace = async (formData: IWorkspace) => { + await workspaceService + .workspaceSlugCheck(formData.slug) + .then(async (res) => { + if (res.status === true) { + setSlugError(false); + await workspaceService + .createWorkspace(formData) + .then((res) => { + setToastAlert({ + type: "success", + title: "Success!", + message: "Workspace created successfully.", + }); + mutate(USER_WORKSPACES, (prevData) => [res, ...(prevData ?? [])]); + onSubmit(res); + }) + .catch((err) => { + console.error(err); + }); + } else setSlugError(true); + }) + .catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "Some error occurred while creating workspace. Please try again.", + }); + }); + }; + + useEffect(() => { + reset(defaultValues); + }, [reset]); + + return ( +
+
+
+
+ + setValue("slug", e.target.value.toLocaleLowerCase().trim().replace(/ /g, "-")) + } + validations={{ + required: "Workspace name is required", + }} + error={errors.name} + /> +
+
+
Workspace slug
+
+ {"https://app.plane.so/"} + +
+ {slugError && ( + Workspace URL is already taken! + )} +
+
+
Company size
+ ( + + {COMPANY_SIZE?.map((item) => ( + + {item.label} + + ))} + + )} + /> + {errors.company_size && ( + {errors.company_size.message} + )} +
+
+
+
+ +
+
+ ); +}; diff --git a/apps/app/components/workspace/confirm-workspace-deletion.tsx b/apps/app/components/workspace/delete-workspace-modal.tsx similarity index 98% rename from apps/app/components/workspace/confirm-workspace-deletion.tsx rename to apps/app/components/workspace/delete-workspace-modal.tsx index f8a407615..63ae374c6 100644 --- a/apps/app/components/workspace/confirm-workspace-deletion.tsx +++ b/apps/app/components/workspace/delete-workspace-modal.tsx @@ -25,7 +25,7 @@ type Props = { onClose: () => void; }; -const ConfirmWorkspaceDeletion: React.FC = ({ isOpen, data, onClose }) => { +export const DeleteWorkspaceModal: React.FC = ({ isOpen, data, onClose }) => { const cancelButtonRef = useRef(null); const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [confirmProjectName, setConfirmProjectName] = useState(""); @@ -194,5 +194,3 @@ const ConfirmWorkspaceDeletion: React.FC = ({ isOpen, data, onClose }) => ); }; - -export default ConfirmWorkspaceDeletion; diff --git a/apps/app/components/workspace/index.ts b/apps/app/components/workspace/index.ts index 024389994..645aaed82 100644 --- a/apps/app/components/workspace/index.ts +++ b/apps/app/components/workspace/index.ts @@ -1,3 +1,5 @@ +export * from "./create-workspace-form"; +export * from "./delete-workspace-modal"; export * from "./sidebar-dropdown"; export * from "./sidebar-menu"; export * from "./help-section"; diff --git a/apps/app/pages/[workspaceSlug]/settings/index.tsx b/apps/app/pages/[workspaceSlug]/settings/index.tsx index ac38dd0ca..31ce42f1f 100644 --- a/apps/app/pages/[workspaceSlug]/settings/index.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/index.tsx @@ -20,7 +20,7 @@ import AppLayout from "layouts/app-layout"; import useToast from "hooks/use-toast"; // components import { ImageUploadModal } from "components/core"; -import ConfirmWorkspaceDeletion from "components/workspace/confirm-workspace-deletion"; +import { DeleteWorkspaceModal } from "components/workspace"; // ui import { Spinner, Button, Input, CustomSelect, OutlineButton } from "components/ui"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; @@ -120,7 +120,7 @@ const WorkspaceSettings: NextPage = (props) => { }} value={watch("logo")} /> - { setIsOpen(false); diff --git a/apps/app/pages/create-workspace.tsx b/apps/app/pages/create-workspace.tsx index 0af942a06..2f1facb22 100644 --- a/apps/app/pages/create-workspace.tsx +++ b/apps/app/pages/create-workspace.tsx @@ -1,92 +1,22 @@ -import React, { useEffect, useState } from "react"; +import React from "react"; import { useRouter } from "next/router"; import Image from "next/image"; -import { mutate } from "swr"; - -// react-hook-form -import { Controller, useForm } from "react-hook-form"; -// services -import workspaceService from "services/workspace.service"; -// hooks -import useToast from "hooks/use-toast"; // constants import { requiredAuth } from "lib/auth"; // layouts import DefaultLayout from "layouts/default-layout"; -// ui -import { CustomSelect, Input } from "components/ui"; // images import Logo from "public/onboarding/logo.svg"; // types -import type { IWorkspace } from "types"; import type { NextPage, NextPageContext } from "next"; -// fetch-keys -import { USER_WORKSPACES } from "constants/fetch-keys"; // constants -import { COMPANY_SIZE } from "constants/workspace"; - -const defaultValues = { - name: "", - slug: "", - company_size: null, -}; +import { CreateWorkspaceForm } from "components/workspace"; const CreateWorkspace: NextPage = () => { - const [slugError, setSlugError] = useState(false); - const router = useRouter(); - const { setToastAlert } = useToast(); - - const { - register, - handleSubmit, - control, - setError, - setValue, - reset, - formState: { errors, isSubmitting }, - } = useForm({ defaultValues }); - - const onSubmit = async (formData: IWorkspace) => { - await workspaceService - .workspaceSlugCheck(formData.slug) - .then(async (res) => { - if (res.status === true) { - setSlugError(false); - await workspaceService - .createWorkspace(formData) - .then((res) => { - setToastAlert({ - type: "success", - title: "Success!", - message: "Workspace created successfully.", - }); - mutate(USER_WORKSPACES, (prevData) => [res, ...(prevData ?? [])]); - router.push(`/${formData.slug}`); - }) - .catch((err) => { - console.error(err); - }); - } else setSlugError(true); - }) - .catch((err) => { - Object.keys(err).map((key) => { - const errorMessage = err?.[key]; - if (!errorMessage) return; - setError(key as keyof IWorkspace, { - message: Array.isArray(errorMessage) ? errorMessage.join(", ") : errorMessage, - }); - }); - }); - }; - - useEffect(() => { - reset(defaultValues); - }, [reset]); - return (
@@ -96,82 +26,7 @@ const CreateWorkspace: NextPage = () => {
-
-
-
-
- - setValue( - "slug", - e.target.value.toLocaleLowerCase().trim().replace(/ /g, "-") - ) - } - validations={{ - required: "Workspace name is required", - }} - error={errors.name} - /> -
-
-
Workspace slug
-
- {"https://app.plane.so/"} - -
- {slugError && ( - - Workspace URL is already taken! - - )} -
-
- ( - - {COMPANY_SIZE?.map((item) => ( - - {item.label} - - ))} - - )} - /> - {errors.company_size && ( - {errors.company_size.message} - )} -
-
-
-
- -
-
+ router.push(`/${res.slug}`)} />
diff --git a/apps/app/pages/magic-sign-in/index.tsx b/apps/app/pages/magic-sign-in.tsx similarity index 100% rename from apps/app/pages/magic-sign-in/index.tsx rename to apps/app/pages/magic-sign-in.tsx diff --git a/apps/app/pages/onboarding/index.tsx b/apps/app/pages/onboarding.tsx similarity index 100% rename from apps/app/pages/onboarding/index.tsx rename to apps/app/pages/onboarding.tsx From 7744d9b69a2de633b7e3f58e6d2ba46e1430a241 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 11 Mar 2023 23:49:02 +0530 Subject: [PATCH 009/256] feat: new issue templates --- .github/ISSUE_TEMPLATE/--bug-report.yaml | 56 +++++++++++++++++++ .github/ISSUE_TEMPLATE/--feature-request.yaml | 28 ++++++++++ .github/ISSUE_TEMPLATE/config.yaml | 6 ++ 3 files changed, 90 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/--bug-report.yaml create mode 100644 .github/ISSUE_TEMPLATE/--feature-request.yaml create mode 100644 .github/ISSUE_TEMPLATE/config.yaml diff --git a/.github/ISSUE_TEMPLATE/--bug-report.yaml b/.github/ISSUE_TEMPLATE/--bug-report.yaml new file mode 100644 index 000000000..4e672326c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/--bug-report.yaml @@ -0,0 +1,56 @@ +name: Bug report +description: Create a bug report to help us improve Plane +title: "[bug]: " +labels: [bug, need testing] +body: +- type: markdown + attributes: + value: | + Thank you for taking the time to fill out this bug report. +- type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered + options: + - label: I have searched the existing issues + required: true +- type: textarea + attributes: + label: Current behavior + description: A concise description of what you're experiencing and what you expect + placeholder: | + When I do , happens and I see the error message attached below: + ```...``` + What I expect is + validations: + required: true +- type: textarea + attributes: + label: Steps to reproduce + description: Add steps to reproduce this behaviour, include console or network logs and screenshots + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + validations: + required: true +- type: dropdown + id: env + attributes: + label: Environment + options: + - Production + - Deploy preview + validations: + required: true +- type: dropdown + id: version + attributes: + label: Version + options: + - Cloud + - Self-hosted + - Local + validations: + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/--feature-request.yaml b/.github/ISSUE_TEMPLATE/--feature-request.yaml new file mode 100644 index 000000000..b7ba11679 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/--feature-request.yaml @@ -0,0 +1,28 @@ +name: Feature request +description: Suggest a feature to improve Plane +title: "[feature]: " +labels: [feature] +body: +- type: markdown + attributes: + value: | + Thank you for taking the time to request a feature for Plane +- type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue related to this feature request already exists + options: + - label: I have searched the existing issues + required: true +- type: textarea + attributes: + label: Summary + description: One paragraph description of the feature + validations: + required: true +- type: textarea + attributes: + label: Why should this be worked on? + description: A concise description of the problems or use cases for this feature request + validations: + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yaml new file mode 100644 index 000000000..29c267831 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yaml @@ -0,0 +1,6 @@ +contact_links: + - name: Help and support + about: Reach out to us on our Discord server or GitHub discussions. + - name: Dedicated support + url: mailto:support@plane.so + about: Write to us if you'd like dedicated support using Plane From 6de6522a41154866ae7b3272d6ab9e86eea5aeaa Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Sat, 11 Mar 2023 23:51:06 +0530 Subject: [PATCH 010/256] chore: permissions for api endpoints (#419) --- apiserver/plane/api/views/cycle.py | 26 +++++++++++++++++++ apiserver/plane/api/views/integration/base.py | 24 ++++++++++++++++- .../plane/api/views/integration/github.py | 18 +++++++++++++ apiserver/plane/api/views/module.py | 5 ++++ 4 files changed, 72 insertions(+), 1 deletion(-) diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index 9a1f40a6d..899c2a956 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -263,6 +263,11 @@ class CycleIssueViewSet(BaseViewSet): class CycleDateCheckEndpoint(BaseAPIView): + + permission_classes = [ + ProjectEntityPermission, + ] + def post(self, request, slug, project_id): try: start_date = request.data.get("start_date") @@ -294,6 +299,11 @@ class CycleDateCheckEndpoint(BaseAPIView): class CurrentUpcomingCyclesEndpoint(BaseAPIView): + + permission_classes = [ + ProjectEntityPermission, + ] + def get(self, request, slug, project_id): try: subquery = CycleFavorite.objects.filter( @@ -332,6 +342,12 @@ class CurrentUpcomingCyclesEndpoint(BaseAPIView): class CompletedCyclesEndpoint(BaseAPIView): + + permission_classes = [ + ProjectEntityPermission, + ] + + def get(self, request, slug, project_id): try: subquery = CycleFavorite.objects.filter( @@ -364,6 +380,11 @@ class CompletedCyclesEndpoint(BaseAPIView): class DraftCyclesEndpoint(BaseAPIView): + + permission_classes = [ + ProjectEntityPermission, + ] + def get(self, request, slug, project_id): try: draft_cycles = Cycle.objects.filter( @@ -386,6 +407,11 @@ class DraftCyclesEndpoint(BaseAPIView): class CycleFavoriteViewSet(BaseViewSet): + + permission_classes = [ + ProjectEntityPermission, + ] + serializer_class = CycleFavoriteSerializer model = CycleFavorite diff --git a/apiserver/plane/api/views/integration/base.py b/apiserver/plane/api/views/integration/base.py index 4f15c347f..8312afa01 100644 --- a/apiserver/plane/api/views/integration/base.py +++ b/apiserver/plane/api/views/integration/base.py @@ -25,7 +25,7 @@ from plane.utils.integrations.github import ( get_github_metadata, delete_github_installation, ) - +from plane.api.permissions import WorkSpaceAdminPermission class IntegrationViewSet(BaseViewSet): serializer_class = IntegrationSerializer @@ -75,11 +75,33 @@ class IntegrationViewSet(BaseViewSet): status=status.HTTP_400_BAD_REQUEST, ) + def destroy(self, request, pk): + try: + integration = Integration.objects.get(pk=pk) + if integration.verified: + return Response( + {"error": "Verified integrations cannot be updated"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + integration.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + except Integration.DoesNotExist: + return Response( + {"error": "Integration Does not exist"}, + status=status.HTTP_404_NOT_FOUND, + ) + class WorkspaceIntegrationViewSet(BaseViewSet): serializer_class = WorkspaceIntegrationSerializer model = WorkspaceIntegration + permission_classes = [ + WorkSpaceAdminPermission, + ] + + def get_queryset(self): return ( super() diff --git a/apiserver/plane/api/views/integration/github.py b/apiserver/plane/api/views/integration/github.py index 5660e9d90..85e1efa7e 100644 --- a/apiserver/plane/api/views/integration/github.py +++ b/apiserver/plane/api/views/integration/github.py @@ -20,9 +20,14 @@ from plane.api.serializers import ( GithubCommentSyncSerializer, ) from plane.utils.integrations.github import get_github_repos +from plane.api.permissions import ProjectBasePermission, ProjectEntityPermission class GithubRepositoriesEndpoint(BaseAPIView): + permission_classes = [ + ProjectBasePermission, + ] + def get(self, request, slug, workspace_integration_id): try: page = request.GET.get("page", 1) @@ -44,6 +49,10 @@ class GithubRepositoriesEndpoint(BaseAPIView): class GithubRepositorySyncViewSet(BaseViewSet): + permission_classes = [ + ProjectBasePermission, + ] + serializer_class = GithubRepositorySyncSerializer model = GithubRepositorySync @@ -148,6 +157,10 @@ class GithubRepositorySyncViewSet(BaseViewSet): class GithubIssueSyncViewSet(BaseViewSet): + permission_classes = [ + ProjectEntityPermission, + ] + serializer_class = GithubIssueSyncSerializer model = GithubIssueSync @@ -159,6 +172,11 @@ class GithubIssueSyncViewSet(BaseViewSet): class GithubCommentSyncViewSet(BaseViewSet): + + permission_classes = [ + ProjectEntityPermission, + ] + serializer_class = GithubCommentSyncSerializer model = GithubCommentSync diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index ce74cfdff..3633859d5 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -307,6 +307,11 @@ class ModuleLinkViewSet(BaseViewSet): class ModuleFavoriteViewSet(BaseViewSet): + + permission_classes = [ + ProjectEntityPermission, + ] + serializer_class = ModuleFavoriteSerializer model = ModuleFavorite From aca0c251b8663a0de7ee7b2f7223c1d57a5706a7 Mon Sep 17 00:00:00 2001 From: Alejandro Pinar Ruiz Date: Sun, 12 Mar 2023 19:20:12 +0100 Subject: [PATCH 011/256] Github actions to push images --- .github/workflows/push-image-backend.yml | 55 +++++++++++++++++++++++ .github/workflows/push-image-frontend.yml | 53 ++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 .github/workflows/push-image-backend.yml create mode 100644 .github/workflows/push-image-frontend.yml diff --git a/.github/workflows/push-image-backend.yml b/.github/workflows/push-image-backend.yml new file mode 100644 index 000000000..5da13bd33 --- /dev/null +++ b/.github/workflows/push-image-backend.yml @@ -0,0 +1,55 @@ +name: Build Api Server Docker Image + +on: + push: + branches: + - 'main' + - 'develop' + - 'feature/**' + tags: + - '*' + +jobs: + build_push_backend: + name: Build Api Server Docker Image + runs-on: ubuntu-20.04 + permissions: + contents: read + packages: write + + steps: + - name: Check out the repo + uses: actions/checkout@v3.3.0 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2.1.0 + with: + platforms: linux/arm64,linux/amd64 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2.5.0 + + - name: Login to Github Container Registry + uses: docker/login-action@v2.1.0 + with: + registry: "ghcr.io" + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4.3.0 + with: + images: ghcr.io/${{ github.repository }}-backend + + - name: Build Api Server + uses: docker/build-push-action@v4.0.0 + with: + context: ./apiserver + file: ./apiserver/Dockerfile.api + platforms: linux/arm64,linux/amd64 + push: true + cache-from: type=gha + cache-to: type=gha + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/push-image-frontend.yml b/.github/workflows/push-image-frontend.yml new file mode 100644 index 000000000..93da76109 --- /dev/null +++ b/.github/workflows/push-image-frontend.yml @@ -0,0 +1,53 @@ +name: Build Frontend Docker Image + +on: + push: + branches: + - 'main' + - 'develop' + - 'feature/**' + +jobs: + build_push_frontend: + name: Build Frontend Docker Image + runs-on: ubuntu-20.04 + permissions: + contents: read + packages: write + + steps: + - name: Check out the repo + uses: actions/checkout@v3.3.0 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2.1.0 + with: + platforms: linux/arm64,linux/amd64 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2.5.0 + + - name: Login to Github Container Registry + uses: docker/login-action@v2.1.0 + with: + registry: "ghcr.io" + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4.3.0 + with: + images: ghcr.io/${{ github.repository }}-frontend + + - name: Build Frontend Server + uses: docker/build-push-action@v4.0.0 + with: + context: . + file: ./apps/app/Dockerfile.web + platforms: linux/arm64,linux/amd64 + push: true + cache-from: type=gha + cache-to: type=gha + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} From b2765d47b44fa621a5a43ab02d6e57a736d22cf4 Mon Sep 17 00:00:00 2001 From: Narayana Vadapalli Date: Mon, 13 Mar 2023 10:15:05 +0530 Subject: [PATCH 012/256] Update push-image-backend.yml --- .github/workflows/push-image-backend.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/push-image-backend.yml b/.github/workflows/push-image-backend.yml index 5da13bd33..abb833922 100644 --- a/.github/workflows/push-image-backend.yml +++ b/.github/workflows/push-image-backend.yml @@ -3,9 +3,8 @@ name: Build Api Server Docker Image on: push: branches: - - 'main' - 'develop' - - 'feature/**' + - 'master' tags: - '*' From fcd64de8af45acdefcdbb318f030fc2a652a777a Mon Sep 17 00:00:00 2001 From: Narayana Vadapalli Date: Mon, 13 Mar 2023 10:19:17 +0530 Subject: [PATCH 013/256] Update push-image-frontend.yml --- .github/workflows/push-image-frontend.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/push-image-frontend.yml b/.github/workflows/push-image-frontend.yml index 93da76109..b725c20ea 100644 --- a/.github/workflows/push-image-frontend.yml +++ b/.github/workflows/push-image-frontend.yml @@ -3,9 +3,8 @@ name: Build Frontend Docker Image on: push: branches: - - 'main' - 'develop' - - 'feature/**' + - 'master' jobs: build_push_frontend: From d5d64e09d4dd67ed43f36f5345c36ebb00e0b77b Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Mon, 13 Mar 2023 23:38:43 +0530 Subject: [PATCH 014/256] style: design (#430) * style: shortcut modal * style: feature setting module icon * style: delete issue modal * style: delete project modal * style: sidebar prompt for chart and other info * fix: create issue modal state icon * fix: workspace dropdown --- .../command-palette/shortcuts-modal.tsx | 58 ++++--- apps/app/components/cycles/sidebar.tsx | 50 ++++-- apps/app/components/icons/cmd-icon.tsx | 11 ++ apps/app/components/icons/index.ts | 1 + .../components/issues/delete-issue-modal.tsx | 86 +++++------ apps/app/components/issues/select/state.tsx | 9 +- apps/app/components/modules/sidebar.tsx | 49 ++++-- .../project/delete-project-modal.tsx | 145 +++++++++--------- .../components/workspace/sidebar-dropdown.tsx | 11 +- .../[projectId]/settings/features.tsx | 4 +- apps/app/public/mac-command.svg | 2 + 11 files changed, 246 insertions(+), 180 deletions(-) create mode 100644 apps/app/components/icons/cmd-icon.tsx create mode 100644 apps/app/public/mac-command.svg diff --git a/apps/app/components/command-palette/shortcuts-modal.tsx b/apps/app/components/command-palette/shortcuts-modal.tsx index 42e5770b9..0cdb051f6 100644 --- a/apps/app/components/command-palette/shortcuts-modal.tsx +++ b/apps/app/components/command-palette/shortcuts-modal.tsx @@ -3,6 +3,8 @@ import React, { useEffect, useState } from "react"; import { Dialog, Transition } from "@headlessui/react"; // icons import { XMarkIcon } from "@heroicons/react/20/solid"; +import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; +import { MacCommandIcon } from "components/icons"; // ui import { Input } from "components/ui"; @@ -15,7 +17,7 @@ const shortcuts = [ { title: "Navigation", shortcuts: [ - { keys: "Ctrl,/,Cmd,K", description: "To open navigator" }, + { keys: "Ctrl,K", description: "To open navigator" }, { keys: "↑", description: "Move up" }, { keys: "↓", description: "Move down" }, { keys: "←", description: "Move left" }, @@ -34,8 +36,8 @@ const shortcuts = [ { keys: "Delete", description: "To bulk delete issues" }, { keys: "H", description: "To open shortcuts guide" }, { - keys: "Ctrl,/,Cmd,Alt,C", - description: "To copy issue url when on issue detail page.", + keys: "Ctrl,Alt,C", + description: "To copy issue url when on issue detail page", }, ], }, @@ -100,13 +102,17 @@ export const ShortcutsModal: React.FC = ({ isOpen, setIsOpen }) => {
- setQuery(e.target.value)} - /> +
+ + setQuery(e.target.value)} + /> +
{query.trim().length > 0 ? ( @@ -114,14 +120,20 @@ export const ShortcutsModal: React.FC = ({ isOpen, setIsOpen }) => { filteredShortcuts.map((shortcut) => (
-
+

{shortcut.description}

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

{title}

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

{description}

-
+
{keys.split(",").map((key, index) => ( - - {key} - + {key === "Ctrl" ? ( + + + + ) : ( + + {key === "Ctrl" ? : key} + + )} ))}
diff --git a/apps/app/components/cycles/sidebar.tsx b/apps/app/components/cycles/sidebar.tsx index 8b5dc000d..674ac16c7 100644 --- a/apps/app/components/cycles/sidebar.tsx +++ b/apps/app/components/cycles/sidebar.tsx @@ -19,6 +19,7 @@ import { UserCircleIcon, ChevronDownIcon, DocumentIcon, + ExclamationCircleIcon, } from "@heroicons/react/24/outline"; // ui import { CustomMenu, Loader, ProgressBar } from "components/ui"; @@ -144,7 +145,7 @@ export const CycleDetailsSidebar: React.FC = ({ {cycle ? ( <>
-
+
= ({
-
+

{cycle.name}

@@ -314,7 +315,7 @@ export const CycleDetailsSidebar: React.FC = ({
-
+
{({ open }) => (
= ({ "" )}
- - - + {isStartValid && isEndValid ? ( + + + ) : ( +
+ + + Invalid date. Please enter valid date. + +
+ )}
@@ -383,7 +392,7 @@ export const CycleDetailsSidebar: React.FC = ({
-
+
{({ open }) => (
= ({ Other Information
- - + {issues.length > 0 ? ( + + + ) : ( +
+ + No issues found. Please add issue. +
+ )}
diff --git a/apps/app/components/icons/cmd-icon.tsx b/apps/app/components/icons/cmd-icon.tsx new file mode 100644 index 000000000..674b373cb --- /dev/null +++ b/apps/app/components/icons/cmd-icon.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import Image from "next/image"; + +import type { Props } from "./types"; +import CMDIcon from "public/mac-command.svg"; + +export const MacCommandIcon: React.FC = ({ width = "14", height = "14" }) => ( + CMDIcon +); + +export default MacCommandIcon; diff --git a/apps/app/components/icons/index.ts b/apps/app/components/icons/index.ts index 6ec881448..deeed5575 100644 --- a/apps/app/components/icons/index.ts +++ b/apps/app/components/icons/index.ts @@ -41,3 +41,4 @@ export * from "./assignment-clipboard-icon"; export * from "./tick-mark-icon"; export * from "./contrast-icon"; export * from "./people-group-icon"; +export * from "./cmd-icon"; \ No newline at end of file diff --git a/apps/app/components/issues/delete-issue-modal.tsx b/apps/app/components/issues/delete-issue-modal.tsx index 58df96f0d..cb915400f 100644 --- a/apps/app/components/issues/delete-issue-modal.tsx +++ b/apps/app/components/issues/delete-issue-modal.tsx @@ -122,51 +122,49 @@ export const DeleteIssueModal: React.FC = ({ isOpen, handleClose, data }) leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - -
-
-
-
-
- - Are you sure you want to delete {`"`} - {data?.project_detail.identifier}-{data?.sequence_id} - {data?.name}?{`"`} - -
-

- All of the data related to the issue will be permanently removed. This - action cannot be undone. -

-
-
+ +
+
+ + + +

Delete Issue

+
+
+ +

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

+
+
+ +
-
-
- -
diff --git a/apps/app/components/issues/select/state.tsx b/apps/app/components/issues/select/state.tsx index eca350fe6..eeb08bfa3 100644 --- a/apps/app/components/issues/select/state.tsx +++ b/apps/app/components/issues/select/state.tsx @@ -56,9 +56,12 @@ export const IssueStateSelect: React.FC = ({ setIsOpen, value, onChange, options={options} label={
- - {selectedOption && - getStateGroupIcon(selectedOption.group, "16", "16", selectedOption.color)} + {selectedOption ? ( + getStateGroupIcon(selectedOption.group, "16", "16", selectedOption.color) + ) : ( + + )} + {selectedOption?.name ?? "State"}
} diff --git a/apps/app/components/modules/sidebar.tsx b/apps/app/components/modules/sidebar.tsx index 72d705907..bc542850c 100644 --- a/apps/app/components/modules/sidebar.tsx +++ b/apps/app/components/modules/sidebar.tsx @@ -14,6 +14,7 @@ import { ChevronDownIcon, DocumentDuplicateIcon, DocumentIcon, + ExclamationCircleIcon, TrashIcon, } from "@heroicons/react/24/outline"; @@ -205,7 +206,7 @@ export const ModuleDetailsSidebar: React.FC = ({ {module ? ( <>
-
+
= ({
-
+

{module.name}

@@ -396,7 +397,7 @@ export const ModuleDetailsSidebar: React.FC = ({
-
+
{({ open }) => (
= ({ )}
- - + {isStartValid && isEndValid ? ( + + + ) : ( +
+ + + Invalid date. Please enter valid date. + +
+ )}
@@ -465,7 +475,7 @@ export const ModuleDetailsSidebar: React.FC = ({
-
+
{({ open }) => (
= ({ Other Information
- - + {issues.length > 0 ? ( + + + ) : ( +
+ + No issues found. Please add issue. +
+ )}
diff --git a/apps/app/components/project/delete-project-modal.tsx b/apps/app/components/project/delete-project-modal.tsx index 69c4edef5..d868d4af4 100644 --- a/apps/app/components/project/delete-project-modal.tsx +++ b/apps/app/components/project/delete-project-modal.tsx @@ -113,86 +113,81 @@ export const DeleteProjectModal: React.FC = (props leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - -
-
-
+ +
+
+
-
- - Delete Project - -
-

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

-
-
-
-

- Enter the project name{" "} - {selectedProject?.name} to - continue: -

- { - setConfirmProjectName(e.target.value); - }} - name="projectName" - /> -
-
-

- To confirm, type delete my project{" "} - below: -

- { - if (e.target.value === "delete my project") { - setConfirmDeleteMyProject(true); - } else { - setConfirmDeleteMyProject(false); - } - }} - name="typeDelete" - /> -
-
+ + +

Delete Project

+
+
+ +

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

+
+
+

+ Enter the project name{" "} + {selectedProject?.name} to continue: +

+ { + setConfirmProjectName(e.target.value); + }} + name="projectName" + /> +
+
+

+ To confirm, type delete my project below: +

+ { + if (e.target.value === "delete my project") { + setConfirmDeleteMyProject(true); + } else { + setConfirmDeleteMyProject(false); + } + }} + name="typeDelete" + /> +
+
+ +
-
-
- -
diff --git a/apps/app/components/workspace/sidebar-dropdown.tsx b/apps/app/components/workspace/sidebar-dropdown.tsx index c50ecd258..2471988e5 100644 --- a/apps/app/components/workspace/sidebar-dropdown.tsx +++ b/apps/app/components/workspace/sidebar-dropdown.tsx @@ -12,7 +12,9 @@ import useWorkspaces from "hooks/use-workspaces"; import userService from "services/user.service"; import authenticationService from "services/authentication.service"; // components -import { Avatar, Loader } from "components/ui"; +import { Avatar, Loader, Tooltip } from "components/ui"; +// helper +import { truncateText } from "helpers/string.helper"; // types import { IWorkspace } from "types"; @@ -96,7 +98,7 @@ export const WorkspaceSidebarDropdown = () => {

{activeWorkspace?.name ? activeWorkspace.name.length > 17 - ? `${activeWorkspace.name.substring(0, 17)}...` + ? `${activeWorkspace.name.substring(0, 15)}...` : activeWorkspace.name : "Loading..."}

@@ -159,7 +161,10 @@ export const WorkspaceSidebarDropdown = () => { activeWorkspace?.name?.charAt(0) ?? "N" )} -
{workspace.name}
+ +
+ {truncateText(workspace.name, 18)} +
= (props) => { const router = useRouter(); @@ -124,7 +124,7 @@ const FeaturesSettings: NextPage = (props) => {
- +

Modules

diff --git a/apps/app/public/mac-command.svg b/apps/app/public/mac-command.svg new file mode 100644 index 000000000..5cee45951 --- /dev/null +++ b/apps/app/public/mac-command.svg @@ -0,0 +1,2 @@ + +mac-command \ No newline at end of file From d6d51c2f4374188e248cc0f123c46114044ad3fb Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 14 Mar 2023 12:16:26 +0530 Subject: [PATCH 015/256] fix: modules sidebar lead select not working (#429) --- apps/app/components/modules/sidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/components/modules/sidebar.tsx b/apps/app/components/modules/sidebar.tsx index bc542850c..76f99f42e 100644 --- a/apps/app/components/modules/sidebar.tsx +++ b/apps/app/components/modules/sidebar.tsx @@ -359,7 +359,7 @@ export const ModuleDetailsSidebar: React.FC = ({ { - submitChanges({ lead: value }); + submitChanges({ lead: val }); }} /> )} From 3f5bbf336cb7e5b50dcfcc2f2771e49241d481a4 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 14 Mar 2023 12:16:38 +0530 Subject: [PATCH 016/256] fix: truncate text function (#431) --- apps/app/helpers/string.helper.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/app/helpers/string.helper.ts b/apps/app/helpers/string.helper.ts index f1c64bdf5..d3ae3ae26 100644 --- a/apps/app/helpers/string.helper.ts +++ b/apps/app/helpers/string.helper.ts @@ -4,8 +4,11 @@ export const replaceUnderscoreIfSnakeCase = (str: string) => str.replace(/_/g, " export const capitalizeFirstLetter = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); -export const truncateText = (str: string, length: number) => - str.length > length ? `${str.substring(0, length)}...` : str; +export const truncateText = (str: string, length: number) => { + if (!str || str === "") return ""; + + return str.length > length ? `${str.substring(0, length)}...` : str; +}; export const createSimilarString = (str: string) => { const shuffled = str From 0ba81a10f1d930c17df025a8d7efcf3c2f3270ae Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 14 Mar 2023 12:18:14 +0530 Subject: [PATCH 017/256] style: made the paddings and text sizes smaller (#433) --- .../components/core/board-view/all-boards.tsx | 2 +- .../core/board-view/board-header.tsx | 8 +-- .../core/board-view/single-issue.tsx | 4 +- .../core/list-view/single-issue.tsx | 10 +-- apps/app/components/cycles/cycles-list.tsx | 4 +- .../components/cycles/single-cycle-card.tsx | 39 ++++++----- .../components/issues/my-issues-list-item.tsx | 12 ++-- .../components/modules/single-module-card.tsx | 66 ++++++------------- apps/app/components/ui/custom-menu.tsx | 2 +- .../components/ui/custom-search-select.tsx | 4 +- apps/app/components/ui/custom-select.tsx | 2 +- .../ui/linear-progress-indicator.tsx | 4 +- apps/app/components/ui/tooltip.tsx | 6 +- .../components/workspace/sidebar-dropdown.tsx | 6 +- .../app/components/workspace/sidebar-menu.tsx | 2 +- apps/app/layouts/app-layout/index.tsx | 2 +- .../projects/[projectId]/modules/index.tsx | 3 +- 17 files changed, 75 insertions(+), 101 deletions(-) diff --git a/apps/app/components/core/board-view/all-boards.tsx b/apps/app/components/core/board-view/all-boards.tsx index 88d303dc7..55517a827 100644 --- a/apps/app/components/core/board-view/all-boards.tsx +++ b/apps/app/components/core/board-view/all-boards.tsx @@ -40,7 +40,7 @@ export const AllBoards: React.FC = ({ <> {groupedByIssues ? (

-
+
{Object.keys(groupedByIssues).map((singleGroup, index) => { const currentState = selectedGroup === "state_detail.name" diff --git a/apps/app/components/core/board-view/board-header.tsx b/apps/app/components/core/board-view/board-header.tsx index 84d421190..8237eb8f6 100644 --- a/apps/app/components/core/board-view/board-header.tsx +++ b/apps/app/components/core/board-view/board-header.tsx @@ -56,13 +56,13 @@ export const BoardHeader: React.FC = ({ >
- {currentState && getStateGroupIcon(currentState.group, "20", "20", bgColor)} + {currentState && getStateGroupIcon(currentState.group, "18", "18", bgColor)}

= ({ ? assignees : addSpaceIfCamelCase(groupTitle)}

- + {groupedByIssues[groupTitle].length}
diff --git a/apps/app/components/core/board-view/single-issue.tsx b/apps/app/components/core/board-view/single-issue.tsx index 795ebbf59..cfb3c4ad7 100644 --- a/apps/app/components/core/board-view/single-issue.tsx +++ b/apps/app/components/core/board-view/single-issue.tsx @@ -24,7 +24,7 @@ import { ViewStateSelect, } from "components/issues/view-select"; // ui -import { ContextMenu, CustomMenu, Tooltip } from "components/ui"; +import { ContextMenu, CustomMenu } from "components/ui"; // icons import { ClipboardDocumentCheckIcon, @@ -233,7 +233,7 @@ export const SingleBoardIssue: React.FC = ({ setContextMenuPosition({ x: e.pageX, y: e.pageY }); }} > -
+
{!isNotAllowed && (
{type && !isNotAllowed && ( diff --git a/apps/app/components/core/list-view/single-issue.tsx b/apps/app/components/core/list-view/single-issue.tsx index 1b6dc23cc..bfbec1db7 100644 --- a/apps/app/components/core/list-view/single-issue.tsx +++ b/apps/app/components/core/list-view/single-issue.tsx @@ -178,7 +178,7 @@ export const SingleListIssue: React.FC = ({
{ e.preventDefault(); setContextMenu(true); @@ -189,21 +189,21 @@ export const SingleListIssue: React.FC = ({ {properties.key && ( - + {issue.project_detail?.identifier}-{issue.sequence_id} )} - {truncateText(issue.name, 50)} + {truncateText(issue.name, 50)} -
+
{properties.priority && ( = ({ ))}
) : ( - + ) ) : ( - + )} diff --git a/apps/app/components/cycles/single-cycle-card.tsx b/apps/app/components/cycles/single-cycle-card.tsx index 01f4f1d00..23279d559 100644 --- a/apps/app/components/cycles/single-cycle-card.tsx +++ b/apps/app/components/cycles/single-cycle-card.tsx @@ -40,7 +40,6 @@ import { CYCLE_CURRENT_AND_UPCOMING_LIST, CYCLE_DRAFT_LIST, CYCLE_ISSUES, - CYCLE_LIST, } from "constants/fetch-keys"; type TSingleStatProps = { @@ -49,7 +48,7 @@ type TSingleStatProps = { handleDeleteCycle: () => void; }; -const stateGroupColours: { +const stateGroupColors: { [key: string]: string; } = { backlog: "#DEE2E6", @@ -231,23 +230,23 @@ export const SingleCycleCard: React.FC = (props) => { cycleIssues && cycleIssues.length > 0 ? (groupedIssues[group].length / cycleIssues.length) * 100 : 0, - color: stateGroupColours[group], + color: stateGroupColors[group], })); return ( -
+
-
+
- - - -

+ + + +

{truncateText(cycle.name, 75)}

- -
- + + + {cycle.is_favorite ? (

-
-
+
+
{cycle.owned_by.avatar && cycle.owned_by.avatar !== "" ? ( = (props) => { )} {cycle.owned_by.first_name}
-
+
+ + Filters + + } + > +

Status

+ {statesList?.map((state) => ( + {}}> + <>{state.name} + + ))} +

Members

+ {members?.map((member) => ( + {}}> + <> + {member.member.first_name && member.member.first_name !== "" + ? member.member.first_name + " " + member.member.last_name + : member.member.email} + + + ))} +

Labels

+ {issueLabels?.map((label) => ( + {}}> + <>{label.name} + + ))} +

Priority

+ {PRIORITIES?.map((priority) => ( + {}}> + {priority ?? "None"} + + ))} +
{({ open }) => ( <> diff --git a/apps/app/components/ui/index.ts b/apps/app/components/ui/index.ts index 7a9108850..94effbfd7 100644 --- a/apps/app/components/ui/index.ts +++ b/apps/app/components/ui/index.ts @@ -11,6 +11,7 @@ export * from "./empty-space"; export * from "./header-button"; export * from "./loader"; export * from "./multi-input"; +export * from "./multi-level-select"; export * from "./outline-button"; export * from "./progress-bar"; export * from "./spinner"; diff --git a/apps/app/components/core/multi-level-select.tsx b/apps/app/components/ui/multi-level-select.tsx similarity index 98% rename from apps/app/components/core/multi-level-select.tsx rename to apps/app/components/ui/multi-level-select.tsx index 36068dcc6..1662c58a8 100644 --- a/apps/app/components/core/multi-level-select.tsx +++ b/apps/app/components/ui/multi-level-select.tsx @@ -1,7 +1,8 @@ import React, { useState } from "react"; +// headless ui import { Listbox, Transition } from "@headlessui/react"; - +// icons import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/20/solid"; type TSelectOption = { @@ -23,9 +24,13 @@ type TMultipleSelectProps = { direction?: "left" | "right"; }; -export const MultiLevelSelect: React.FC = (props) => { - const { options, selected, setSelected, label, direction = "right" } = props; - +export const MultiLevelSelect: React.FC = ({ + options, + selected, + setSelected, + label, + direction = "right", +}) => { const [openChildFor, setOpenChildFor] = useState(null); return ( From 32d37ec45effc633d841392ea3c60e257b7a693b Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Wed, 15 Mar 2023 10:59:43 +0530 Subject: [PATCH 020/256] refactor: custom search select component (#440) --- .../components/ui/custom-search-select.tsx | 323 ++++++------------ 1 file changed, 109 insertions(+), 214 deletions(-) diff --git a/apps/app/components/ui/custom-search-select.tsx b/apps/app/components/ui/custom-search-select.tsx index 10e1091d1..a085e2e3b 100644 --- a/apps/app/components/ui/custom-search-select.tsx +++ b/apps/app/components/ui/custom-search-select.tsx @@ -1,10 +1,8 @@ import React, { useState } from "react"; - // headless ui import { Combobox, Transition } from "@headlessui/react"; // icons import { CheckIcon, ChevronDownIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; - type CustomSearchSelectProps = { value: any; onChange: any; @@ -25,7 +23,6 @@ type CustomSearchSelectProps = { multiple?: boolean; footerOption?: JSX.Element; }; - export const CustomSearchSelect = ({ label, textAlignment, @@ -49,221 +46,119 @@ export const CustomSearchSelect = ({ ? options : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase())); + const props: any = { + value, + onChange, + disabled, + }; + + if (multiple) props.multiple = true; + return ( - <> - {/* TODO: Improve this multiple logic */} - {multiple ? ( - - {({ open }: any) => ( - <> - {customButton ? ( - {customButton} - ) : ( - - {label} - {!noChevron && !disabled && ( - + + {({ open }: any) => ( + <> + {customButton ? ( + {customButton} + ) : ( + + {label} + {!noChevron && !disabled && ( + )} - - ) : ( - - {({ open }: any) => ( - <> - {customButton ? ( - {customButton} - ) : ( - - {label} - {!noChevron && !disabled && ( - - )} - - + +
+ + setQuery(e.target.value)} + placeholder="Type to search..." + displayValue={(assigned: any) => assigned?.name} + /> +
+
- -
- - setQuery(e.target.value)} - placeholder="Type to search..." - displayValue={(assigned: any) => assigned?.name} - /> -
-
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( - - `${active || selected ? "bg-hover-gray" : ""} ${ - selected ? "font-medium" : "" - } flex cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5 text-gray-500` - } - > - {({ selected }) => ( - <> - {option.content} - {selected && } - - )} - - )) - ) : ( -

No matching results

- ) - ) : ( -

Loading...

- )} -
- {footerOption} -
- - - )} - + {filteredOptions ? ( + filteredOptions.length > 0 ? ( + filteredOptions.map((option) => ( + + `${active || selected ? "bg-hover-gray" : ""} ${ + selected ? "font-medium" : "" + } flex cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5 text-gray-500` + } + > + {({ active, selected }) => ( + <> + {option.content} +
+ +
+ + )} +
+ )) + ) : ( +

No matching results

+ ) + ) : ( +

Loading...

+ )} +
+ {footerOption} +
+
+ )} - +
); }; From dbd6de0988bb2eb3cb7365a62638f29ea8e212c7 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Wed, 15 Mar 2023 11:00:05 +0530 Subject: [PATCH 021/256] feat: views template created (#439) --- .../components/views/delete-view-modal.tsx | 152 ++++++++++++++++++ apps/app/components/views/form.tsx | 101 ++++++++++++ apps/app/components/views/index.ts | 3 + apps/app/components/views/modal.tsx | 148 +++++++++++++++++ apps/app/constants/fetch-keys.ts | 10 +- .../projects/[projectId]/views/[viewId].tsx | 98 +++++++++++ apps/app/services/views.service.ts | 85 ++++++++++ apps/app/types/index.d.ts | 1 + apps/app/types/views.d.ts | 35 ++++ 9 files changed, 630 insertions(+), 3 deletions(-) create mode 100644 apps/app/components/views/delete-view-modal.tsx create mode 100644 apps/app/components/views/form.tsx create mode 100644 apps/app/components/views/index.ts create mode 100644 apps/app/components/views/modal.tsx create mode 100644 apps/app/pages/[workspaceSlug]/projects/[projectId]/views/[viewId].tsx create mode 100644 apps/app/services/views.service.ts create mode 100644 apps/app/types/views.d.ts diff --git a/apps/app/components/views/delete-view-modal.tsx b/apps/app/components/views/delete-view-modal.tsx new file mode 100644 index 000000000..a9688b4f5 --- /dev/null +++ b/apps/app/components/views/delete-view-modal.tsx @@ -0,0 +1,152 @@ +import React, { useRef, useState } from "react"; + +import { useRouter } from "next/router"; + +import { mutate } from "swr"; + +// headless ui +import { Dialog, Transition } from "@headlessui/react"; +// services +import viewsService from "services/views.service"; +// hooks +import useToast from "hooks/use-toast"; +// ui +import { Button } from "components/ui"; +// icons +import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; +// types +import type { IView } from "types"; +// fetch-keys +import { VIEWS_LIST } from "constants/fetch-keys"; + +type Props = { + isOpen: boolean; + setIsOpen: React.Dispatch>; + data?: IView; +}; + +export const DeleteViewModal: React.FC = ({ isOpen, setIsOpen, data }) => { + const [isDeleteLoading, setIsDeleteLoading] = useState(false); + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { setToastAlert } = useToast(); + + const cancelButtonRef = useRef(null); + + const handleClose = () => { + setIsOpen(false); + setIsDeleteLoading(false); + }; + + const handleDeletion = async () => { + setIsDeleteLoading(true); + + if (!workspaceSlug || !data) return; + await viewsService + .deleteView(projectId as string, data.id) + .then(() => { + mutate(VIEWS_LIST(projectId as string)); + router.push(`/${workspaceSlug}/projects/${projectId}/issues`); + handleClose(); + + setToastAlert({ + type: "success", + title: "Success!", + message: "View deleted successfully.", + }); + }) + .catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "View could not be deleted. Please try again.", + }); + setIsDeleteLoading(false); + }); + }; + + return ( + + + +
+ + +
+
+ + +
+
+
+
+
+ + Delete View + +
+

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

+
+
+
+
+
+ + +
+
+
+
+
+
+
+ ); +}; diff --git a/apps/app/components/views/form.tsx b/apps/app/components/views/form.tsx new file mode 100644 index 000000000..d331b17c3 --- /dev/null +++ b/apps/app/components/views/form.tsx @@ -0,0 +1,101 @@ +import { useEffect } from "react"; + +// react-hook-form +import { useForm } from "react-hook-form"; +// ui +import { Button, Input, TextArea } from "components/ui"; +// types +import { IView } from "types"; + +type Props = { + handleFormSubmit: (values: IView) => Promise; + handleClose: () => void; + status: boolean; + data?: IView; +}; + +const defaultValues: Partial = { + name: "", + description: "", +}; + +export const ViewForm: React.FC = ({ handleFormSubmit, handleClose, status, data }) => { + const { + register, + formState: { errors, isSubmitting }, + handleSubmit, + reset, + } = useForm({ + defaultValues, + }); + + const handleCreateUpdateView = async (formData: IView) => { + await handleFormSubmit(formData); + + reset({ + ...defaultValues, + }); + }; + + useEffect(() => { + reset({ + ...defaultValues, + ...data, + }); + }, [data, reset]); + + return ( +
+
+

+ {status ? "Update" : "Create"} View +

+
+
+ +
+
+