From f13c190676faf79efee2e1f2abed78341e57f282 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Wed, 22 May 2024 12:39:34 +0530 Subject: [PATCH] [WEB-1396] chore: remove/ disable all actionable items from deploy url in case url is embedded using Iframe. (#4544) * fix: is in iframe validation check * chore: remove/ disable all actionable items from deploy url in case url is embeded using Iframe. * chore: remove copy issue link option if clipboard write access is not granted. --------- Co-authored-by: sriram veeraghanta --- space/components/issues/navbar/controls.tsx | 5 ++- .../comment/comment-detail-card.tsx | 4 +- .../comment/comment-reactions.tsx | 39 +++++++++++------- .../issues/peek-overview/header.tsx | 4 +- .../issues/peek-overview/issue-activity.tsx | 41 ++++++++++--------- .../issues/peek-overview/issue-reaction.tsx | 4 +- .../peek-overview/issue-vote-reactions.tsx | 28 ++++++++++--- .../hooks/use-clipboard-write-permission.tsx | 28 +++++++++++++ space/hooks/use-is-in-iframe.tsx | 17 ++++++++ 9 files changed, 127 insertions(+), 43 deletions(-) create mode 100644 space/hooks/use-clipboard-write-permission.tsx create mode 100644 space/hooks/use-is-in-iframe.tsx diff --git a/space/components/issues/navbar/controls.tsx b/space/components/issues/navbar/controls.tsx index 4b9bb7d5f..20c0ca408 100644 --- a/space/components/issues/navbar/controls.tsx +++ b/space/components/issues/navbar/controls.tsx @@ -12,6 +12,7 @@ import { UserAvatar } from "@/components/issues/navbar/user-avatar"; import { queryParamGenerator } from "@/helpers/query-param-generator"; // hooks import { useProject, useIssueFilter, useIssueDetails } from "@/hooks/store"; +import useIsInIframe from "@/hooks/use-is-in-iframe"; // types import { TIssueLayout } from "@/types/issue"; @@ -39,6 +40,8 @@ export const NavbarControls: FC = observer((props) => { // derived values const activeLayout = issueFilters?.display_filters?.layout || undefined; + const isInIframe = useIsInIframe(); + useEffect(() => { if (workspaceSlug && projectId && settings) { const viewsAcceptable: string[] = []; @@ -111,7 +114,7 @@ export const NavbarControls: FC = observer((props) => { - + {!isInIframe && } ); }); diff --git a/space/components/issues/peek-overview/comment/comment-detail-card.tsx b/space/components/issues/peek-overview/comment/comment-detail-card.tsx index 449b7883c..3ede0333b 100644 --- a/space/components/issues/peek-overview/comment/comment-detail-card.tsx +++ b/space/components/issues/peek-overview/comment/comment-detail-card.tsx @@ -11,6 +11,7 @@ import { CommentReactions } from "@/components/issues/peek-overview"; import { timeAgo } from "@/helpers/date-time.helper"; // hooks import { useIssueDetails, useProject, useUser } from "@/hooks/store"; +import useIsInIframe from "@/hooks/use-is-in-iframe"; // types import { Comment } from "@/types/issue"; @@ -25,6 +26,7 @@ export const CommentCard: React.FC = observer((props) => { const { workspace } = useProject(); const { peekId, deleteIssueComment, updateIssueComment } = useIssueDetails(); const { data: currentUser } = useUser(); + const isInIframe = useIsInIframe(); // derived values const workspaceId = workspace?.id; @@ -138,7 +140,7 @@ export const CommentCard: React.FC = observer((props) => { - {currentUser?.id === comment?.actor_detail?.id && ( + {!isInIframe && currentUser?.id === comment?.actor_detail?.id && ( = observer((props) => { // hooks const { addCommentReaction, removeCommentReaction, details, peekId } = useIssueDetails(); const { data: user } = useUser(); + const isInIframe = useIsInIframe(); const commentReactions = peekId ? details[peekId].comments.find((c) => c.id === commentId)?.comment_reactions : []; const groupedReactions = peekId ? groupReactions(commentReactions ?? [], "reaction") : {}; @@ -58,15 +61,17 @@ export const CommentReactions: React.FC = observer((props) => { return (
- { - if (user) handleReactionClick(value); - else router.push(`/?next_path=${pathName}?${queryParam}`); - }} - position="top" - selected={userReactions?.map((r) => r.reaction)} - size="md" - /> + {!isInIframe && ( + { + if (user) handleReactionClick(value); + else router.push(`/?next_path=${pathName}?${queryParam}`); + }} + position="top" + selected={userReactions?.map((r) => r.reaction)} + size="md" + /> + )} {Object.keys(groupedReactions || {}).map((reaction) => { const reactions = groupedReactions?.[reaction] ?? []; @@ -89,14 +94,20 @@ export const CommentReactions: React.FC = observer((props) => {
- {(peekMode === "side" || peekMode === "modal") && ( + {isClipboardWriteAllowed && (peekMode === "side" || peekMode === "modal") && (
- {currentUser ? ( - <> - {canComment && ( -
- -
- )} - - ) : ( -
-

- - Sign in to add your comment -

- - - -
- )} + {!isInIframe && + (currentUser ? ( + <> + {canComment && ( +
+ +
+ )} + + ) : ( +
+

+ + Sign in to add your comment +

+ + + +
+ ))} )} diff --git a/space/components/issues/peek-overview/issue-reaction.tsx b/space/components/issues/peek-overview/issue-reaction.tsx index 991125312..87210f377 100644 --- a/space/components/issues/peek-overview/issue-reaction.tsx +++ b/space/components/issues/peek-overview/issue-reaction.tsx @@ -1,6 +1,7 @@ import { useParams } from "next/navigation"; import { IssueEmojiReactions, IssueVotes } from "@/components/issues/peek-overview"; import { useProject } from "@/hooks/store"; +import useIsInIframe from "@/hooks/use-is-in-iframe"; // type IssueReactionsProps = { // workspaceSlug: string; @@ -11,6 +12,7 @@ export const IssueReactions: React.FC = () => { const { workspace_slug: workspaceSlug, project_id: projectId } = useParams(); const { canVote, canReact } = useProject(); + const isInIframe = useIsInIframe(); return (
@@ -21,7 +23,7 @@ export const IssueReactions: React.FC = () => {
)} - {canReact && ( + {!isInIframe && canReact && (
diff --git a/space/components/issues/peek-overview/issue-vote-reactions.tsx b/space/components/issues/peek-overview/issue-vote-reactions.tsx index 388d59072..1e565e862 100644 --- a/space/components/issues/peek-overview/issue-vote-reactions.tsx +++ b/space/components/issues/peek-overview/issue-vote-reactions.tsx @@ -5,9 +5,11 @@ import { observer } from "mobx-react-lite"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { Tooltip } from "@plane/ui"; // helpers +import { cn } from "@/helpers/common.helper"; import { queryParamGenerator } from "@/helpers/query-param-generator"; // hooks import { useIssueDetails, useUser } from "@/hooks/store"; +import useIsInIframe from "@/hooks/use-is-in-iframe"; type TIssueVotes = { workspaceSlug: string; @@ -32,6 +34,8 @@ export const IssueVotes: React.FC = observer((props) => { const issueDetailsStore = useIssueDetails(); const { data: user, fetchCurrentUser } = useUser(); + const isInIframe = useIsInIframe(); + const issueId = issueDetailsStore.peekId; const votes = issueId ? issueDetailsStore.details[issueId]?.votes : []; @@ -94,12 +98,18 @@ export const IssueVotes: React.FC = observer((props) => { type="button" disabled={isSubmitting} onClick={(e) => { + if (isInIframe) return; if (user) handleVote(e, 1); else router.push(`/?next_path=${pathName}?${queryParam}`); }} - className={`flex items-center justify-center gap-x-1 overflow-hidden rounded border px-2 h-7 focus:outline-none ${ - isUpVotedByUser ? "border-custom-primary-200 text-custom-primary-200" : "border-custom-border-300" - }`} + className={cn( + "flex items-center justify-center gap-x-1 overflow-hidden rounded border px-2 h-7 focus:outline-none", + { + "border-custom-primary-200 text-custom-primary-200": isUpVotedByUser, + "border-custom-border-300": !isUpVotedByUser, + "cursor-default": isInIframe, + } + )} > arrow_upward_alt {allUpVotes.length} @@ -128,12 +138,18 @@ export const IssueVotes: React.FC = observer((props) => { type="button" disabled={isSubmitting} onClick={(e) => { + if (isInIframe) return; if (user) handleVote(e, -1); else router.push(`/?next_path=${pathName}?${queryParam}`); }} - className={`flex items-center justify-center gap-x-1 h-7 overflow-hidden rounded border px-2 focus:outline-none ${ - isDownVotedByUser ? "border-red-600 text-red-600" : "border-custom-border-300" - }`} + className={cn( + "flex items-center justify-center gap-x-1 h-7 overflow-hidden rounded border px-2 focus:outline-none", + { + "border-red-600 text-red-600": isDownVotedByUser, + "border-custom-border-300": !isDownVotedByUser, + "cursor-default": isInIframe, + } + )} > arrow_downward_alt {allDownVotes.length} diff --git a/space/hooks/use-clipboard-write-permission.tsx b/space/hooks/use-clipboard-write-permission.tsx new file mode 100644 index 000000000..0cafbb7ef --- /dev/null +++ b/space/hooks/use-clipboard-write-permission.tsx @@ -0,0 +1,28 @@ +import { useState, useEffect } from "react"; + +const useClipboardWritePermission = () => { + const [isClipboardWriteAllowed, setClipboardWriteAllowed] = useState(false); + + useEffect(() => { + const checkClipboardWriteAccess = () => { + navigator.permissions + .query({ name: "clipboard-write" as PermissionName }) + .then((result) => { + if (result.state === "granted") { + setClipboardWriteAllowed(true); + } else { + setClipboardWriteAllowed(false); + } + }) + .catch(() => { + setClipboardWriteAllowed(false); + }); + }; + + checkClipboardWriteAccess(); + }, []); + + return isClipboardWriteAllowed; +}; + +export default useClipboardWritePermission; diff --git a/space/hooks/use-is-in-iframe.tsx b/space/hooks/use-is-in-iframe.tsx new file mode 100644 index 000000000..2b5823644 --- /dev/null +++ b/space/hooks/use-is-in-iframe.tsx @@ -0,0 +1,17 @@ +import { useState, useEffect } from "react"; + +const useIsInIframe = () => { + const [isInIframe, setIsInIframe] = useState(false); + + useEffect(() => { + const checkIfInIframe = () => { + setIsInIframe(window.self !== window.top); + }; + + checkIfInIframe(); + }, []); + + return isInIframe; +}; + +export default useIsInIframe;