diff --git a/admin/Dockerfile.admin b/admin/Dockerfile.admin index ad9469110e7..3d9897705e8 100644 --- a/admin/Dockerfile.admin +++ b/admin/Dockerfile.admin @@ -83,4 +83,6 @@ ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL ENV NEXT_TELEMETRY_DISABLED 1 ENV TURBO_TELEMETRY_DISABLED 1 -EXPOSE 3000 \ No newline at end of file +EXPOSE 3000 + +CMD node admin/server.js admin \ No newline at end of file diff --git a/admin/ce/components/common/upgrade-button.tsx b/admin/ce/components/common/upgrade-button.tsx index aa3c95fdbed..cdbe32a5baf 100644 --- a/admin/ce/components/common/upgrade-button.tsx +++ b/admin/ce/components/common/upgrade-button.tsx @@ -9,8 +9,9 @@ import { getButtonStyling } from "@plane/ui"; import { cn } from "@/helpers/common.helper"; export const UpgradeButton: React.FC = () => ( - - Available on One - - + + // + // Available on One + // + // ); diff --git a/admin/core/components/admin-sidebar/help-section.tsx b/admin/core/components/admin-sidebar/help-section.tsx index abba68e3eae..dcb96d0a512 100644 --- a/admin/core/components/admin-sidebar/help-section.tsx +++ b/admin/core/components/admin-sidebar/help-section.tsx @@ -61,17 +61,6 @@ export const HelpSection: FC = observer(() => { {!isSidebarCollapsed && "Redirect to plane"} - - - + )} + + + ))} + + + ); +}; \ No newline at end of file diff --git a/web/core/components/issues/index.ts b/web/core/components/issues/index.ts index 483d53da918..42114945798 100644 --- a/web/core/components/issues/index.ts +++ b/web/core/components/issues/index.ts @@ -12,6 +12,7 @@ export * from "./issue-update-status"; export * from "./create-issue-toast-action-items"; export * from "./relations"; export * from "./issue-detail-widgets"; +export * from "./custom-properties"; // issue details export * from "./issue-detail"; diff --git a/web/core/components/issues/issue-detail/issue-activity/activity/actions/custom-properties.tsx b/web/core/components/issues/issue-detail/issue-activity/activity/actions/custom-properties.tsx new file mode 100644 index 00000000000..96c8e352e8e --- /dev/null +++ b/web/core/components/issues/issue-detail/issue-activity/activity/actions/custom-properties.tsx @@ -0,0 +1,45 @@ +"use client"; + +import { FC } from "react"; +import { observer } from "mobx-react"; +// hooks +import { useIssueDetail } from "@/hooks/store"; +// components +import { IssueActivityBlockComponent, IssueLink } from "./"; +// icons +import { DoubleCircleIcon } from "@plane/ui"; + +type TIssueCustomPropertyActivity = { activityId: string; showIssue?: boolean; ends: "top" | "bottom" | undefined }; + +export const IssueCustomPropertyActivity: FC = observer((props) => { + const { activityId, showIssue = true, ends } = props; + // hooks + const { + activity: { getActivityById }, + } = useIssueDetail(); + + const activity = getActivityById(activityId); + + if (!activity) return <>; + + const isNewProperty = !activity.old_value; + const customPropertyKey = activity?.field.replace("Custom Property ", ""); + const newValue = activity.new_value; + const oldValue = activity.old_value; + + return ( + } + activityId={activityId} + ends={ends} + > + <> + {isNewProperty ? ( + <>Added a Custom Property {customPropertyKey} with value {newValue}. + ) : ( + <>Updated the value of {customPropertyKey} from {oldValue} to {newValue}. + )} + + + ); +}); diff --git a/web/core/components/issues/issue-detail/issue-activity/activity/actions/index.ts b/web/core/components/issues/issue-detail/issue-activity/activity/actions/index.ts index ea69163085a..f76b1c66549 100644 --- a/web/core/components/issues/issue-detail/issue-activity/activity/actions/index.ts +++ b/web/core/components/issues/issue-detail/issue-activity/activity/actions/index.ts @@ -17,6 +17,7 @@ export * from "./attachment"; export * from "./archived-at"; export * from "./inbox"; export * from "./label-activity-chip"; +export * from "./custom-properties"; // helpers export * from "./helpers/activity-block"; diff --git a/web/core/components/issues/issue-detail/issue-activity/activity/activity-list.tsx b/web/core/components/issues/issue-detail/issue-activity/activity/activity-list.tsx index 18cd3481fe9..6fc4e1e27c4 100644 --- a/web/core/components/issues/issue-detail/issue-activity/activity/activity-list.tsx +++ b/web/core/components/issues/issue-detail/issue-activity/activity/activity-list.tsx @@ -24,6 +24,7 @@ import { IssueAttachmentActivity, IssueArchivedAtActivity, IssueInboxActivity, + IssueCustomPropertyActivity, } from "./actions"; type TIssueActivityItem = { @@ -42,6 +43,11 @@ export const IssueActivityItem: FC = observer((props) => { const componentDefaultProps = { activityId, ends }; const activityField = getActivityById(activityId)?.field; + + if (activityField && activityField.startsWith("Custom Property")) { + return ; + } + switch (activityField) { case null: // default issue creation return ; diff --git a/web/core/components/issues/issue-detail/sidebar.tsx b/web/core/components/issues/issue-detail/sidebar.tsx index 102dc6e5a33..e3a0ae77ed6 100644 --- a/web/core/components/issues/issue-detail/sidebar.tsx +++ b/web/core/components/issues/issue-detail/sidebar.tsx @@ -2,7 +2,8 @@ import React from "react"; import { observer } from "mobx-react"; -import { CalendarCheck2, CalendarClock, LayoutPanelTop, Signal, Tag, Triangle, UserCircle2, Users } from "lucide-react"; +import { CalendarCheck2, CalendarClock, LayoutPanelTop, Signal, Tag, Triangle, UserCircle2, Users, Info } from "lucide-react"; +import axios from "axios"; // ui import { ContrastIcon, DiceIcon, DoubleCircleIcon } from "@plane/ui"; // components @@ -14,7 +15,7 @@ import { StateDropdown, } from "@/components/dropdowns"; import { ButtonAvatars } from "@/components/dropdowns/member/avatar"; -import { IssueCycleSelect, IssueLabel, IssueModuleSelect, IssueParentSelect } from "@/components/issues"; +import { IssueCycleSelect, IssueLabel, IssueModuleSelect, IssueParentSelect, CustomProperties} from "@/components/issues"; // helpers import { cn } from "@/helpers/common.helper"; import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper"; @@ -26,6 +27,8 @@ import { IssueAdditionalPropertyValuesUpdate } from "@/plane-web/components/issu import { IssueWorklogProperty } from "@/plane-web/components/issues"; // components import type { TIssueOperations } from "./root"; +import { ISSUE_ADDITIONAL_PROPERTIES } from "@/constants/issue"; +import { CustomProperty } from "../custom-properties"; type Props = { workspaceSlug: string; @@ -53,13 +56,52 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { // derived values const projectDetails = getProjectById(issue.project_id); const stateDetails = getStateById(issue.state_id); - + const customProperties = issue?.custom_properties || []; + const issue_type_id = issue?.type_id || "afd30f86-5ae5-428c-aa3a-e633ea973740"; const minDate = issue.start_date ? getDate(issue.start_date) : null; minDate?.setDate(minDate.getDate()); const maxDate = issue.target_date ? getDate(issue.target_date) : null; maxDate?.setDate(maxDate.getDate()); + const handleCustomPropertiesUpdate = async (updatedProperties: CustomProperty[]) => { + try { + const updateRequests = updatedProperties.map((property) => { + const customPropertyId = property?.id || ""; + const apiUrl = `/api/v1/workspaces/${workspaceSlug}/issues/${issueId}/custom-properties/`; + if (customPropertyId) { + return axios.patch( + `${apiUrl}${customPropertyId}/`, + { value: property.value }, + { + headers: { + 'x-api-key': 'TEST_API_TOKEN', + 'Content-Type': 'application/json' + } + } + ); + } else { + return axios.post( + apiUrl, + { + key: property.key, + value: property.value, + issue_type_custom_property: property.issue_type_custom_property, + }, + { + headers: { + 'x-api-key': 'TEST_API_TOKEN', + 'Content-Type': 'application/json' + } + } + ); + } + }); + await Promise.all(updateRequests); + } catch (error) { + } + }; + return ( <>
@@ -282,7 +324,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { />
- + = observer((props) => { isDisabled={!isEditable} /> )} + + {ISSUE_ADDITIONAL_PROPERTIES.map((prop: any) => + issue[prop.key] ? ( +
+
+ + {prop?.title} +
+
+ {issue[prop.key]} +
+
+ ) : null + )} + + diff --git a/web/core/components/issues/issue-layouts/filters/applied-filters/additional-properties.tsx b/web/core/components/issues/issue-layouts/filters/applied-filters/additional-properties.tsx new file mode 100644 index 00000000000..c90a3533b16 --- /dev/null +++ b/web/core/components/issues/issue-layouts/filters/applied-filters/additional-properties.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { observer } from "mobx-react"; + +// icons +import { X } from "lucide-react"; + +type Props = { + handleRemove: (val: string) => void; + values: string[]; + editable: boolean | undefined; +}; + +export const AppliedAdditionalPropertiesFilters: React.FC = observer((props) => { + const { handleRemove, values } = props; + + return ( + <> + {values.map((element) => ( +
+ {element} + +
+ ))} + + ); +}); diff --git a/web/core/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx b/web/core/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx index 268358efea2..64acb2ccac6 100644 --- a/web/core/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx +++ b/web/core/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx @@ -14,6 +14,7 @@ import { AppliedProjectFilters, AppliedStateFilters, AppliedStateGroupFilters, + AppliedAdditionalPropertiesFilters } from "@/components/issues"; // constants // helpers @@ -36,6 +37,7 @@ type Props = { const membersFilters = ["assignees", "mentions", "created_by", "subscriber"]; const dateFilters = ["start_date", "target_date"]; +const additionalFilters = ["hub_code", "customer_code", "reference_number", "trip_reference_number", "vendor_code", "worker_code"]; export const AppliedFiltersList: React.FC = observer((props) => { const { @@ -134,6 +136,13 @@ export const AppliedFiltersList: React.FC = observer((props) => { values={value} /> )} + {additionalFilters?.includes(filterKey) && ( + handleRemoveFilter(filterKey, val)} + values={value} + /> + )} {isEditingAllowed && ( + )} + + ) : ( +

