From b302e7a003049979d74d336f58f3f187ffa1fdfa Mon Sep 17 00:00:00 2001 From: dakshesh14 Date: Tue, 5 Sep 2023 18:13:42 +0530 Subject: [PATCH 1/7] feat: update, delete link refactor: using old fetch-key --- .../web-view/create-update-link-form.tsx | 36 +++++--- web/components/web-view/issue-attachments.tsx | 23 ++++- web/components/web-view/issue-link-list.tsx | 87 +++++++++++++++++-- web/constants/fetch-keys.ts | 2 - .../projects/[projectId]/issues/[issueId].tsx | 10 +-- 5 files changed, 131 insertions(+), 27 deletions(-) diff --git a/web/components/web-view/create-update-link-form.tsx b/web/components/web-view/create-update-link-form.tsx index 3e1d1368c6a..fa1a3393906 100644 --- a/web/components/web-view/create-update-link-form.tsx +++ b/web/components/web-view/create-update-link-form.tsx @@ -1,5 +1,5 @@ // react -import React from "react"; +import React, { useEffect } from "react"; // next import { useRouter } from "next/router"; @@ -14,7 +14,7 @@ import { useForm } from "react-hook-form"; import issuesService from "services/issues.service"; // fetch keys -import { M_ISSUE_DETAILS } from "constants/fetch-keys"; +import { ISSUE_DETAILS } from "constants/fetch-keys"; // hooks import useToast from "hooks/use-toast"; @@ -26,13 +26,14 @@ import { PrimaryButton, Input } from "components/ui"; import type { linkDetails, IIssueLink } from "types"; type Props = { - links?: linkDetails[]; + isOpen: boolean; data?: linkDetails; + links?: linkDetails[]; onSuccess: () => void; }; export const CreateUpdateLinkForm: React.FC = (props) => { - const { data, links, onSuccess } = props; + const { isOpen, data, links, onSuccess } = props; const router = useRouter(); const { workspaceSlug, projectId, issueId } = router.query; @@ -42,6 +43,7 @@ export const CreateUpdateLinkForm: React.FC = (props) => { const { register, handleSubmit, + reset, formState: { errors, isSubmitting }, } = useForm({ defaultValues: { @@ -50,6 +52,22 @@ export const CreateUpdateLinkForm: React.FC = (props) => { }, }); + useEffect(() => { + if (!data) return; + reset({ + title: data.title, + url: data.url, + }); + }, [data, reset]); + + useEffect(() => { + if (!isOpen) + reset({ + title: "", + url: "", + }); + }, [isOpen, reset]); + const onSubmit = async (formData: IIssueLink) => { if (!workspaceSlug || !projectId || !issueId) return; @@ -65,9 +83,7 @@ export const CreateUpdateLinkForm: React.FC = (props) => { ) .then(() => { onSuccess(); - mutate( - M_ISSUE_DETAILS(workspaceSlug.toString(), projectId.toString(), issueId.toString()) - ); + mutate(ISSUE_DETAILS(issueId.toString())); }) .catch((err) => { if (err?.status === 400) @@ -95,7 +111,7 @@ export const CreateUpdateLinkForm: React.FC = (props) => { ); mutate( - M_ISSUE_DETAILS(workspaceSlug.toString(), projectId.toString(), issueId.toString()), + ISSUE_DETAILS(issueId.toString()), (prevData) => ({ ...prevData, issue_link: updatedLinks }), false ); @@ -110,9 +126,7 @@ export const CreateUpdateLinkForm: React.FC = (props) => { ) .then(() => { onSuccess(); - mutate( - M_ISSUE_DETAILS(workspaceSlug.toString(), projectId.toString(), issueId.toString()) - ); + mutate(ISSUE_DETAILS(issueId.toString())); }); } }; diff --git a/web/components/web-view/issue-attachments.tsx b/web/components/web-view/issue-attachments.tsx index ba6523e9ba2..f40387094ac 100644 --- a/web/components/web-view/issue-attachments.tsx +++ b/web/components/web-view/issue-attachments.tsx @@ -21,10 +21,11 @@ import { ISSUE_ATTACHMENTS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys import useToast from "hooks/use-toast"; // icons -import { ChevronRightIcon } from "@heroicons/react/24/outline"; +import { ChevronRightIcon, XMarkIcon } from "@heroicons/react/24/outline"; // components import { Label, WebViewModal } from "components/web-view"; +import { DeleteAttachmentModal } from "components/issues"; // types import type { IIssueAttachment } from "types"; @@ -42,6 +43,9 @@ export const IssueAttachments: React.FC = (props) => { const [isOpen, setIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); + const [deleteAttachment, setDeleteAttachment] = useState(null); + const [attachmentDeleteModal, setAttachmentDeleteModal] = useState(false); + const { setToastAlert } = useToast(); const onDrop = useCallback( @@ -112,6 +116,12 @@ export const IssueAttachments: React.FC = (props) => { return (
+ + setIsOpen(false)} modalTitle="Insert file">
= (props) => { {attachment.attributes.name} + {allowed && ( + + )}
))} + +
+ )}
))} // Issues export const ISSUE_DETAILS = (issueId: string) => `ISSUE_DETAILS_${issueId.toUpperCase()}`; -export const M_ISSUE_DETAILS = (workspaceSlug: string, projectId: string, issueId: string) => - `M_ISSUE_DETAILS_${workspaceSlug.toUpperCase()}_${projectId.toUpperCase()}_${issueId}`; export const SUB_ISSUES = (issueId: string) => `SUB_ISSUES_${issueId.toUpperCase()}`; export const ISSUE_ATTACHMENTS = (issueId: string) => `ISSUE_ATTACHMENTS_${issueId.toUpperCase()}`; export const ARCHIVED_ISSUE_DETAILS = (issueId: string) => diff --git a/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index 7054c86a55f..6992feac036 100644 --- a/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -14,7 +14,7 @@ import { useForm } from "react-hook-form"; import issuesService from "services/issues.service"; // fetch key -import { M_ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; +import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; // hooks import useUser from "hooks/use-user"; @@ -66,9 +66,7 @@ const MobileWebViewIssueDetail = () => { mutate: mutateIssueDetails, error, } = useSWR( - workspaceSlug && projectId && issueId - ? M_ISSUE_DETAILS(workspaceSlug.toString(), projectId.toString(), issueId.toString()) - : null, + workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId.toString()) : null, workspaceSlug && projectId && issueId ? () => issuesService.retrieve(workspaceSlug.toString(), projectId.toString(), issueId.toString()) @@ -91,7 +89,7 @@ const MobileWebViewIssueDetail = () => { if (!workspaceSlug || !projectId || !issueId) return; mutate( - M_ISSUE_DETAILS(workspaceSlug.toString(), projectId.toString(), issueId.toString()), + ISSUE_DETAILS(issueId.toString()), (prevData) => { if (!prevData) return prevData; @@ -161,7 +159,7 @@ const MobileWebViewIssueDetail = () => { - + ); From 2f23dcc9ebfb213c34c16aa4c8aaf889cfeca02f Mon Sep 17 00:00:00 2001 From: dakshesh14 Date: Wed, 6 Sep 2023 15:57:59 +0530 Subject: [PATCH 2/7] feat: issue activity with ability to view & add comment feat: click on view more to view more options in the issue detail --- web/components/web-view/add-comment.tsx | 129 ++++++++++ web/components/web-view/index.ts | 4 + web/components/web-view/issue-activity.tsx | 233 ++++++++++++++++++ .../web-view/issue-properties-detail.tsx | 93 ++++++- web/components/web-view/select-assignee.tsx | 95 +++++++ web/components/web-view/select-estimate.tsx | 83 +++++++ web/components/web-view/select-priority.tsx | 14 +- web/components/web-view/select-state.tsx | 2 +- web/components/web-view/web-view-modal.tsx | 8 +- .../projects/[projectId]/issues/[issueId].tsx | 7 + 10 files changed, 648 insertions(+), 20 deletions(-) create mode 100644 web/components/web-view/add-comment.tsx create mode 100644 web/components/web-view/issue-activity.tsx create mode 100644 web/components/web-view/select-assignee.tsx create mode 100644 web/components/web-view/select-estimate.tsx diff --git a/web/components/web-view/add-comment.tsx b/web/components/web-view/add-comment.tsx new file mode 100644 index 00000000000..b5bff0cb57f --- /dev/null +++ b/web/components/web-view/add-comment.tsx @@ -0,0 +1,129 @@ +import React from "react"; + +// next +import { useRouter } from "next/router"; + +// react-hook-form +import { useForm, Controller } from "react-hook-form"; + +// hooks +import useProjectDetails from "hooks/use-project-details"; + +// components +import { TipTapEditor } from "components/tiptap"; + +// icons +import { Send } from "lucide-react"; + +// ui +import { Icon, SecondaryButton, Tooltip, PrimaryButton } from "components/ui"; + +// types +import type { IIssueComment } from "types"; + +const defaultValues: Partial = { + access: "INTERNAL", + comment_html: "", +}; + +type Props = { + disabled?: boolean; + onSubmit: (data: IIssueComment) => Promise; +}; + +const commentAccess = [ + { + icon: "lock", + key: "INTERNAL", + label: "Private", + }, + { + icon: "public", + key: "EXTERNAL", + label: "Public", + }, +]; + +export const AddComment: React.FC = ({ disabled = false, onSubmit }) => { + const editorRef = React.useRef(null); + + const router = useRouter(); + const { workspaceSlug } = router.query; + + const { projectDetails } = useProjectDetails(); + + const showAccessSpecifier = projectDetails?.is_deployed; + + const { + control, + formState: { isSubmitting }, + handleSubmit, + reset, + } = useForm({ defaultValues }); + + const handleAddComment = async (formData: IIssueComment) => { + if (!formData.comment_html || isSubmitting) return; + + await onSubmit(formData).then(() => { + reset(defaultValues); + editorRef.current?.clearEditor(); + }); + }; + + return ( +
+
+ {showAccessSpecifier && ( +
+ ( +
+ {commentAccess.map((access) => ( + + + + ))} +
+ )} + /> +
+ )} + ( +

" : value} + customClassName="p-3 min-h-[100px] shadow-sm" + debouncedUpdatesEnabled={false} + onChange={(comment_json: Object, comment_html: string) => onChange(comment_html)} + /> + )} + /> +
+ +
+ + + +
+
+ ); +}; diff --git a/web/components/web-view/index.ts b/web/components/web-view/index.ts index 817f5f2f151..2b87ad82029 100644 --- a/web/components/web-view/index.ts +++ b/web/components/web-view/index.ts @@ -8,3 +8,7 @@ export * from "./issue-attachments"; export * from "./issue-properties-detail"; export * from "./issue-link-list"; export * from "./create-update-link-form"; +export * from "./issue-activity"; +export * from "./select-assignee"; +export * from "./select-estimate"; +export * from "./add-comment"; diff --git a/web/components/web-view/issue-activity.tsx b/web/components/web-view/issue-activity.tsx new file mode 100644 index 00000000000..39f6036c296 --- /dev/null +++ b/web/components/web-view/issue-activity.tsx @@ -0,0 +1,233 @@ +// react +import React from "react"; + +// next +import Link from "next/link"; +import { useRouter } from "next/router"; + +// swr +import useSWR, { mutate } from "swr"; + +// fetch key +import { PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; + +// services +import issuesService from "services/issues.service"; + +// hooks +import useUser from "hooks/use-user"; +import useToast from "hooks/use-toast"; + +// components +import { Label, AddComment } from "components/web-view"; +import { CommentCard } from "components/issues/comment"; +import { ActivityIcon, ActivityMessage } from "components/core"; + +// helpers +import { timeAgo } from "helpers/date-time.helper"; + +// ui +import { Icon } from "components/ui"; + +// types +import type { IIssue, IIssueComment } from "types"; + +type Props = { + allowed: boolean; + issueDetails: IIssue; +}; + +export const IssueActivity: React.FC = (props) => { + const { issueDetails, allowed } = props; + + const router = useRouter(); + const { workspaceSlug, projectId, issueId } = router.query; + + const { user } = useUser(); + const { setToastAlert } = useToast(); + + const { data: issueActivities, mutate: mutateIssueActivity } = useSWR( + workspaceSlug && projectId && issueId ? PROJECT_ISSUES_ACTIVITY(issueId.toString()) : null, + workspaceSlug && projectId && issueId + ? () => + issuesService.getIssueActivities( + workspaceSlug.toString(), + projectId.toString(), + issueId.toString() + ) + : null + ); + + const handleCommentUpdate = async (comment: any) => { + if (!workspaceSlug || !projectId || !issueId) return; + + await issuesService + .patchIssueComment( + workspaceSlug as string, + projectId as string, + issueId as string, + comment.id, + comment, + user + ) + .then(() => mutateIssueActivity()); + }; + + const handleCommentDelete = async (commentId: string) => { + if (!workspaceSlug || !projectId || !issueId) return; + + mutateIssueActivity((prevData: any) => prevData?.filter((p: any) => p.id !== commentId), false); + + await issuesService + .deleteIssueComment( + workspaceSlug as string, + projectId as string, + issueId as string, + commentId, + user + ) + .then(() => mutateIssueActivity()); + }; + + const handleAddComment = async (formData: IIssueComment) => { + if (!workspaceSlug || !issueDetails) return; + + await issuesService + .createIssueComment( + workspaceSlug.toString(), + issueDetails.project, + issueDetails.id, + formData, + user + ) + .then(() => { + mutate(PROJECT_ISSUES_ACTIVITY(issueDetails.id)); + }) + .catch(() => + setToastAlert({ + type: "error", + title: "Error!", + message: "Comment could not be posted. Please try again.", + }) + ); + }; + + return ( +
+ +
+
    + {issueActivities?.map((activityItem, index) => { + // determines what type of action is performed + const message = activityItem.field ? ( + + ) : ( + "created the issue." + ); + + if ("field" in activityItem && activityItem.field !== "updated_by") { + return ( +
  • +
    + {issueActivities.length > 1 && index !== issueActivities.length - 1 ? ( +
    +
  • + ); + } else if ("comment_json" in activityItem) + return ( +
    + +
    + ); + })} +
  • +
    + +
    +
  • +
