Merge branch 'develop' of github.com:makeplane/plane into update-file-uploads

This commit is contained in:
pablohashescobar 2024-04-02 12:51:14 +05:30
commit d560d1f7df
40 changed files with 298 additions and 177 deletions

View File

@ -6,7 +6,6 @@ on:
branches:
- master
- preview
- develop
release:
types: [released, prereleased]

View File

@ -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.

View File

@ -1,4 +1,4 @@
{
"name": "plane-api",
"version": "0.16.0"
"version": "0.17.0"
}

View File

@ -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

View File

@ -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 $@

View File

@ -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

View File

@ -39,7 +39,7 @@ http {
}
location /${BUCKET_NAME}/ {
proxy_pass http://plane-minio:9000/uploads/;
proxy_pass http://plane-minio:9000/${BUCKET_NAME}/;
}
}
}

View File

@ -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": [

View File

@ -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",

View File

@ -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<EditorContainerProps> = (props) => {
onMouseLeave={() => {
hideDragHandle?.();
}}
className={`cursor-text ${editorClassNames}`}
className={cn(`cursor-text`, { "active-editor": editor?.isFocused && editor?.isEditable }, editorClassNames)}
>
{children}
</div>

View File

@ -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,

View File

@ -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",

View File

@ -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",

View File

@ -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,

View File

@ -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",

View File

@ -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",

View File

@ -1,7 +1,7 @@
{
"name": "eslint-config-custom",
"private": true,
"version": "0.16.0",
"version": "0.17.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {},

View File

@ -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,

View File

@ -1,6 +1,6 @@
{
"name": "tsconfig",
"version": "0.16.0",
"version": "0.17.0",
"private": true,
"files": [
"base.json",

View File

@ -1,6 +1,6 @@
{
"name": "@plane/types",
"version": "0.16.0",
"version": "0.17.0",
"private": true,
"main": "./src/index.d.ts"
}

View File

@ -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",

View File

@ -91,7 +91,7 @@ const CustomSelect = (props: ICustomSelectProps) => {
)}
</>
{isOpen && (
<Listbox.Options className="fixed z-10" onClick={() => closeDropdown()} static>
<Listbox.Options className="fixed z-20" onClick={() => closeDropdown()} static>
<div
className={cn(
"my-1 overflow-y-scroll rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 text-xs shadow-custom-shadow-rg focus:outline-none min-w-[12rem] whitespace-nowrap",

View File

@ -1,6 +1,6 @@
{
"name": "space",
"version": "0.16.0",
"version": "0.17.0",
"private": true,
"scripts": {
"dev": "turbo run develop",

View File

@ -1,9 +1,7 @@
// icons
import { Triangle } from "lucide-react";
import { IDefaultAnalyticsResponse, TStateGroups } from "@plane/types";
// types
import { STATE_GROUPS } from "@/constants/state";
import { IDefaultAnalyticsResponse, TStateGroups } from "@plane/types";
// constants
import { STATE_GROUPS } from "@/constants/state";
type Props = {
defaultAnalytics: IDefaultAnalyticsResponse;
@ -16,7 +14,7 @@ export const AnalyticsDemand: React.FC<Props> = ({ defaultAnalytics }) => (
<h4 className="text-base font-medium text-custom-text-100">Total open tasks</h4>
<h3 className="mt-1 text-xl font-semibold">{defaultAnalytics.open_issues}</h3>
</div>
<div className="space-y-6">
<div className="space-y-6 pb-2">
{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<Props> = ({ defaultAnalytics }) => (
);
})}
</div>
<div className="!mt-6 flex w-min items-center gap-2 whitespace-nowrap rounded-md border border-custom-border-200 bg-custom-background-80 p-2 text-xs">
<p className="flex items-center gap-1 text-custom-text-200">
<Triangle className="h-4 w-4" />
<span>Estimate Demand:</span>
</p>
<p className="font-medium">
{defaultAnalytics.open_estimate_sum}/{defaultAnalytics.total_estimate_sum}
</p>
</div>
</div>
);

View File

@ -69,7 +69,9 @@ export const CommandPaletteHelpActions: React.FC<Props> = (props) => {
<Command.Item
onSelect={() => {
closePalette();
(window as any)?.$crisp.push(["do", "chat:open"]);
if (window) {
window.$crisp.push(["do", "chat:show"]);
}
}}
className="focus:outline-none"
>

View File

@ -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,6 +282,7 @@ export const CommandModal: React.FC = observer(() => {
setSearchTerm={(newSearchTerm) => setSearchTerm(newSearchTerm)}
/>
)}
{workspaceSlug && workspaceProjectIds && workspaceProjectIds.length > 0 && (
<Command.Group heading="Issue">
<Command.Item
onSelect={() => {
@ -298,6 +299,7 @@ export const CommandModal: React.FC = observer(() => {
<kbd>C</kbd>
</Command.Item>
</Command.Group>
)}
{workspaceSlug && (
<Command.Group heading="Project">

View File

@ -36,7 +36,8 @@ export const ShortcutsModal: FC<Props> = (props) => {
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 h-full w-full overflow-y-auto">
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="my-10 flex items-center justify-center p-4 text-center sm:p-0 md:my-20">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
@ -46,8 +47,7 @@ export const ShortcutsModal: FC<Props> = (props) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="h-full w-full">
<div className="my-10 flex items-center justify-center p-4 text-center sm:p-0 md:my-20">
<Dialog.Panel className="relative flex h-full items-center justify-center">
<div className="flex h-[61vh] w-full flex-col space-y-4 overflow-hidden rounded-lg bg-custom-background-100 p-5 shadow-custom-shadow-md transition-all sm:w-[28rem]">
<Dialog.Title as="h3" className="flex justify-between">
<span className="text-lg font-medium">Keyboard shortcuts</span>
@ -71,10 +71,10 @@ export const ShortcutsModal: FC<Props> = (props) => {
</div>
<ShortcutCommandsList searchQuery={query} />
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);

View File

@ -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 = () => {
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink label="Dashboard" icon={<LayoutGrid className="h-4 w-4 text-custom-text-300" />} />
}
link={<BreadcrumbLink label="Home" icon={<Home className="h-4 w-4 text-custom-text-300" />} />}
/>
</Breadcrumbs>
</div>

View File

@ -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<IQuickActionProps> = observer((props) => {
const { issue, handleDelete, handleUpdate, customActionButton, portalElement, readOnly = false } = props;
@ -89,19 +90,6 @@ export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
</div>
</CustomMenu.MenuItem>
)}
{isEditingAllowed && (
<CustomMenu.MenuItem
onClick={() => {
setTrackElement(activeLayout);
setCreateUpdateIssueModal(true);
}}
>
<div className="flex items-center gap-2">
<Copy className="h-3 w-3" />
Make a copy
</div>
</CustomMenu.MenuItem>
)}
{isDeletingAllowed && (
<CustomMenu.MenuItem
onClick={() => {

View File

@ -84,7 +84,7 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = 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

View File

@ -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<IssueFormProps> = 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<IssueFormProps> = 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<IssueFormProps> = observer((props) => {
parent_id: formData.parent_id,
});
}
if (projectId && routeProjectId !== projectId) fetchCycles(workspaceSlug, projectId);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [projectId]);

View File

@ -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,

View File

@ -226,7 +226,7 @@ export const CreateProjectForm: FC<Props> = 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<Props> = 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}
/>

View File

@ -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<IProjectDetailsForm> = (props) => {
// store hooks
const { captureProjectEvent } = useEventTracker();
const { updateProject } = useProject();
const { isMobile } = usePlatformOS();
// form info
const {
handleSubmit,
@ -229,6 +232,9 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
/>
)}
/>
<span className="text-xs text-red-500">
<>{errors?.name?.message}</>
</span>
</div>
<div className="flex flex-col gap-1">
<h4 className="text-sm">Description</h4>
@ -249,22 +255,25 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
)}
/>
</div>
<div className="flex w-full items-center justify-between gap-10">
<div className="flex w-full justify-between gap-10">
<div className="flex w-1/2 flex-col gap-1">
<h4 className="text-sm">Identifier</h4>
<h4 className="text-sm">Project ID</h4>
<div className="relative">
<Controller
control={control}
name="identifier"
rules={{
required: "Identifier is required",
validate: (value) => /^[A-Z0-9]+$/.test(value.toUpperCase()) || "Identifier must be in uppercase.",
required: "Project ID is required",
validate: (value) =>
/^[ÇŞĞIİÖÜA-Z0-9]+$/.test(value.toUpperCase()) ||
"Only Alphanumeric & Non-latin characters are allowed.",
minLength: {
value: 1,
message: "Identifier must at least be of 1 character",
message: "Project ID must at least be of 1 character",
},
maxLength: {
value: 12,
message: "Identifier must at most be of 5 characters",
value: 5,
message: "Project ID must at most be of 5 characters",
},
}}
render={({ field: { value, ref } }) => (
@ -276,12 +285,24 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
onChange={handleIdentifierChange}
ref={ref}
hasError={Boolean(errors.identifier)}
placeholder="Enter identifier"
placeholder="Enter Project ID"
className="w-full font-medium"
disabled={!isAdmin}
/>
)}
/>
<Tooltip
isMobile={isMobile}
tooltipContent="Helps you identify issues in the project uniquely, (e.g. APP-123). Max 5 characters."
className="text-sm"
position="right-top"
>
<Info className="absolute right-2 top-2.5 h-4 w-4 text-custom-text-400" />
</Tooltip>
</div>
<span className="text-xs text-red-500">
<>{errors?.identifier?.message}</>
</span>
</div>
<div className="flex w-1/2 flex-col gap-1">
<h4 className="text-sm">Network</h4>

View File

@ -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,
};
};

View File

@ -1,6 +1,6 @@
{
"name": "web",
"version": "0.16.0",
"version": "0.17.0",
"private": true,
"scripts": {
"dev": "turbo run develop",

View File

@ -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 (
<>

View File

@ -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("/"),
}}
/>

View File

@ -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."
>
<EmptySpaceItem Icon={Boxes} title="Continue to Dashboard" href="/" />
<EmptySpaceItem Icon={Boxes} title="Continue to home" href="/" />
</EmptySpace>
</>
) : (
@ -105,7 +105,7 @@ const WorkspaceInvitationPage: NextPageWithLayout = observer(() => {
{!currentUser ? (
<EmptySpaceItem Icon={User2} title="Sign in to continue" href="/" />
) : (
<EmptySpaceItem Icon={Boxes} title="Continue to Dashboard" href="/" />
<EmptySpaceItem Icon={Boxes} title="Continue to home" href="/" />
)}
<EmptySpaceItem Icon={Star} title="Star us on GitHub" href="https://github.com/makeplane" />
<EmptySpaceItem

View File

@ -2733,7 +2733,7 @@
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^18.2.42":
"@types/react@*", "@types/react@18.2.42", "@types/react@^18.2.42":
version "18.2.42"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.42.tgz#6f6b11a904f6d96dda3c2920328a97011a00aba7"
integrity sha512-c1zEr96MjakLYus/wPnuWDo1/zErfdU9rNsIGmE+NV71nx88FG9Ttgo5dqorXTu/LImX2f63WBP986gJkMPNbA==