No Matches Found

+ )} + + )} + + ); +}); \ No newline at end of file diff --git a/web/core/components/issues/issue-layouts/filters/header/filters/custom-properties.tsx b/web/core/components/issues/issue-layouts/filters/header/filters/custom-properties.tsx new file mode 100644 index 00000000000..e67eaaac70d --- /dev/null +++ b/web/core/components/issues/issue-layouts/filters/header/filters/custom-properties.tsx @@ -0,0 +1,142 @@ +"use client"; + +import React, { useMemo, useState } from "react"; +// import sortBy from "lodash/sortBy"; +import { observer } from "mobx-react"; +import { useParams } from "next/navigation"; +import { IState } from "@plane/types"; +import { WorkspaceService } from "@/services/workspace.service"; +import { API_BASE_URL } from "@/helpers/common.helper"; +// import { Loader, StateGroupIcon } from "@plane/ui"; +import { FilterHeader, FilterOption } from "@/components/issues"; + +type Props = { + appliedFilters: string[] | null; + handleUpdate: (val: string) => void; + searchQuery: string; +}; + +export const FilterCustomProperty: React.FC = observer((props) => { + const { workspaceSlug } = useParams(); + const workspaceService = new WorkspaceService(API_BASE_URL); + const { appliedFilters, handleUpdate, searchQuery } = props; + + const [mainPreviewEnabled, setMainPreviewEnabled] = useState(true); + const [groupPreviewEnabled, setGroupPreviewEnabled] = useState>({}); + const [renderMoreGroupItems, setRenderMoreGroupItems] = useState>({}); + const [groupedProperties, setGroupedProperties] = useState>({}); + + const appliedFiltersCount = appliedFilters?.length ?? 0; + + const handleViewToggle = (groupKey: string) => { + setRenderMoreGroupItems((prev) => { + return { + ...prev, + [groupKey]: !prev[groupKey] + }; + }); + }; + + React.useEffect(() => { + const fetchCustomProperties = async (): Promise => { + try { + const data = await workspaceService.getIssuesCustomProperties(workspaceSlug.toString()); + + if (Array.isArray(data)) { + // Convert array to an object with group keys + const groupedData: Record = data.reduce((acc, item) => { + const key = item.group || "default"; // Adjust based on API response + acc[key] = acc[key] ? [...acc[key], item] : [item]; + return acc; + }, {} as Record); + + setGroupedProperties(groupedData); + } else { + setGroupedProperties(data); + } + + Object.keys(data).forEach((groupKey) => { + setGroupPreviewEnabled((prev) => ({ + ...prev, + [groupKey]: true, + })); + }); + } catch (error) { + console.error("Error fetching custom properties:", error); + } + }; + + + fetchCustomProperties(); + }, [workspaceSlug]); + + const filteredGroupOptions = useMemo(() => { + return Object.keys(groupedProperties).reduce>((acc, groupKey) => { + const properties = groupedProperties[groupKey]; + + const filteredValues = properties + .filter((property) => property?.toLowerCase()?.includes(searchQuery.toLowerCase())) + .map((property) => property); + + if (filteredValues?.length > 0) { + acc[groupKey] = filteredValues; + } + + return acc; + }, {}); + }, [searchQuery, groupedProperties]); + + const toggleGroupPreview = (groupKey: string) => { + setGroupPreviewEnabled((prev) => ({ + ...prev, + [groupKey]: !prev[groupKey], + })); + }; + + return ( + <> + 0 ? ` (${appliedFiltersCount})` : ""}`} + isPreviewEnabled={mainPreviewEnabled} + handleIsPreviewEnabled={() => setMainPreviewEnabled(!mainPreviewEnabled)} + /> + {mainPreviewEnabled && ( +
+ {Object.keys(filteredGroupOptions).map((groupKey) => { + const properties = filteredGroupOptions[groupKey]; + return ( +
+ toggleGroupPreview(groupKey)} + /> + {groupPreviewEnabled[groupKey] && ( +
+ {properties + .slice(0, renderMoreGroupItems[groupKey] ? properties.length : 5) + .map((property) => ( + handleUpdate(`${groupKey}:${property}`)} + title={property} + /> + ))} + {properties.length > 5 ? : null} +
+ )} +
+ ); + })} +
+ )} + + ); +}); diff --git a/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx b/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx index 0bf180aefa7..71ace44f61a 100644 --- a/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx +++ b/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx @@ -16,16 +16,18 @@ import { FilterState, FilterStateGroup, FilterTargetDate, + FilterAdditionalProperties, FilterCycle, FilterModule, FilterIssueGrouping, } from "@/components/issues"; // constants -import { ILayoutDisplayFiltersOptions } from "@/constants/issue"; +import { ILayoutDisplayFiltersOptions, ISSUE_ADDITIONAL_PROPERTIES } from "@/constants/issue"; // hooks import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web components import { FilterIssueTypes } from "@/plane-web/components/issues"; +import { FilterCustomProperty } from "./custom-properties"; type Props = { filters: IIssueFilterOptions; @@ -242,6 +244,31 @@ export const FilterSelection: React.FC = observer((props) => { /> )} + + {ISSUE_ADDITIONAL_PROPERTIES.map((prop: any) => ( +
+ handleFiltersUpdate(prop.key as keyof IIssueFilterOptions, val)} + searchQuery={filtersSearchQuery} + /> +
+ ))} + + {/* custom_properties */} + {isFilterEnabled("custom_properties") && ( +
+ handleFiltersUpdate("custom_properties", val)} + // handleUpdate={(val) => handleFiltersUpdate("custom_properties", null, val)} + searchQuery={filtersSearchQuery} + /> +
+ )} ); diff --git a/web/core/components/issues/issue-layouts/filters/header/filters/index.ts b/web/core/components/issues/issue-layouts/filters/header/filters/index.ts index ab5756bf4d7..495020f9244 100644 --- a/web/core/components/issues/issue-layouts/filters/header/filters/index.ts +++ b/web/core/components/issues/issue-layouts/filters/header/filters/index.ts @@ -11,3 +11,4 @@ export * from "./state"; export * from "./cycle"; export * from "./module"; export * from "./target-date"; +export * from "./additional-properties"; diff --git a/web/core/components/issues/issue-layouts/spreadsheet/columns/index.ts b/web/core/components/issues/issue-layouts/spreadsheet/columns/index.ts index 3439d398b41..eec02b8ca18 100644 --- a/web/core/components/issues/issue-layouts/spreadsheet/columns/index.ts +++ b/web/core/components/issues/issue-layouts/spreadsheet/columns/index.ts @@ -12,3 +12,4 @@ export * from "./sub-issue-column"; export * from "./updated-on-column"; export * from "./module-column"; export * from "./cycle-column"; +export * from "./standard-property-column"; diff --git a/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx b/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx new file mode 100644 index 00000000000..23959e4701a --- /dev/null +++ b/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import { observer } from "mobx-react"; +// types +import { TIssue } from "@plane/types"; +import { Row, Tooltip } from "@plane/ui"; + +type Props = { + issue: TIssue; + property: string; +}; + +export const SpreadsheetStandardPropertyColumn: React.FC = observer((props) => { + const { issue, property } = props; + + return ( + + + {issue?.[property]} + + + ); +}); diff --git a/web/core/components/issues/issue-layouts/spreadsheet/issue-column.tsx b/web/core/components/issues/issue-layouts/spreadsheet/issue-column.tsx index dcd8773a0f1..9b8fb27e3db 100644 --- a/web/core/components/issues/issue-layouts/spreadsheet/issue-column.tsx +++ b/web/core/components/issues/issue-layouts/spreadsheet/issue-column.tsx @@ -43,6 +43,7 @@ export const IssueColumn = observer((props: Props) => { > , updates: any) => updateIssue && updateIssue(issue.project_id, issue.id, data).then(() => { diff --git a/web/core/components/issues/peek-overview/properties.tsx b/web/core/components/issues/peek-overview/properties.tsx index fcc706cbb86..b938f268784 100644 --- a/web/core/components/issues/peek-overview/properties.tsx +++ b/web/core/components/issues/peek-overview/properties.tsx @@ -2,7 +2,8 @@ import { FC } from "react"; import { observer } from "mobx-react"; -import { Signal, Tag, Triangle, LayoutPanelTop, CalendarClock, CalendarCheck2, Users, UserCircle2 } from "lucide-react"; +import { Signal, Tag, Triangle, LayoutPanelTop, CalendarClock, CalendarCheck2, Users, UserCircle2, Info } from "lucide-react"; +import axios from "axios"; // hooks // ui icons import { DiceIcon, DoubleCircleIcon, ContrastIcon } from "@plane/ui"; @@ -21,6 +22,7 @@ import { IssueParentSelect, IssueLabel, TIssueOperations, + CustomProperties } from "@/components/issues"; // helpers import { cn } from "@/helpers/common.helper"; @@ -29,7 +31,9 @@ import { shouldHighlightIssueDueDate } from "@/helpers/issue.helper"; import { useIssueDetail, useMember, useProject, useProjectState } from "@/hooks/store"; // plane web components import { IssueAdditionalPropertyValuesUpdate } from "@/plane-web/components/issue-types/values"; -import { IssueWorklogProperty } from "@/plane-web/components/issues"; +import { IssueWorklogProperty} from "@/plane-web/components/issues"; +import { ISSUE_ADDITIONAL_PROPERTIES } from "@/constants/issue"; +import { CustomProperty } from "../custom-properties"; interface IPeekOverviewProperties { workspaceSlug: string; @@ -39,6 +43,7 @@ interface IPeekOverviewProperties { issueOperations: TIssueOperations; } + export const PeekOverviewProperties: FC = observer((props) => { const { workspaceSlug, projectId, issueId, issueOperations, disabled } = props; // store hooks @@ -53,15 +58,54 @@ export const PeekOverviewProperties: FC = observer((pro if (!issue) return <>; const createdByDetails = getUserDetails(issue?.created_by); const projectDetails = getProjectById(issue.project_id); + const customProperties = issue?.custom_properties || []; + const issue_type_id = issue?.type_id || "afd30f86-5ae5-428c-aa3a-e633ea973740"; const isEstimateEnabled = projectDetails?.estimate; const stateDetails = getStateById(issue.state_id); - const minDate = getDate(issue.start_date); minDate?.setDate(minDate.getDate()); const maxDate = getDate(issue.target_date); maxDate?.setDate(maxDate.getDate()); + const handleCustomPropertiesUpdate = async (updatedProperties: CustomProperty[]) => { + try { + const updateRequests = updatedProperties.map((property) => { + const customPropertyId = property?.id || ""; + const apiUrl = `/api/v1/workspaces/${workspaceSlug}/issues/${issueId}/custom-properties/`; + if (customPropertyId) { + return axios.patch( + `${apiUrl}${customPropertyId}/`, + { value: property.value }, + { + headers: { + 'x-api-key': 'TEST_API_TOKEN', + 'Content-Type': 'application/json' + } + } + ); + } else { + return axios.post( + apiUrl, + { + key: property.key, + value: property.value, + issue_type_custom_property: property.issue_type_custom_property, + }, + { + headers: { + 'x-api-key': 'TEST_API_TOKEN', + 'Content-Type': 'application/json' + } + } + ); + } + }); + await Promise.all(updateRequests); + } catch (error) { + } + }; + return (
Properties
@@ -299,6 +343,26 @@ export const PeekOverviewProperties: FC = observer((pro isDisabled={disabled} /> )} + + {ISSUE_ADDITIONAL_PROPERTIES.map((prop: any) => + issue[prop.key] ? ( +
+
+ + {prop?.title} +
+
{issue[prop.key]}
+
+ ) : null + )} + +
); diff --git a/web/core/components/workspace/sidebar/dropdown.tsx b/web/core/components/workspace/sidebar/dropdown.tsx index a60a7d7e1b1..1310618f191 100644 --- a/web/core/components/workspace/sidebar/dropdown.tsx +++ b/web/core/components/workspace/sidebar/dropdown.tsx @@ -105,6 +105,7 @@ export const SidebarDropdown = observer(() => { {({ open }) => ( <> { )}
- - - - Create workspace - - {userLinks(workspaceSlug?.toString() ?? "").map( (link, index) => allowPermissions(link.access, EUserPermissionsLevel.WORKSPACE) && ( diff --git a/web/core/components/workspace/sidebar/help-section.tsx b/web/core/components/workspace/sidebar/help-section.tsx index 5570b5727a0..ed3dcd6dd93 100644 --- a/web/core/components/workspace/sidebar/help-section.tsx +++ b/web/core/components/workspace/sidebar/help-section.tsx @@ -15,7 +15,6 @@ import { useAppTheme, useCommandPalette, useInstance, useTransient, useUserSetti import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web components import { PlaneVersionNumber } from "@/plane-web/components/global"; -import { WorkspaceEditionBadge } from "@/plane-web/components/workspace"; import { ENABLE_LOCAL_DB_CACHE } from "@/plane-web/constants/issues"; export interface WorkspaceHelpSectionProps { @@ -52,123 +51,9 @@ export const SidebarHelpSection: React.FC = observer( } )} > +
- - - - - - } - customButtonClassName={`relative grid place-items-center rounded-md p-1.5 outline-none ${isCollapsed ? "w-full" : ""}`} - menuButtonOnClick={() => !isNeedHelpOpen && setIsNeedHelpOpen(true)} - onMenuClose={() => setIsNeedHelpOpen(false)} - placement={isCollapsed ? "left-end" : "top-end"} - maxHeight="lg" - closeOnSelect - > - - - - Documentation - - - {config?.intercom_app_id && config?.is_intercom_enabled && ( - - - - )} - - - - Contact sales - - -
- {ENABLE_LOCAL_DB_CACHE && ( - -
{ - e.preventDefault(); - e.stopPropagation(); - }} - className="flex w-full items-center justify-between text-xs hover:bg-custom-background-80" - > - Local Cache - toggleLocalDB(workspaceSlug?.toString(), projectId?.toString())} - /> -
-
- )} - - - - - - - - - Discord - - -
- -
- -
-
- -
-