+
+
+ ); +}; diff --git a/web/components/web-view/issue-properties-detail.tsx b/web/components/web-view/issue-properties-detail.tsx index 44e8a968bec..6fb03baa7ad 100644 --- a/web/components/web-view/issue-properties-detail.tsx +++ b/web/components/web-view/issue-properties-detail.tsx @@ -1,30 +1,46 @@ // react -import React from "react"; +import React, { useState } from "react"; // react hook forms -import { Controller } from "react-hook-form"; +import { Control, Controller } from "react-hook-form"; + +// icons +import { ChevronDownIcon, PlayIcon } from "lucide-react"; + +// hooks +import useEstimateOption from "hooks/use-estimate-option"; // ui -import { Icon } from "components/ui"; +import { Icon, SecondaryButton } from "components/ui"; // components -import { Label, StateSelect, PrioritySelect } from "components/web-view"; +import { + Label, + StateSelect, + PrioritySelect, + AssigneeSelect, + EstimateSelect, +} from "components/web-view"; // types import type { IIssue } from "types"; type Props = { - control: any; + control: Control; submitChanges: (data: Partial) => Promise; }; export const IssuePropertiesDetail: React.FC = (props) => { const { control, submitChanges } = props; + const [isViewAllOpen, setIsViewAllOpen] = useState(false); + + const { isEstimateActive } = useEstimateOption(); + return (
-
+
@@ -44,10 +60,10 @@ export const IssuePropertiesDetail: React.FC = (props) => {
-
+
- + Priority
@@ -64,6 +80,67 @@ export const IssuePropertiesDetail: React.FC = (props) => {
+
+
+
+ + Assignee +
+
+ ( + submitChanges({ assignees_list: [val] })} + /> + )} + /> +
+
+
+ {isViewAllOpen && ( + <> + {isEstimateActive && ( +
+
+
+ + Estimate +
+
+ ( + submitChanges({ estimate_point: val })} + /> + )} + /> +
+
+
+ )} + + )} +
+ setIsViewAllOpen((prev) => !prev)} + className="w-full flex justify-center items-center gap-1 !py-2" + > + + {isViewAllOpen ? "View less" : "View all"} + + + +
); }; diff --git a/web/components/web-view/select-assignee.tsx b/web/components/web-view/select-assignee.tsx new file mode 100644 index 00000000000..13ebd377f5c --- /dev/null +++ b/web/components/web-view/select-assignee.tsx @@ -0,0 +1,95 @@ +// react +import React, { useState } from "react"; + +// next +import { useRouter } from "next/router"; + +// swr +import useSWR from "swr"; + +// icons +import { ChevronDownIcon } from "@heroicons/react/24/outline"; + +// services +import projectService from "services/project.service"; + +// fetch key +import { PROJECT_MEMBERS } from "constants/fetch-keys"; + +// components +import { Avatar } from "components/ui/avatar"; +import { WebViewModal } from "./web-view-modal"; + +type Props = { + value: string[]; + onChange: (value: any) => void; + disabled?: boolean; +}; + +export const AssigneeSelect: React.FC = (props) => { + const { value, onChange, disabled = false } = props; + + const [isOpen, setIsOpen] = useState(false); + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { data: members } = useSWR( + workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null, + workspaceSlug && projectId + ? () => projectService.projectMembers(workspaceSlug as string, projectId as string) + : null + ); + + const selectedAssignees = members?.filter((member) => value?.includes(member.member.id)); + + return ( + <> + { + setIsOpen(false); + }} + > + ({ + label: member.member.display_name, + value: member.member.id, + checked: value?.includes(member.member.id), + icon: , + onClick: () => { + setIsOpen(false); + if (disabled) return; + onChange(member.member.id); + }, + })) || [] + } + /> + + + + + ); +}; diff --git a/web/components/web-view/select-estimate.tsx b/web/components/web-view/select-estimate.tsx new file mode 100644 index 00000000000..751375d7bae --- /dev/null +++ b/web/components/web-view/select-estimate.tsx @@ -0,0 +1,83 @@ +// react +import React, { useState } from "react"; + +// icons +import { ChevronDownIcon, PlayIcon } from "lucide-react"; + +// hooks +import useEstimateOption from "hooks/use-estimate-option"; + +// components +import { WebViewModal } from "./web-view-modal"; + +type Props = { + value: any; + onChange: (value: any) => void; + disabled?: boolean; +}; + +export const EstimateSelect: React.FC = (props) => { + const { value, onChange, disabled = false } = props; + + const [isOpen, setIsOpen] = useState(false); + + const { estimatePoints } = useEstimateOption(); + + return ( + <> + { + setIsOpen(false); + }} + > + { + setIsOpen(false); + if (disabled) return; + onChange(null); + }, + icon: , + }, + ...estimatePoints?.map((point) => ({ + label: point.value, + value: point.key, + checked: point.key === value, + icon: , + onClick: () => { + setIsOpen(false); + if (disabled) return; + onChange(point.key); + }, + })), + ]} + /> + + + + + ); +}; diff --git a/web/components/web-view/select-priority.tsx b/web/components/web-view/select-priority.tsx index 11f7ab9f121..adb12714ac1 100644 --- a/web/components/web-view/select-priority.tsx +++ b/web/components/web-view/select-priority.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; // icons -import { ChevronDownIcon } from "@heroicons/react/24/outline"; +import { ChevronDownIcon } from "lucide-react"; // constants import { PRIORITIES } from "constants/project"; @@ -35,11 +35,16 @@ export const PrioritySelect: React.FC = (props) => { }} > ({ label: priority ? capitalizeFirstLetter(priority) : "None", value: priority, + checked: priority === value, + onClick: () => { + setIsOpen(false); + if (disabled) return; + onChange(priority); + }, icon: ( = (props) => { {getPriorityIcon(priority, "text-sm")} ), - onClick: () => { - setIsOpen(false); - if (disabled) return; - onChange(priority); - }, })) || [] } /> diff --git a/web/components/web-view/select-state.tsx b/web/components/web-view/select-state.tsx index bceabfd2c85..c28d30f19c7 100644 --- a/web/components/web-view/select-state.tsx +++ b/web/components/web-view/select-state.tsx @@ -57,11 +57,11 @@ export const StateSelect: React.FC = (props) => { }} > ({ label: state.name, value: state.id, + checked: state.id === selectedState?.id, icon: getStateGroupIcon(state.group, "16", "16", state.color), onClick: () => { setIsOpen(false); diff --git a/web/components/web-view/web-view-modal.tsx b/web/components/web-view/web-view-modal.tsx index 980ddc79acd..93f9ab46dda 100644 --- a/web/components/web-view/web-view-modal.tsx +++ b/web/components/web-view/web-view-modal.tsx @@ -74,24 +74,24 @@ export const WebViewModal = (props: Props) => { }; type OptionsProps = { - selectedOption: string | null; options: Array<{ label: string; value: string | null; + checked: boolean; icon?: any; onClick: () => void; }>; }; -const Options: React.FC = ({ options, selectedOption }) => ( +const Options: React.FC = ({ options }) => (
{options.map((option) => (
diff --git a/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index 6992feac036..637c953e769 100644 --- a/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -33,6 +33,7 @@ import { IssueAttachments, IssuePropertiesDetail, IssueLinks, + IssueActivity, } from "components/web-view"; // types @@ -81,6 +82,10 @@ const MobileWebViewIssueDetail = () => { description: issueDetails.description, description_html: issueDetails.description_html, state: issueDetails.state, + assignees_list: + issueDetails.assignees_list ?? issueDetails.assignee_details?.map((user) => user.id), + labels_list: issueDetails.labels_list ?? issueDetails.labels, + labels: issueDetails.labels_list ?? issueDetails.labels, }); }, [issueDetails, reset]); @@ -160,6 +165,8 @@ const MobileWebViewIssueDetail = () => { + +
); From d6e6cea37de067da8e0a837f8b1570994e450c8f Mon Sep 17 00:00:00 2001 From: dakshesh14 Date: Wed, 6 Sep 2023 16:08:41 +0530 Subject: [PATCH 3/7] fix: upload image not working on mobile --- web/components/web-view/issue-attachments.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/components/web-view/issue-attachments.tsx b/web/components/web-view/issue-attachments.tsx index f40387094ac..838ee8e444c 100644 --- a/web/components/web-view/issue-attachments.tsx +++ b/web/components/web-view/issue-attachments.tsx @@ -96,7 +96,7 @@ export const IssueAttachments: React.FC = (props) => { [issueId, projectId, setToastAlert, workspaceSlug] ); - const { getRootProps } = useDropzone({ + const { getRootProps, getInputProps } = useDropzone({ onDrop, maxSize: 5 * 1024 * 1024, disabled: !allowed || isLoading, @@ -130,6 +130,7 @@ export const IssueAttachments: React.FC = (props) => { !allowed || isLoading ? "cursor-not-allowed" : "cursor-pointer" }`} > + {isLoading ? (

Uploading...

) : ( From c524f8c6635840d9ead64060fd55c9d4e02a7ab8 Mon Sep 17 00:00:00 2001 From: dakshesh14 Date: Thu, 7 Sep 2023 13:26:26 +0530 Subject: [PATCH 4/7] feat: select blocker, blocking, and parent dev: auth layout for web-view, console.log callback for web-view --- web/components/web-view/index.ts | 3 + web/components/web-view/issue-activity.tsx | 3 +- web/components/web-view/issue-attachments.tsx | 12 +- .../web-view/issue-properties-detail.tsx | 203 +++++++++++++++++- web/components/web-view/select-assignee.tsx | 1 - web/components/web-view/select-blocked.tsx | 87 ++++++++ web/components/web-view/select-blocker.tsx | 87 ++++++++ web/components/web-view/select-parent.tsx | 76 +++++++ web/components/web-view/web-view-modal.tsx | 6 +- web/layouts/web-view-layout/index.tsx | 48 +++++ .../projects/[projectId]/issues/[issueId].tsx | 14 +- web/services/api.service.ts | 10 +- 12 files changed, 525 insertions(+), 25 deletions(-) create mode 100644 web/components/web-view/select-blocked.tsx create mode 100644 web/components/web-view/select-blocker.tsx create mode 100644 web/components/web-view/select-parent.tsx create mode 100644 web/layouts/web-view-layout/index.tsx diff --git a/web/components/web-view/index.ts b/web/components/web-view/index.ts index 2b87ad82029..342bc4838fa 100644 --- a/web/components/web-view/index.ts +++ b/web/components/web-view/index.ts @@ -12,3 +12,6 @@ export * from "./issue-activity"; export * from "./select-assignee"; export * from "./select-estimate"; export * from "./add-comment"; +export * from "./select-parent"; +export * from "./select-blocker"; +export * from "./select-blocked"; diff --git a/web/components/web-view/issue-activity.tsx b/web/components/web-view/issue-activity.tsx index 39f6036c296..cd7c366c2c8 100644 --- a/web/components/web-view/issue-activity.tsx +++ b/web/components/web-view/issue-activity.tsx @@ -44,7 +44,6 @@ export const IssueActivity: React.FC = (props) => { const { workspaceSlug, projectId, issueId } = router.query; const { user } = useUser(); - const { setToastAlert } = useToast(); const { data: issueActivities, mutate: mutateIssueActivity } = useSWR( workspaceSlug && projectId && issueId ? PROJECT_ISSUES_ACTIVITY(issueId.toString()) : null, @@ -104,7 +103,7 @@ export const IssueActivity: React.FC = (props) => { mutate(PROJECT_ISSUES_ACTIVITY(issueDetails.id)); }) .catch(() => - setToastAlert({ + console.log({ type: "error", title: "Error!", message: "Comment could not be posted. Please try again.", diff --git a/web/components/web-view/issue-attachments.tsx b/web/components/web-view/issue-attachments.tsx index 838ee8e444c..1b0242a3a99 100644 --- a/web/components/web-view/issue-attachments.tsx +++ b/web/components/web-view/issue-attachments.tsx @@ -17,9 +17,6 @@ import { useDropzone } from "react-dropzone"; // fetch key import { ISSUE_ATTACHMENTS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; -// hooks -import useToast from "hooks/use-toast"; - // icons import { ChevronRightIcon, XMarkIcon } from "@heroicons/react/24/outline"; @@ -46,8 +43,6 @@ export const IssueAttachments: React.FC = (props) => { const [deleteAttachment, setDeleteAttachment] = useState(null); const [attachmentDeleteModal, setAttachmentDeleteModal] = useState(false); - const { setToastAlert } = useToast(); - const onDrop = useCallback( (acceptedFiles: File[]) => { if (!acceptedFiles[0] || !workspaceSlug) return; @@ -77,23 +72,24 @@ export const IssueAttachments: React.FC = (props) => { false ); mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); - setToastAlert({ + console.log({ type: "success", title: "Success!", message: "File added successfully.", }); + setIsOpen(false); setIsLoading(false); }) .catch((err) => { setIsLoading(false); - setToastAlert({ + console.log({ type: "error", title: "error!", message: "Something went wrong. please check file type & size (max 5 MB)", }); }); }, - [issueId, projectId, setToastAlert, workspaceSlug] + [issueId, projectId, workspaceSlug] ); const { getRootProps, getInputProps } = useDropzone({ diff --git a/web/components/web-view/issue-properties-detail.tsx b/web/components/web-view/issue-properties-detail.tsx index 6fb03baa7ad..b304fd2a3b0 100644 --- a/web/components/web-view/issue-properties-detail.tsx +++ b/web/components/web-view/issue-properties-detail.tsx @@ -1,17 +1,21 @@ // react import React, { useState } from "react"; +// next +import { useRouter } from "next/router"; + // react hook forms -import { Control, Controller } from "react-hook-form"; +import { Control, Controller, useWatch } from "react-hook-form"; // icons -import { ChevronDownIcon, PlayIcon } from "lucide-react"; +import { BlockedIcon, BlockerIcon } from "components/icons"; +import { ChevronDownIcon, PlayIcon, User, X, CalendarDays } from "lucide-react"; // hooks import useEstimateOption from "hooks/use-estimate-option"; // ui -import { Icon, SecondaryButton } from "components/ui"; +import { Icon, SecondaryButton, CustomDatePicker } from "components/ui"; // components import { @@ -20,6 +24,8 @@ import { PrioritySelect, AssigneeSelect, EstimateSelect, + ParentSelect, + BlockerSelect, } from "components/web-view"; // types @@ -33,6 +39,24 @@ type Props = { export const IssuePropertiesDetail: React.FC = (props) => { const { control, submitChanges } = props; + const blockerIssue = useWatch({ + control, + name: "blocker_issues", + }); + + const blockedIssue = useWatch({ + control, + name: "blocked_issues", + }); + + const startDate = useWatch({ + control, + name: "start_date", + }); + + const router = useRouter(); + const { workspaceSlug } = router.query; + const [isViewAllOpen, setIsViewAllOpen] = useState(false); const { isEstimateActive } = useEstimateOption(); @@ -124,6 +148,179 @@ export const IssuePropertiesDetail: React.FC = (props) => {
)} +
+
+
+ + Parent +
+
+ ( + submitChanges({ parent: val })} + /> + )} + /> +
+
+
+
+
+
+
+ + Blocking +
+
+ ( + + submitChanges({ + blocker_issues: val, + blockers_list: val?.map((i: any) => i.blocker_issue_detail?.id ?? ""), + }) + } + /> + )} + /> +
+
+ {blockerIssue && + blockerIssue.map((issue) => ( +
+ + + {`${issue.blocker_issue_detail?.project_detail.identifier}-${issue.blocker_issue_detail?.sequence_id}`} + + +
+ ))} +
+
+
+
+
+
+ + Blocked by +
+
+ ( + + submitChanges({ + blocked_issues: val, + blocks_list: val?.map((i: any) => i.blocker_issue_detail?.id ?? ""), + }) + } + /> + )} + /> +
+
+ {blockedIssue && + blockedIssue.map((issue) => ( +
+ + + {`${issue?.blocked_issue_detail?.project_detail?.identifier}-${issue?.blocked_issue_detail?.sequence_id}`} + + +
+ ))} +
+
+ +
+
+
+
+ + Due date +
+
+ ( + + submitChanges({ + target_date: val, + }) + } + className="border-transparent !shadow-none !w-[6.75rem]" + minDate={startDate ? new Date(startDate) : undefined} + /> + )} + /> +
+
+
+
)}
diff --git a/web/components/web-view/select-assignee.tsx b/web/components/web-view/select-assignee.tsx index 13ebd377f5c..479d8afc243 100644 --- a/web/components/web-view/select-assignee.tsx +++ b/web/components/web-view/select-assignee.tsx @@ -87,7 +87,6 @@ export const AssigneeSelect: React.FC = (props) => { ) : ( "No assignees" )} - {/* {selectedAssignee?.member.display_name || "Select assignee"} */} diff --git a/web/components/web-view/select-blocked.tsx b/web/components/web-view/select-blocked.tsx new file mode 100644 index 00000000000..0056cad5c0c --- /dev/null +++ b/web/components/web-view/select-blocked.tsx @@ -0,0 +1,87 @@ +// react +import React, { useState } from "react"; + +// next +import { useRouter } from "next/router"; + +// hooks +import useToast from "hooks/use-toast"; + +// icons +import { ChevronDown } from "lucide-react"; + +// components +import { ExistingIssuesListModal } from "components/core"; + +// types +import { BlockeIssueDetail, ISearchIssueResponse } from "types"; + +type Props = { + value: any; + onChange: (value: any) => void; + disabled?: boolean; +}; + +export const BlockedSelect: React.FC = (props) => { + const { value, onChange, disabled = false } = props; + + const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false); + + const router = useRouter(); + const { issueId } = router.query; + + const { setToastAlert } = useToast(); + + const onSubmit = async (data: ISearchIssueResponse[]) => { + if (data.length === 0) { + setToastAlert({ + type: "error", + title: "Error!", + message: "Please select at least one issue.", + }); + + return; + } + + const selectedIssues: { blocker_issue_detail: BlockeIssueDetail }[] = data.map((i) => ({ + blocker_issue_detail: { + id: i.id, + name: i.name, + sequence_id: i.sequence_id, + project_detail: { + id: i.project_id, + identifier: i.project__identifier, + name: i.project__name, + }, + }, + })); + + onChange([...(value || []), ...selectedIssues]); + + setIsBlockedModalOpen(false); + }; + + return ( + <> + setIsBlockedModalOpen(false)} + searchParams={{ blocker_blocked_by: true, issue_id: issueId!.toString() }} + handleOnSubmit={onSubmit} + workspaceLevelToggle + /> + + + + ); +}; diff --git a/web/components/web-view/select-blocker.tsx b/web/components/web-view/select-blocker.tsx new file mode 100644 index 00000000000..a46cdfcaa43 --- /dev/null +++ b/web/components/web-view/select-blocker.tsx @@ -0,0 +1,87 @@ +// react +import React, { useState } from "react"; + +// next +import { useRouter } from "next/router"; + +// hooks +import useToast from "hooks/use-toast"; + +// icons +import { ChevronDown } from "lucide-react"; + +// components +import { ExistingIssuesListModal } from "components/core"; + +// types +import { BlockeIssueDetail, ISearchIssueResponse } from "types"; + +type Props = { + value: any; + onChange: (value: any) => void; + disabled?: boolean; +}; + +export const BlockerSelect: React.FC = (props) => { + const { value, onChange, disabled = false } = props; + + const [isBlockerModalOpen, setIsBlockerModalOpen] = useState(false); + + const router = useRouter(); + const { issueId } = router.query; + + const { setToastAlert } = useToast(); + + const onSubmit = async (data: ISearchIssueResponse[]) => { + if (data.length === 0) { + setToastAlert({ + type: "error", + title: "Error!", + message: "Please select at least one issue.", + }); + + return; + } + + const selectedIssues: { blocker_issue_detail: BlockeIssueDetail }[] = data.map((i) => ({ + blocker_issue_detail: { + id: i.id, + name: i.name, + sequence_id: i.sequence_id, + project_detail: { + id: i.project_id, + identifier: i.project__identifier, + name: i.project__name, + }, + }, + })); + + onChange([...(value || []), ...selectedIssues]); + + setIsBlockerModalOpen(false); + }; + + return ( + <> + setIsBlockerModalOpen(false)} + searchParams={{ blocker_blocked_by: true, issue_id: issueId!.toString() }} + handleOnSubmit={onSubmit} + workspaceLevelToggle + /> + + + + ); +}; diff --git a/web/components/web-view/select-parent.tsx b/web/components/web-view/select-parent.tsx new file mode 100644 index 00000000000..e5975b7b5a0 --- /dev/null +++ b/web/components/web-view/select-parent.tsx @@ -0,0 +1,76 @@ +// react +import React, { useState } from "react"; + +// next +import { useRouter } from "next/router"; + +// swr +import useSWR from "swr"; + +// services +import issuesService from "services/issues.service"; + +// fetch key +import { ISSUE_DETAILS } from "constants/fetch-keys"; + +// components +import { ParentIssuesListModal } from "components/issues"; + +// types +import { ISearchIssueResponse } from "types"; + +type Props = { + value: string | null; + onChange: (value: any) => void; + disabled?: boolean; +}; + +export const ParentSelect: React.FC = (props) => { + const { value, onChange, disabled = false } = props; + + const [isParentModalOpen, setIsParentModalOpen] = useState(false); + const [selectedParentIssue, setSelectedParentIssue] = useState(null); + + const router = useRouter(); + const { workspaceSlug, projectId, issueId } = router.query; + + const { data: issueDetails } = useSWR( + workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId.toString()) : null, + workspaceSlug && projectId && issueId + ? () => + issuesService.retrieve(workspaceSlug.toString(), projectId.toString(), issueId.toString()) + : null + ); + + return ( + <> + setIsParentModalOpen(false)} + onChange={(issue) => { + onChange(issue.id); + setSelectedParentIssue(issue); + }} + issueId={issueId as string} + projectId={projectId as string} + /> + + + + ); +}; diff --git a/web/components/web-view/web-view-modal.tsx b/web/components/web-view/web-view-modal.tsx index 93f9ab46dda..2be28a01c4e 100644 --- a/web/components/web-view/web-view-modal.tsx +++ b/web/components/web-view/web-view-modal.tsx @@ -47,7 +47,7 @@ export const WebViewModal = (props: Props) => { leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - +
= ({ options }) => ( -
+
{options.map((option) => ( -
+
= ({ children }) => { + const { data: currentUser, error } = useSWR(CURRENT_USER, () => userService.currentUser()); + + if (!currentUser && !error) { + return ( +
+
+

Loading your profile...

+ +
+
+ ); + } + + return ( +
+ {error ? ( +
+ +

You are not authorized to view this page.

+
+ ) : ( + children + )} +
+ ); +}; + +export default WebViewLayout; diff --git a/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index 637c953e769..ede258c73b1 100644 --- a/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -21,7 +21,7 @@ import useUser from "hooks/use-user"; import useProjectMembers from "hooks/use-project-members"; // layouts -import DefaultLayout from "layouts/default-layout"; +import WebViewLayout from "layouts/web-view-layout"; // ui import { Spinner } from "components/ui"; @@ -128,25 +128,25 @@ const MobileWebViewIssueDetail = () => { if (!error && !issueDetails) return ( - +
Loading...
-
+ ); if (error) return ( - +
{error?.response?.data || "Something went wrong"}
-
+ ); return ( - +
{
-
+ ); }; diff --git a/web/services/api.service.ts b/web/services/api.service.ts index 361fea03eaa..c71c75ba8e7 100644 --- a/web/services/api.service.ts +++ b/web/services/api.service.ts @@ -8,11 +8,19 @@ const nonValidatedRoutes = [ "/reset-password", "/workspace-member-invitation", "/sign-up", + "/m/", ]; const validateRouteCheck = (route: string): boolean => { let validationToggle = false; - const routeCheck = nonValidatedRoutes.find((_route: string) => _route === route); + + let routeCheck = false; + nonValidatedRoutes.forEach((_route: string) => { + if (route.includes(_route)) { + routeCheck = true; + } + }); + if (routeCheck) validationToggle = true; return validationToggle; }; From 9228f0190861566295df24e676588c09bac35994 Mon Sep 17 00:00:00 2001 From: dakshesh14 Date: Thu, 7 Sep 2023 16:49:01 +0530 Subject: [PATCH 5/7] style: made design consistant --- web/components/web-view/add-comment.tsx | 6 ++- web/components/web-view/issue-activity.tsx | 13 ++++-- web/components/web-view/issue-attachments.tsx | 45 +++++++++++------- web/components/web-view/issue-link-list.tsx | 9 ++-- .../web-view/issue-properties-detail.tsx | 46 ++++++++++++------- web/components/web-view/select-assignee.tsx | 4 +- web/components/web-view/select-estimate.tsx | 4 +- web/components/web-view/select-priority.tsx | 4 +- web/components/web-view/select-state.tsx | 4 +- web/components/web-view/sub-issues.tsx | 4 +- web/layouts/web-view-layout/index.tsx | 16 +++++++ .../projects/[projectId]/issues/[issueId].tsx | 18 ++++++++ 12 files changed, 120 insertions(+), 53 deletions(-) diff --git a/web/components/web-view/add-comment.tsx b/web/components/web-view/add-comment.tsx index b5bff0cb57f..b4f49d7beb0 100644 --- a/web/components/web-view/add-comment.tsx +++ b/web/components/web-view/add-comment.tsx @@ -120,7 +120,11 @@ export const AddComment: React.FC = ({ disabled = false, onSubmit }) => {
- +
diff --git a/web/components/web-view/issue-activity.tsx b/web/components/web-view/issue-activity.tsx index cd7c366c2c8..55089d60d00 100644 --- a/web/components/web-view/issue-activity.tsx +++ b/web/components/web-view/issue-activity.tsx @@ -103,11 +103,14 @@ export const IssueActivity: React.FC = (props) => { mutate(PROJECT_ISSUES_ACTIVITY(issueDetails.id)); }) .catch(() => - console.log({ - type: "error", - title: "Error!", - message: "Comment could not be posted. Please try again.", - }) + console.log( + "toast", + JSON.stringify({ + type: "error", + title: "Error!", + message: "Comment could not be posted. Please try again.", + }) + ) ); }; diff --git a/web/components/web-view/issue-attachments.tsx b/web/components/web-view/issue-attachments.tsx index 1b0242a3a99..ba454031817 100644 --- a/web/components/web-view/issue-attachments.tsx +++ b/web/components/web-view/issue-attachments.tsx @@ -18,7 +18,7 @@ import { useDropzone } from "react-dropzone"; import { ISSUE_ATTACHMENTS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; // icons -import { ChevronRightIcon, XMarkIcon } from "@heroicons/react/24/outline"; +import { FileText, ChevronRight, X, Image as ImageIcon } from "lucide-react"; // components import { Label, WebViewModal } from "components/web-view"; @@ -31,6 +31,8 @@ type Props = { allowed: boolean; }; +const isImage = (fileName: string) => /\.(gif|jpe?g|tiff?|png|webp|bmp)$/i.test(fileName); + export const IssueAttachments: React.FC = (props) => { const { allowed } = props; @@ -72,21 +74,27 @@ export const IssueAttachments: React.FC = (props) => { false ); mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); - console.log({ - type: "success", - title: "Success!", - message: "File added successfully.", - }); + console.log( + "toast", + JSON.stringify({ + type: "success", + title: "Success!", + message: "File added successfully.", + }) + ); setIsOpen(false); setIsLoading(false); }) .catch((err) => { setIsLoading(false); - console.log({ - type: "error", - title: "error!", - message: "Something went wrong. please check file type & size (max 5 MB)", - }); + console.log( + "toast", + JSON.stringify({ + type: "error", + title: "error!", + message: "Something went wrong. please check file type & size (max 5 MB)", + }) + ); }); }, [issueId, projectId, workspaceSlug] @@ -132,7 +140,7 @@ export const IssueAttachments: React.FC = (props) => { ) : ( <>

Upload

- + )}
@@ -147,8 +155,13 @@ export const IssueAttachments: React.FC = (props) => { className="px-3 border border-custom-border-200 rounded-[4px] py-2 flex justify-between items-center bg-custom-background-100" > - - {attachment.attributes.name} + + {isImage(attachment.attributes.name) ? ( + + ) : ( + + )} + {attachment.attributes.name} {allowed && ( @@ -159,7 +172,7 @@ export const IssueAttachments: React.FC = (props) => { setAttachmentDeleteModal(true); }} > - + )}
@@ -167,7 +180,7 @@ export const IssueAttachments: React.FC = (props) => { diff --git a/web/components/web-view/issue-link-list.tsx b/web/components/web-view/issue-link-list.tsx index e7922ba8c06..5f1da6a6297 100644 --- a/web/components/web-view/issue-link-list.tsx +++ b/web/components/web-view/issue-link-list.tsx @@ -12,7 +12,8 @@ import { mutate } from "swr"; import issuesService from "services/issues.service"; // icons -import { LinkIcon, PlusIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline"; +// import { LinkIcon, PlusIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline"; +import { Link as LinkIcon, Plus, Pencil, X } from "lucide-react"; // components import { Label, WebViewModal, CreateUpdateLinkForm } from "components/web-view"; @@ -108,7 +109,7 @@ export const IssueLinks: React.FC = (props) => { setSelectedLink(link.id); }} > - +
)} @@ -128,7 +129,7 @@ export const IssueLinks: React.FC = (props) => { onClick={() => setIsOpen(true)} className="w-full !py-2 text-custom-text-300 !text-base flex items-center justify-center" > - + Add
diff --git a/web/components/web-view/issue-properties-detail.tsx b/web/components/web-view/issue-properties-detail.tsx index b304fd2a3b0..6a247365b99 100644 --- a/web/components/web-view/issue-properties-detail.tsx +++ b/web/components/web-view/issue-properties-detail.tsx @@ -9,13 +9,13 @@ import { Control, Controller, useWatch } from "react-hook-form"; // icons import { BlockedIcon, BlockerIcon } from "components/icons"; -import { ChevronDownIcon, PlayIcon, User, X, CalendarDays } from "lucide-react"; +import { ChevronDown, PlayIcon, User, X, CalendarDays, LayoutGrid, Users } from "lucide-react"; // hooks import useEstimateOption from "hooks/use-estimate-option"; // ui -import { Icon, SecondaryButton, CustomDatePicker } from "components/ui"; +import { SecondaryButton, CustomDatePicker } from "components/ui"; // components import { @@ -67,8 +67,8 @@ export const IssuePropertiesDetail: React.FC = (props) => {
- - State + + State
= (props) => {
- - Priority + + + + + Priority
= (props) => {
- - Assignee + + Assignee
= (props) => {
- - Estimate + + Estimate
= (props) => {
- - Parent + + Parent
= (props) => {
- Blocking + Blocking
= (props) => {
- Blocked by + Blocked by
= (props) => {
- - Due date + + Due date
= (props) => { {isViewAllOpen ? "View less" : "View all"} - diff --git a/web/components/web-view/select-assignee.tsx b/web/components/web-view/select-assignee.tsx index 479d8afc243..27eae5db1a9 100644 --- a/web/components/web-view/select-assignee.tsx +++ b/web/components/web-view/select-assignee.tsx @@ -8,7 +8,7 @@ import { useRouter } from "next/router"; import useSWR from "swr"; // icons -import { ChevronDownIcon } from "@heroicons/react/24/outline"; +import { ChevronDown } from "lucide-react"; // services import projectService from "services/project.service"; @@ -87,7 +87,7 @@ export const AssigneeSelect: React.FC = (props) => { ) : ( "No assignees" )} - + ); diff --git a/web/components/web-view/select-estimate.tsx b/web/components/web-view/select-estimate.tsx index 751375d7bae..765e6187dc0 100644 --- a/web/components/web-view/select-estimate.tsx +++ b/web/components/web-view/select-estimate.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; // icons -import { ChevronDownIcon, PlayIcon } from "lucide-react"; +import { ChevronDown, PlayIcon } from "lucide-react"; // hooks import useEstimateOption from "hooks/use-estimate-option"; @@ -76,7 +76,7 @@ export const EstimateSelect: React.FC = (props) => { ) : ( "No estimate" )} - + ); diff --git a/web/components/web-view/select-priority.tsx b/web/components/web-view/select-priority.tsx index adb12714ac1..87067bc7d87 100644 --- a/web/components/web-view/select-priority.tsx +++ b/web/components/web-view/select-priority.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; // icons -import { ChevronDownIcon } from "lucide-react"; +import { ChevronDown } from "lucide-react"; // constants import { PRIORITIES } from "constants/project"; @@ -76,7 +76,7 @@ export const PrioritySelect: React.FC = (props) => { } > {value ? capitalizeFirstLetter(value) : "None"} - + ); diff --git a/web/components/web-view/select-state.tsx b/web/components/web-view/select-state.tsx index c28d30f19c7..3d530dd48c3 100644 --- a/web/components/web-view/select-state.tsx +++ b/web/components/web-view/select-state.tsx @@ -8,7 +8,7 @@ import { useRouter } from "next/router"; import useSWR from "swr"; // icons -import { ChevronDownIcon } from "@heroicons/react/24/outline"; +import { ChevronDown } from "lucide-react"; // services import stateService from "services/state.service"; @@ -82,7 +82,7 @@ export const StateSelect: React.FC = (props) => { } > {selectedState?.name || "Select a state"} - + ); diff --git a/web/components/web-view/sub-issues.tsx b/web/components/web-view/sub-issues.tsx index 8110e589563..4299d9e3a3a 100644 --- a/web/components/web-view/sub-issues.tsx +++ b/web/components/web-view/sub-issues.tsx @@ -8,7 +8,7 @@ import { useRouter } from "next/router"; import useSWR, { mutate } from "swr"; // icons -import { XMarkIcon } from "@heroicons/react/24/outline"; +import { X } from "lucide-react"; // services import issuesService from "services/issues.service"; @@ -98,7 +98,7 @@ export const SubIssueList: React.FC = (props) => {

{subIssue.name}

))} diff --git a/web/layouts/web-view-layout/index.tsx b/web/layouts/web-view-layout/index.tsx index fc907fe9e8f..1f1f2858a51 100644 --- a/web/layouts/web-view-layout/index.tsx +++ b/web/layouts/web-view-layout/index.tsx @@ -1,3 +1,5 @@ +import { useEffect } from "react"; + // swr import useSWR from "swr"; @@ -17,9 +19,23 @@ type Props = { children: React.ReactNode; }; +const getIfInWebview = (userAgent: NavigatorID["userAgent"]) => { + if (/iphone|ipod|ipad/.test(userAgent) || userAgent.includes("wv")) return true; + else return false; +}; +const useMobileDetect = () => { + useEffect(() => {}, []); + const userAgent = typeof navigator === "undefined" ? "SSR" : navigator.userAgent; + return getIfInWebview(userAgent); +}; + const WebViewLayout: React.FC = ({ children }) => { const { data: currentUser, error } = useSWR(CURRENT_USER, () => userService.currentUser()); + const data = useMobileDetect(); + + console.log("data: ", data); + if (!currentUser && !error) { return (
diff --git a/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index ede258c73b1..31ef7909db2 100644 --- a/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -38,6 +38,7 @@ import { // types import type { IIssue } from "types"; +import { GetServerSidePropsContext } from "next"; const MobileWebViewIssueDetail = () => { const router = useRouter(); @@ -172,4 +173,21 @@ const MobileWebViewIssueDetail = () => { ); }; +const getIfInWebview = (userAgent: NavigatorID["userAgent"]) => { + if (/iphone|ipod|ipad/.test(userAgent) || userAgent.includes("wv")) return true; + else return false; +}; + +export const getServerSideProps = async (context: GetServerSidePropsContext) => { + const navigator = context.req.headers["user-agent"] || ""; + + const isWebView = getIfInWebview(navigator); + + return { + props: { + isWebView, + }, + }; +}; + export default MobileWebViewIssueDetail; From 57bee5fb1bd90a1217e26ccaea7a2a60db563f81 Mon Sep 17 00:00:00 2001 From: dakshesh14 Date: Thu, 7 Sep 2023 18:05:44 +0530 Subject: [PATCH 6/7] fix: displaying page only on web-view --- web/layouts/web-view-layout/index.tsx | 10 +++------- .../projects/[projectId]/issues/[issueId].tsx | 18 ------------------ 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/web/layouts/web-view-layout/index.tsx b/web/layouts/web-view-layout/index.tsx index 1f1f2858a51..a9bdd3dc72a 100644 --- a/web/layouts/web-view-layout/index.tsx +++ b/web/layouts/web-view-layout/index.tsx @@ -1,5 +1,3 @@ -import { useEffect } from "react"; - // swr import useSWR from "swr"; @@ -23,8 +21,8 @@ const getIfInWebview = (userAgent: NavigatorID["userAgent"]) => { if (/iphone|ipod|ipad/.test(userAgent) || userAgent.includes("wv")) return true; else return false; }; + const useMobileDetect = () => { - useEffect(() => {}, []); const userAgent = typeof navigator === "undefined" ? "SSR" : navigator.userAgent; return getIfInWebview(userAgent); }; @@ -32,9 +30,7 @@ const useMobileDetect = () => { const WebViewLayout: React.FC = ({ children }) => { const { data: currentUser, error } = useSWR(CURRENT_USER, () => userService.currentUser()); - const data = useMobileDetect(); - - console.log("data: ", data); + const isWebview = useMobileDetect(); if (!currentUser && !error) { return ( @@ -49,7 +45,7 @@ const WebViewLayout: React.FC = ({ children }) => { return (
- {error ? ( + {error || !isWebview ? (

You are not authorized to view this page.

diff --git a/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index 31ef7909db2..ede258c73b1 100644 --- a/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -38,7 +38,6 @@ import { // types import type { IIssue } from "types"; -import { GetServerSidePropsContext } from "next"; const MobileWebViewIssueDetail = () => { const router = useRouter(); @@ -173,21 +172,4 @@ const MobileWebViewIssueDetail = () => { ); }; -const getIfInWebview = (userAgent: NavigatorID["userAgent"]) => { - if (/iphone|ipod|ipad/.test(userAgent) || userAgent.includes("wv")) return true; - else return false; -}; - -export const getServerSideProps = async (context: GetServerSidePropsContext) => { - const navigator = context.req.headers["user-agent"] || ""; - - const isWebView = getIfInWebview(navigator); - - return { - props: { - isWebView, - }, - }; -}; - export default MobileWebViewIssueDetail; From 5bff030599b012b58f4f83e0c1fa79c939976fc8 Mon Sep 17 00:00:00 2001 From: dakshesh14 Date: Thu, 7 Sep 2023 18:06:10 +0530 Subject: [PATCH 7/7] style: removed overflow hidden --- web/layouts/web-view-layout/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/layouts/web-view-layout/index.tsx b/web/layouts/web-view-layout/index.tsx index a9bdd3dc72a..c5afdcc3d54 100644 --- a/web/layouts/web-view-layout/index.tsx +++ b/web/layouts/web-view-layout/index.tsx @@ -44,7 +44,7 @@ const WebViewLayout: React.FC = ({ children }) => { } return ( -
+
{error || !isWebview ? (