From 61875722e4241921024f47976e4cc7405f3b60f4 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Mon, 3 Apr 2023 11:41:46 +0530 Subject: [PATCH 01/29] chore: add auto generate description option to create issue modals (#672) --- apps/app/components/issues/form.tsx | 69 ++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index 800630c15..b320107ee 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -6,6 +6,10 @@ import { useRouter } from "next/router"; // react-hook-form import { Controller, useForm } from "react-hook-form"; +// services +import aiService from "services/ai.service"; +// hooks +import useToast from "hooks/use-toast"; // components import { GptAssistantModal } from "components/core"; import { @@ -83,10 +87,13 @@ export const IssueForm: FC = ({ const [parentIssueListModalOpen, setParentIssueListModalOpen] = useState(false); const [gptAssistantModal, setGptAssistantModal] = useState(false); + const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false); const router = useRouter(); const { workspaceSlug } = router.query; + const { setToastAlert } = useToast(); + const { register, formState: { errors, isSubmitting }, @@ -102,6 +109,8 @@ export const IssueForm: FC = ({ reValidateMode: "onChange", }); + const issueName = watch("name"); + const handleTitleChange = (e: ChangeEvent) => { const value = e.target.value; const similarIssue = issues?.find((i: IIssue) => cosineSimilarity(i.name, value) > 0.7); @@ -126,6 +135,44 @@ export const IssueForm: FC = ({ setValue("description_html", `${watch("description_html")}

${response}

`); }; + const handelAutoGenerateDescription = async () => { + if (!workspaceSlug || !projectId) return; + + setIAmFeelingLucky(true); + + aiService + .createGptTask(workspaceSlug as string, projectId as string, { + prompt: issueName, + task: "Generate a proper description for this issue in context of a project management software.", + }) + .then((res) => { + if (res.response === "") + setToastAlert({ + type: "error", + title: "Error!", + message: + "Issue title isn't informative enough to generate the description. Please try with a different title.", + }); + else handleAiAssistance(res.response_html); + }) + .catch((err) => { + if (err.status === 429) + setToastAlert({ + type: "error", + title: "Error!", + message: + "You have reached the maximum number of requests of 50 requests per month per user.", + }); + else + setToastAlert({ + type: "error", + title: "Error!", + message: "Some error occurred. Please try again.", + }); + }) + .finally(() => setIAmFeelingLucky(false)); + }; + useEffect(() => { setFocus("name"); @@ -245,10 +292,28 @@ export const IssueForm: FC = ({ )}
-
+
+ {issueName && issueName !== "" && ( + + )} diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index b320107ee..b37ba8db5 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -332,7 +332,7 @@ export const IssueForm: FC = ({ } onJSONChange={(jsonValue) => setValue("description", jsonValue)} onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} - placeholder="Description" + placeholder="Describe the issue..." /> )} /> diff --git a/apps/app/components/pages/single-page-block.tsx b/apps/app/components/pages/single-page-block.tsx index f138c39f8..6dd8b02bc 100644 --- a/apps/app/components/pages/single-page-block.tsx +++ b/apps/app/components/pages/single-page-block.tsx @@ -315,9 +315,9 @@ export const SinglePageBlock: React.FC = ({ block, projectDetails }) => { onJSONChange={(jsonValue) => setValue("description", jsonValue)} onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} placeholder="Block description..." - customClassName="border border-transparent" + customClassName="border border-transparent text-sm" noBorder - borderOnFocus + borderOnFocus={false} /> )} /> diff --git a/apps/app/components/rich-text-editor/index.tsx b/apps/app/components/rich-text-editor/index.tsx index 93ce1a60b..4c44650b9 100644 --- a/apps/app/components/rich-text-editor/index.tsx +++ b/apps/app/components/rich-text-editor/index.tsx @@ -185,25 +185,23 @@ const RemirrorRichTextEditor: FC = (props) => { }; return ( -
+
{ onBlur(jsonValue, htmlValue); }} > - {/* {(!value || value === "" || value?.content?.[0]?.content === undefined) && ( -

- {placeholder || "Enter text..."} -

- )} */} + {(!value || value === "" || value?.content?.[0]?.content === undefined) && placeholder && ( +

{placeholder}

+ )} {imageLoader && ( diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index fff14ef06..c12580afb 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -65,11 +65,11 @@ const SinglePage: NextPage = (props) => { workspaceSlug && projectId && pageId ? PAGE_DETAILS(pageId as string) : null, workspaceSlug && projectId ? () => - pagesService.getPageDetails( - workspaceSlug as string, - projectId as string, - pageId as string - ) + pagesService.getPageDetails( + workspaceSlug as string, + projectId as string, + pageId as string + ) : null ); @@ -77,11 +77,11 @@ const SinglePage: NextPage = (props) => { workspaceSlug && projectId && pageId ? PAGE_BLOCKS_LIST(pageId as string) : null, workspaceSlug && projectId ? () => - pagesService.listPageBlocks( - workspaceSlug as string, - projectId as string, - pageId as string - ) + pagesService.listPageBlocks( + workspaceSlug as string, + projectId as string, + pageId as string + ) : null ); @@ -272,8 +272,9 @@ const SinglePage: NextPage = (props) => { key={label.id} className="group flex items-center gap-1 rounded-2xl border px-2 py-0.5 text-xs" style={{ - backgroundColor: `${label?.color && label.color !== "" ? label.color : "#000000" - }20`, + backgroundColor: `${ + label?.color && label.color !== "" ? label.color : "#000000" + }20`, }} > = (props) => { <> {watch("color") && watch("color") !== "" ? ( = (props) => { )} +

setCreateBlockForm(true)}> + {block.name} +

+
+
+ {block.issue && block.sync && ( +
+ {isSyncing ? ( + + ) : ( + + )} + {isSyncing ? "Syncing..." : "Synced"} +
+ )} + {block.issue && ( + + + + {projectDetails?.identifier}-{block.issue_detail?.sequence_id} + + + )} + + + } noBorder noChevron> + {block.issue ? ( + <> + + <>Turn sync {block.sync ? "off" : "on"} + + + Copy issue link + + + ) : ( + + Push into issues + + )} + + Delete block + + +
+
+
setCreateBlockForm(true)} + > + ( + + )} + /> + setGptAssistantModal(false)} + inset="top-2 left-0" + content={block.description_stripped} + htmlContent={block.description_html} + onResponse={handleAiAssistance} + projectId={projectId as string} + /> +
)} - {block.issue && ( - - - - {projectDetails?.identifier}-{block.issue_detail?.sequence_id} - - - )} - - } noBorder noChevron> - {block.issue ? ( - <> - - <>Turn sync {block.sync ? "off" : "on"} - - Copy issue link - - ) : ( - <> - - Push into issues - - {/* - Edit and push into issues - */} - - )} - Delete block - -
-
-
- ( - setValue("description", jsonValue)} - onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} - placeholder="Block description..." - customClassName="border border-transparent text-sm" - noBorder - borderOnFocus={false} - /> - )} - /> - setGptAssistantModal(false)} - inset="top-2 left-0" - content={block.description_stripped} - htmlContent={block.description_html} - onResponse={handleAiAssistance} - projectId={projectId as string} - /> -
- + + )} + ); }; diff --git a/apps/app/components/pages/single-page-detailed-item.tsx b/apps/app/components/pages/single-page-detailed-item.tsx index 47156ad92..b07b87f89 100644 --- a/apps/app/components/pages/single-page-detailed-item.tsx +++ b/apps/app/components/pages/single-page-detailed-item.tsx @@ -5,9 +5,15 @@ import { useRouter } from "next/router"; import dynamic from "next/dynamic"; // ui -import { CustomMenu, Loader } from "components/ui"; +import { CustomMenu, Loader, Tooltip } from "components/ui"; // icons -import { PencilIcon, StarIcon, TrashIcon } from "@heroicons/react/24/outline"; +import { + LockClosedIcon, + LockOpenIcon, + PencilIcon, + StarIcon, + TrashIcon, +} from "@heroicons/react/24/outline"; // helpers import { truncateText } from "helpers/string.helper"; import { renderShortTime } from "helpers/date-time.helper"; @@ -20,6 +26,7 @@ type TSingleStatProps = { handleDeletePage: () => void; handleAddToFavorites: () => void; handleRemoveFromFavorites: () => void; + partialUpdatePage: (page: IPage, formData: Partial) => void; }; const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { @@ -37,90 +44,139 @@ export const SinglePageDetailedItem: React.FC = ({ handleDeletePage, handleAddToFavorites, handleRemoveFromFavorites, + partialUpdatePage, }) => { const router = useRouter(); const { workspaceSlug, projectId } = router.query; return ( -
-
-
- - + + +
+
+

{truncateText(page.name, 75)}

-
- - {page.label_details.length > 0 && - page.label_details.map((label) => ( -
- - {label.name} -
- ))} -
+ {page.label_details.length > 0 && + page.label_details.map((label) => ( +
+ + {label.name} +
+ ))} +
-
-

{renderShortTime(page.updated_at)}

- {page.is_favorite ? ( - - ) : ( - - )} - - - - - Edit Page - - - - - - Delete Page - - - +
+

{renderShortTime(page.updated_at)}

+ {page.is_favorite ? ( + + ) : ( + + )} + + + + + { + e.preventDefault(); + e.stopPropagation(); + handleEditPage(); + }} + > + + + Edit Page + + + { + e.preventDefault(); + e.stopPropagation(); + handleDeletePage(); + }} + > + + + Delete Page + + + +
+
+
+
+ {page.blocks.length > 0 ? ( + + ) : null} +
+
-
-
-
- {page.blocks.length > 0 ? ( - - ) : null} -
-
-
+ + ); }; diff --git a/apps/app/components/pages/single-page-list-item.tsx b/apps/app/components/pages/single-page-list-item.tsx index e4cc69147..db3d1d95f 100644 --- a/apps/app/components/pages/single-page-list-item.tsx +++ b/apps/app/components/pages/single-page-list-item.tsx @@ -6,7 +6,14 @@ import { useRouter } from "next/router"; // ui import { CustomMenu, Tooltip } from "components/ui"; // icons -import { DocumentTextIcon, PencilIcon, StarIcon, TrashIcon } from "@heroicons/react/24/outline"; +import { + DocumentTextIcon, + LockClosedIcon, + LockOpenIcon, + PencilIcon, + StarIcon, + TrashIcon, +} from "@heroicons/react/24/outline"; // helpers import { truncateText } from "helpers/string.helper"; import { renderShortDate, renderShortTime } from "helpers/date-time.helper"; @@ -19,6 +26,7 @@ type TSingleStatProps = { handleDeletePage: () => void; handleAddToFavorites: () => void; handleRemoveFromFavorites: () => void; + partialUpdatePage: (page: IPage, formData: Partial) => void; }; export const SinglePageListItem: React.FC = ({ @@ -27,6 +35,7 @@ export const SinglePageListItem: React.FC = ({ handleDeletePage, handleAddToFavorites, handleRemoveFromFavorites, + partialUpdatePage, }) => { const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -94,6 +103,29 @@ export const SinglePageListItem: React.FC = ({ )} + + + { diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index c12580afb..35097094b 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -10,6 +10,9 @@ import { useForm } from "react-hook-form"; import { Popover, Transition } from "@headlessui/react"; // react-color import { TwitterPicker } from "react-color"; +// react-beautiful-dnd +import { DragDropContext, DropResult } from "react-beautiful-dnd"; +import StrictModeDroppable from "components/dnd/StrictModeDroppable"; // lib import { requiredAdmin, requiredAuth } from "lib/auth"; // services @@ -21,16 +24,24 @@ import useToast from "hooks/use-toast"; // layouts import AppLayout from "layouts/app-layout"; // components -import { SinglePageBlock } from "components/pages"; +import { CreateUpdateBlockInline, SinglePageBlock } from "components/pages"; // ui import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { CustomSearchSelect, Loader, PrimaryButton, TextArea, Tooltip } from "components/ui"; // icons -import { ArrowLeftIcon, PlusIcon, ShareIcon, StarIcon } from "@heroicons/react/24/outline"; +import { + ArrowLeftIcon, + LockClosedIcon, + LockOpenIcon, + PlusIcon, + ShareIcon, + StarIcon, +} from "@heroicons/react/24/outline"; import { ColorPalletteIcon } from "components/icons"; // helpers import { renderShortTime } from "helpers/date-time.helper"; import { copyTextToClipboard } from "helpers/string.helper"; +import { orderArrayBy } from "helpers/array.helper"; // types import type { NextPage, GetServerSidePropsContext } from "next"; import { IIssueLabels, IPage, IPageBlock, UserAuth } from "types"; @@ -44,13 +55,14 @@ import { const SinglePage: NextPage = (props) => { const [isAddingBlock, setIsAddingBlock] = useState(false); + const [createBlockForm, setCreateBlockForm] = useState(false); const router = useRouter(); const { workspaceSlug, projectId, pageId } = router.query; const { setToastAlert } = useToast(); - const { handleSubmit, reset, watch, setValue, control } = useForm({ + const { handleSubmit, reset, watch, setValue } = useForm({ defaultValues: { name: "" }, }); @@ -131,34 +143,6 @@ const SinglePage: NextPage = (props) => { }); }; - const createPageBlock = async () => { - if (!workspaceSlug || !projectId || !pageId) return; - - setIsAddingBlock(true); - - await pagesService - .createPageBlock(workspaceSlug as string, projectId as string, pageId as string, { - name: "New block", - }) - .then((res) => { - mutate( - PAGE_BLOCKS_LIST(pageId as string), - (prevData) => [...(prevData as IPageBlock[]), res], - false - ); - }) - .catch(() => { - setToastAlert({ - type: "error", - title: "Error!", - message: "Page could not be created. Please try again.", - }); - }) - .finally(() => { - setIsAddingBlock(false); - }); - }; - const handleAddToFavorites = () => { if (!workspaceSlug || !projectId || !pageId) return; @@ -195,6 +179,50 @@ const SinglePage: NextPage = (props) => { ); }; + const handleOnDragEnd = (result: DropResult) => { + if (!result.destination || !workspaceSlug || !projectId || !pageId || !pageBlocks) return; + + const { source, destination } = result; + + let newSortOrder = pageBlocks.find((p) => p.id === result.draggableId)?.sort_order ?? 65535; + + if (destination.index === 0) newSortOrder = pageBlocks[0].sort_order - 10000; + else if (destination.index === pageBlocks.length - 1) + newSortOrder = pageBlocks[pageBlocks.length - 1].sort_order + 10000; + else { + if (destination.index > source.index) + newSortOrder = + (pageBlocks[destination.index].sort_order + + pageBlocks[destination.index + 1].sort_order) / + 2; + else if (destination.index < source.index) + newSortOrder = + (pageBlocks[destination.index - 1].sort_order + + pageBlocks[destination.index].sort_order) / + 2; + } + + const newBlocksList = pageBlocks.map((p) => ({ + ...p, + sort_order: p.id === result.draggableId ? newSortOrder : p.sort_order, + })); + mutate( + PAGE_BLOCKS_LIST(pageId as string), + orderArrayBy(newBlocksList, "sort_order", "ascending"), + false + ); + + pagesService.patchPageBlock( + workspaceSlug as string, + projectId as string, + pageId as string, + result.draggableId, + { + sort_order: newSortOrder, + } + ); + }; + const handleCopyText = () => { const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; @@ -354,7 +382,7 @@ const SinglePage: NextPage = (props) => { }} /> ) : ( - + )} @@ -378,6 +406,19 @@ const SinglePage: NextPage = (props) => { )}
+ {pageDetails.access ? ( + + ) : ( + + )} {pageDetails.is_favorite ? ( + + {!createBlockForm && ( + + )} + {createBlockForm && ( + setCreateBlockForm(false)} /> + )} ) : ( From 62cca1c7cdff10d61869070a1cdca69b77f13575 Mon Sep 17 00:00:00 2001 From: Kunal Vishwakarma <116634168+kunalv17@users.noreply.github.com> Date: Mon, 3 Apr 2023 23:51:46 +0530 Subject: [PATCH 14/29] feat: issue redirect (#685) * feat: open issue in new tab for list and kanban view * fix: used a tag --- apps/app/components/core/board-view/single-issue.tsx | 11 +++++++++++ apps/app/components/core/list-view/single-issue.tsx | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/apps/app/components/core/board-view/single-issue.tsx b/apps/app/components/core/board-view/single-issue.tsx index 4f92e3040..3b28ef428 100644 --- a/apps/app/components/core/board-view/single-issue.tsx +++ b/apps/app/components/core/board-view/single-issue.tsx @@ -33,6 +33,8 @@ import { PencilIcon, TrashIcon, XMarkIcon, + ArrowTopRightOnSquareIcon, + } from "@heroicons/react/24/outline"; // helpers import { handleIssuesMutation } from "constants/issue"; @@ -211,6 +213,15 @@ export const SingleBoardIssue: React.FC = ({ Copy issue link + + + Open issue in new tab + +
= ({ Copy issue link + + + Open issue in new tab + +
Date: Mon, 3 Apr 2023 23:52:39 +0530 Subject: [PATCH 15/29] fix: cmdk hover style (#690) --- apps/app/styles/command-pallette.css | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/app/styles/command-pallette.css b/apps/app/styles/command-pallette.css index c28486f45..86fcb51da 100644 --- a/apps/app/styles/command-pallette.css +++ b/apps/app/styles/command-pallette.css @@ -34,7 +34,6 @@ background-color: rgb(229 231 235); } - -[aria-selected="true"] { +[cmdk-item][aria-selected="true"] { background-color: rgb(229 231 235); -} \ No newline at end of file +} From a1f0f43992616dafcb2305be5f265bafcf7c2a2a Mon Sep 17 00:00:00 2001 From: Kunal Vishwakarma <116634168+kunalv17@users.noreply.github.com> Date: Mon, 3 Apr 2023 23:54:26 +0530 Subject: [PATCH 16/29] feat: icon picker (#689) * feat: icon picker * style: icon picker modal --------- Co-authored-by: Dakshesh Jain --- .../components/emoji-icon-picker/icons.json | 607 ++++++++++++++++++ .../components/emoji-icon-picker/index.tsx | 128 +++- .../components/emoji-icon-picker/types.d.ts | 2 + apps/app/styles/globals.css | 5 + 4 files changed, 720 insertions(+), 22 deletions(-) create mode 100644 apps/app/components/emoji-icon-picker/icons.json diff --git a/apps/app/components/emoji-icon-picker/icons.json b/apps/app/components/emoji-icon-picker/icons.json new file mode 100644 index 000000000..f844f22d4 --- /dev/null +++ b/apps/app/components/emoji-icon-picker/icons.json @@ -0,0 +1,607 @@ +{ + "material_rounded": [ + { + "name": "search" + }, + { + "name": "home" + }, + { + "name": "menu" + }, + { + "name": "close" + }, + { + "name": "settings" + }, + { + "name": "done" + }, + { + "name": "check_circle" + }, + { + "name": "favorite" + }, + { + "name": "add" + }, + { + "name": "delete" + }, + { + "name": "arrow_back" + }, + { + "name": "star" + }, + { + "name": "logout" + }, + { + "name": "add_circle" + }, + { + "name": "cancel" + }, + { + "name": "arrow_drop_down" + }, + { + "name": "more_vert" + }, + { + "name": "check" + }, + { + "name": "check_box" + }, + { + "name": "toggle_on" + }, + { + "name": "open_in_new" + }, + { + "name": "refresh" + }, + { + "name": "login" + }, + { + "name": "radio_button_unchecked" + }, + { + "name": "more_horiz" + }, + { + "name": "apps" + }, + { + "name": "radio_button_checked" + }, + { + "name": "download" + }, + { + "name": "remove" + }, + { + "name": "toggle_off" + }, + { + "name": "bolt" + }, + { + "name": "arrow_upward" + }, + { + "name": "filter_list" + }, + { + "name": "delete_forever" + }, + { + "name": "autorenew" + }, + { + "name": "key" + }, + { + "name": "sort" + }, + { + "name": "sync" + }, + { + "name": "add_box" + }, + { + "name": "block" + }, + { + "name": "restart_alt" + }, + { + "name": "menu_open" + }, + { + "name": "shopping_cart_checkout" + }, + { + "name": "expand_circle_down" + }, + { + "name": "backspace" + }, + { + "name": "undo" + }, + { + "name": "done_all" + }, + { + "name": "do_not_disturb_on" + }, + { + "name": "open_in_full" + }, + { + "name": "double_arrow" + }, + { + "name": "sync_alt" + }, + { + "name": "zoom_in" + }, + { + "name": "done_outline" + }, + { + "name": "drag_indicator" + }, + { + "name": "fullscreen" + }, + { + "name": "star_half" + }, + { + "name": "settings_accessibility" + }, + { + "name": "reply" + }, + { + "name": "exit_to_app" + }, + { + "name": "unfold_more" + }, + { + "name": "library_add" + }, + { + "name": "cached" + }, + { + "name": "select_check_box" + }, + { + "name": "terminal" + }, + { + "name": "change_circle" + }, + { + "name": "disabled_by_default" + }, + { + "name": "swap_horiz" + }, + { + "name": "swap_vert" + }, + { + "name": "app_registration" + }, + { + "name": "download_for_offline" + }, + { + "name": "close_fullscreen" + }, + { + "name": "file_open" + }, + { + "name": "minimize" + }, + { + "name": "open_with" + }, + { + "name": "dataset" + }, + { + "name": "add_task" + }, + { + "name": "start" + }, + { + "name": "keyboard_voice" + }, + { + "name": "create_new_folder" + }, + { + "name": "forward" + }, + { + "name": "download" + }, + { + "name": "settings_applications" + }, + { + "name": "compare_arrows" + }, + { + "name": "redo" + }, + { + "name": "zoom_out" + }, + { + "name": "publish" + }, + { + "name": "html" + }, + { + "name": "token" + }, + { + "name": "switch_access_shortcut" + }, + { + "name": "fullscreen_exit" + }, + { + "name": "sort_by_alpha" + }, + { + "name": "delete_sweep" + }, + { + "name": "indeterminate_check_box" + }, + { + "name": "view_timeline" + }, + { + "name": "settings_backup_restore" + }, + { + "name": "arrow_drop_down_circle" + }, + { + "name": "assistant_navigation" + }, + { + "name": "sync_problem" + }, + { + "name": "clear_all" + }, + { + "name": "density_medium" + }, + { + "name": "heart_plus" + }, + { + "name": "filter_alt_off" + }, + { + "name": "expand" + }, + { + "name": "subdirectory_arrow_right" + }, + { + "name": "download_done" + }, + { + "name": "arrow_outward" + }, + { + "name": "123" + }, + { + "name": "swipe_left" + }, + { + "name": "auto_mode" + }, + { + "name": "saved_search" + }, + { + "name": "place_item" + }, + { + "name": "system_update_alt" + }, + { + "name": "javascript" + }, + { + "name": "search_off" + }, + { + "name": "output" + }, + { + "name": "select_all" + }, + { + "name": "fit_screen" + }, + { + "name": "swipe_up" + }, + { + "name": "dynamic_form" + }, + { + "name": "hide_source" + }, + { + "name": "swipe_right" + }, + { + "name": "switch_access_shortcut_add" + }, + { + "name": "browse_gallery" + }, + { + "name": "css" + }, + { + "name": "density_small" + }, + { + "name": "assistant_direction" + }, + { + "name": "check_small" + }, + { + "name": "youtube_searched_for" + }, + { + "name": "move_up" + }, + { + "name": "swap_horizontal_circle" + }, + { + "name": "data_thresholding" + }, + { + "name": "install_mobile" + }, + { + "name": "move_down" + }, + { + "name": "dataset_linked" + }, + { + "name": "keyboard_command_key" + }, + { + "name": "view_kanban" + }, + { + "name": "swipe_down" + }, + { + "name": "key_off" + }, + { + "name": "transcribe" + }, + { + "name": "send_time_extension" + }, + { + "name": "swipe_down_alt" + }, + { + "name": "swipe_left_alt" + }, + { + "name": "swipe_right_alt" + }, + { + "name": "swipe_up_alt" + }, + { + "name": "keyboard_option_key" + }, + { + "name": "cycle" + }, + { + "name": "rebase" + }, + { + "name": "rebase_edit" + }, + { + "name": "empty_dashboard" + }, + { + "name": "magic_exchange" + }, + { + "name": "acute" + }, + { + "name": "point_scan" + }, + { + "name": "step_into" + }, + { + "name": "cheer" + }, + { + "name": "emoticon" + }, + { + "name": "explosion" + }, + { + "name": "water_bottle" + }, + { + "name": "weather_hail" + }, + { + "name": "syringe" + }, + { + "name": "pill" + }, + { + "name": "genetics" + }, + { + "name": "allergy" + }, + { + "name": "medical_mask" + }, + { + "name": "body_fat" + }, + { + "name": "barefoot" + }, + { + "name": "infrared" + }, + { + "name": "wrist" + }, + { + "name": "metabolism" + }, + { + "name": "conditions" + }, + { + "name": "taunt" + }, + { + "name": "altitude" + }, + { + "name": "tibia" + }, + { + "name": "footprint" + }, + { + "name": "eyeglasses" + }, + { + "name": "man_3" + }, + { + "name": "woman_2" + }, + { + "name": "rheumatology" + }, + { + "name": "tornado" + }, + { + "name": "landslide" + }, + { + "name": "foggy" + }, + { + "name": "severe_cold" + }, + { + "name": "tsunami" + }, + { + "name": "vape_free" + }, + { + "name": "sign_language" + }, + { + "name": "emoji_symbols" + }, + { + "name": "clear_night" + }, + { + "name": "emoji_food_beverage" + }, + { + "name": "hive" + }, + { + "name": "thunderstorm" + }, + { + "name": "communication" + }, + { + "name": "rocket" + }, + { + "name": "pets" + }, + { + "name": "public" + }, + { + "name": "quiz" + }, + { + "name": "mood" + }, + { + "name": "gavel" + }, + { + "name": "eco" + }, + { + "name": "diamond" + }, + { + "name": "forest" + }, + { + "name": "rainy" + }, + { + "name": "skull" + } + ] +} diff --git a/apps/app/components/emoji-icon-picker/index.tsx b/apps/app/components/emoji-icon-picker/index.tsx index 69a2c45e7..e78af4c10 100644 --- a/apps/app/components/emoji-icon-picker/index.tsx +++ b/apps/app/components/emoji-icon-picker/index.tsx @@ -1,10 +1,13 @@ import React, { useEffect, useState, useRef } from "react"; // headless ui import { Tab, Transition, Popover } from "@headlessui/react"; +// react colors +import { TwitterPicker } from "react-color"; // types import { Props } from "./types"; // emojis import emojis from "./emojis.json"; +import icons from "./icons.json"; // helpers import { getRecentEmojis, saveRecentEmoji } from "./helpers"; import { getRandomEmoji } from "helpers/common.helper"; @@ -22,10 +25,18 @@ const tabOptions = [ }, ]; -const EmojiIconPicker: React.FC = ({ label, value, onChange }) => { +const EmojiIconPicker: React.FC = ({ + label, + value, + onChange, + onIconColorChange, + onIconsClick, +}) => { const ref = useRef(null); const [isOpen, setIsOpen] = useState(false); + const [openColorPicker, setOpenColorPicker] = useState(false); + const [activeColor, setActiveColor] = useState("#020617"); const [recentEmojis, setRecentEmojis] = useState([]); @@ -58,20 +69,25 @@ const EmojiIconPicker: React.FC = ({ label, value, onChange }) => { leaveFrom="transform opacity-100 scale-100" leaveTo="transform opacity-0 scale-95" > - -
+ +
- + {tabOptions.map((tab) => ( - - `-my-1 w-1/2 border-b py-2 text-center text-sm font-medium outline-none transition-colors ${ - selected ? "border-theme" : "border-transparent" - }` - } - > - {tab.title} + + {({ selected }) => ( + + )} ))} @@ -79,12 +95,12 @@ const EmojiIconPicker: React.FC = ({ label, value, onChange }) => { {recentEmojis.length > 0 && (
-

Recent Emojis

-
+ {/*

Recent Emojis

*/} +
{recentEmojis.map((emoji) => (
)} +
-

All Emojis

-
+ {/*

All Emojis

*/} +
{emojis.map((emoji) => (
- -

Coming Soon...

-
+
+
+
+ {[ + "#D687FF", + "#F7AE59", + "#FF6B00", + "#8CC1FF", + "#FCBE1D", + "#18904F", + "#ADF672", + "#05C3FF", + "#000000", + ].map((curCol) => ( + setActiveColor(curCol)} + /> + ))} + +
+
+ { + setActiveColor(color.hex); + if (onIconColorChange) onIconColorChange(color.hex); + }} + triangle="hide" + width="205px" + /> +
+
+
+ +
+ {icons.material_rounded.map((icon) => ( + + ))} +
+
+
diff --git a/apps/app/components/emoji-icon-picker/types.d.ts b/apps/app/components/emoji-icon-picker/types.d.ts index 2d3e26353..0162640f5 100644 --- a/apps/app/components/emoji-icon-picker/types.d.ts +++ b/apps/app/components/emoji-icon-picker/types.d.ts @@ -2,4 +2,6 @@ export type Props = { label: string | React.ReactNode; value: any; onChange: (data: any) => void; + onIconsClick?: (data: any) => void; + onIconColorChange?: (data: any) => void; }; diff --git a/apps/app/styles/globals.css b/apps/app/styles/globals.css index f4dd63e48..eeed87dd4 100644 --- a/apps/app/styles/globals.css +++ b/apps/app/styles/globals.css @@ -128,4 +128,9 @@ .react-datepicker-popper { z-index: 30 !important; } + +.conical-gradient{ + background: conic-gradient(from 180deg at 50% 50%, #FF6B00 0deg, #F7AE59 70.5deg, #3F76FF 151.12deg, #05C3FF 213deg, #18914F 289.87deg, #F6F172 329.25deg, #FF6B00 360deg); +} + /* end react datepicker styling */ From 67952bc225e09efb486ab1c6db603bca1f313387 Mon Sep 17 00:00:00 2001 From: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Date: Mon, 3 Apr 2023 23:57:19 +0530 Subject: [PATCH 17/29] feat: added user role on onboarding event (#684) * feat: added tracker for views * feat: added user role on onboarding event --- apps/app/components/onboarding/user-details.tsx | 14 ++++++++++---- apps/app/pages/onboarding.tsx | 5 +++-- apps/app/services/user.service.ts | 12 +++++++++--- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/apps/app/components/onboarding/user-details.tsx b/apps/app/components/onboarding/user-details.tsx index febfc0a39..ab7af9ab3 100644 --- a/apps/app/components/onboarding/user-details.tsx +++ b/apps/app/components/onboarding/user-details.tsx @@ -22,9 +22,10 @@ const defaultValues: Partial = { type Props = { user?: IUser; setStep: React.Dispatch>; + setUserRole: React.Dispatch>; }; -export const UserDetails: React.FC = ({ user, setStep }) => { +export const UserDetails: React.FC = ({ user, setStep, setUserRole }) => { const { setToastAlert } = useToast(); const { @@ -53,13 +54,15 @@ export const UserDetails: React.FC = ({ user, setStep }) => { }; useEffect(() => { - if (user) + if (user) { reset({ first_name: user.first_name, last_name: user.last_name, role: user.role, }); - }, [user, reset]); + setUserRole(user.role); + } + }, [user, reset, setUserRole]); return (
@@ -101,7 +104,10 @@ export const UserDetails: React.FC = ({ user, setStep }) => { render={({ field: { value, onChange } }) => ( { + onChange(value); + setUserRole(value ?? null); + }} label={value ? value.toString() : "Select your role"} input width="w-full" diff --git a/apps/app/pages/onboarding.tsx b/apps/app/pages/onboarding.tsx index b2a7e987f..0c1c95326 100644 --- a/apps/app/pages/onboarding.tsx +++ b/apps/app/pages/onboarding.tsx @@ -24,6 +24,7 @@ import type { NextPage, GetServerSidePropsContext } from "next"; const Onboarding: NextPage = () => { const [step, setStep] = useState(1); + const [userRole, setUserRole] = useState(null); const [workspace, setWorkspace] = useState(); @@ -40,7 +41,7 @@ const Onboarding: NextPage = () => { Plane Logo
{step === 1 ? ( - + ) : step === 2 ? ( ) : ( @@ -69,7 +70,7 @@ const Onboarding: NextPage = () => { onClick={() => { if (step === 8) { userService - .updateUserOnBoard() + .updateUserOnBoard({ userRole }) .then(() => { router.push("/"); }) diff --git a/apps/app/services/user.service.ts b/apps/app/services/user.service.ts index 95f42554d..cab79ab3a 100644 --- a/apps/app/services/user.service.ts +++ b/apps/app/services/user.service.ts @@ -47,10 +47,16 @@ class UserService extends APIService { }); } - async updateUserOnBoard(): Promise { - return this.patch("/api/users/me/onboard/", { is_onboarded: true }) + async updateUserOnBoard({ userRole }: any): Promise { + return this.patch("/api/users/me/onboard/", { + is_onboarded: true, + }) .then((response) => { - if (trackEvent) trackEventServices.trackUserOnboardingCompleteEvent(response.data); + if (trackEvent) + trackEventServices.trackUserOnboardingCompleteEvent({ + ...response.data, + user_role: userRole ?? "None", + }); return response?.data; }) .catch((error) => { From d990f0038ba56e0c1bdda9238a3381240c41af8c Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Mon, 3 Apr 2023 23:58:24 +0530 Subject: [PATCH 18/29] fix: module link create url validation (#678) --- apiserver/plane/api/serializers/module.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apiserver/plane/api/serializers/module.py b/apiserver/plane/api/serializers/module.py index 8e976d318..ea9edd82c 100644 --- a/apiserver/plane/api/serializers/module.py +++ b/apiserver/plane/api/serializers/module.py @@ -139,6 +139,16 @@ class ModuleLinkSerializer(BaseSerializer): "module", ] + # Validation if url already exists + def create(self, validated_data): + if ModuleLink.objects.filter( + url=validated_data.get("url"), module_id=validated_data.get("module_id") + ).exists(): + raise serializers.ValidationError( + {"error": "URL already exists for this Issue"} + ) + return ModuleLink.objects.create(**validated_data) + class ModuleSerializer(BaseSerializer): project_detail = ProjectSerializer(read_only=True, source="project") From 1bb93f1f5030bc4e41e03ea50e15198a7c2900c0 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Mon, 3 Apr 2023 23:58:35 +0530 Subject: [PATCH 19/29] dev: back migration to update label colors (#664) --- apiserver/back_migration.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apiserver/back_migration.py b/apiserver/back_migration.py index 93f07134f..ff5fc74e4 100644 --- a/apiserver/back_migration.py +++ b/apiserver/back_migration.py @@ -3,7 +3,7 @@ import uuid import random from django.contrib.auth.hashers import make_password from plane.db.models import ProjectIdentifier -from plane.db.models import Issue, IssueComment, User, Project +from plane.db.models import Issue, IssueComment, User, Project, Label # Update description and description html values for old descriptions @@ -134,3 +134,18 @@ def update_project_cover_images(): except Exception as e: print(e) print("Failed") + + +def update_label_color(): + try: + labels = Label.objects.filter(color="") + updated_labels = [] + for label in labels: + label.color = "#" + "%06x" % random.randint(0, 0xFFFFFF) + updated_labels.append(label) + + Label.objects.bulk_update(updated_labels, ["color"], batch_size=100) + print("Success") + except Exception as e: + print(e) + print("Failed") From 588247f1c1f6e6e23f3cdf56c6f554a1ff62d54a Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Tue, 4 Apr 2023 00:00:03 +0530 Subject: [PATCH 20/29] dev: back migration for project member views (#663) --- apiserver/back_migration.py | 27 ++++++++++++++++++++++++++- apiserver/plane/db/models/project.py | 7 +++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/apiserver/back_migration.py b/apiserver/back_migration.py index ff5fc74e4..1ba312934 100644 --- a/apiserver/back_migration.py +++ b/apiserver/back_migration.py @@ -3,7 +3,8 @@ import uuid import random from django.contrib.auth.hashers import make_password from plane.db.models import ProjectIdentifier -from plane.db.models import Issue, IssueComment, User, Project, Label +from plane.db.models import Issue, IssueComment, User, Project, ProjectMember + # Update description and description html values for old descriptions @@ -136,6 +137,30 @@ def update_project_cover_images(): print("Failed") +def update_user_view_property(): + try: + project_members = ProjectMember.objects.all() + updated_project_members = [] + for project_member in project_members: + project_member.default_props = { + "filters": {"type": None}, + "orderBy": "-created_at", + "collapsed": True, + "issueView": "list", + "filterIssue": None, + "groupByProperty": True, + "showEmptyGroups": True, + } + updated_project_members.append(project_member) + + ProjectMember.objects.bulk_update( + updated_project_members, ["default_props"], batch_size=100 + ) + print("Success") + except Exception as e: + print(e) + print("Failed") + def update_label_color(): try: labels = Label.objects.filter(color="") diff --git a/apiserver/plane/db/models/project.py b/apiserver/plane/db/models/project.py index 4b1af4bed..b3c8f669a 100644 --- a/apiserver/plane/db/models/project.py +++ b/apiserver/plane/db/models/project.py @@ -21,10 +21,13 @@ ROLE_CHOICES = ( def get_default_props(): return { + "filters": {"type": None}, + "orderBy": "-created_at", + "collapsed": True, "issueView": "list", - "groupByProperty": None, - "orderBy": None, "filterIssue": None, + "groupByProperty": True, + "showEmptyGroups": True, } From 4af59219917bf75efaa646cb72ffc15d835be26e Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 4 Apr 2023 00:13:21 +0530 Subject: [PATCH 21/29] chore: restrict users from creating workspace with reserved slugs (#692) --- .../components/workspace/create-workspace-form.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/app/components/workspace/create-workspace-form.tsx b/apps/app/components/workspace/create-workspace-form.tsx index 3f63dbf92..af98c00f3 100644 --- a/apps/app/components/workspace/create-workspace-form.tsx +++ b/apps/app/components/workspace/create-workspace-form.tsx @@ -27,6 +27,15 @@ type Props = { setDefaultValues: Dispatch>; }; +const restrictedUrls = [ + "create-workspace", + "error", + "invitations", + "magic-sign-in", + "onboarding", + "signin", +]; + export const CreateWorkspaceForm: React.FC = ({ onSubmit, defaultValues, @@ -49,7 +58,7 @@ export const CreateWorkspaceForm: React.FC = ({ await workspaceService .workspaceSlugCheck(formData.slug) .then(async (res) => { - if (res.status === true) { + if (res.status === true && !restrictedUrls.includes(formData.slug)) { setSlugError(false); await workspaceService .createWorkspace(formData) From 51be70d81443ee7012fa7ba684a73209ee118382 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 4 Apr 2023 00:27:08 +0530 Subject: [PATCH 22/29] chore: remove github importer tab (#693) --- apps/app/layouts/settings-navbar.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/app/layouts/settings-navbar.tsx b/apps/app/layouts/settings-navbar.tsx index 0aa413892..b990eaf48 100644 --- a/apps/app/layouts/settings-navbar.tsx +++ b/apps/app/layouts/settings-navbar.tsx @@ -29,10 +29,10 @@ const SettingsNavbar: React.FC = ({ profilePage = false }) => { label: "Integrations", href: `/${workspaceSlug}/settings/integrations`, }, - { - label: "Import/Export", - href: `/${workspaceSlug}/settings/import-export`, - }, + // { + // label: "Import/Export", + // href: `/${workspaceSlug}/settings/import-export`, + // }, ]; const projectLinks: Array<{ From dad36b404db202cb299022b273dfaa0c219abefc Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 4 Apr 2023 14:07:17 +0530 Subject: [PATCH 23/29] fix: pages ai modal (#694) --- .../components/pages/single-page-block.tsx | 104 ++++++++++++++---- 1 file changed, 80 insertions(+), 24 deletions(-) diff --git a/apps/app/components/pages/single-page-block.tsx b/apps/app/components/pages/single-page-block.tsx index 213edd5ef..7f01811db 100644 --- a/apps/app/components/pages/single-page-block.tsx +++ b/apps/app/components/pages/single-page-block.tsx @@ -8,16 +8,19 @@ import { mutate } from "swr"; // react-hook-form import { Controller, useForm } from "react-hook-form"; +// react-beautiful-dnd +import { Draggable } from "react-beautiful-dnd"; // services import pagesService from "services/pages.service"; import issuesService from "services/issues.service"; +import aiService from "services/ai.service"; // hooks import useToast from "hooks/use-toast"; // components import { GptAssistantModal } from "components/core"; import { CreateUpdateBlockInline } from "components/pages"; // ui -import { CustomMenu, Input, Loader } from "components/ui"; +import { CustomMenu, Loader } from "components/ui"; // icons import { LayerDiagonalIcon } from "components/icons"; import { ArrowPathIcon } from "@heroicons/react/20/solid"; @@ -34,7 +37,6 @@ import { copyTextToClipboard } from "helpers/string.helper"; import { IIssue, IPageBlock, IProject } from "types"; // fetch-keys import { PAGE_BLOCKS_LIST } from "constants/fetch-keys"; -import { Draggable } from "react-beautiful-dnd"; type Props = { block: IPageBlock; @@ -54,6 +56,7 @@ const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor export const SinglePageBlock: React.FC = ({ block, projectDetails, index }) => { const [isSyncing, setIsSyncing] = useState(false); const [createBlockForm, setCreateBlockForm] = useState(false); + const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false); const [gptAssistantModal, setGptAssistantModal] = useState(false); @@ -164,6 +167,44 @@ export const SinglePageBlock: React.FC = ({ block, projectDetails, index }); }; + const handelAutoGenerateDescription = async () => { + if (!workspaceSlug || !projectId) return; + + setIAmFeelingLucky(true); + + aiService + .createGptTask(workspaceSlug as string, projectId as string, { + prompt: block.name, + task: "Generate a proper description for this issue in context of a project management software.", + }) + .then((res) => { + if (res.response === "") + setToastAlert({ + type: "error", + title: "Error!", + message: + "Block title isn't informative enough to generate the description. Please try with a different title.", + }); + else handleAiAssistance(res.response_html); + }) + .catch((err) => { + if (err.status === 429) + setToastAlert({ + type: "error", + title: "Error!", + message: + "You have reached the maximum number of requests of 50 requests per month per user.", + }); + else + setToastAlert({ + type: "error", + title: "Error!", + message: "Some error occurred. Please try again.", + }); + }) + .finally(() => setIAmFeelingLucky(false)); + }; + const handleAiAssistance = async (response: string) => { if (!workspaceSlug || !projectId) return; @@ -287,6 +328,22 @@ export const SinglePageBlock: React.FC = ({ block, projectDetails, index )} +
-
setCreateBlockForm(true)} - > - ( - - )} - /> +
+
setCreateBlockForm(true)}> + ( + + )} + /> +
Date: Tue, 4 Apr 2023 16:21:46 +0530 Subject: [PATCH 24/29] chore: minor pages UI (#695) * chore: fix minor ui bugs in pages * chore: shortcut to add new block * chore: keyboard accessibility * chore: block options position --- .../pages/create-update-block-inline.tsx | 25 +- .../components/pages/single-page-block.tsx | 240 +++++++++--------- .../projects/[projectId]/pages/[pageId].tsx | 36 ++- .../projects/[projectId]/settings/labels.tsx | 8 +- 4 files changed, 165 insertions(+), 144 deletions(-) diff --git a/apps/app/components/pages/create-update-block-inline.tsx b/apps/app/components/pages/create-update-block-inline.tsx index baf5accf8..dbbe54696 100644 --- a/apps/app/components/pages/create-update-block-inline.tsx +++ b/apps/app/components/pages/create-update-block-inline.tsx @@ -22,6 +22,7 @@ type Props = { handleClose: () => void; data?: IPageBlock; setIsSyncing?: React.Dispatch>; + focus?: keyof IPageBlock; }; const defaultValues = { @@ -38,7 +39,12 @@ const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor ), }); -export const CreateUpdateBlockInline: React.FC = ({ handleClose, data, setIsSyncing }) => { +export const CreateUpdateBlockInline: React.FC = ({ + handleClose, + data, + setIsSyncing, + focus, +}) => { const router = useRouter(); const { workspaceSlug, projectId, pageId } = router.query; @@ -50,6 +56,7 @@ export const CreateUpdateBlockInline: React.FC = ({ handleClose, data, se control, watch, setValue, + setFocus, reset, formState: { isSubmitting }, } = useForm({ @@ -57,9 +64,10 @@ export const CreateUpdateBlockInline: React.FC = ({ handleClose, data, se }); const onClose = useCallback(() => { - handleClose(); + if (data) handleClose(); + reset(); - }, [handleClose, reset]); + }, [handleClose, reset, data]); const createPageBlock = async (formData: Partial) => { if (!workspaceSlug || !projectId || !pageId) return; @@ -126,6 +134,8 @@ export const CreateUpdateBlockInline: React.FC = ({ handleClose, data, se }; useEffect(() => { + if (focus) setFocus(focus); + if (!data) return; reset({ @@ -134,7 +144,7 @@ export const CreateUpdateBlockInline: React.FC = ({ handleClose, data, se description: data.description, description_html: data.description_html, }); - }, [reset, data]); + }, [reset, data, focus, setFocus]); useEffect(() => { window.addEventListener("keydown", (e: KeyboardEvent) => { @@ -156,9 +166,10 @@ export const CreateUpdateBlockInline: React.FC = ({ handleClose, data, se name="name" placeholder="Title" register={register} - required={true} className="min-h-10 block w-full resize-none overflow-hidden border-none bg-transparent py-1 text-base ring-0 -ml-2 focus:ring-gray-200" role="textbox" + autoComplete="off" + maxLength={255} />
= ({ handleClose, data, se />
- Cancel - + Cancel + {data ? isSubmitting ? "Updating..." diff --git a/apps/app/components/pages/single-page-block.tsx b/apps/app/components/pages/single-page-block.tsx index 7f01811db..4ab8e81ed 100644 --- a/apps/app/components/pages/single-page-block.tsx +++ b/apps/app/components/pages/single-page-block.tsx @@ -42,6 +42,7 @@ type Props = { block: IPageBlock; projectDetails: IProject | undefined; index: number; + handleNewBlock: () => void; }; const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { @@ -53,7 +54,12 @@ const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor ), }); -export const SinglePageBlock: React.FC = ({ block, projectDetails, index }) => { +export const SinglePageBlock: React.FC = ({ + block, + projectDetails, + index, + handleNewBlock, +}) => { const [isSyncing, setIsSyncing] = useState(false); const [createBlockForm, setCreateBlockForm] = useState(false); const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false); @@ -65,7 +71,7 @@ export const SinglePageBlock: React.FC = ({ block, projectDetails, index const { setToastAlert } = useToast(); - const { handleSubmit, watch, reset, setValue, control, register } = useForm({ + const { handleSubmit, watch, reset, setValue, register } = useForm({ defaultValues: { name: "", description: {}, @@ -273,12 +279,29 @@ export const SinglePageBlock: React.FC = ({ block, projectDetails, index reset({ ...block }); }, [reset, block]); + useEffect(() => { + window.addEventListener("keydown", (e: KeyboardEvent) => { + if (e.key === "Enter" && !createBlockForm) handleNewBlock(); + }); + + return () => { + window.removeEventListener("keydown", (e: KeyboardEvent) => { + if (e.key === "Enter" && !createBlockForm) handleNewBlock(); + }); + }; + }, [handleNewBlock, createBlockForm]); + return ( - + {(provided, snapshot) => ( <> {createBlockForm ? ( -
+
setCreateBlockForm(false)} data={block} @@ -287,131 +310,110 @@ export const SinglePageBlock: React.FC = ({ block, projectDetails, index
) : (
-
-
- -

setCreateBlockForm(true)}> - {block.name} -

-
-
- {block.issue && block.sync && ( -
- {isSyncing ? ( - - ) : ( - - )} - {isSyncing ? "Syncing..." : "Synced"} -
- )} - {block.issue && ( - - - - {projectDetails?.identifier}-{block.issue_detail?.sequence_id} - - - )} - +
+ {block.issue && block.sync && ( +
+ {isSyncing ? ( + ) : ( - <> - I{"'"}m feeling lucky - + )} - - - - } noBorder noChevron> - {block.issue ? ( - <> - - <>Turn sync {block.sync ? "off" : "on"} - - - Copy issue link - - - ) : ( - - Push into issues + {isSyncing ? "Syncing..." : "Synced"} +
+ )} + + + + } noBorder noChevron> + {block.issue ? ( + <> + + <>Turn sync {block.sync ? "off" : "on"} - )} - - Delete block + + Copy issue link + + + ) : ( + + Push into issues - -
+ )} + Delete block +
-
-
setCreateBlockForm(true)}> - ( - - )} - /> -
- setGptAssistantModal(false)} - inset="top-2 left-0" - content={block.description_stripped} - htmlContent={block.description_html} - onResponse={handleAiAssistance} - projectId={projectId as string} - /> +
+ {block.issue && ( + + + + {projectDetails?.identifier}-{block.issue_detail?.sequence_id} + + + )} +

setCreateBlockForm(true)} + > + {block.name} +

+ setGptAssistantModal(false)} + inset="top-8 left-0" + content={block.description_stripped} + htmlContent={block.description_html} + onResponse={handleAiAssistance} + projectId={projectId as string} + />
)} diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index 35097094b..29cdede37 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { useRouter } from "next/router"; @@ -54,9 +54,10 @@ import { } from "constants/fetch-keys"; const SinglePage: NextPage = (props) => { - const [isAddingBlock, setIsAddingBlock] = useState(false); const [createBlockForm, setCreateBlockForm] = useState(false); + const scrollToRef = useRef(null); + const router = useRouter(); const { workspaceSlug, projectId, pageId } = router.query; @@ -238,6 +239,13 @@ const SinglePage: NextPage = (props) => { ); }; + const handleNewBlock = () => { + setCreateBlockForm(true); + scrollToRef.current?.scrollIntoView({ + behavior: "smooth", + }); + }; + const options = labels?.map((label) => ({ value: label.id, @@ -449,14 +457,15 @@ const SinglePage: NextPage = (props) => { {pageBlocks.length !== 0 && ( - {(provided, snapshot) => ( -
+ {(provided) => ( +
{pageBlocks.map((block, index) => ( ))} {provided.placeholder} @@ -469,20 +478,19 @@ const SinglePage: NextPage = (props) => { )} {createBlockForm && ( - setCreateBlockForm(false)} /> +
+ setCreateBlockForm(false)} + focus="name" + /> +
)} ) : ( diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx index 534409ec4..013f74a38 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx @@ -46,7 +46,7 @@ const LabelsSettings: NextPage = (props) => { const router = useRouter(); const { workspaceSlug, projectId } = router.query; - const scollToRef = useRef(null); + const scrollToRef = useRef(null); const { data: projectDetails } = useSWR( workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, @@ -130,7 +130,7 @@ const LabelsSettings: NextPage = (props) => { setLabelForm={setLabelForm} isUpdating={isUpdating} labelToUpdate={labelToUpdate} - ref={scollToRef} + ref={scrollToRef} /> )} <> @@ -147,7 +147,7 @@ const LabelsSettings: NextPage = (props) => { addLabelToGroup={() => addLabelToGroup(label)} editLabel={(label) => { editLabel(label); - scollToRef.current?.scrollIntoView({ + scrollToRef.current?.scrollIntoView({ behavior: "smooth", }); }} @@ -163,7 +163,7 @@ const LabelsSettings: NextPage = (props) => { addLabelToGroup={addLabelToGroup} editLabel={(label) => { editLabel(label); - scollToRef.current?.scrollIntoView({ + scrollToRef.current?.scrollIntoView({ behavior: "smooth", }); }} From adf366b325890028a982125c47cf980ce127cd2e Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Tue, 4 Apr 2023 18:07:01 +0530 Subject: [PATCH 25/29] chore: gpt environment variables in the example file (#698) --- apiserver/.env.example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apiserver/.env.example b/apiserver/.env.example index 2241e2217..15056f072 100644 --- a/apiserver/.env.example +++ b/apiserver/.env.example @@ -19,3 +19,6 @@ GITHUB_CLIENT_SECRET="" # Flags DISABLE_COLLECTSTATIC=1 DOCKERIZED=1 +# GPT Envs +OPENAI_API_KEY=0 +GPT_ENGINE=0 \ No newline at end of file From 7f7ceec24c5a29ac4f2da1500b89064e387edf9f Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Tue, 4 Apr 2023 18:07:17 +0530 Subject: [PATCH 26/29] chore: return user role in user onboard endpoint (#682) --- apiserver/plane/api/views/people.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apiserver/plane/api/views/people.py b/apiserver/plane/api/views/people.py index cafda3efd..78ae5b2fc 100644 --- a/apiserver/plane/api/views/people.py +++ b/apiserver/plane/api/views/people.py @@ -17,6 +17,7 @@ from plane.db.models import ( WorkspaceMemberInvite, Issue, IssueActivity, + WorkspaceMember, ) from plane.utils.paginator import BasePaginator @@ -72,6 +73,20 @@ class UpdateUserOnBoardedEndpoint(BaseAPIView): user = User.objects.get(pk=request.user.id) user.is_onboarded = request.data.get("is_onboarded", False) user.save() + + if user.last_workspace_id is not None: + user_role = WorkspaceMember.objects.filter( + workspace_id=user.last_workspace_id, member=request.user.id + ).first() + return Response( + { + "message": "Updated successfully", + "role": user_role.company_role + if user_role is not None + else None, + }, + status=status.HTTP_200_OK, + ) return Response( {"message": "Updated successfully"}, status=status.HTTP_200_OK ) From 0036ac6afb343ea9c097ff77abb2449c66759d95 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 4 Apr 2023 18:31:28 +0530 Subject: [PATCH 27/29] fix: minor pages ui (#700) --- .../pages/create-update-block-inline.tsx | 6 ++-- .../pages/single-page-detailed-item.tsx | 33 ++++++------------- .../projects/[projectId]/pages/[pageId].tsx | 4 +-- 3 files changed, 15 insertions(+), 28 deletions(-) diff --git a/apps/app/components/pages/create-update-block-inline.tsx b/apps/app/components/pages/create-update-block-inline.tsx index dbbe54696..b5f36722f 100644 --- a/apps/app/components/pages/create-update-block-inline.tsx +++ b/apps/app/components/pages/create-update-block-inline.tsx @@ -148,15 +148,15 @@ export const CreateUpdateBlockInline: React.FC = ({ useEffect(() => { window.addEventListener("keydown", (e: KeyboardEvent) => { - if (e.key === "Escape") onClose(); + if (e.key === "Escape") handleClose(); }); return () => { window.removeEventListener("keydown", (e: KeyboardEvent) => { - if (e.key === "Escape") onClose(); + if (e.key === "Escape") handleClose(); }); }; - }, [onClose]); + }, [handleClose]); return (
diff --git a/apps/app/components/pages/single-page-detailed-item.tsx b/apps/app/components/pages/single-page-detailed-item.tsx index b07b87f89..e53a1c78b 100644 --- a/apps/app/components/pages/single-page-detailed-item.tsx +++ b/apps/app/components/pages/single-page-detailed-item.tsx @@ -50,9 +50,9 @@ export const SinglePageDetailedItem: React.FC = ({ const { workspaceSlug, projectId } = router.query; return ( - - -
+ ); }; diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index 29cdede37..c840b40d1 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -477,7 +477,7 @@ const SinglePage: NextPage = (props) => { {!createBlockForm && ( )} {createBlockForm && ( -
+
setCreateBlockForm(false)} focus="name" From 9ce158fc10dcccda53729d28beb84afa4beaf2b2 Mon Sep 17 00:00:00 2001 From: sphynxux <122926002+sphynxux@users.noreply.github.com> Date: Tue, 4 Apr 2023 18:50:23 +0530 Subject: [PATCH 28/29] add screenshots, feature, docker compose steps on readme (#701) --- README.md | 115 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 6af8396ac..e462b2780 100644 --- a/README.md +++ b/README.md @@ -2,35 +2,128 @@

- Plane Logo + Plane Logo

+

Plane

+

Open-source, self-hosted project planning tool

+

Discord Discord

-
-Plane is an open-source project planning tool that is designed to help individuals and teams streamline their issues, sprints, and product roadmaps. It is easy to use and can be accessed by anyone, making it an ideal choice for a wide range of projects and organizations. -

+

+ + Plane Screens + +

+ +Meet Plane. An open-source software development tool to manage issues, sprints, and product roadmaps with peace of mind 🧘‍♀️. + > Plane is still in its early days, not everything will be perfect yet, and hiccups may happen. Please let us know of any suggestions, ideas, or bugs that you encounter on our [Discord](https://discord.com/invite/29tPNhaV) or GitHub issues, and we will use your feedback to improve on our upcoming releases. -## Getting Started +The easiest way to get started with Plane is by creating a [Plane Cloud](https://app.plane.so) account. Plane Cloud offers a hosted solution for Plane. If you prefer to self-host Plane, please refer to our [deployment documentation](https://docs.plane.so/self-hosting). -Visit https://app.plane.so to get started with Plane. -## Documentation +## ⚡️ Quick start with Docker Compose + +### Docker Compose Setup + +- Clone the Repository + +```bash +git clone https://github.com/makeplane/plane +``` + +- Change Directory + +```bash +cd plane +``` + +- Run setup.sh + +```bash +./setup.sh localhost +``` + +> If running in a cloud env replace localhost with public facing IP address of the VM + + +- Run Docker compose up + +```bash +docker-compose up +``` + + +## 🚀 Features + +* **Issue Planning and Tracking**: Quickly create issues and add details using a powerful rich text editor that supports file uploads. Add sub-properties and references to issues for better organization and tracking. +* **Issue Attachments**: Collaborate effectively by attaching files to issues, making it easy for your team to find and share important project-related documents. +* **Layouts**: Customize your project view with your preferred layout - choose from List, Kanban, or Calendar to visualize your project in a way that makes sense to you. +* **Cycles**: Plan sprints with Cycles to keep your team on track and productive. Gain insights into your project's progress with burn-down charts and other useful features. +* **Modules**: Break down your large projects into smaller, more manageable modules. Assign modules between teams to easily track and plan your project's progress. +* **Views**: Create custom filters to display only the issues that matter to you. Save and share your filters in just a few clicks. +* **Pages**: Plane pages function as an AI-powered notepad, allowing you to easily document issues, cycle plans, and module details, and then synchronize them with your issues. +* **Command K**: Enjoy a better user experience with the new Command + K menu. Easily manage and navigate through your projects from one convenient location. +* **GitHub Sync**: Streamline your planning process by syncing your GitHub issues with Plane. Keep all your issues in one place for better tracking and collaboration. + +## 📸 Screenshots + +

+ + Plane Issue Details + +

+

+ + Plane Cycles and Modules + +

+

+ + Plane Quick Lists + +

+

+ + Plane Command K + +

+ +## 📚Documentation For full documentation, visit [docs.plane.so](https://docs.plane.so/) To see how to Contribute, visit [here](https://github.com/makeplane/plane/blob/master/CONTRIBUTING.md). -## Status - +## 🔋 Status - [x] Early Community Previews: We are open-sourcing and sharing the development version of Plane - [ ] Alpha: We are testing Plane with a closed set of customers @@ -38,7 +131,7 @@ To see how to Contribute, visit [here](https://github.com/makeplane/plane/blob/m - [ ] Public Beta: Stable enough for most non-enterprise use-cases - [ ] Public: Production-ready -## Community +## ❤️ Community The Plane community can be found on GitHub Discussions, where you can ask questions, voice ideas, and share your projects. @@ -46,6 +139,6 @@ To chat with other community members you can join the [Plane Discord](https://di Our [Code of Conduct](https://github.com/makeplane/plane/blob/master/CODE_OF_CONDUCT.md) applies to all Plane community channels. -## Security +## ⛓️ Security If you believe you have found a security vulnerability in Plane, we encourage you to responsibly disclose this and not open a public issue. We will investigate all legitimate reports. Email security@plane.so to disclose any security vulnerabilities. From 3519be9ce8dfe39dc145274174a8d5af121d9ed9 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 4 Apr 2023 19:27:13 +0530 Subject: [PATCH 29/29] fix: remirror empty state (#702) * fix: minor pages ui * fix: remirror empty state --- apps/app/components/core/gpt-assistant-modal.tsx | 6 +++--- apps/app/styles/editor.css | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/app/components/core/gpt-assistant-modal.tsx b/apps/app/components/core/gpt-assistant-modal.tsx index d3280ad66..b50b1e0f8 100644 --- a/apps/app/components/core/gpt-assistant-modal.tsx +++ b/apps/app/components/core/gpt-assistant-modal.tsx @@ -125,12 +125,12 @@ export const GptAssistantModal: React.FC = ({ isOpen ? "block" : "hidden" }`} > - {((content && content !== "") || htmlContent) && ( -
+ {((content && content !== "") || htmlContent !== "

") && ( +
Content: {content}

} - customClassName="-mx-3 -my-3" + customClassName="-m-3" noBorder borderOnFocus={false} editable={false} diff --git a/apps/app/styles/editor.css b/apps/app/styles/editor.css index 55cb8a8e1..c9d58dfaf 100644 --- a/apps/app/styles/editor.css +++ b/apps/app/styles/editor.css @@ -363,6 +363,10 @@ img.ProseMirror-separator { min-height: 50px; } +.remirror-section .remirror-editor-wrapper .remirror-editor { + min-height: 0 !important; +} + .remirror-editor-wrapper { padding-top: 8px; }