diff --git a/plane-src/apps/web/ce/components/projects/external-contours/issue-header.tsx b/plane-src/apps/web/ce/components/projects/external-contours/issue-header.tsx index 5cf8c7a..eaf3507 100644 --- a/plane-src/apps/web/ce/components/projects/external-contours/issue-header.tsx +++ b/plane-src/apps/web/ce/components/projects/external-contours/issue-header.tsx @@ -7,16 +7,13 @@ import { useCallback, useEffect, useState } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; -import { MoveDiagonal, MoveRight } from "lucide-react"; +import { Check, MoveDiagonal, MoveRight, X } from "lucide-react"; import { useTranslation } from "@plane/i18n"; import { IconButton } from "@plane/propel/icon-button"; -import { Button } from "@plane/propel/button"; import { CenterPanelIcon, - CheckCircleFilledIcon, ChevronDownIcon, ChevronUpIcon, - CloseCircleFilledIcon, CopyLinkIcon, FullScreenPanelIcon, SidePanelIcon, @@ -100,6 +97,7 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont contourRequest.capabilities?.can_source_decide ?? (contourRequest.status === "closed" && contourRequest.source_decision !== "accepted"); const isSourceAccepted = contourRequest.source_decision === "accepted"; + const isDecisionSubmitting = loader === "mutation-loading"; const redirectToRelativeIssue = useCallback( (direction: "next" | "prev") => { @@ -291,16 +289,30 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
{canReviewClosedRequest && ( - <> - - - +
+ + + + + + +
)} {isSourceAccepted && ( diff --git a/plane-src/apps/web/ce/components/projects/external-contours/issue-root.tsx b/plane-src/apps/web/ce/components/projects/external-contours/issue-root.tsx index 1411171..676a9c0 100644 --- a/plane-src/apps/web/ce/components/projects/external-contours/issue-root.tsx +++ b/plane-src/apps/web/ce/components/projects/external-contours/issue-root.tsx @@ -13,14 +13,14 @@ import { useTranslation } from "@plane/i18n"; import { LabelPropertyIcon, PriorityIcon, PriorityPropertyIcon } from "@plane/propel/icons"; import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import type { TExternalContourRequest, TIssue, TNameDescriptionLoader } from "@plane/types"; -import { EFileAssetType } from "@plane/types"; +import { EFileAssetType, EIssueServiceType } from "@plane/types"; import { getTextContent } from "@plane/utils"; import { DescriptionVersionsRoot } from "@/components/core/description-versions"; import { DescriptionInput } from "@/components/editor/rich-text/description-input"; import { DescriptionInputLoader } from "@/components/editor/rich-text/description-input/loader"; -import { IssueAttachmentRoot } from "@/components/issues/attachment"; import type { TIssueOperations } from "@/components/issues/issue-detail"; import { IssueActivity } from "@/components/issues/issue-detail/issue-activity"; +import { IssueDetailWidgets } from "@/components/issues/issue-detail-widgets"; import { IssueReaction } from "@/components/issues/issue-detail/reactions"; import { IssueTitleInput } from "@/components/issues/title-input"; import { useProjectExternalContours } from "@/hooks/store/use-project-external-contours"; @@ -329,8 +329,16 @@ export const ExternalContoursIssueMainContent = observer(function ExternalContou -
- +
+
diff --git a/plane-src/apps/web/ce/components/projects/external-contours/peek-shell.tsx b/plane-src/apps/web/ce/components/projects/external-contours/peek-shell.tsx index 6d363c1..b10996c 100644 --- a/plane-src/apps/web/ce/components/projects/external-contours/peek-shell.tsx +++ b/plane-src/apps/web/ce/components/projects/external-contours/peek-shell.tsx @@ -14,6 +14,7 @@ import usePeekOverviewOutsideClickDetector from "@/hooks/use-peek-overview-outsi import { ExternalContoursIssueActionsHeader, type TExternalContourPeekMode } from "./issue-header"; const SIDE_PEEK_WIDTH_STORAGE_KEY = "nodedc:external-contour-peek-width"; +const SIDE_PEEK_MIN_WIDTH = 780; type Props = { workspaceSlug: string; @@ -41,9 +42,9 @@ export const ExternalContoursPeekShell = observer(function ExternalContoursPeekS } = props; const [peekMode, setPeekMode] = useState("side-peek"); const [sidePeekWidth, setSidePeekWidth] = useState(() => { - if (typeof window === "undefined") return 720; + if (typeof window === "undefined") return SIDE_PEEK_MIN_WIDTH; - const fallbackWidth = Math.max(640, Math.floor(window.innerWidth * 0.5)); + const fallbackWidth = Math.max(SIDE_PEEK_MIN_WIDTH, Math.floor(window.innerWidth * 0.54)); const storedWidth = window.localStorage.getItem(SIDE_PEEK_WIDTH_STORAGE_KEY); const parsedWidth = storedWidth ? parseInt(storedWidth, 10) : NaN; @@ -72,10 +73,9 @@ export const ExternalContoursPeekShell = observer(function ExternalContoursPeekS (event: MouseEvent) => { if (!isResizingPeek) return; - const maxWidth = Math.max(720, window.innerWidth - 48); - const minWidth = 640; + const maxWidth = Math.max(SIDE_PEEK_MIN_WIDTH, window.innerWidth - 48); const deltaX = event.clientX - initialMouseXRef.current; - const nextWidth = Math.min(Math.max(initialPeekWidthRef.current - deltaX, minWidth), maxWidth); + const nextWidth = Math.min(Math.max(initialPeekWidthRef.current - deltaX, SIDE_PEEK_MIN_WIDTH), maxWidth); setSidePeekWidth(nextWidth); }, [isResizingPeek] @@ -94,7 +94,7 @@ export const ExternalContoursPeekShell = observer(function ExternalContoursPeekS useEffect(() => { const handleWindowResize = () => { - const maxWidth = Math.max(720, window.innerWidth - 48); + const maxWidth = Math.max(SIDE_PEEK_MIN_WIDTH, window.innerWidth - 48); setSidePeekWidth((currentWidth) => Math.min(currentWidth, maxWidth)); }; @@ -105,8 +105,8 @@ export const ExternalContoursPeekShell = observer(function ExternalContoursPeekS useEffect(() => { if (typeof window === "undefined") return; - const maxWidth = Math.max(720, window.innerWidth - 48); - const clampedWidth = Math.min(Math.max(sidePeekWidth, 640), maxWidth); + const maxWidth = Math.max(SIDE_PEEK_MIN_WIDTH, window.innerWidth - 48); + const clampedWidth = Math.min(Math.max(sidePeekWidth, SIDE_PEEK_MIN_WIDTH), maxWidth); window.localStorage.setItem(SIDE_PEEK_WIDTH_STORAGE_KEY, String(clampedWidth)); @@ -151,7 +151,7 @@ export const ExternalContoursPeekShell = observer(function ExternalContoursPeekS ? "absolute z-[25] flex flex-col overflow-hidden border border-subtle/70 bg-surface-1/80 backdrop-blur-2xl transition-all duration-300" : "h-full w-full", !embedIssue && { - "top-3 right-3 bottom-3 w-[calc(100%-1.5rem)] rounded-[28px] border md:min-w-[640px] md:max-w-[calc(100vw-1.5rem)]": + "top-3 right-3 bottom-3 w-[calc(100%-1.5rem)] rounded-[28px] border md:min-w-[780px] md:max-w-[calc(100vw-1.5rem)]": peekMode === "side-peek", "top-[8.33%] left-[8.33%] size-5/6 rounded-[28px]": peekMode === "modal", "absolute inset-0 m-4 rounded-[28px]": peekMode === "full-screen", diff --git a/plane-src/apps/web/styles/globals.css b/plane-src/apps/web/styles/globals.css index 9173377..ab4362c 100644 --- a/plane-src/apps/web/styles/globals.css +++ b/plane-src/apps/web/styles/globals.css @@ -3603,11 +3603,16 @@ .nodedc-external-detail-toolbar { display: flex; - flex-wrap: wrap; + flex-wrap: nowrap; align-items: center; + justify-content: flex-end; gap: 0.5rem; } + .nodedc-external-detail-toolbar > .flex { + flex-wrap: nowrap !important; + } + .nodedc-external-toolbar-cluster { display: flex; align-items: center; @@ -3618,6 +3623,85 @@ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.018) !important; } + .nodedc-external-decision-cluster { + display: flex; + align-items: center; + gap: 0.45rem; + min-height: 3.05rem; + padding: 0.28rem !important; + border: 0 !important; + outline: none !important; + border-radius: 999px; + background: rgba(255, 255, 255, 0.055) !important; + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.018), + 0 10px 28px rgba(0, 0, 0, 0.1) !important; + -webkit-backdrop-filter: blur(18px); + backdrop-filter: blur(18px); + } + + .nodedc-external-decision-button { + display: grid; + place-items: center; + width: 2.5rem; + min-width: 2.5rem; + height: 2.5rem; + border: 0 !important; + outline: none !important; + border-radius: 999px; + padding: 0 !important; + transition: + transform 160ms ease, + background 160ms ease, + color 160ms ease, + opacity 160ms ease; + } + + .nodedc-external-decision-button:hover:not(:disabled) { + transform: translateY(-1px); + } + + .nodedc-external-decision-button:disabled { + cursor: progress; + opacity: 0.58; + } + + .nodedc-external-decision-button-accept { + background: rgb(var(--nodedc-card-active-rgb)) !important; + color: rgb(var(--nodedc-on-card-active-rgb)) !important; + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.18), + 0 8px 18px rgba(var(--nodedc-card-active-rgb), 0.2) !important; + } + + .nodedc-external-decision-button-accept:hover:not(:disabled) { + background: color-mix(in srgb, rgb(var(--nodedc-card-active-rgb)) 86%, white) !important; + } + + .nodedc-external-decision-button-decline { + background: rgba(255, 255, 255, 0.12) !important; + color: rgba(255, 255, 255, 0.72) !important; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04) !important; + } + + .nodedc-external-decision-button-decline:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.18) !important; + color: rgba(255, 255, 255, 0.86) !important; + } + + .nodedc-external-detail-widgets { + --nodedc-widget-surface-rgb: 255 255 255; + } + + .nodedc-external-detail-widgets [data-slot="button"], + .nodedc-external-detail-widgets button { + outline: none !important; + } + + .nodedc-external-detail-widgets .nodedc-attachment-upload { + min-height: 4.5rem; + } + .nodedc-external-priority-inline { display: inline-flex; align-items: center;