Compare commits

...
Author SHA1 Message Date
AaronandGitHub 2b406849a9 [WEB-4309] chore: migrate from Yarn to pnpm for package management #7198 2025-06-12 15:01:17 +05:30
Akshita GoyalandGitHub ad11a34efc [WEB-4236] fix: divided settings scroll for sidebar and main content (#7201)
* fix: divided settings scroll for sidebar and main content

* fix: handled icons

* fix: mobile css
2025-06-11 16:11:40 +05:30
Prateek ShouryaandGitHub 9c28db8b7b [WEB-4300] improvement: add allowedProjectIds to create work item modal (#7195) 2025-06-10 20:32:39 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
32d5fea3d3 chore(deps): bump requests (#7193)
Bumps the pip group with 1 update in the /apiserver/requirements directory: [requests](https://github.com/psf/requests).


Updates `requests` from 2.32.2 to 2.32.4
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.32.4
  dependency-type: direct:production
  dependency-group: pip
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-10 17:39:48 +05:30
Prateek ShouryaandGitHub 6adc721b34 [WEB-4283] fix: update group key handling in issue store utilities for state groups (#7191)
* fix: update group key handling in issue store utilities for state groups

- Introduced a new function to determine the default group key based on the provided groupByKey.
- Updated references to use the new function for improved clarity and maintainability.
- Adjusted the mapping for "state_detail.group" in the ISSUE_GROUP_BY_KEY to ensure consistency.
- Enhanced the getArrayStringArray method to handle group values more effectively.

* refactor: clean up filters constants
2025-06-10 13:56:42 +05:30
Anmol Singh BhatiaandGitHub 531748dcc3 [WEB-4288] fix: auth page tab index (#7189)
* fix: auth page tab index

* chore: code refactor
2025-06-10 01:47:59 +05:30
Sangmin AhnandGitHub 9965f48ba7 fix: prevent prematurely triggered Japanese label creation (#7084) 2025-06-09 16:07:42 +05:30
Saurabh KumarandGitHub d15d7549f7 [SILO-303] Add external id and external source in project model #7182 2025-06-09 16:02:09 +05:30
Vamsi KrishnaandGitHub 8fcffd2338 [WEB-4196]fix: sub work item copy link message #7186 2025-06-09 15:46:57 +05:30
Vamsi KrishnaandGitHub 07e937cd8e [WEB-4094]chore: workspace notifications refactor (#7061)
* chore: workspace notifications refactor

* fix: url params

* fix: added null checks to avoid run time errors

* fix: notifications header color fix
2025-06-09 15:33:57 +05:30
Farahat AbdrabouhandGitHub 1f1b421735 Docs: Correct numeric values in contributing guide #7184 2025-06-09 13:22:07 +05:30
sriram veeraghanta 5a43ec8411 chore: turbo repo version upgrade 2025-06-09 13:20:07 +05:30
sriram veeraghanta c86e7e02bc chore: upgrade tar-fs package to fix vulnerabilities 2025-06-09 13:19:14 +05:30
sriram veeraghanta d91d7a2f60 chore: tar-fs patch upgrade 2025-06-09 12:58:18 +05:30
67 changed files with 16870 additions and 563 deletions
+1 -2
View File
@@ -75,8 +75,7 @@ package-lock.json
# lock files
package-lock.json
pnpm-lock.yaml
pnpm-workspace.yaml
yarn.lock
.npmrc
.secrets
+4 -4
View File
@@ -69,14 +69,14 @@ chmod +x setup.sh
docker compose -f docker-compose-local.yml up
```
5. Start web apps:
4. Start web apps:
```bash
yarn dev
pnpm dev
```
6. Open your browser to http://localhost:3001/god-mode/ and register yourself as instance admin
7. Open up your browser to http://localhost:3000 then log in using the same credentials from the previous step
5. Open your browser to http://localhost:3001/god-mode/ and register yourself as instance admin
6. Open up your browser to http://localhost:3000 then log in using the same credentials from the previous step
Thats it! Youre all set to begin coding. Remember to refresh your browser if changes dont auto-reload. Happy contributing! 🎉
+10 -10
View File
@@ -16,13 +16,13 @@
},
"dependencies": {
"@headlessui/react": "^1.7.19",
"@plane/constants": "*",
"@plane/hooks": "*",
"@plane/propel": "*",
"@plane/services": "*",
"@plane/types": "*",
"@plane/ui": "*",
"@plane/utils": "*",
"@plane/constants": "workspace:*",
"@plane/hooks": "workspace:*",
"@plane/propel": "workspace:*",
"@plane/services": "workspace:*",
"@plane/types": "workspace:*",
"@plane/ui": "workspace:*",
"@plane/utils": "workspace:*",
"@tailwindcss/typography": "^0.5.9",
"@types/lodash": "^4.17.0",
"autoprefixer": "10.4.14",
@@ -42,9 +42,9 @@
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"@plane/eslint-config": "*",
"@plane/tailwind-config": "*",
"@plane/typescript-config": "*",
"@plane/eslint-config": "workspace:*",
"@plane/tailwind-config": "workspace:*",
"@plane/typescript-config": "workspace:*",
"@types/node": "18.16.1",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.2.18",
@@ -0,0 +1,23 @@
# Generated by Django 4.2.21 on 2025-06-06 12:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('db', '0096_user_is_email_valid_user_masked_at'),
]
operations = [
migrations.AddField(
model_name='project',
name='external_id',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='project',
name='external_source',
field=models.CharField(blank=True, max_length=255, null=True),
),
]
+3
View File
@@ -122,6 +122,9 @@ class Project(BaseModel):
# timezone
TIMEZONE_CHOICES = tuple(zip(pytz.all_timezones, pytz.all_timezones))
timezone = models.CharField(max_length=255, default="UTC", choices=TIMEZONE_CHOICES)
# external_id for imports
external_source = models.CharField(max_length=255, null=True, blank=True)
external_id = models.CharField(max_length=255, blank=True, null=True)
@property
def cover_image_url(self):
+1 -1
View File
@@ -9,4 +9,4 @@ factory-boy==3.3.0
freezegun==1.2.2
coverage==7.2.7
httpx==0.24.1
requests==2.32.2
requests==2.32.4
+3 -3
View File
@@ -20,9 +20,9 @@
"@hocuspocus/extension-logger": "^2.15.0",
"@hocuspocus/extension-redis": "^2.15.0",
"@hocuspocus/server": "^2.15.0",
"@plane/constants": "*",
"@plane/editor": "*",
"@plane/types": "*",
"@plane/constants": "workspace:*",
"@plane/editor": "workspace:*",
"@plane/types": "workspace:*",
"@tiptap/core": "2.10.4",
"@tiptap/html": "2.11.0",
"axios": "^1.8.3",
+2 -16
View File
@@ -5,13 +5,6 @@
"version": "0.26.1",
"license": "AGPL-3.0",
"private": true,
"workspaces": [
"web",
"space",
"admin",
"live",
"packages/*"
],
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev --concurrency=13",
@@ -24,14 +17,7 @@
"devDependencies": {
"prettier": "latest",
"prettier-plugin-tailwindcss": "^0.5.4",
"turbo": "^2.5.3"
"turbo": "^2.5.4"
},
"resolutions": {
"nanoid": "3.3.8",
"esbuild": "0.25.0",
"@babel/helpers": "7.26.10",
"@babel/runtime": "7.26.10",
"chokidar": "3.6.0"
},
"packageManager": "yarn@1.22.22"
"packageManager": "pnpm@10.12.1"
}
+1 -39
View File
@@ -136,45 +136,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_PAGE: TIssueFiltersToDisplayByPageType = {
],
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
display_filters: {
group_by: [
"state",
"cycle",
"module",
"state_detail.group",
"priority",
"labels",
"assignees",
"created_by",
null,
],
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
type: [null, "active", "backlog"],
},
extra_options: {
access: true,
values: ["show_empty_groups"],
},
},
},
draft_issues: {
list: {
filters: ["priority", "state_group", "cycle", "module", "labels", "start_date", "target_date", "issue_type"],
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
display_filters: {
group_by: ["state_detail.group", "cycle", "module", "priority", "project", "labels", null],
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
type: [null, "active", "backlog"],
},
extra_options: {
access: true,
values: ["show_empty_groups"],
},
},
kanban: {
filters: ["priority", "state_group", "cycle", "module", "labels", "start_date", "target_date", "issue_type"],
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
display_filters: {
group_by: ["state_detail.group", "cycle", "module", "priority", "project", "labels"],
group_by: ["state", "cycle", "module", "priority", "labels", "assignees", "created_by", null],
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
type: [null, "active", "backlog"],
},
+2 -2
View File
@@ -21,8 +21,8 @@
"reflect-metadata": "^0.2.2"
},
"devDependencies": {
"@plane/eslint-config": "*",
"@plane/typescript-config": "*",
"@plane/eslint-config": "workspace:*",
"@plane/typescript-config": "workspace:*",
"@types/express": "^4.17.21",
"@types/node": "^20.14.9",
"@types/reflect-metadata": "^0.1.0",
+23 -22
View File
@@ -36,27 +36,27 @@
"dependencies": {
"@floating-ui/react": "^0.26.4",
"@hocuspocus/provider": "^2.15.0",
"@plane/types": "*",
"@plane/ui": "*",
"@plane/utils": "*",
"@plane/types": "workspace:*",
"@plane/ui": "workspace:*",
"@plane/utils": "workspace:*",
"@tiptap/core": "2.10.4",
"@tiptap/extension-blockquote": "2.10.4",
"@tiptap/extension-character-count": "2.11.0",
"@tiptap/extension-collaboration": "2.11.0",
"@tiptap/extension-image": "2.11.0",
"@tiptap/extension-list-item": "2.11.0",
"@tiptap/extension-mention": "2.11.0",
"@tiptap/extension-placeholder": "2.11.0",
"@tiptap/extension-task-item": "2.11.0",
"@tiptap/extension-task-list": "2.11.0",
"@tiptap/extension-text-align": "2.11.0",
"@tiptap/extension-text-style": "2.11.0",
"@tiptap/extension-underline": "2.11.0",
"@tiptap/html": "2.11.0",
"@tiptap/pm": "2.11.0",
"@tiptap/react": "2.11.0",
"@tiptap/starter-kit": "2.11.0",
"@tiptap/suggestion": "2.11.0",
"@tiptap/extension-character-count": "2.10.4",
"@tiptap/extension-collaboration": "2.10.4",
"@tiptap/extension-image": "2.10.4",
"@tiptap/extension-list-item": "2.10.4",
"@tiptap/extension-mention": "2.10.4",
"@tiptap/extension-placeholder": "2.10.4",
"@tiptap/extension-task-item": "2.10.4",
"@tiptap/extension-task-list": "2.10.4",
"@tiptap/extension-text-align": "2.10.4",
"@tiptap/extension-text-style": "2.10.4",
"@tiptap/extension-underline": "2.10.4",
"@tiptap/html": "2.10.4",
"@tiptap/pm": "2.10.4",
"@tiptap/react": "2.10.4",
"@tiptap/starter-kit": "2.10.4",
"@tiptap/suggestion": "2.10.4",
"class-variance-authority": "^0.7.0",
"highlight.js": "^11.8.0",
"jsx-dom-cjs": "^8.0.3",
@@ -64,6 +64,7 @@
"lowlight": "^3.0.0",
"lucide-react": "^0.469.0",
"prosemirror-codemark": "^0.4.2",
"prosemirror-model": "^1.25.1",
"prosemirror-utils": "^1.2.2",
"tippy.js": "^6.3.7",
"tiptap-markdown": "^0.8.10",
@@ -74,9 +75,9 @@
"yjs": "^13.6.20"
},
"devDependencies": {
"@plane/eslint-config": "*",
"@plane/tailwind-config": "*",
"@plane/typescript-config": "*",
"@plane/eslint-config": "workspace:*",
"@plane/tailwind-config": "workspace:*",
"@plane/typescript-config": "workspace:*",
"@types/node": "18.15.3",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.2.18",
+5 -1
View File
@@ -1,4 +1,5 @@
import { EditorState, Selection } from "@tiptap/pm/state";
import { Node as ProsemirrorNode } from "prosemirror-model";
// plane imports
import { cn } from "@plane/utils";
// constants
@@ -21,7 +22,10 @@ export const getEditorClassNames = ({ noBorder, borderOnFocus, containerClassNam
);
// Helper function to find the parent node of a specific type
export function findParentNodeOfType(selection: Selection, typeName: string) {
export function findParentNodeOfType(selection: Selection, typeName: string): {
node: ProsemirrorNode;
pos: number;
} | null {
let depth = selection.$anchor.depth;
while (depth > 0) {
const node = selection.$anchor.node(depth);
@@ -14,21 +14,16 @@ export const setText = (editor: Editor, range?: Range) => {
export const toggleHeading = (editor: Editor, level: 1 | 2 | 3 | 4 | 5 | 6, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).setNode(CORE_EXTENSIONS.HEADING, { level }).run();
// @ts-expect-error tiptap types are incorrect
else editor.chain().focus().toggleHeading({ level }).run();
};
export const toggleBold = (editor: Editor, range?: Range) => {
// @ts-expect-error tiptap types are incorrect
if (range) editor.chain().focus().deleteRange(range).toggleBold().run();
// @ts-expect-error tiptap types are incorrect
else editor.chain().focus().toggleBold().run();
};
export const toggleItalic = (editor: Editor, range?: Range) => {
// @ts-expect-error tiptap types are incorrect
if (range) editor.chain().focus().deleteRange(range).toggleItalic().run();
// @ts-expect-error tiptap types are incorrect
else editor.chain().focus().toggleItalic().run();
};
@@ -67,16 +62,12 @@ export const toggleCodeBlock = (editor: Editor, range?: Range) => {
};
export const toggleOrderedList = (editor: Editor, range?: Range) => {
// @ts-expect-error tiptap types are incorrect
if (range) editor.chain().focus().deleteRange(range).toggleOrderedList().run();
// @ts-expect-error tiptap types are incorrect
else editor.chain().focus().toggleOrderedList().run();
};
export const toggleBulletList = (editor: Editor, range?: Range) => {
// @ts-expect-error tiptap types are incorrect
if (range) editor.chain().focus().deleteRange(range).toggleBulletList().run();
// @ts-expect-error tiptap types are incorrect
else editor.chain().focus().toggleBulletList().run();
};
@@ -86,9 +77,7 @@ export const toggleTaskList = (editor: Editor, range?: Range) => {
};
export const toggleStrike = (editor: Editor, range?: Range) => {
// @ts-expect-error tiptap types are incorrect
if (range) editor.chain().focus().deleteRange(range).toggleStrike().run();
// @ts-expect-error tiptap types are incorrect
else editor.chain().focus().toggleStrike().run();
};
@@ -12,9 +12,7 @@ import {
const RICH_TEXT_EDITOR_EXTENSIONS = CoreEditorExtensionsWithoutProps;
const DOCUMENT_EDITOR_EXTENSIONS = [...CoreEditorExtensionsWithoutProps, ...DocumentEditorExtensionsWithoutProps];
// editor schemas
// @ts-expect-error tiptap types are incorrect
const richTextEditorSchema = getSchema(RICH_TEXT_EDITOR_EXTENSIONS);
// @ts-expect-error tiptap types are incorrect
const documentEditorSchema = getSchema(DOCUMENT_EDITOR_EXTENSIONS);
/**
@@ -56,7 +54,6 @@ export const convertBase64StringToBinaryData = (document: string): ArrayBuffer =
*/
export const getBinaryDataFromRichTextEditorHTMLString = (descriptionHTML: string): Uint8Array => {
// convert HTML to JSON
// @ts-expect-error tiptap types are incorrect
const contentJSON = generateJSON(descriptionHTML ?? "<p></p>", RICH_TEXT_EDITOR_EXTENSIONS);
// convert JSON to Y.Doc format
const transformedData = prosemirrorJSONToYDoc(richTextEditorSchema, contentJSON, "default");
@@ -72,7 +69,6 @@ export const getBinaryDataFromRichTextEditorHTMLString = (descriptionHTML: strin
*/
export const getBinaryDataFromDocumentEditorHTMLString = (descriptionHTML: string): Uint8Array => {
// convert HTML to JSON
// @ts-expect-error tiptap types are incorrect
const contentJSON = generateJSON(descriptionHTML ?? "<p></p>", DOCUMENT_EDITOR_EXTENSIONS);
// convert JSON to Y.Doc format
const transformedData = prosemirrorJSONToYDoc(documentEditorSchema, contentJSON, "default");
@@ -101,7 +97,6 @@ export const getAllDocumentFormatsFromRichTextEditorBinaryData = (
const type = yDoc.getXmlFragment("default");
const contentJSON = yXmlFragmentToProseMirrorRootNode(type, richTextEditorSchema).toJSON();
// convert to HTML
// @ts-expect-error tiptap types are incorrect
const contentHTML = generateHTML(contentJSON, RICH_TEXT_EDITOR_EXTENSIONS);
return {
@@ -131,7 +126,6 @@ export const getAllDocumentFormatsFromDocumentEditorBinaryData = (
const type = yDoc.getXmlFragment("default");
const contentJSON = yXmlFragmentToProseMirrorRootNode(type, documentEditorSchema).toJSON();
// convert to HTML
// @ts-expect-error tiptap types are incorrect
const contentHTML = generateHTML(contentJSON, DOCUMENT_EDITOR_EXTENSIONS);
return {
+2 -1
View File
@@ -19,7 +19,8 @@
"react": "^18.3.1"
},
"devDependencies": {
"@plane/eslint-config": "*",
"@plane/eslint-config": "workspace:*",
"@plane/typescript-config": "workspace:*",
"@types/node": "^22.5.4",
"@types/react": "^18.3.11",
"tsup": "8.4.0",
+2 -2
View File
@@ -11,11 +11,11 @@
"lint:errors": "eslint src --ext .ts,.tsx --quiet"
},
"dependencies": {
"@plane/utils": "*",
"@plane/utils": "workspace:*",
"intl-messageformat": "^10.7.11"
},
"devDependencies": {
"@plane/eslint-config": "*",
"@plane/eslint-config": "workspace:*",
"@types/node": "^22.5.4",
"typescript": "^5.3.3"
}
+1 -1
View File
@@ -15,7 +15,7 @@
"winston-daily-rotate-file": "^5.0.0"
},
"devDependencies": {
"@plane/eslint-config": "*",
"@plane/eslint-config": "workspace:*",
"@types/node": "^22.5.4",
"typescript": "^5.3.3"
}
+3 -3
View File
@@ -24,9 +24,9 @@
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@plane/eslint-config": "*",
"@plane/tailwind-config": "*",
"@plane/typescript-config": "*",
"@plane/eslint-config": "workspace:*",
"@plane/tailwind-config": "workspace:*",
"@plane/typescript-config": "workspace:*",
"@types/react": "18.3.1",
"@types/react-dom": "18.3.0",
"typescript": "^5.3.3"
+1 -1
View File
@@ -9,7 +9,7 @@
"lint:errors": "eslint src --ext .ts,.tsx --quiet"
},
"dependencies": {
"@plane/constants": "*",
"@plane/constants": "workspace:*",
"axios": "^1.8.3"
}
}
+1 -1
View File
@@ -14,7 +14,7 @@
"zod": "^3.22.2"
},
"devDependencies": {
"@plane/eslint-config": "*",
"@plane/eslint-config": "workspace:*",
"@types/node": "^22.5.4",
"typescript": "^5.3.3"
}
+8 -1
View File
@@ -1,4 +1,4 @@
import { EIssueServiceType } from "@plane/constants";
import { EIssueServiceType, EIssuesStoreType } from "@plane/constants";
import { TIssuePriorities } from "../issues";
import { TIssueAttachment } from "./issue_attachment";
import { TIssueLink } from "./issue_link";
@@ -181,3 +181,10 @@ export type TPublicIssuesResponse = {
extra_stats: null;
results: TPublicIssueResponseResults;
};
export interface IWorkItemPeekOverview {
embedIssue?: boolean;
embedRemoveCurrentNotification?: () => void;
is_draft?: boolean;
storeType?: EIssuesStoreType;
}
+10 -8
View File
@@ -26,13 +26,15 @@
"react-dom": "^18.3.1"
},
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.1.10",
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
"@atlaskit/pragmatic-drag-and-drop": "^1.1.10",
"@blueprintjs/core": "^4.16.3",
"@blueprintjs/popover2": "^1.13.3",
"@headlessui/react": "^1.7.3",
"@plane/hooks": "*",
"@plane/utils": "*",
"@plane/constants": "workspace:*",
"@plane/hooks": "workspace:*",
"@plane/types": "workspace:*",
"@plane/utils": "workspace:*",
"@popperjs/core": "^2.11.8",
"@radix-ui/react-scroll-area": "^1.2.3",
"clsx": "^2.0.0",
@@ -48,9 +50,9 @@
},
"devDependencies": {
"@chromatic-com/storybook": "^1.4.0",
"@plane/eslint-config": "*",
"@plane/tailwind-config": "*",
"@plane/typescript-config": "*",
"@plane/eslint-config": "workspace:*",
"@plane/tailwind-config": "workspace:*",
"@plane/typescript-config": "workspace:*",
"@storybook/addon-essentials": "^8.1.1",
"@storybook/addon-interactions": "^8.1.1",
"@storybook/addon-links": "^8.1.1",
@@ -58,14 +60,14 @@
"@storybook/addon-styling-webpack": "^1.0.0",
"@storybook/addon-webpack5-compiler-swc": "^1.0.2",
"@storybook/blocks": "^8.1.1",
"@storybook/react": "^8.1.1",
"@storybook/react-webpack5": "^8.1.1",
"@storybook/react": "^8.1.1",
"@storybook/test": "^8.1.1",
"@types/lodash": "^4.17.6",
"@types/node": "^20.5.2",
"@types/react": "^18.3.11",
"@types/react-color": "^3.0.9",
"@types/react-dom": "^18.2.18",
"@types/react": "^18.3.11",
"autoprefixer": "^10.4.19",
"classnames": "^2.3.2",
"postcss-cli": "^11.0.0",
@@ -12,7 +12,7 @@ type TUseDropdownKeyDown = {
export const useDropdownKeyDown: TUseDropdownKeyDown = (onOpen, onClose, isOpen, selectActiveItem?) => {
const handleKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLElement>) => {
if (event.key === "Enter") {
if (event.key === "Enter" && !event.nativeEvent.isComposing) {
if (!isOpen) {
event.stopPropagation();
onOpen();
+6 -1
View File
@@ -16,16 +16,21 @@
"lint:errors": "eslint src --ext .ts,.tsx --quiet"
},
"dependencies": {
"@plane/constants": "workspace:*",
"@plane/types": "workspace:*",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"isomorphic-dompurify": "^2.16.0",
"lodash": "^4.17.21",
"lucide-react": "^0.469.0",
"react": "^18.3.1",
"tailwind-merge": "^2.5.5",
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"@plane/eslint-config": "*",
"@types/lodash": "^4.17.17",
"@plane/eslint-config": "workspace:*",
"@plane/typescript-config": "workspace:*",
"@types/node": "^22.5.4",
"@types/react": "^18.3.11",
"@types/zxcvbn": "^4.4.5",
+16263
View File
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
packages:
- web
- space
- admin
- live
- packages/*
onlyBuiltDependencies:
- '@swc/core'
- core-js
- esbuild
- sharp
- unrs-resolver
@@ -40,7 +40,7 @@ export const AuthEmailForm: FC<TAuthEmailForm> = observer((props) => {
const isButtonDisabled = email.length === 0 || Boolean(emailError?.email) || isSubmitting;
const [isFocused, setIsFocused] = useState(true)
const [isFocused, setIsFocused] = useState(true);
const inputRef = useRef<HTMLInputElement>(null);
return (
@@ -54,9 +54,12 @@ export const AuthEmailForm: FC<TAuthEmailForm> = observer((props) => {
`relative flex items-center rounded-md bg-onboarding-background-200 border`,
!isFocused && Boolean(emailError?.email) ? `border-red-500` : `border-onboarding-border-100`
)}
tabIndex={-1}
onFocus={() => {setIsFocused(true)}}
onBlur={() => {setIsFocused(false)}}
onFocus={() => {
setIsFocused(true);
}}
onBlur={() => {
setIsFocused(false);
}}
>
<Input
id="email"
@@ -70,14 +73,18 @@ export const AuthEmailForm: FC<TAuthEmailForm> = observer((props) => {
autoFocus
ref={inputRef}
/>
{email.length > 0 && (
<XCircle
className="h-[46px] w-11 px-3 stroke-custom-text-400 hover:cursor-pointer text-xs"
{email.length > 0 && (
<button
type="button"
aria-label="Clear email"
onClick={() => {
setEmail("");
inputRef.current?.focus();
}}
/>
tabIndex={-1}
>
<XCircle className="h-[46px] w-11 px-3 stroke-custom-text-400 hover:cursor-pointer text-xs" />
</button>
)}
</div>
{emailError?.email && !isFocused && (
@@ -92,4 +99,4 @@ export const AuthEmailForm: FC<TAuthEmailForm> = observer((props) => {
</Button>
</form>
);
});
});
+10 -10
View File
@@ -19,13 +19,13 @@
"@emotion/styled": "^11.11.0",
"@headlessui/react": "^1.7.13",
"@mui/material": "^5.14.1",
"@plane/constants": "*",
"@plane/editor": "*",
"@plane/i18n": "*",
"@plane/propel": "*",
"@plane/services": "*",
"@plane/types": "*",
"@plane/ui": "*",
"@plane/constants": "workspace:*",
"@plane/editor": "workspace:*",
"@plane/i18n": "workspace:*",
"@plane/propel": "workspace:*",
"@plane/services": "workspace:*",
"@plane/types": "workspace:*",
"@plane/ui": "workspace:*",
"axios": "^1.8.3",
"clsx": "^2.0.0",
"date-fns": "^4.1.0",
@@ -51,9 +51,9 @@
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"@plane/eslint-config": "*",
"@plane/tailwind-config": "*",
"@plane/typescript-config": "*",
"@plane/eslint-config": "workspace:*",
"@plane/tailwind-config": "workspace:*",
"@plane/typescript-config": "workspace:*",
"@types/dompurify": "^3.0.5",
"@types/lodash": "^4.17.1",
"@types/node": "18.14.1",
-1
View File
@@ -1,6 +1,5 @@
{
"$schema": "https://turbo.build/schema.json",
"ui": "tui",
"globalEnv": [
"NODE_ENV",
"NEXT_PUBLIC_API_BASE_URL",
@@ -1,22 +1,14 @@
"use client";
import { useCallback, useEffect } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import useSWR from "swr";
// plane imports
import { ENotificationLoader, ENotificationQueryParamType } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
// components
import { LogoSpinner } from "@/components/common";
import { PageHead } from "@/components/core";
import { SimpleEmptyState } from "@/components/empty-state";
import { InboxContentRoot } from "@/components/inbox";
import { IssuePeekOverview } from "@/components/issues";
import { NotificationsRoot } from "@/components/workspace-notifications";
// hooks
import { useIssueDetail, useUserPermissions, useWorkspace, useWorkspaceNotifications } from "@/hooks/store";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
import { useWorkspaceIssueProperties } from "@/hooks/use-workspace-issue-properties";
import { useWorkspace } from "@/hooks/store";
const WorkspaceDashboardPage = observer(() => {
const { workspaceSlug } = useParams();
@@ -24,98 +16,15 @@ const WorkspaceDashboardPage = observer(() => {
const { t } = useTranslation();
// hooks
const { currentWorkspace } = useWorkspace();
const {
currentSelectedNotificationId,
setCurrentSelectedNotificationId,
notificationLiteByNotificationId,
notificationIdsByWorkspaceId,
getNotifications,
} = useWorkspaceNotifications();
const { fetchUserProjectInfo } = useUserPermissions();
const { setPeekIssue } = useIssueDetail();
// derived values
const pageTitle = currentWorkspace?.name
? t("notification.page_label", { workspace: currentWorkspace?.name })
: undefined;
const { workspace_slug, project_id, issue_id, is_inbox_issue } =
notificationLiteByNotificationId(currentSelectedNotificationId);
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/intake/issue-detail" });
// fetching workspace work item properties
useWorkspaceIssueProperties(workspaceSlug);
// fetch workspace notifications
const notificationMutation =
currentWorkspace && notificationIdsByWorkspaceId(currentWorkspace.id)
? ENotificationLoader.MUTATION_LOADER
: ENotificationLoader.INIT_LOADER;
const notificationLoader =
currentWorkspace && notificationIdsByWorkspaceId(currentWorkspace.id)
? ENotificationQueryParamType.CURRENT
: ENotificationQueryParamType.INIT;
useSWR(
currentWorkspace?.slug ? `WORKSPACE_NOTIFICATION` : null,
currentWorkspace?.slug
? () => getNotifications(currentWorkspace?.slug, notificationMutation, notificationLoader)
: null
);
// fetching user project member info
const { isLoading: projectMemberInfoLoader } = useSWR(
workspace_slug && project_id && is_inbox_issue
? `PROJECT_MEMBER_PERMISSION_INFO_${workspace_slug}_${project_id}`
: null,
workspace_slug && project_id && is_inbox_issue ? () => fetchUserProjectInfo(workspace_slug, project_id) : null
);
const embedRemoveCurrentNotification = useCallback(
() => setCurrentSelectedNotificationId(undefined),
[setCurrentSelectedNotificationId]
);
// clearing up the selected notifications when unmounting the page
useEffect(
() => () => {
setCurrentSelectedNotificationId(undefined);
setPeekIssue(undefined);
},
[setCurrentSelectedNotificationId, setPeekIssue]
);
return (
<>
<PageHead title={pageTitle} />
<div className="w-full h-full overflow-hidden overflow-y-auto">
{!currentSelectedNotificationId ? (
<div className="w-full h-screen flex justify-center items-center">
<SimpleEmptyState title={t("notification.empty_state.detail.title")} assetPath={resolvedPath} />
</div>
) : (
<>
{is_inbox_issue === true && workspace_slug && project_id && issue_id ? (
<>
{projectMemberInfoLoader ? (
<div className="w-full h-full flex justify-center items-center">
<LogoSpinner />
</div>
) : (
<InboxContentRoot
setIsMobileSidebar={() => {}}
isMobileSidebar={false}
workspaceSlug={workspace_slug}
projectId={project_id}
inboxIssueId={issue_id}
isNotificationEmbed
embedRemoveCurrentNotification={embedRemoveCurrentNotification}
/>
)}
</>
) : (
<IssuePeekOverview embedIssue embedRemoveCurrentNotification={embedRemoveCurrentNotification} />
)}
</>
)}
</div>
<NotificationsRoot workspaceSlug={workspaceSlug?.toString()} />
</>
);
});
@@ -2,7 +2,7 @@
import { CommandPalette } from "@/components/command-palette";
import { ContentWrapper } from "@/components/core";
import { SettingsContentLayout, SettingsHeader } from "@/components/settings";
import { SettingsHeader } from "@/components/settings";
import { AuthenticationWrapper } from "@/lib/wrappers";
import { WorkspaceAuthWrapper } from "@/plane-web/layouts/workspace-wrapper";
@@ -15,8 +15,8 @@ export default function SettingsLayout({ children }: { children: React.ReactNode
{/* Header */}
<SettingsHeader />
{/* Content */}
<ContentWrapper className="px-4 md:pl-12 md:pt-page-y md:flex w-full">
<SettingsContentLayout>{children}</SettingsContentLayout>
<ContentWrapper className="px-4 md:pl-12 md:flex w-full">
<div className="w-full h-full overflow-hidden">{children}</div>
</ContentWrapper>
</main>
</WorkspaceAuthWrapper>
@@ -39,13 +39,13 @@ const WorkspaceSettingLayout: FC<IWorkspaceSettingLayout> = observer((props) =>
hamburgerContent={WorkspaceSettingsSidebar}
activePath={getWorkspaceActivePath(pathname) || ""}
/>
<div className="inset-y-0 flex flex-row w-full">
<div className="inset-y-0 flex flex-row w-full h-full">
{workspaceUserInfo && !isAuthorized ? (
<NotAuthorizedView section="settings" className="h-auto" />
) : (
<div className="relative flex h-full w-full">
<div className="hidden md:block">{<WorkspaceSettingsSidebar />}</div>
{children}
<div className="w-full h-full overflow-y-scroll md:pt-page-y">{children}</div>
</div>
)}
</div>
@@ -24,7 +24,9 @@ const ProfileSettingsLayout = observer((props: Props) => {
<div className="hidden md:block">
<ProfileSidebar />
</div>
<SettingsContentWrapper>{children}</SettingsContentWrapper>
<div className="w-full h-full overflow-y-scroll md:pt-page-y">
<SettingsContentWrapper>{children}</SettingsContentWrapper>
</div>
</div>
</>
);
@@ -36,7 +36,7 @@ const ProjectSettingsLayout = observer((props: Props) => {
<ProjectAuthWrapper workspaceSlug={workspaceSlug?.toString()} projectId={projectId?.toString()}>
<div className="relative flex h-full w-full">
<div className="hidden md:block">{projectId && <ProjectSettingsSidebar />}</div>
{children}
<div className="w-full h-full overflow-y-scroll md:pt-page-y">{children}</div>
</div>
</ProjectAuthWrapper>
</>
+4 -2
View File
@@ -47,17 +47,19 @@ const WORKSPACE_ACTION_LINKS = [
},
];
export const ProjectActionIcons = ({ type, size, className }: { type: string; size?: number; className?: string }) => {
const ProjectActionIcons = ({ type, size, className = "" }: { type: string; size?: number; className?: string }) => {
const icons = {
profile: CircleUser,
security: KeyRound,
activity: Activity,
appearance: Settings2,
preferences: Settings2,
notifications: Bell,
"api-tokens": KeyRound,
};
if (type === undefined) return null;
const Icon = icons[type as keyof typeof icons];
if (!Icon) return null;
return <Icon size={size} className={className} />;
};
export const ProfileLayoutSidebar = observer(() => {
@@ -39,6 +39,7 @@ export const IssueLevelModals: FC<TIssueLevelModalsProps> = observer((props) =>
toggleDeleteIssueModal,
isBulkDeleteIssueModalOpen,
toggleBulkDeleteIssueModal,
createWorkItemAllowedProjectIds,
} = useCommandPalette();
// derived values
const issueDetails = issueId ? getIssueById(issueId) : undefined;
@@ -80,6 +81,7 @@ export const IssueLevelModals: FC<TIssueLevelModalsProps> = observer((props) =>
data={getCreateIssueModalData()}
isDraft={isDraftIssue}
onSubmit={handleCreateIssueSubmit}
allowedProjectIds={createWorkItemAllowedProjectIds}
/>
{workspaceSlug && projectId && issueId && issueDetails && (
<DeleteIssueModal
@@ -4,21 +4,29 @@ import { observer } from "mobx-react-lite";
import { ISearchIssueResponse, TIssue } from "@plane/types";
// components
import { IssueModalContext } from "@/components/issues";
// hooks
import { useUser } from "@/hooks/store/user/user-user";
export type TIssueModalProviderProps = {
templateId?: string;
dataForPreload?: Partial<TIssue>;
allowedProjectIds?: string[];
children: React.ReactNode;
};
export const IssueModalProvider = observer((props: TIssueModalProviderProps) => {
const { children } = props;
const { children, allowedProjectIds } = props;
// states
const [selectedParentIssue, setSelectedParentIssue] = useState<ISearchIssueResponse | null>(null);
// store hooks
const { projectsWithCreatePermissions } = useUser();
// derived values
const projectIdsWithCreatePermissions = Object.keys(projectsWithCreatePermissions ?? {});
return (
<IssueModalContext.Provider
value={{
allowedProjectIds: allowedProjectIds ?? projectIdsWithCreatePermissions,
workItemTemplateId: null,
setWorkItemTemplateId: () => {},
isApplyingTemplate: false,
@@ -1 +1,2 @@
export * from "./notification-card/root";
export * from "./list-root";
@@ -0,0 +1,8 @@
import { NotificationCardListRoot } from "./notification-card/root";
export type TNotificationListRoot = {
workspaceSlug: string;
workspaceId: string;
};
export const NotificationListRoot = (props: TNotificationListRoot) => <NotificationCardListRoot {...props} />;
+25
View File
@@ -0,0 +1,25 @@
import { EIssueServiceType } from "@plane/constants";
import { IWorkItemPeekOverview } from "@plane/types";
import { IssuePeekOverview } from "@/components/issues";
import { useIssueDetail } from "@/hooks/store";
import { TPeekIssue } from "@/store/issue/issue-details/root.store";
export type TNotificationPreview = {
isWorkItem: boolean;
PeekOverviewComponent: React.ComponentType<IWorkItemPeekOverview>;
setPeekWorkItem: (peekIssue: TPeekIssue | undefined) => void;
};
/**
* This function returns if the current active notification is related to work item or an epic.
* @returns isWorkItem: boolean, peekOverviewComponent: IWorkItemPeekOverview, setPeekWorkItem
*/
export const useNotificationPreview = (): TNotificationPreview => {
const { peekIssue, setPeekIssue } = useIssueDetail(EIssueServiceType.ISSUES);
return {
isWorkItem: Boolean(peekIssue),
PeekOverviewComponent: IssuePeekOverview,
setPeekWorkItem: setPeekIssue,
};
};
@@ -55,7 +55,6 @@ export const AuthEmailForm: FC<TAuthEmailForm> = observer((props) => {
`relative flex items-center rounded-md bg-onboarding-background-200 border`,
!isFocused && Boolean(emailError?.email) ? `border-red-500` : `border-onboarding-border-100`
)}
tabIndex={-1}
onFocus={() => {
setIsFocused(true);
}}
@@ -84,6 +83,7 @@ export const AuthEmailForm: FC<TAuthEmailForm> = observer((props) => {
}}
className="absolute right-3 size-5 grid place-items-center"
aria-label={t("aria_labels.auth_forms.clear_email")}
tabIndex={-1}
>
<XCircle className="size-5 stroke-custom-text-400" />
</button>
+10 -4
View File
@@ -14,8 +14,9 @@ import { DateRangeDropdown, ProjectDropdown } from "@/components/dropdowns";
// constants
// helpers
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
import { shouldRenderProject } from "@/helpers/project.helper";
import { getTabIndex } from "@/helpers/tab-indices.helper";
// hooks
import { useUser } from "@/hooks/store/user/user-user";
type Props = {
handleFormSubmit: (values: Partial<ICycle>, dirtyFields: any) => Promise<void>;
@@ -36,7 +37,10 @@ const defaultValues: Partial<ICycle> = {
export const CycleForm: React.FC<Props> = (props) => {
const { handleFormSubmit, handleClose, status, projectId, setActiveProject, data, isMobile = false } = props;
// plane hooks
const { t } = useTranslation();
// store hooks
const { projectsWithCreatePermissions } = useUser();
// form data
const {
formState: { errors, isSubmitting, dirtyFields },
@@ -75,12 +79,14 @@ export const CycleForm: React.FC<Props> = (props) => {
<ProjectDropdown
value={value}
onChange={(val) => {
onChange(val);
setActiveProject(val);
if (!Array.isArray(val)) {
onChange(val);
setActiveProject(val);
}
}}
multiple={false}
buttonVariant="border-with-text"
renderCondition={(project) => shouldRenderProject(project)}
renderCondition={(project) => !!projectsWithCreatePermissions?.[project.id]}
tabIndex={getIndex("cover_image")}
/>
</div>
@@ -46,10 +46,7 @@ export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSub
type: TOAST_TYPE.SUCCESS,
title: t("common.link_copied"),
message: t("entity.link_copied_to_clipboard", {
entity:
issueServiceType === EIssueServiceType.ISSUES
? t("issue.label", { count: 1 })
: t("epic.label", { count: 1 }),
entity: t("epic.label", { count: 1 }),
}),
});
});
@@ -99,7 +99,7 @@ export const IssueLabelSelect: React.FC<IIssueLabelSelect> = observer((props) =>
setQuery("");
}
if (query !== "" && e.key === "Enter" && canCreateLabel) {
if (query !== "" && e.key === "Enter" && !e.nativeEvent.isComposing && canCreateLabel) {
e.stopPropagation();
e.preventDefault();
await handleAddLabel(query);
@@ -5,7 +5,7 @@ import isNil from "lodash/isNil";
import { observer } from "mobx-react";
import { Bell, BellOff } from "lucide-react";
// plane-i18n
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { EUserPermissions, EUserPermissionsLevel, EIssueServiceType } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
// UI
import { Button, Loader, TOAST_TYPE, setToast } from "@plane/ui";
@@ -16,17 +16,18 @@ export type TIssueSubscription = {
workspaceSlug: string;
projectId: string;
issueId: string;
serviceType?: EIssueServiceType;
};
export const IssueSubscription: FC<TIssueSubscription> = observer((props) => {
const { workspaceSlug, projectId, issueId } = props;
const { workspaceSlug, projectId, issueId, serviceType = EIssueServiceType.ISSUES } = props;
const { t } = useTranslation();
// hooks
const {
subscription: { getSubscriptionByIssueId },
createSubscription,
removeSubscription,
} = useIssueDetail();
} = useIssueDetail(serviceType);
// state
const [loading, setLoading] = useState(false);
// hooks
@@ -53,12 +54,12 @@ export const IssueSubscription: FC<TIssueSubscription> = observer((props) => {
: t("issue.subscription.actions.subscribed"),
});
setLoading(false);
} catch (error) {
} catch {
setLoading(false);
setToast({
type: TOAST_TYPE.ERROR,
title: t("toast.error"),
message: t("commons.error.message"),
message: t("common.error.message"),
});
}
};
@@ -78,7 +79,7 @@ export const IssueSubscription: FC<TIssueSubscription> = observer((props) => {
variant="outline-primary"
className="hover:!bg-custom-primary-100/20"
onClick={handleSubscription}
disabled={!isEditable}
disabled={!isEditable || loading}
>
{loading ? (
<span>
@@ -158,7 +158,7 @@ export const LabelDropdown = (props: ILabelDropdownProps) => {
setQuery("");
}
if (query !== "" && e.key === "Enter" && canCreateLabel) {
if (query !== "" && e.key === "Enter" && !e.nativeEvent.isComposing && canCreateLabel) {
e.preventDefault();
await handleAddLabel(query);
}
@@ -13,7 +13,12 @@ import { CreateIssueToastActionItems, IssuesModalProps } from "@/components/issu
// constants
// hooks
import { useIssueModal } from "@/hooks/context/use-issue-modal";
import { useEventTracker, useCycle, useIssues, useModule, useIssueDetail, useUser, useProject } from "@/hooks/store";
import { useCycle } from "@/hooks/store/use-cycle";
import { useEventTracker } from "@/hooks/store/use-event-tracker";
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
import { useIssues } from "@/hooks/store/use-issues";
import { useModule } from "@/hooks/store/use-module";
import { useProject } from "@/hooks/store/use-project";
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
import { useIssuesActions } from "@/hooks/use-issues-actions";
// services
@@ -59,14 +64,13 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
const { t } = useTranslation();
const { captureIssueEvent } = useEventTracker();
const { workspaceSlug, projectId: routerProjectId, cycleId, moduleId, workItem } = useParams();
const { projectsWithCreatePermissions } = useUser();
const { fetchCycleDetails } = useCycle();
const { fetchModuleDetails } = useModule();
const { issues } = useIssues(storeType);
const { issues: projectIssues } = useIssues(EIssuesStoreType.PROJECT);
const { issues: draftIssues } = useIssues(EIssuesStoreType.WORKSPACE_DRAFT);
const { fetchIssue } = useIssueDetail();
const { handleCreateUpdatePropertyValues } = useIssueModal();
const { allowedProjectIds, handleCreateUpdatePropertyValues } = useIssueModal();
const { getProjectByIdentifier } = useProject();
// pathname
const pathname = usePathname();
@@ -76,7 +80,6 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
const routerProjectIdentifier = workItem?.toString().split("-")[0];
const projectIdFromRouter = getProjectByIdentifier(routerProjectIdentifier)?.id;
const projectId = data?.project_id ?? routerProjectId?.toString() ?? projectIdFromRouter;
const projectIdsWithCreatePermissions = Object.keys(projectsWithCreatePermissions ?? {});
const fetchIssueDetail = async (issueId: string | undefined) => {
setDescription(undefined);
@@ -114,10 +117,9 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
return;
}
// if data is not present, set active project to the project
// in the url. This has the least priority.
if (projectIdsWithCreatePermissions && projectIdsWithCreatePermissions.length > 0 && !activeProjectId)
setActiveProjectId(projectId?.toString() ?? projectIdsWithCreatePermissions?.[0]);
// if data is not present, set active project to the first project in the allowedProjectIds array
if (allowedProjectIds && allowedProjectIds.length > 0 && !activeProjectId)
setActiveProjectId(projectId?.toString() ?? allowedProjectIds?.[0]);
// clearing up the description state when we leave the component
return () => setDescription(undefined);
@@ -346,7 +348,7 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
const handleDuplicateIssueModal = (value: boolean) => setIsDuplicateModalOpen(value);
// don't open the modal if there are no projects
if (!projectIdsWithCreatePermissions || projectIdsWithCreatePermissions.length === 0 || !activeProjectId) return null;
if (!allowedProjectIds || allowedProjectIds.length === 0 || !activeProjectId) return null;
const commonIssueModalProps: IssueFormProps = {
issueTitleRef: issueTitleRef,
@@ -10,10 +10,9 @@ import { TIssue } from "@plane/types";
// components
import { ProjectDropdown } from "@/components/dropdowns";
// helpers
import { shouldRenderProject } from "@/helpers/project.helper";
import { getTabIndex } from "@/helpers/tab-indices.helper";
// store hooks
import { useUser } from "@/hooks/store";
// hooks
import { useIssueModal } from "@/hooks/context/use-issue-modal";
import { usePlatformOS } from "@/hooks/use-platform-os";
type TIssueProjectSelectProps = {
@@ -25,8 +24,9 @@ type TIssueProjectSelectProps = {
export const IssueProjectSelect: React.FC<TIssueProjectSelectProps> = observer((props) => {
const { control, disabled = false, handleFormChange } = props;
// store hooks
const { projectsWithCreatePermissions } = useUser();
const { isMobile } = usePlatformOS();
// context hooks
const { allowedProjectIds } = useIssueModal();
const { getIndex } = getTabIndex(ETabIndices.ISSUE_FORM, isMobile);
@@ -37,26 +37,22 @@ export const IssueProjectSelect: React.FC<TIssueProjectSelectProps> = observer((
rules={{
required: true,
}}
render={({ field: { value, onChange } }) =>
projectsWithCreatePermissions && projectsWithCreatePermissions[value!] ? (
<div className="h-7">
<ProjectDropdown
value={value}
onChange={(projectId) => {
onChange(projectId);
handleFormChange();
}}
multiple={false}
buttonVariant="border-with-text"
renderCondition={(project) => shouldRenderProject(project)}
tabIndex={getIndex("project_id")}
disabled={disabled}
/>
</div>
) : (
<></>
)
}
render={({ field: { value, onChange } }) => (
<div className="h-7">
<ProjectDropdown
value={value}
onChange={(projectId) => {
onChange(projectId);
handleFormChange();
}}
multiple={false}
buttonVariant="border-with-text"
renderCondition={(project) => allowedProjectIds.includes(project.id)}
tabIndex={getIndex("project_id")}
disabled={disabled}
/>
</div>
)}
/>
);
});
@@ -47,6 +47,7 @@ export type THandleParentWorkItemDetailsProps = {
};
export type TIssueModalContext = {
allowedProjectIds: string[];
workItemTemplateId: string | null;
setWorkItemTemplateId: React.Dispatch<React.SetStateAction<string | null>>;
isApplyingTemplate: boolean;
@@ -29,6 +29,7 @@ export interface IssuesModalProps {
};
isProjectSelectionDisabled?: boolean;
templateId?: string;
allowedProjectIds?: string[];
}
export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((props) => {
@@ -43,7 +44,11 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
if (!props.isOpen) return null;
return (
<IssueModalProvider templateId={props.templateId} dataForPreload={dataForPreload}>
<IssueModalProvider
templateId={props.templateId}
dataForPreload={dataForPreload}
allowedProjectIds={props.allowedProjectIds}
>
<CreateUpdateIssueModalBase {...props} />
</IssueModalProvider>
);
@@ -14,7 +14,7 @@ import {
EUserPermissionsLevel,
} from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { TIssue } from "@plane/types";
import { TIssue, IWorkItemPeekOverview } from "@plane/types";
// plane ui
import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
// components
@@ -24,14 +24,8 @@ import { IssueView, TIssueOperations } from "@/components/issues";
import { useEventTracker, useIssueDetail, useIssues, useUserPermissions } from "@/hooks/store";
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
interface IIssuePeekOverview {
embedIssue?: boolean;
embedRemoveCurrentNotification?: () => void;
is_draft?: boolean;
storeType?: EIssuesStoreType;
}
export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) => {
const {
embedIssue = false,
embedRemoveCurrentNotification,
+5 -3
View File
@@ -13,9 +13,9 @@ import { DateRangeDropdown, ProjectDropdown, MemberDropdown } from "@/components
import { ModuleStatusSelect } from "@/components/modules";
// helpers
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
import { shouldRenderProject } from "@/helpers/project.helper";
import { getTabIndex } from "@/helpers/tab-indices.helper";
// types
// hooks
import { useUser } from "@/hooks/store/user/user-user";
type Props = {
handleFormSubmit: (values: Partial<IModule>, dirtyFields: any) => Promise<void>;
@@ -37,6 +37,8 @@ const defaultValues: Partial<IModule> = {
export const ModuleForm: React.FC<Props> = (props) => {
const { handleFormSubmit, handleClose, status, projectId, setActiveProject, data, isMobile = false } = props;
// store hooks
const { projectsWithCreatePermissions } = useUser();
// form info
const {
formState: { errors, isSubmitting, dirtyFields },
@@ -93,7 +95,7 @@ export const ModuleForm: React.FC<Props> = (props) => {
}}
multiple={false}
buttonVariant="border-with-text"
renderCondition={(project) => shouldRenderProject(project)}
renderCondition={(project) => !!projectsWithCreatePermissions?.[project.id]}
tabIndex={getIndex("cover_image")}
/>
</div>
@@ -12,7 +12,7 @@ export const SettingsContentWrapper = observer((props: TProps) => {
return (
<div
className={cn("flex flex-col w-full items-center mx-auto py-4 md:py-0", {
"p-4 max-w-[800px] 2xl:max-w-[1000px]": size === "md",
"md:p-4 max-w-[800px] 2xl:max-w-[1000px]": size === "md",
"md:px-16": size === "lg",
})}
>
@@ -37,7 +37,7 @@ export const SettingsSidebar = observer((props: SettingsSidebarProps) => {
return (
<div
className={cn("flex w-[250px] flex-col gap-2 flex-shrink-0 pb-5", {
className={cn("flex w-[250px] flex-col gap-2 flex-shrink-0 overflow-y-scroll h-full md:pt-page-y ", {
"absolute left-0 top-[42px] z-50 h-fit max-h-[400px] overflow-scroll bg-custom-background-100 border border-custom-border-100 rounded shadow-sm p-4":
isMobile,
})}
@@ -45,9 +45,9 @@ export const SettingsSidebar = observer((props: SettingsSidebarProps) => {
{/* Header */}
<SettingsSidebarHeader customHeader={customHeader} />
{/* Navigation */}
<div className="divide-y divide-custom-border-100 overflow-x-hidden w-full">
<div className="divide-y divide-custom-border-100 overflow-x-hidden w-full h-full overflow-y-scroll">
{categories.map((category) => (
<div key={category} className="py-3 h-full">
<div key={category} className="py-3">
<span className="text-sm font-semibold text-custom-text-350 capitalize mb-2">{t(category)}</span>
{groupedSettings[category].length > 0 && (
<div className="relative flex flex-col gap-0.5 h-full mt-2">
@@ -1,116 +1,116 @@
"use client";
import { FC, useCallback } from "react";
import { useCallback, useEffect } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import useSWR from "swr";
// plane imports
import { NOTIFICATION_TABS, TNotificationTab } from "@plane/constants";
import { ENotificationLoader, ENotificationQueryParamType } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
// components
import { Header, Row, ERowVariant, EHeaderVariant, ContentWrapper } from "@plane/ui";
import { CountChip } from "@/components/common";
import {
NotificationsLoader,
NotificationEmptyState,
NotificationSidebarHeader,
AppliedFilters,
} from "@/components/workspace-notifications";
// helpers
import { cn } from "@/helpers/common.helper";
import { getNumberCount } from "@/helpers/string.helper";
import { cn } from "@plane/utils";
import { LogoSpinner } from "@/components/common";
import { SimpleEmptyState } from "@/components/empty-state";
import { InboxContentRoot } from "@/components/inbox";
// hooks
import { useWorkspace, useWorkspaceNotifications } from "@/hooks/store";
import { NotificationCardListRoot } from "@/plane-web/components/workspace-notifications";
export const NotificationsSidebarRoot: FC = observer(() => {
const { workspaceSlug } = useParams();
import { useUserPermissions, useWorkspace, useWorkspaceNotifications } from "@/hooks/store";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
import { useWorkspaceIssueProperties } from "@/hooks/use-workspace-issue-properties";
import { useNotificationPreview } from "@/plane-web/hooks/use-notification-preview";
type NotificationsRootProps = {
workspaceSlug?: string;
};
export const NotificationsRoot = observer(({ workspaceSlug }: NotificationsRootProps) => {
// plane hooks
const { t } = useTranslation();
// hooks
const { getWorkspaceBySlug } = useWorkspace();
const { currentWorkspace } = useWorkspace();
const {
currentSelectedNotificationId,
unreadNotificationsCount,
loader,
setCurrentSelectedNotificationId,
notificationLiteByNotificationId,
notificationIdsByWorkspaceId,
currentNotificationTab,
setCurrentNotificationTab,
getNotifications,
} = useWorkspaceNotifications();
const { t } = useTranslation();
const { fetchUserProjectInfo } = useUserPermissions();
const { isWorkItem, PeekOverviewComponent, setPeekWorkItem } = useNotificationPreview();
// derived values
const workspace = workspaceSlug ? getWorkspaceBySlug(workspaceSlug.toString()) : undefined;
const notificationIds = workspace ? notificationIdsByWorkspaceId(workspace.id) : undefined;
const { workspace_slug, project_id, issue_id, is_inbox_issue } =
notificationLiteByNotificationId(currentSelectedNotificationId);
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/intake/issue-detail" });
const handleTabClick = useCallback(
(tabValue: TNotificationTab) => {
if (currentNotificationTab !== tabValue) {
setCurrentNotificationTab(tabValue);
}
},
[currentNotificationTab, setCurrentNotificationTab]
// fetching workspace work item properties
useWorkspaceIssueProperties(workspaceSlug);
// fetch workspace notifications
const notificationMutation =
currentWorkspace && notificationIdsByWorkspaceId(currentWorkspace.id)
? ENotificationLoader.MUTATION_LOADER
: ENotificationLoader.INIT_LOADER;
const notificationLoader =
currentWorkspace && notificationIdsByWorkspaceId(currentWorkspace.id)
? ENotificationQueryParamType.CURRENT
: ENotificationQueryParamType.INIT;
useSWR(
currentWorkspace?.slug ? `WORKSPACE_NOTIFICATION_${currentWorkspace?.slug}` : null,
currentWorkspace?.slug
? () => getNotifications(currentWorkspace?.slug, notificationMutation, notificationLoader)
: null
);
if (!workspaceSlug || !workspace) return <></>;
// fetching user project member info
const { isLoading: projectMemberInfoLoader } = useSWR(
workspace_slug && project_id && is_inbox_issue
? `PROJECT_MEMBER_PERMISSION_INFO_${workspace_slug}_${project_id}`
: null,
workspace_slug && project_id && is_inbox_issue ? () => fetchUserProjectInfo(workspace_slug, project_id) : null
);
const embedRemoveCurrentNotification = useCallback(
() => setCurrentSelectedNotificationId(undefined),
[setCurrentSelectedNotificationId]
);
// clearing up the selected notifications when unmounting the page
useEffect(
() => () => {
setPeekWorkItem(undefined);
},
[setCurrentSelectedNotificationId, setPeekWorkItem]
);
return (
<div
className={cn(
"relative border-0 md:border-r border-custom-border-200 z-[10] flex-shrink-0 bg-custom-background-100 h-full transition-all overflow-hidden",
currentSelectedNotificationId ? "w-0 md:w-2/6" : "w-full md:w-2/6"
)}
>
<div className="relative w-full h-full overflow-hidden flex flex-col">
<Row className="h-[3.75rem] border-b border-custom-border-200 flex">
<NotificationSidebarHeader workspaceSlug={workspaceSlug.toString()} />
</Row>
<Header variant={EHeaderVariant.SECONDARY} className="justify-start">
{NOTIFICATION_TABS.map((tab) => (
<div
key={tab.value}
className="h-full px-3 relative cursor-pointer"
onClick={() => handleTabClick(tab.value)}
>
<div
className={cn(
`relative h-full flex justify-center items-center gap-1 text-sm transition-all`,
currentNotificationTab === tab.value
? "text-custom-primary-100"
: "text-custom-text-100 hover:text-custom-text-200"
)}
>
<div className="font-medium">{t(tab.i18n_label)}</div>
{tab.count(unreadNotificationsCount) > 0 && (
<CountChip count={getNumberCount(tab.count(unreadNotificationsCount))} />
)}
</div>
{currentNotificationTab === tab.value && (
<div className="border absolute bottom-0 right-0 left-0 rounded-t-md border-custom-primary-100" />
<div className={cn("w-full h-full overflow-hidden ", isWorkItem && "overflow-y-auto")}>
{!currentSelectedNotificationId ? (
<div className="w-full h-screen flex justify-center items-center">
<SimpleEmptyState title={t("notification.empty_state.detail.title")} assetPath={resolvedPath} />
</div>
) : (
<>
{is_inbox_issue === true && workspace_slug && project_id && issue_id ? (
<>
{projectMemberInfoLoader ? (
<div className="w-full h-full flex justify-center items-center">
<LogoSpinner />
</div>
) : (
<InboxContentRoot
setIsMobileSidebar={() => {}}
isMobileSidebar={false}
workspaceSlug={workspace_slug}
projectId={project_id}
inboxIssueId={issue_id}
isNotificationEmbed
embedRemoveCurrentNotification={embedRemoveCurrentNotification}
/>
)}
</div>
))}
</Header>
{/* applied filters */}
<AppliedFilters workspaceSlug={workspaceSlug.toString()} />
{/* rendering notifications */}
{loader === "init-loader" ? (
<div className="relative w-full h-full overflow-hidden">
<NotificationsLoader />
</div>
) : (
<>
{notificationIds && notificationIds.length > 0 ? (
<ContentWrapper variant={ERowVariant.HUGGING}>
<NotificationCardListRoot workspaceSlug={workspaceSlug.toString()} workspaceId={workspace?.id} />
</ContentWrapper>
) : (
<div className="relative w-full h-full flex justify-center items-center">
<NotificationEmptyState />
</div>
)}
</>
)}
</div>
</>
) : (
<PeekOverviewComponent embedIssue embedRemoveCurrentNotification={embedRemoveCurrentNotification} />
)}
</>
)}
</div>
);
});
@@ -20,7 +20,7 @@ export const NotificationSidebarHeader: FC<TNotificationSidebarHeader> = observe
if (!workspaceSlug) return <></>;
return (
<Header className="my-auto">
<Header className="my-auto bg-custom-background-100">
<Header.LeftItem>
<div className="block bg-custom-sidebar-background-100 md:hidden">
<SidebarHamburgerToggle />
@@ -6,3 +6,5 @@ export * from "./header";
export * from "./filters";
export * from "./notification-card";
export * from "./root";
@@ -0,0 +1,118 @@
"use client";
import { FC, useCallback } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane imports
import { NOTIFICATION_TABS, TNotificationTab } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
// components
import { Header, Row, ERowVariant, EHeaderVariant, ContentWrapper } from "@plane/ui";
import { CountChip } from "@/components/common";
import {
NotificationsLoader,
NotificationEmptyState,
NotificationSidebarHeader,
AppliedFilters,
} from "@/components/workspace-notifications";
// helpers
import { cn } from "@/helpers/common.helper";
import { getNumberCount } from "@/helpers/string.helper";
// hooks
import { useWorkspace, useWorkspaceNotifications } from "@/hooks/store";
import { NotificationListRoot } from "@/plane-web/components/workspace-notifications/list-root";
export const NotificationsSidebarRoot: FC = observer(() => {
const { workspaceSlug } = useParams();
// hooks
const { getWorkspaceBySlug } = useWorkspace();
const {
currentSelectedNotificationId,
unreadNotificationsCount,
loader,
notificationIdsByWorkspaceId,
currentNotificationTab,
setCurrentNotificationTab,
} = useWorkspaceNotifications();
const { t } = useTranslation();
// derived values
const workspace = workspaceSlug ? getWorkspaceBySlug(workspaceSlug.toString()) : undefined;
const notificationIds = workspace ? notificationIdsByWorkspaceId(workspace.id) : undefined;
const handleTabClick = useCallback(
(tabValue: TNotificationTab) => {
if (currentNotificationTab !== tabValue) {
setCurrentNotificationTab(tabValue);
}
},
[currentNotificationTab, setCurrentNotificationTab]
);
if (!workspaceSlug || !workspace) return <></>;
return (
<div
className={cn(
"relative border-0 md:border-r border-custom-border-200 z-[10] flex-shrink-0 bg-custom-background-100 h-full transition-all max-md:overflow-hidden",
currentSelectedNotificationId ? "w-0 md:w-2/6" : "w-full md:w-2/6"
)}
>
<div className="relative w-full h-full flex flex-col">
<Row className="h-[3.75rem] border-b border-custom-border-200 flex">
<NotificationSidebarHeader workspaceSlug={workspaceSlug.toString()} />
</Row>
<Header variant={EHeaderVariant.SECONDARY} className="justify-start">
{NOTIFICATION_TABS.map((tab) => (
<div
key={tab.value}
className="h-full px-3 relative cursor-pointer"
onClick={() => handleTabClick(tab.value)}
>
<div
className={cn(
`relative h-full flex justify-center items-center gap-1 text-sm transition-all`,
currentNotificationTab === tab.value
? "text-custom-primary-100"
: "text-custom-text-100 hover:text-custom-text-200"
)}
>
<div className="font-medium">{t(tab.i18n_label)}</div>
{tab.count(unreadNotificationsCount) > 0 && (
<CountChip count={getNumberCount(tab.count(unreadNotificationsCount))} />
)}
</div>
{currentNotificationTab === tab.value && (
<div className="border absolute bottom-0 right-0 left-0 rounded-t-md border-custom-primary-100" />
)}
</div>
))}
</Header>
{/* applied filters */}
<AppliedFilters workspaceSlug={workspaceSlug.toString()} />
{/* rendering notifications */}
{loader === "init-loader" ? (
<div className="relative w-full h-full overflow-hidden">
<NotificationsLoader />
</div>
) : (
<>
{notificationIds && notificationIds.length > 0 ? (
<ContentWrapper variant={ERowVariant.HUGGING}>
<NotificationListRoot workspaceSlug={workspaceSlug.toString()} workspaceId={workspace?.id} />
</ContentWrapper>
) : (
<div className="relative w-full h-full flex justify-center items-center">
<NotificationEmptyState />
</div>
)}
</>
)}
</div>
</div>
);
});
+1 -1
View File
@@ -21,7 +21,7 @@ export const useDropdownKeyDown: TUseDropdownKeyDown = (onEnterKeyDown, onEscKey
const handleKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLElement>) => {
if (event.key === "Enter") {
if (event.key === "Enter" && !event.nativeEvent.isComposing) {
stopEventPropagation(event);
onEnterKeyDown();
} else if (event.key === "Escape") {
+7 -2
View File
@@ -26,6 +26,7 @@ export interface IBaseCommandPaletteStore {
isDeleteIssueModalOpen: boolean;
isBulkDeleteIssueModalOpen: boolean;
createIssueStoreType: TCreateModalStoreTypes;
createWorkItemAllowedProjectIds: string[] | undefined;
allStickiesModal: boolean;
projectListOpenMap: Record<string, boolean>;
getIsProjectListOpen: (projectId: string) => boolean;
@@ -36,7 +37,7 @@ export interface IBaseCommandPaletteStore {
toggleCreateCycleModal: (value?: boolean) => void;
toggleCreateViewModal: (value?: boolean) => void;
toggleCreatePageModal: (value?: TCreatePageModal) => void;
toggleCreateIssueModal: (value?: boolean, storeType?: TCreateModalStoreTypes) => void;
toggleCreateIssueModal: (value?: boolean, storeType?: TCreateModalStoreTypes, allowedProjectIds?: string[]) => void;
toggleCreateModuleModal: (value?: boolean) => void;
toggleDeleteIssueModal: (value?: boolean) => void;
toggleBulkDeleteIssueModal: (value?: boolean) => void;
@@ -57,6 +58,7 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
isBulkDeleteIssueModalOpen: boolean = false;
createPageModal: TCreatePageModal = DEFAULT_CREATE_PAGE_MODAL_DATA;
createIssueStoreType: TCreateModalStoreTypes = EIssuesStoreType.PROJECT;
createWorkItemAllowedProjectIds: IBaseCommandPaletteStore["createWorkItemAllowedProjectIds"] = undefined;
allStickiesModal: boolean = false;
projectListOpenMap: Record<string, boolean> = {};
@@ -74,6 +76,7 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
isBulkDeleteIssueModalOpen: observable.ref,
createPageModal: observable,
createIssueStoreType: observable,
createWorkItemAllowedProjectIds: observable,
allStickiesModal: observable,
projectListOpenMap: observable,
// projectPages: computed,
@@ -214,13 +217,15 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
* @param storeType
* @returns
*/
toggleCreateIssueModal = (value?: boolean, storeType?: TCreateModalStoreTypes) => {
toggleCreateIssueModal = (value?: boolean, storeType?: TCreateModalStoreTypes, allowedProjectIds?: string[]) => {
if (value !== undefined) {
this.isCreateIssueModalOpen = value;
this.createIssueStoreType = storeType || EIssuesStoreType.PROJECT;
this.createWorkItemAllowedProjectIds = allowedProjectIds ?? undefined;
} else {
this.isCreateIssueModalOpen = !this.isCreateIssueModalOpen;
this.createIssueStoreType = EIssuesStoreType.PROJECT;
this.createWorkItemAllowedProjectIds = undefined;
}
};
@@ -5,7 +5,6 @@ import isEmpty from "lodash/isEmpty";
import orderBy from "lodash/orderBy";
import set from "lodash/set";
import uniq from "lodash/uniq";
import { runInAction } from "mobx";
import { ALL_ISSUES, EIssueFilterType, FILTER_TO_ISSUE_MAP, ISSUE_PRIORITIES } from "@plane/constants";
import {
IIssueDisplayFilterOptions,
@@ -318,10 +317,22 @@ export const getGroupedWorkItemIds = (
};
}
// Get the default key for the group by key
const getDefaultGroupKey = (groupByKey: TIssueGroupByOptions) => {
switch (groupByKey) {
case "state_detail.group":
return "state__group";
case null:
return null;
default:
return ISSUE_GROUP_BY_KEY[groupByKey];
}
};
// Group work items
const groupKey = ISSUE_GROUP_BY_KEY[groupByKey];
const groupKey = getDefaultGroupKey(groupByKey);
const groupedWorkItems = groupBy(workItems, (item) => {
const value = item[groupKey];
const value = groupKey ? item[groupKey] : null;
if (Array.isArray(value)) {
if (value.length === 0) return "None";
// Sort & join to build deterministic set-like key
@@ -121,7 +121,7 @@ export interface IBaseIssuesStore {
export const ISSUE_GROUP_BY_KEY: Record<TIssueDisplayFilterOptions, keyof TIssue> = {
project: "project_id",
state: "state_id",
"state_detail.group": "state__group" as keyof TIssue, // state_detail.group is only being used for state_group display,
"state_detail.group": "state_id", // state_detail.group is only being used for state_group display,
priority: "priority",
labels: "label_ids",
created_by: "created_by",
@@ -137,7 +137,7 @@ export const ISSUE_FILTER_DEFAULT_DATA: Record<TIssueDisplayFilterOptions, keyof
cycle: "cycle_id",
module: "module_ids",
state: "state_id",
"state_detail.group": "state_group" as keyof TIssue, // state_detail.group is only being used for state_group display,
"state_detail.group": "state__group", // state_detail.group is only being used for state_group display,
priority: "priority",
labels: "label_ids",
created_by: "created_by",
@@ -1594,11 +1594,11 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
// if unGrouped, then return the path as ALL_ISSUES along with orderByUpdates
if (!this.issueGroupKey) return action ? [{ path: [ALL_ISSUES], action }, ...orderByUpdates] : orderByUpdates;
const issueGroupKey = issue?.[this.issueGroupKey] as string | string[] | null | undefined;
const issueGroupKeyValue = issue?.[this.issueGroupKey] as string | string[] | null | undefined;
const issueBeforeUpdateGroupKey = issueBeforeUpdate?.[this.issueGroupKey] as string | string[] | null | undefined;
// if grouped, the get the Difference between the two issue properties (this.issueGroupKey) on which groupBy is performed
const groupActionsArray = getDifference(
this.getArrayStringArray(issue, issueGroupKey, this.groupBy),
this.getArrayStringArray(issue, issueGroupKeyValue, this.groupBy),
this.getArrayStringArray(issueBeforeUpdate, issueBeforeUpdateGroupKey, this.groupBy),
action
);
@@ -1632,7 +1632,7 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
groupActionsArray,
subGroupActionsArray,
this.getArrayStringArray(issueBeforeUpdate, issueBeforeUpdateGroupKey, this.groupBy),
this.getArrayStringArray(issue, issueGroupKey, this.groupBy),
this.getArrayStringArray(issue, issueGroupKeyValue, this.groupBy),
this.getArrayStringArray(issueBeforeUpdate, issueBeforeUpdateSubGroupKey, this.subGroupBy),
this.getArrayStringArray(issue, issueSubGroupKey, this.subGroupBy)
),
@@ -1690,12 +1690,13 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
return issueKeyActions;
}
/**
* get the groupByKey issue property on which actions are to be decided in the form of array
* @param value
* @param groupByKey
* @returns an array of issue property values
*/
// /**
// * Normalizes group values into a consistent string array format
// * @param issueObject - The issue object to extract values from
// * @param value - The raw value (string, array, or null/undefined)
// * @param groupByKey - The group by key to handle special cases
// * @returns Normalized array of string values
// */
getArrayStringArray = (
issueObject: Partial<TIssue> | undefined,
value: string | string[] | undefined | null,
@@ -1708,9 +1709,23 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
// if array return the array
if (Array.isArray(value)) return value;
// if the groupKey is state group then return the group based on state_id
return this.getDefaultGroupValue(issueObject, value, groupByKey);
};
// /**
// * Gets the default value for a group when the primary value is empty
// * @param issueObject - The issue object to extract fallback values from
// * @param groupByKey - The group by key to determine fallback logic
// * @returns Default group value as string array
// */
private getDefaultGroupValue = (
issueObject: Partial<TIssue>,
value: string,
groupByKey?: TIssueGroupByOptions
): string[] => {
// Handle special case for state group
if (groupByKey === "state_detail.group") {
return [this.rootIssueStore.rootStore.state.stateMap?.[value]?.group];
return [this.rootIssueStore.rootStore.state.stateMap?.[value]?.group ?? issueObject.state__group];
}
return [value];
@@ -204,7 +204,7 @@ export class IssueDetail implements IIssueDetail {
this.commentReaction = new IssueCommentReactionStore(this);
this.subIssues = new IssueSubIssuesStore(this, serviceType);
this.link = new IssueLinkStore(this, serviceType);
this.subscription = new IssueSubscriptionStore(this);
this.subscription = new IssueSubscriptionStore(this, serviceType);
this.relation = new IssueRelationStore(this);
}
@@ -1,10 +1,10 @@
import set from "lodash/set";
import { action, makeObservable, observable, runInAction } from "mobx";
// services
import { EIssueServiceType } from "@plane/constants";
import { IssueService } from "@/services/issue/issue.service";
// types
import { IIssueDetail } from "./root.store";
export interface IIssueSubscriptionStoreActions {
addSubscription: (issueId: string, isSubscribed: boolean | undefined | null) => void;
fetchSubscriptions: (workspaceSlug: string, projectId: string, issueId: string) => Promise<boolean>;
@@ -27,7 +27,7 @@ export class IssueSubscriptionStore implements IIssueSubscriptionStore {
// services
issueService;
constructor(rootStore: IIssueDetail) {
constructor(rootStore: IIssueDetail, serviceType: EIssueServiceType) {
makeObservable(this, {
// observables
subscriptionMap: observable,
@@ -40,7 +40,7 @@ export class IssueSubscriptionStore implements IIssueSubscriptionStore {
// root store
this.rootIssueDetail = rootStore;
// services
this.issueService = new IssueService();
this.issueService = new IssueService(serviceType);
}
// helper methods
+1 -11
View File
@@ -1,12 +1,10 @@
import sortBy from "lodash/sortBy";
// types
import { EUserPermissions } from "@plane/constants";
import { TProjectDisplayFilters, TProjectFilters, TProjectOrderByOptions } from "@plane/types";
// helpers
import { getDate } from "@/helpers/date-time.helper";
import { satisfiesDateFilter } from "@/helpers/filter.helper";
// plane web constants
// types
// plane web imports
import { TProject } from "@/plane-web/types";
/**
@@ -49,14 +47,6 @@ export const orderJoinedProjects = (
export const projectIdentifierSanitizer = (identifier: string): string =>
identifier.replace(/[^ÇŞĞIİÖÜA-Za-z0-9]/g, "");
/**
* @description Checks if the project should be rendered or not based on the user role
* @param {TProject} project
* @returns {boolean}
*/
export const shouldRenderProject = (project: TProject): boolean =>
!!project.member_role && project.member_role >= EUserPermissions.MEMBER;
/**
* @description filters projects based on the filter
* @param {TProject} project
+11 -11
View File
@@ -21,14 +21,14 @@
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
"@headlessui/react": "^1.7.3",
"@intercom/messenger-js-sdk": "^0.0.12",
"@plane/constants": "*",
"@plane/editor": "*",
"@plane/hooks": "*",
"@plane/i18n": "*",
"@plane/propel": "*",
"@plane/types": "*",
"@plane/ui": "*",
"@plane/utils": "*",
"@plane/constants": "workspace:*",
"@plane/editor": "workspace:*",
"@plane/hooks": "workspace:*",
"@plane/i18n": "workspace:*",
"@plane/propel": "workspace:*",
"@plane/types": "workspace:*",
"@plane/ui": "workspace:*",
"@plane/utils": "workspace:*",
"@popperjs/core": "^2.11.8",
"@react-pdf/renderer": "^3.4.5",
"@tanstack/react-table": "^8.21.3",
@@ -69,9 +69,9 @@
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"@plane/eslint-config": "*",
"@plane/tailwind-config": "*",
"@plane/typescript-config": "*",
"@plane/eslint-config": "workspace:*",
"@plane/tailwind-config": "workspace:*",
"@plane/typescript-config": "workspace:*",
"@types/dompurify": "^3.0.5",
"@types/lodash": "^4.14.202",
"@types/node": "18.16.1",
+43 -96
View File
@@ -4240,15 +4240,6 @@ bind-event-listener@^3.0.0:
resolved "https://registry.npmjs.org/bind-event-listener/-/bind-event-listener-3.0.0.tgz#c90f9a7fcb65cac21045f810c20ef7e647a74921"
integrity sha512-PJvH288AWQhKs2v9zyfYdPzlPqf5bXbGMmhmUIY9x4dAUGIWgomO771oBQNwJnMQSnUIXhKu6sgzpBRXTlvb8Q==
bl@^4.0.3:
version "4.1.0"
resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
dependencies:
buffer "^5.5.0"
inherits "^2.0.4"
readable-stream "^3.4.0"
bluebird@^3.7.2:
version "3.7.2"
resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
@@ -4333,14 +4324,6 @@ buffer-from@^1.0.0:
resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
buffer@^5.5.0:
version "5.7.1"
resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.1.13"
buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
@@ -4506,11 +4489,6 @@ chokidar@3.6.0, chokidar@^3.3.0, chokidar@^3.5.2, chokidar@^3.5.3, chokidar@^3.6
optionalDependencies:
fsevents "~2.3.2"
chownr@^1.1.1:
version "1.1.4"
resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
chromatic@^11.4.0:
version "11.25.2"
resolved "https://registry.npmjs.org/chromatic/-/chromatic-11.25.2.tgz#cb93dc1332d8f6b70d97a3ef126bc6d03429d396"
@@ -5442,7 +5420,7 @@ encodeurl@~2.0.0:
resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
end-of-stream@^1.1.0, end-of-stream@^1.4.1:
end-of-stream@^1.1.0:
version "1.4.4"
resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
@@ -6315,11 +6293,6 @@ fresh@0.5.2:
resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
fs-extra@^10.0.0:
version "10.1.0"
resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
@@ -6780,7 +6753,7 @@ icss-utils@^5.0.0, icss-utils@^5.1.0:
resolved "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
ieee754@^1.1.13, ieee754@^1.2.1:
ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
@@ -6833,7 +6806,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -8019,7 +7992,7 @@ minipass@^4.2.4:
resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
mkdirp-classic@^0.5.3:
version "0.5.3"
resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
@@ -9474,7 +9447,7 @@ read-cache@^1.0.0:
dependencies:
pify "^2.3.0"
readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.2:
readable-stream@^3.4.0, readable-stream@^3.6.2:
version "3.6.2"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
@@ -10527,20 +10500,10 @@ tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1:
resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
tar-fs@^2.0.0:
version "2.1.2"
resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz#425f154f3404cb16cb8ff6e671d45ab2ed9596c5"
integrity sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==
dependencies:
chownr "^1.1.1"
mkdirp-classic "^0.5.2"
pump "^3.0.0"
tar-stream "^2.1.4"
tar-fs@^3.0.4:
version "3.0.8"
resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz#8f62012537d5ff89252d01e48690dc4ebed33ab7"
integrity sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==
tar-fs@3.0.9, tar-fs@^2.0.0, tar-fs@^3.0.4:
version "3.0.9"
resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.9.tgz#d570793c6370d7078926c41fa422891566a0b617"
integrity sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==
dependencies:
pump "^3.0.0"
tar-stream "^3.1.5"
@@ -10548,17 +10511,6 @@ tar-fs@^3.0.4:
bare-fs "^4.0.1"
bare-path "^3.0.0"
tar-stream@^2.1.4:
version "2.2.0"
resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
dependencies:
bl "^4.0.3"
end-of-stream "^1.4.1"
fs-constants "^1.0.0"
inherits "^2.0.3"
readable-stream "^3.1.1"
tar-stream@^3.1.5:
version "3.1.7"
resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz#24b3fb5eabada19fe7338ed6d26e5f7c482e792b"
@@ -10866,47 +10818,47 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
turbo-darwin-64@2.5.3:
version "2.5.3"
resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-2.5.3.tgz#e1f19e816f76e0d636e31e66f8238c43bf870f45"
integrity sha512-YSItEVBUIvAGPUDpAB9etEmSqZI3T6BHrkBkeSErvICXn3dfqXUfeLx35LfptLDEbrzFUdwYFNmt8QXOwe9yaw==
turbo-darwin-64@2.5.4:
version "2.5.4"
resolved "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.5.4.tgz#f03b3f071365c626d05e84d0d4b9db96348f2951"
integrity sha512-ah6YnH2dErojhFooxEzmvsoZQTMImaruZhFPfMKPBq8sb+hALRdvBNLqfc8NWlZq576FkfRZ/MSi4SHvVFT9PQ==
turbo-darwin-arm64@2.5.3:
version "2.5.3"
resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-2.5.3.tgz#f80074fd786f703bcb0415e13df225ba781950fc"
integrity sha512-5PefrwHd42UiZX7YA9m1LPW6x9YJBDErXmsegCkVp+GjmWrADfEOxpFrGQNonH3ZMj77WZB2PVE5Aw3gA+IOhg==
turbo-darwin-arm64@2.5.4:
version "2.5.4"
resolved "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.5.4.tgz#614becf281da75af5a01094373f380ac2b48190a"
integrity sha512-2+Nx6LAyuXw2MdXb7pxqle3MYignLvS7OwtsP9SgtSBaMlnNlxl9BovzqdYAgkUW3AsYiQMJ/wBRb7d+xemM5A==
turbo-linux-64@2.5.3:
version "2.5.3"
resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-2.5.3.tgz#93bfe009a24a76295c8164896845b5098c293293"
integrity sha512-M9xigFgawn5ofTmRzvjjLj3Lqc05O8VHKuOlWNUlnHPUltFquyEeSkpQNkE/vpPdOR14AzxqHbhhxtfS4qvb1w==
turbo-linux-64@2.5.4:
version "2.5.4"
resolved "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.5.4.tgz#fc7425f35feb19a8373e7926eccdd4a3b2fb77a4"
integrity sha512-5May2kjWbc8w4XxswGAl74GZ5eM4Gr6IiroqdLhXeXyfvWEdm2mFYCSWOzz0/z5cAgqyGidF1jt1qzUR8hTmOA==
turbo-linux-arm64@2.5.3:
version "2.5.3"
resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-2.5.3.tgz#bf4664561094711aa289d92b9443de2aefca5d6e"
integrity sha512-auJRbYZ8SGJVqvzTikpg1bsRAsiI9Tk0/SDkA5Xgg0GdiHDH/BOzv1ZjDE2mjmlrO/obr19Dw+39OlMhwLffrw==
turbo-linux-arm64@2.5.4:
version "2.5.4"
resolved "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.5.4.tgz#ab5418a88089e4ec20b2c28c3d3c6b8a99b07a81"
integrity sha512-/2yqFaS3TbfxV3P5yG2JUI79P7OUQKOUvAnx4MV9Bdz6jqHsHwc9WZPpO4QseQm+NvmgY6ICORnoVPODxGUiJg==
turbo-windows-64@2.5.3:
version "2.5.3"
resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-2.5.3.tgz#acbc2db093c7a74f0e692b899e649284285a2e7b"
integrity sha512-arLQYohuHtIEKkmQSCU9vtrKUg+/1TTstWB9VYRSsz+khvg81eX6LYHtXJfH/dK7Ho6ck+JaEh5G+QrE1jEmCQ==
turbo-windows-64@2.5.4:
version "2.5.4"
resolved "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.5.4.tgz#fc78585d3950e5cd64bed8d6648f078e7f32f9a5"
integrity sha512-EQUO4SmaCDhO6zYohxIjJpOKRN3wlfU7jMAj3CgcyTPvQR/UFLEKAYHqJOnJtymbQmiiM/ihX6c6W6Uq0yC7mA==
turbo-windows-arm64@2.5.3:
version "2.5.3"
resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-2.5.3.tgz#78e8cfdb49b69fbadf03031532f1524be3661729"
integrity sha512-3JPn66HAynJ0gtr6H+hjY4VHpu1RPKcEwGATvGUTmLmYSYBQieVlnGDRMMoYN066YfyPqnNGCfhYbXfH92Cm0g==
turbo-windows-arm64@2.5.4:
version "2.5.4"
resolved "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.5.4.tgz#5134537cc9fa27f4647f2f899b7ba2dacfc6ad22"
integrity sha512-oQ8RrK1VS8lrxkLriotFq+PiF7iiGgkZtfLKF4DDKsmdbPo0O9R2mQxm7jHLuXraRCuIQDWMIw6dpcr7Iykf4A==
turbo@^2.5.3:
version "2.5.3"
resolved "https://registry.yarnpkg.com/turbo/-/turbo-2.5.3.tgz#657dcae430552d9bb237e9e1d91f711c465c9f28"
integrity sha512-iHuaNcq5GZZnr3XDZNuu2LSyCzAOPwDuo5Qt+q64DfsTP1i3T2bKfxJhni2ZQxsvAoxRbuUK5QetJki4qc5aYA==
turbo@^2.5.4:
version "2.5.4"
resolved "https://registry.npmjs.org/turbo/-/turbo-2.5.4.tgz#e46213a4560b94e56c014e0fd56d06605de16753"
integrity sha512-kc8ZibdRcuWUG1pbYSBFWqmIjynlD8Lp7IB6U3vIzvOv9VG+6Sp8bzyeBWE3Oi8XV5KsQrznyRTBPvrf99E4mA==
optionalDependencies:
turbo-darwin-64 "2.5.3"
turbo-darwin-arm64 "2.5.3"
turbo-linux-64 "2.5.3"
turbo-linux-arm64 "2.5.3"
turbo-windows-64 "2.5.3"
turbo-windows-arm64 "2.5.3"
turbo-darwin-64 "2.5.4"
turbo-darwin-arm64 "2.5.4"
turbo-linux-64 "2.5.4"
turbo-linux-arm64 "2.5.4"
turbo-windows-64 "2.5.4"
turbo-windows-arm64 "2.5.4"
tween-functions@^1.2.0:
version "1.2.0"
@@ -11190,11 +11142,6 @@ use-callback-ref@^1.3.3:
dependencies:
tslib "^2.0.0"
use-debounce@^9.0.4:
version "9.0.4"
resolved "https://registry.npmjs.org/use-debounce/-/use-debounce-9.0.4.tgz#51d25d856fbdfeb537553972ce3943b897f1ac85"
integrity sha512-6X8H/mikbrt0XE8e+JXRtZ8yYVvKkdYRfmIhWZYsP8rcNs9hk3APV8Ua2mFkKRLcJKVdnX2/Vwrmg2GWKUQEaQ==
use-font-face-observer@^1.2.2:
version "1.2.2"
resolved "https://registry.npmjs.org/use-font-face-observer/-/use-font-face-observer-1.2.2.tgz#ed230d907589c6b17e8c8b896c9f5913968ac5ed"