diff --git a/.github/workflows/build-branch.yml b/.github/workflows/build-branch.yml index 2298f9558..306f92957 100644 --- a/.github/workflows/build-branch.yml +++ b/.github/workflows/build-branch.yml @@ -6,7 +6,6 @@ on: branches: - master - preview - - develop release: types: [released, prereleased] diff --git a/README.md b/README.md index 11db5ceba..ece8ff1e2 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ If you want more control over your data, prefer to self-host Plane, please refer | Installation Methods | Documentation Link | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Docker | [![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white)](https://docs.plane.so/docker-compose) | +| Docker | [![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white)](https://docs.plane.so/self-hosting/methods/docker-compose) | | Kubernetes | [![Kubernetes](https://img.shields.io/badge/kubernetes-%23326ce5.svg?style=for-the-badge&logo=kubernetes&logoColor=white)](https://docs.plane.so/kubernetes) | `Instance admin` can configure instance settings using our [God-mode](https://docs.plane.so/instance-admin) feature. diff --git a/apiserver/package.json b/apiserver/package.json index 060944406..2474aa2f2 100644 --- a/apiserver/package.json +++ b/apiserver/package.json @@ -1,4 +1,4 @@ { "name": "plane-api", - "version": "0.16.0" + "version": "0.17.0" } diff --git a/deploy/selfhost/docker-compose.yml b/deploy/selfhost/docker-compose.yml index b8d50177c..522baf63d 100644 --- a/deploy/selfhost/docker-compose.yml +++ b/deploy/selfhost/docker-compose.yml @@ -5,11 +5,11 @@ x-app-env: &app-env - NGINX_PORT=${NGINX_PORT:-80} - WEB_URL=${WEB_URL:-http://localhost} - DEBUG=${DEBUG:-0} - - SENTRY_DSN=${SENTRY_DSN:-""} + - SENTRY_DSN=${SENTRY_DSN} - SENTRY_ENVIRONMENT=${SENTRY_ENVIRONMENT:-"production"} - - CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-""} + - CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS} # Gunicorn Workers - - GUNICORN_WORKERS=${GUNICORN_WORKERS:-2} + - GUNICORN_WORKERS=${GUNICORN_WORKERS:-1} #DB SETTINGS - PGHOST=${PGHOST:-plane-db} - PGDATABASE=${PGDATABASE:-plane} @@ -17,11 +17,11 @@ x-app-env: &app-env - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-plane} - POSTGRES_DB=${POSTGRES_DB:-plane} - PGDATA=${PGDATA:-/var/lib/postgresql/data} - - DATABASE_URL=${DATABASE_URL:-postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${PGHOST}/${PGDATABASE}} + - DATABASE_URL=${DATABASE_URL:-postgresql://plane:plane@plane-db/plane} # REDIS SETTINGS - REDIS_HOST=${REDIS_HOST:-plane-redis} - REDIS_PORT=${REDIS_PORT:-6379} - - REDIS_URL=${REDIS_URL:-redis://${REDIS_HOST}:6379/} + - REDIS_URL=${REDIS_URL:-redis://plane-redis:6379/} # Application secret - SECRET_KEY=${SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5} # DATA STORE SETTINGS @@ -39,7 +39,7 @@ x-app-env: &app-env services: web: <<: *app-env - image: ${DOCKERHUB_USER:-makeplane}/plane-frontend:${APP_RELEASE:-latest} + image: ${DOCKERHUB_USER:-makeplane}/plane-frontend:${APP_RELEASE:-stable} pull_policy: ${PULL_POLICY:-always} restart: unless-stopped command: /usr/local/bin/start.sh web/server.js web @@ -51,7 +51,7 @@ services: space: <<: *app-env - image: ${DOCKERHUB_USER:-makeplane}/plane-space:${APP_RELEASE:-latest} + image: ${DOCKERHUB_USER:-makeplane}/plane-space:${APP_RELEASE:-stable} pull_policy: ${PULL_POLICY:-always} restart: unless-stopped command: /usr/local/bin/start.sh space/server.js space @@ -64,7 +64,7 @@ services: api: <<: *app-env - image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-latest} + image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-stable} pull_policy: ${PULL_POLICY:-always} restart: unless-stopped command: ./bin/takeoff @@ -78,7 +78,7 @@ services: worker: <<: *app-env - image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-latest} + image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-stable} pull_policy: ${PULL_POLICY:-always} restart: unless-stopped command: ./bin/worker @@ -91,7 +91,7 @@ services: beat-worker: <<: *app-env - image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-latest} + image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-stable} pull_policy: ${PULL_POLICY:-always} restart: unless-stopped command: ./bin/beat @@ -104,7 +104,7 @@ services: migrator: <<: *app-env - image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-latest} + image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-stable} pull_policy: ${PULL_POLICY:-always} restart: no command: > @@ -118,7 +118,7 @@ services: plane-db: <<: *app-env - image: postgres:15.2-alpine + image: postgres:15.5-alpine pull_policy: if_not_present restart: unless-stopped command: postgres -c 'max_connections=1000' @@ -126,7 +126,7 @@ services: - pgdata:/var/lib/postgresql/data plane-redis: <<: *app-env - image: redis:6.2.7-alpine + image: redis:7.2.4-alpine pull_policy: if_not_present restart: unless-stopped volumes: @@ -144,7 +144,7 @@ services: # Comment this if you already have a reverse proxy running proxy: <<: *app-env - image: ${DOCKERHUB_USER:-makeplane}/plane-proxy:${APP_RELEASE:-latest} + image: ${DOCKERHUB_USER:-makeplane}/plane-proxy:${APP_RELEASE:-stable} pull_policy: ${PULL_POLICY:-always} ports: - ${NGINX_PORT}:80 diff --git a/deploy/selfhost/install.sh b/deploy/selfhost/install.sh index 16b6ea7c3..fdedf370e 100755 --- a/deploy/selfhost/install.sh +++ b/deploy/selfhost/install.sh @@ -17,16 +17,16 @@ function print_header() { clear cat <<"EOF" ---------------------------------------- - ____ _ -| _ \| | __ _ _ __ ___ -| |_) | |/ _` | '_ \ / _ \ -| __/| | (_| | | | | __/ -|_| |_|\__,_|_| |_|\___| - ---------------------------------------- +-------------------------------------------- + ____ _ ///////// +| _ \| | __ _ _ __ ___ ///////// +| |_) | |/ _` | '_ \ / _ \ ///// ///// +| __/| | (_| | | | | __/ ///// ///// +|_| |_|\__,_|_| |_|\___| //// + //// +-------------------------------------------- Project management tool from the future ---------------------------------------- +-------------------------------------------- EOF } @@ -66,7 +66,7 @@ function buildLocalImage() { cd $PLANE_TEMP_CODE_DIR if [ "$BRANCH" == "master" ]; then - export APP_RELEASE=latest + export APP_RELEASE=stable fi docker compose -f build.yml build --no-cache >&2 @@ -99,17 +99,17 @@ function download() { curl -H 'Cache-Control: no-cache, no-store' -s -o $PLANE_INSTALL_DIR/docker-compose.yaml https://raw.githubusercontent.com/makeplane/plane/$BRANCH/deploy/selfhost/docker-compose.yml?$(date +%s) curl -H 'Cache-Control: no-cache, no-store' -s -o $PLANE_INSTALL_DIR/variables-upgrade.env https://raw.githubusercontent.com/makeplane/plane/$BRANCH/deploy/selfhost/variables.env?$(date +%s) - if [ -f "$PLANE_INSTALL_DIR/.env" ]; + if [ -f "$DOCKER_ENV_PATH" ]; then - cp $PLANE_INSTALL_DIR/.env $PLANE_INSTALL_DIR/archive/$TS.env + cp $DOCKER_ENV_PATH $PLANE_INSTALL_DIR/archive/$TS.env else - mv $PLANE_INSTALL_DIR/variables-upgrade.env $PLANE_INSTALL_DIR/.env + mv $PLANE_INSTALL_DIR/variables-upgrade.env $DOCKER_ENV_PATH fi if [ "$BRANCH" != "master" ]; then cp $PLANE_INSTALL_DIR/docker-compose.yaml $PLANE_INSTALL_DIR/temp.yaml - sed -e 's@${APP_RELEASE:-latest}@'"$BRANCH"'@g' \ + sed -e 's@${APP_RELEASE:-stable}@'"$BRANCH"'@g' \ $PLANE_INSTALL_DIR/temp.yaml > $PLANE_INSTALL_DIR/docker-compose.yaml rm $PLANE_INSTALL_DIR/temp.yaml @@ -131,9 +131,9 @@ function download() { fi echo "" - echo "Latest version is now available for you to use" + echo "Most recent Stable version is now available for you to use" echo "" - echo "In case of Upgrade, your new setting file is availabe as 'variables-upgrade.env'. Please compare and set the required values in '.env 'file." + echo "In case of Upgrade, your new setting file is availabe as 'variables-upgrade.env'. Please compare and set the required values in 'plane.env 'file." echo "" } @@ -144,7 +144,7 @@ function startServices() { if [ -n "$migrator_container_id" ]; then local idx=0 while docker inspect --format='{{.State.Status}}' $migrator_container_id | grep -q "running"; do - local message=">>> Waiting for Data Migration to finish" + local message=">> Waiting for Data Migration to finish" local dots=$(printf '%*s' $idx | tr ' ' '.') echo -ne "\r$message$dots" ((idx++)) @@ -152,13 +152,18 @@ function startServices() { done fi printf "\r\033[K" + echo "" + echo " Data Migration completed successfully ✅" # if migrator exit status is not 0, show error message and exit if [ -n "$migrator_container_id" ]; then local migrator_exit_code=$(docker inspect --format='{{.State.ExitCode}}' $migrator_container_id) if [ $migrator_exit_code -ne 0 ]; then echo "Plane Server failed to start ❌" - stopServices + # stopServices + echo + echo "Please check the logs for the 'migrator' service and resolve the issue(s)." + echo "Stop the services by running the command: ./setup.sh stop" exit 1 fi fi @@ -167,26 +172,35 @@ function startServices() { local idx2=0 while ! docker logs $api_container_id 2>&1 | grep -m 1 -i "Application startup complete" | grep -q "."; do - local message=">>> Waiting for API Service to Start" + local message=">> Waiting for API Service to Start" local dots=$(printf '%*s' $idx2 | tr ' ' '.') echo -ne "\r$message$dots" ((idx2++)) sleep 1 done printf "\r\033[K" + echo " API Service started successfully ✅" + source "${DOCKER_ENV_PATH}" + echo " Plane Server started successfully ✅" + echo "" + echo " You can access the application at $WEB_URL" + echo "" + } function stopServices() { docker compose -f $DOCKER_FILE_PATH --env-file=$DOCKER_ENV_PATH down } function restartServices() { - docker compose -f $DOCKER_FILE_PATH --env-file=$DOCKER_ENV_PATH restart + # docker compose -f $DOCKER_FILE_PATH --env-file=$DOCKER_ENV_PATH restart + stopServices + startServices } function upgrade() { echo "***** STOPPING SERVICES ****" stopServices echo - echo "***** DOWNLOADING LATEST VERSION ****" + echo "***** DOWNLOADING STABLE VERSION ****" download echo "***** PLEASE VALIDATE AND START SERVICES ****" @@ -303,15 +317,15 @@ function askForAction() { elif [ "$ACTION" == "2" ] || [ "$DEFAULT_ACTION" == "start" ] then startServices - askForAction + # askForAction elif [ "$ACTION" == "3" ] || [ "$DEFAULT_ACTION" == "stop" ] then stopServices - askForAction + # askForAction elif [ "$ACTION" == "4" ] || [ "$DEFAULT_ACTION" == "restart" ] then restartServices - askForAction + # askForAction elif [ "$ACTION" == "5" ] || [ "$DEFAULT_ACTION" == "upgrade" ] then upgrade @@ -343,7 +357,7 @@ fi if [ "$BRANCH" == "master" ]; then - export APP_RELEASE=latest + export APP_RELEASE=stable fi # REMOVE SPECIAL CHARACTERS FROM BRANCH NAME @@ -354,7 +368,21 @@ fi mkdir -p $PLANE_INSTALL_DIR/archive DOCKER_FILE_PATH=$PLANE_INSTALL_DIR/docker-compose.yaml -DOCKER_ENV_PATH=$PLANE_INSTALL_DIR/.env +DOCKER_ENV_PATH=$PLANE_INSTALL_DIR/plane.env + +# BACKWARD COMPATIBILITY +OLD_DOCKER_ENV_PATH=$PLANE_INSTALL_DIR/.env +if [ -f "$OLD_DOCKER_ENV_PATH" ]; +then + mv "$OLD_DOCKER_ENV_PATH" "$DOCKER_ENV_PATH" + OS_NAME=$(uname) + if [ "$OS_NAME" == "Darwin" ]; + then + sed -i '' -e 's@APP_RELEASE=latest@APP_RELEASE=stable@' "$DOCKER_ENV_PATH" + else + sed -i -e 's@APP_RELEASE=latest@APP_RELEASE=stable@' "$DOCKER_ENV_PATH" + fi +fi print_header askForAction $@ diff --git a/deploy/selfhost/variables.env b/deploy/selfhost/variables.env index fe79b934c..e37350cf4 100644 --- a/deploy/selfhost/variables.env +++ b/deploy/selfhost/variables.env @@ -1,4 +1,4 @@ -APP_RELEASE=latest +APP_RELEASE=stable WEB_REPLICAS=1 SPACE_REPLICAS=1 @@ -41,4 +41,4 @@ BUCKET_NAME=uploads FILE_SIZE_LIMIT=5242880 # Gunicorn Workers -GUNICORN_WORKERS=2 +GUNICORN_WORKERS=1 diff --git a/nginx/nginx.conf.template b/nginx/nginx.conf.template index ee2532bf5..5b438139c 100644 --- a/nginx/nginx.conf.template +++ b/nginx/nginx.conf.template @@ -39,7 +39,7 @@ http { } location /${BUCKET_NAME}/ { - proxy_pass http://plane-minio:9000/uploads/; + proxy_pass http://plane-minio:9000/${BUCKET_NAME}/; } } } diff --git a/package.json b/package.json index 9239a9b41..c87384f39 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "repository": "https://github.com/makeplane/plane.git", - "version": "0.16.0", + "version": "0.17.0", "license": "AGPL-3.0", "private": true, "workspaces": [ diff --git a/packages/editor/core/package.json b/packages/editor/core/package.json index 571fb8588..a0d7351d3 100644 --- a/packages/editor/core/package.json +++ b/packages/editor/core/package.json @@ -1,6 +1,6 @@ { "name": "@plane/editor-core", - "version": "0.16.0", + "version": "0.17.0", "description": "Core Editor that powers Plane", "private": true, "main": "./dist/index.mjs", diff --git a/packages/editor/core/src/ui/components/editor-container.tsx b/packages/editor/core/src/ui/components/editor-container.tsx index 1b2504b58..2d6081525 100644 --- a/packages/editor/core/src/ui/components/editor-container.tsx +++ b/packages/editor/core/src/ui/components/editor-container.tsx @@ -1,5 +1,6 @@ import { Editor } from "@tiptap/react"; import { FC, ReactNode } from "react"; +import { cn } from "src/lib/utils"; interface EditorContainerProps { editor: Editor | null; @@ -53,7 +54,7 @@ export const EditorContainer: FC = (props) => { onMouseLeave={() => { hideDragHandle?.(); }} - className={`cursor-text ${editorClassNames}`} + className={cn(`cursor-text`, { "active-editor": editor?.isFocused && editor?.isEditable }, editorClassNames)} > {children} diff --git a/packages/editor/core/src/ui/mentions/suggestion.ts b/packages/editor/core/src/ui/mentions/suggestion.ts index 40e75a1e3..3f1b8eeec 100644 --- a/packages/editor/core/src/ui/mentions/suggestion.ts +++ b/packages/editor/core/src/ui/mentions/suggestion.ts @@ -22,7 +22,7 @@ export const Suggestion = (suggestions: IMentionSuggestion[]) => ({ // @ts-ignore popup = tippy("body", { getReferenceClientRect: props.clientRect, - appendTo: () => document.querySelector("#editor-container"), + appendTo: () => document.querySelector(".active-editor") ?? document.querySelector("#editor-container"), content: reactRenderer.element, showOnCreate: true, interactive: true, diff --git a/packages/editor/document-editor/package.json b/packages/editor/document-editor/package.json index 870d5edd9..be00fcce4 100644 --- a/packages/editor/document-editor/package.json +++ b/packages/editor/document-editor/package.json @@ -1,6 +1,6 @@ { "name": "@plane/document-editor", - "version": "0.16.0", + "version": "0.17.0", "description": "Package that powers Plane's Pages Editor", "main": "./dist/index.mjs", "module": "./dist/index.mjs", diff --git a/packages/editor/extensions/package.json b/packages/editor/extensions/package.json index f95aa4d7e..711ab8a96 100644 --- a/packages/editor/extensions/package.json +++ b/packages/editor/extensions/package.json @@ -1,6 +1,6 @@ { "name": "@plane/editor-extensions", - "version": "0.16.0", + "version": "0.17.0", "description": "Package that powers Plane's Editor with extensions", "private": true, "main": "./dist/index.mjs", diff --git a/packages/editor/extensions/src/extensions/slash-commands.tsx b/packages/editor/extensions/src/extensions/slash-commands.tsx index f37d18c68..c52178b81 100644 --- a/packages/editor/extensions/src/extensions/slash-commands.tsx +++ b/packages/editor/extensions/src/extensions/slash-commands.tsx @@ -330,7 +330,7 @@ const renderItems = () => { // @ts-ignore popup = tippy("body", { getReferenceClientRect: props.clientRect, - appendTo: () => document.querySelector("#editor-container"), + appendTo: () => document.querySelector(".active-editor") ?? document.querySelector("#editor-container"), content: component.element, showOnCreate: true, interactive: true, diff --git a/packages/editor/lite-text-editor/package.json b/packages/editor/lite-text-editor/package.json index c84cb7a9b..4ac62e378 100644 --- a/packages/editor/lite-text-editor/package.json +++ b/packages/editor/lite-text-editor/package.json @@ -1,6 +1,6 @@ { "name": "@plane/lite-text-editor", - "version": "0.16.0", + "version": "0.17.0", "description": "Package that powers Plane's Comment Editor", "private": true, "main": "./dist/index.mjs", diff --git a/packages/editor/rich-text-editor/package.json b/packages/editor/rich-text-editor/package.json index 794650678..117cf5c41 100644 --- a/packages/editor/rich-text-editor/package.json +++ b/packages/editor/rich-text-editor/package.json @@ -1,6 +1,6 @@ { "name": "@plane/rich-text-editor", - "version": "0.16.0", + "version": "0.17.0", "description": "Rich Text Editor that powers Plane", "private": true, "main": "./dist/index.mjs", diff --git a/packages/eslint-config-custom/package.json b/packages/eslint-config-custom/package.json index f7577ab87..5a5216764 100644 --- a/packages/eslint-config-custom/package.json +++ b/packages/eslint-config-custom/package.json @@ -1,7 +1,7 @@ { "name": "eslint-config-custom", "private": true, - "version": "0.16.0", + "version": "0.17.0", "main": "index.js", "license": "MIT", "devDependencies": {}, diff --git a/packages/tailwind-config-custom/package.json b/packages/tailwind-config-custom/package.json index d7e807b91..8fc1aa599 100644 --- a/packages/tailwind-config-custom/package.json +++ b/packages/tailwind-config-custom/package.json @@ -1,6 +1,6 @@ { "name": "tailwind-config-custom", - "version": "0.16.0", + "version": "0.17.0", "description": "common tailwind configuration across monorepo", "main": "index.js", "private": true, diff --git a/packages/tsconfig/package.json b/packages/tsconfig/package.json index e0829e87b..30d9d9f73 100644 --- a/packages/tsconfig/package.json +++ b/packages/tsconfig/package.json @@ -1,6 +1,6 @@ { "name": "tsconfig", - "version": "0.16.0", + "version": "0.17.0", "private": true, "files": [ "base.json", diff --git a/packages/types/package.json b/packages/types/package.json index 9c9938845..c96bc9f59 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@plane/types", - "version": "0.16.0", + "version": "0.17.0", "private": true, "main": "./src/index.d.ts" } diff --git a/packages/ui/package.json b/packages/ui/package.json index fdd67dcc1..c65789aa7 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -2,7 +2,7 @@ "name": "@plane/ui", "description": "UI components shared across multiple apps internally", "private": true, - "version": "0.16.0", + "version": "0.17.0", "main": "./dist/index.js", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", diff --git a/packages/ui/src/dropdowns/custom-select.tsx b/packages/ui/src/dropdowns/custom-select.tsx index 37608ea8d..2d669cc05 100644 --- a/packages/ui/src/dropdowns/custom-select.tsx +++ b/packages/ui/src/dropdowns/custom-select.tsx @@ -91,7 +91,7 @@ const CustomSelect = (props: ICustomSelectProps) => { )} {isOpen && ( - closeDropdown()} static> + closeDropdown()} static>
= ({ defaultAnalytics }) => (

Total open tasks

{defaultAnalytics.open_issues}

-
+
{defaultAnalytics?.open_issues_classified.map((group) => { const percentage = ((group.state_count / defaultAnalytics.total_issues) * 100).toFixed(0); @@ -50,14 +48,5 @@ export const AnalyticsDemand: React.FC = ({ defaultAnalytics }) => ( ); })}
-
-

- - Estimate Demand: -

-

- {defaultAnalytics.open_estimate_sum}/{defaultAnalytics.total_estimate_sum} -

-
); diff --git a/web/components/command-palette/actions/help-actions.tsx b/web/components/command-palette/actions/help-actions.tsx index 10c7675d3..539ec00aa 100644 --- a/web/components/command-palette/actions/help-actions.tsx +++ b/web/components/command-palette/actions/help-actions.tsx @@ -69,7 +69,9 @@ export const CommandPaletteHelpActions: React.FC = (props) => { { closePalette(); - (window as any)?.$crisp.push(["do", "chat:open"]); + if (window) { + window.$crisp.push(["do", "chat:show"]); + } }} className="focus:outline-none" > diff --git a/web/components/command-palette/command-modal.tsx b/web/components/command-palette/command-modal.tsx index ddbf45dc8..aa36eabed 100644 --- a/web/components/command-palette/command-modal.tsx +++ b/web/components/command-palette/command-modal.tsx @@ -40,7 +40,7 @@ const issueService = new IssueService(); export const CommandModal: React.FC = observer(() => { // hooks - const { getProjectById } = useProject(); + const { getProjectById, workspaceProjectIds } = useProject(); const { isMobile } = usePlatformOS(); // states const [placeholder, setPlaceholder] = useState("Type a command or search..."); @@ -282,22 +282,24 @@ export const CommandModal: React.FC = observer(() => { setSearchTerm={(newSearchTerm) => setSearchTerm(newSearchTerm)} /> )} - - { - closePalette(); - setTrackElement("Command Palette"); - toggleCreateIssueModal(true); - }} - className="focus:bg-custom-background-80" - > -
- - Create new issue -
- C -
-
+ {workspaceSlug && workspaceProjectIds && workspaceProjectIds.length > 0 && ( + + { + closePalette(); + setTrackElement("Command Palette"); + toggleCreateIssueModal(true); + }} + className="focus:bg-custom-background-80" + > +
+ + Create new issue +
+ C +
+
+ )} {workspaceSlug && ( diff --git a/web/components/command-palette/shortcuts-modal/modal.tsx b/web/components/command-palette/shortcuts-modal/modal.tsx index e1fd67057..bbaa55464 100644 --- a/web/components/command-palette/shortcuts-modal/modal.tsx +++ b/web/components/command-palette/shortcuts-modal/modal.tsx @@ -36,18 +36,18 @@ export const ShortcutsModal: FC = (props) => {
-
- - -
+
+
+ +
Keyboard shortcuts @@ -71,9 +71,9 @@ export const ShortcutsModal: FC = (props) => {
-
- - + + +
diff --git a/web/components/headers/workspace-dashboard.tsx b/web/components/headers/workspace-dashboard.tsx index 99e605ee6..880b44406 100644 --- a/web/components/headers/workspace-dashboard.tsx +++ b/web/components/headers/workspace-dashboard.tsx @@ -1,6 +1,6 @@ import Image from "next/image"; import { useTheme } from "next-themes"; -import { LayoutGrid, Zap } from "lucide-react"; +import { Home, Zap } from "lucide-react"; // images import githubBlackImage from "/public/logos/github-black.png"; import githubWhiteImage from "/public/logos/github-white.png"; @@ -25,9 +25,7 @@ export const WorkspaceDashboardHeader = () => { } /> - } + link={} />} />
diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/draft-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/draft-issue.tsx index b773e7031..9502e7623 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/draft-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/draft-issue.tsx @@ -1,20 +1,21 @@ import { useState } from "react"; import omit from "lodash/omit"; import { observer } from "mobx-react"; -// hooks -import { Copy, Pencil, Trash2 } from "lucide-react"; +// icons +import { Pencil, Trash2 } from "lucide-react"; +// types import { TIssue } from "@plane/types"; +// ui import { CustomMenu } from "@plane/ui"; +// components import { CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues"; +// constant import { EIssuesStoreType } from "@/constants/issue"; import { EUserProjectRoles } from "@/constants/project"; +// hooks import { useEventTracker, useIssues, useUser } from "@/hooks/store"; -// ui -// components -// helpers // types import { IQuickActionProps } from "../list/list-view-types"; -// constant export const DraftIssueQuickActions: React.FC = observer((props) => { const { issue, handleDelete, handleUpdate, customActionButton, portalElement, readOnly = false } = props; @@ -89,19 +90,6 @@ export const DraftIssueQuickActions: React.FC = observer((pro )} - {isEditingAllowed && ( - { - setTrackElement(activeLayout); - setCreateUpdateIssueModal(true); - }} - > -
- - Make a copy -
-
- )} {isDeletingAllowed && ( { diff --git a/web/components/issues/issue-modal/draft-issue-layout.tsx b/web/components/issues/issue-modal/draft-issue-layout.tsx index 5ec7e3a99..df89d363e 100644 --- a/web/components/issues/issue-modal/draft-issue-layout.tsx +++ b/web/components/issues/issue-modal/draft-issue-layout.tsx @@ -84,7 +84,7 @@ export const DraftIssueLayout: React.FC = observer((props) => { const payload = { ...changesMade, - name: changesMade?.name && changesMade?.name?.trim() === "" ? changesMade.name?.trim() : "Untitled", + name: changesMade?.name && changesMade?.name?.trim() !== "" ? changesMade.name?.trim() : "Untitled", }; await issueDraftService diff --git a/web/components/issues/issue-modal/form.tsx b/web/components/issues/issue-modal/form.tsx index 6c9872369..6112a27fe 100644 --- a/web/components/issues/issue-modal/form.tsx +++ b/web/components/issues/issue-modal/form.tsx @@ -26,6 +26,7 @@ import { renderFormattedPayloadDate, getDate } from "@/helpers/date-time.helper" import { getChangedIssuefields } from "@/helpers/issue.helper"; import { shouldRenderProject } from "@/helpers/project.helper"; import { useApplication, useEstimate, useIssueDetail, useMention, useProject, useWorkspace } from "@/hooks/store"; +import { useProjectIssueProperties } from "@/hooks/use-project-issue-properties"; // services import { AIService } from "@/services/ai.service"; import { FileService } from "@/services/file.service"; @@ -121,6 +122,7 @@ export const IssueFormRoot: FC = observer((props) => { // store hooks const { config: { envConfig }, + router: { projectId: routeProjectId }, } = useApplication(); const { getProjectById } = useProject(); const { areEstimatesEnabledForProject } = useEstimate(); @@ -128,6 +130,7 @@ export const IssueFormRoot: FC = observer((props) => { const { issue: { getIssueById }, } = useIssueDetail(); + const { fetchCycles } = useProjectIssueProperties(); // form info const { formState: { errors, isDirty, isSubmitting, dirtyFields }, @@ -160,6 +163,7 @@ export const IssueFormRoot: FC = observer((props) => { parent_id: formData.parent_id, }); } + if (projectId && routeProjectId !== projectId) fetchCycles(workspaceSlug, projectId); // eslint-disable-next-line react-hooks/exhaustive-deps }, [projectId]); diff --git a/web/components/onboarding/onboarding-sidebar.tsx b/web/components/onboarding/onboarding-sidebar.tsx index eba9f2d33..695dbdd5f 100644 --- a/web/components/onboarding/onboarding-sidebar.tsx +++ b/web/components/onboarding/onboarding-sidebar.tsx @@ -10,11 +10,11 @@ import { ContrastIcon, FileText, LayersIcon, - LayoutGrid, PenSquare, Search, Settings, Bell, + Home, } from "lucide-react"; import { IWorkspace } from "@plane/types"; import { Avatar, DiceIcon, PhotoFilterIcon } from "@plane/ui"; @@ -26,8 +26,8 @@ import projectEmoji from "public/emoji/project-emoji.svg"; const workspaceLinks = [ { - Icon: LayoutGrid, - name: "Dashboard", + Icon: Home, + name: "Home", }, { Icon: BarChart2, diff --git a/web/components/project/create-project-form.tsx b/web/components/project/create-project-form.tsx index 14d723e6d..27a19d7f8 100644 --- a/web/components/project/create-project-form.tsx +++ b/web/components/project/create-project-form.tsx @@ -226,7 +226,7 @@ export const CreateProjectForm: FC = observer((props) => { control={control} name="name" rules={{ - required: "Title is required", + required: "Name is required", maxLength: { value: 255, message: "Title should be less than 255 characters", @@ -240,7 +240,7 @@ export const CreateProjectForm: FC = observer((props) => { value={value} onChange={handleNameChange(onChange)} hasError={Boolean(errors.name)} - placeholder="Project title" + placeholder="Project name" className="w-full focus:border-blue-400" tabIndex={1} /> diff --git a/web/components/project/form.tsx b/web/components/project/form.tsx index 3e770a618..98a3e8d02 100644 --- a/web/components/project/form.tsx +++ b/web/components/project/form.tsx @@ -1,7 +1,7 @@ import { FC, useEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; // icons -import { Lock } from "lucide-react"; +import { Info, Lock } from "lucide-react"; import { IProject, IWorkspace } from "@plane/types"; // ui import { @@ -13,6 +13,7 @@ import { setToast, CustomEmojiIconPicker, EmojiIconPickerTypes, + Tooltip, } from "@plane/ui"; // components import { ImagePickerPopover } from "@/components/core"; @@ -24,6 +25,7 @@ import { renderFormattedDate } from "@/helpers/date-time.helper"; // hooks import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper"; import { useEventTracker, useProject } from "@/hooks/store"; +import { usePlatformOS } from "@/hooks/use-platform-os"; // services import { ProjectService } from "@/services/project"; // types @@ -42,6 +44,7 @@ export const ProjectDetailsForm: FC = (props) => { // store hooks const { captureProjectEvent } = useEventTracker(); const { updateProject } = useProject(); + const { isMobile } = usePlatformOS(); // form info const { handleSubmit, @@ -229,6 +232,9 @@ export const ProjectDetailsForm: FC = (props) => { /> )} /> + + <>{errors?.name?.message} +

Description

@@ -249,39 +255,54 @@ export const ProjectDetailsForm: FC = (props) => { )} />
-
+
-

Identifier

- /^[A-Z0-9]+$/.test(value.toUpperCase()) || "Identifier must be in uppercase.", - minLength: { - value: 1, - message: "Identifier must at least be of 1 character", - }, - maxLength: { - value: 12, - message: "Identifier must at most be of 5 characters", - }, - }} - render={({ field: { value, ref } }) => ( - - )} - /> +

Project ID

+
+ + /^[ÇŞĞIİÖÜA-Z0-9]+$/.test(value.toUpperCase()) || + "Only Alphanumeric & Non-latin characters are allowed.", + minLength: { + value: 1, + message: "Project ID must at least be of 1 character", + }, + maxLength: { + value: 5, + message: "Project ID must at most be of 5 characters", + }, + }} + render={({ field: { value, ref } }) => ( + + )} + /> + + + +
+ + <>{errors?.identifier?.message} +

Network

diff --git a/web/hooks/use-project-issue-properties.ts b/web/hooks/use-project-issue-properties.ts new file mode 100644 index 000000000..195dd4e5c --- /dev/null +++ b/web/hooks/use-project-issue-properties.ts @@ -0,0 +1,89 @@ +import { useCycle, useEstimate, useLabel, useMember, useModule, useProjectState } from "./store"; + +export const useProjectIssueProperties = () => { + const { fetchProjectStates } = useProjectState(); + const { + project: { fetchProjectMembers }, + } = useMember(); + const { fetchProjectLabels } = useLabel(); + const { fetchAllCycles: fetchProjectAllCycles } = useCycle(); + const { fetchModules: fetchProjectAllModules } = useModule(); + const { fetchProjectEstimates } = useEstimate(); + + // fetching project states + const fetchStates = async ( + workspaceSlug: string | string[] | undefined, + projectId: string | string[] | undefined + ) => { + if (workspaceSlug && projectId) { + await fetchProjectStates(workspaceSlug.toString(), projectId.toString()); + } + }; + // fetching project members + const fetchMembers = async ( + workspaceSlug: string | string[] | undefined, + projectId: string | string[] | undefined + ) => { + if (workspaceSlug && projectId) { + await fetchProjectMembers(workspaceSlug.toString(), projectId.toString()); + } + }; + + // fetching project labels + const fetchLabels = async ( + workspaceSlug: string | string[] | undefined, + projectId: string | string[] | undefined + ) => { + if (workspaceSlug && projectId) { + await fetchProjectLabels(workspaceSlug.toString(), projectId.toString()); + } + }; + // fetching project cycles + const fetchCycles = async ( + workspaceSlug: string | string[] | undefined, + projectId: string | string[] | undefined + ) => { + if (workspaceSlug && projectId) { + await fetchProjectAllCycles(workspaceSlug.toString(), projectId.toString()); + } + }; + // fetching project modules + const fetchModules = async ( + workspaceSlug: string | string[] | undefined, + projectId: string | string[] | undefined + ) => { + if (workspaceSlug && projectId) { + await fetchProjectAllModules(workspaceSlug.toString(), projectId.toString()); + } + }; + // fetching project estimates + const fetchEstimates = async ( + workspaceSlug: string | string[] | undefined, + projectId: string | string[] | undefined + ) => { + if (workspaceSlug && projectId) { + await fetchProjectEstimates(workspaceSlug.toString(), projectId.toString()); + } + }; + + const fetchAll = async (workspaceSlug: string | string[] | undefined, projectId: string | string[] | undefined) => { + if (workspaceSlug && projectId) { + await fetchStates(workspaceSlug, projectId); + await fetchMembers(workspaceSlug, projectId); + await fetchLabels(workspaceSlug, projectId); + await fetchCycles(workspaceSlug, projectId); + await fetchModules(workspaceSlug, projectId); + await fetchEstimates(workspaceSlug, projectId); + } + }; + + return { + fetchAll, + fetchStates, + fetchMembers, + fetchLabels, + fetchCycles, + fetchModules, + fetchEstimates, + }; +}; diff --git a/web/package.json b/web/package.json index 3ec941b80..de73ad407 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "web", - "version": "0.16.0", + "version": "0.17.0", "private": true, "scripts": { "dev": "turbo run develop", diff --git a/web/pages/[workspaceSlug]/index.tsx b/web/pages/[workspaceSlug]/index.tsx index f7931ce90..19b8b1b7e 100644 --- a/web/pages/[workspaceSlug]/index.tsx +++ b/web/pages/[workspaceSlug]/index.tsx @@ -14,7 +14,7 @@ import { NextPageWithLayout } from "@/lib/types"; const WorkspacePage: NextPageWithLayout = observer(() => { const { currentWorkspace } = useWorkspace(); // derived values - const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Dashboard` : undefined; + const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Home` : undefined; return ( <> diff --git a/web/pages/invitations/index.tsx b/web/pages/invitations/index.tsx index 7fdb4f03e..5d22ab587 100644 --- a/web/pages/invitations/index.tsx +++ b/web/pages/invitations/index.tsx @@ -220,7 +220,7 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => { description="You can see here if someone invites you to a workspace." image={emptyInvitation} primaryButton={{ - text: "Back to dashboard", + text: "Back to home", onClick: () => router.push("/"), }} /> diff --git a/web/pages/workspace-invitations/index.tsx b/web/pages/workspace-invitations/index.tsx index 7018eef33..85ed5e593 100644 --- a/web/pages/workspace-invitations/index.tsx +++ b/web/pages/workspace-invitations/index.tsx @@ -81,7 +81,7 @@ const WorkspaceInvitationPage: NextPageWithLayout = observer(() => { title={`You are already a member of ${invitationDetail.workspace.name}`} description="Your workspace is where you'll create projects, collaborate on your issues, and organize different streams of work in your Plane account." > - + ) : ( @@ -105,7 +105,7 @@ const WorkspaceInvitationPage: NextPageWithLayout = observer(() => { {!currentUser ? ( ) : ( - + )}