From 0586d30a331e57b6ea64c48a2a38e46d7f31f909 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Mon, 31 Jul 2023 16:57:28 +0530 Subject: [PATCH 01/12] style: profile page responsiveness added (#1710) * refactor: folder structure * style: mobile responsiveness added * chore: add user profile redirection * chore: profile page authorization --- apps/app/components/issues/activity.tsx | 15 ++- apps/app/components/profile/navbar.tsx | 22 +++- .../components/profile/overview/activity.tsx | 91 +++++++++++++ apps/app/components/profile/overview/index.ts | 1 + .../components/profile/overview/workload.tsx | 10 +- .../profile/profile-issues-view-options.tsx | 21 ++- apps/app/components/profile/sidebar.tsx | 71 ++++++---- .../ui/dropdowns/custom-search-select.tsx | 4 +- apps/app/hooks/use-profile-issues.tsx | 2 - apps/app/hooks/use-projects.tsx | 10 +- apps/app/layouts/profile-layout.tsx | 53 ++++++++ .../profile/[userId]/assigned.tsx | 47 ++----- .../profile/[userId]/created.tsx | 46 ++----- .../profile/[userId]/index.tsx | 122 +++--------------- .../profile/[userId]/subscribed.tsx | 46 ++----- .../projects/[projectId]/settings/members.tsx | 15 ++- .../pages/[workspaceSlug]/settings/index.tsx | 1 - .../[workspaceSlug]/settings/members.tsx | 16 ++- 18 files changed, 323 insertions(+), 270 deletions(-) create mode 100644 apps/app/components/profile/overview/activity.tsx create mode 100644 apps/app/layouts/profile-layout.tsx diff --git a/apps/app/components/issues/activity.tsx b/apps/app/components/issues/activity.tsx index 2780d3ba1..540d81df9 100644 --- a/apps/app/components/issues/activity.tsx +++ b/apps/app/components/issues/activity.tsx @@ -1,5 +1,6 @@ import React from "react"; +import Link from "next/link"; import { useRouter } from "next/router"; import useSWR from "swr"; @@ -143,13 +144,17 @@ export const IssueActivitySection: React.FC = ({ issueId, user }) => { {activityItem.field === "archived_at" && activityItem.new_value !== "restore" ? ( Plane - ) : ( + ) : activityItem.actor_detail.is_bot ? ( - {activityItem.actor_detail.first_name} - {activityItem.actor_detail.is_bot - ? " Bot" - : " " + activityItem.actor_detail.last_name} + {activityItem.actor_detail.first_name} Bot + ) : ( + + + {activityItem.actor_detail.first_name}{" "} + {activityItem.actor_detail.last_name} + + )}{" "} {message}{" "} diff --git a/apps/app/components/profile/navbar.tsx b/apps/app/components/profile/navbar.tsx index 305ac80d7..e8988d670 100644 --- a/apps/app/components/profile/navbar.tsx +++ b/apps/app/components/profile/navbar.tsx @@ -1,15 +1,26 @@ +import React from "react"; + import { useRouter } from "next/router"; import Link from "next/link"; // components import { ProfileIssuesViewOptions } from "components/profile"; +// types +import { UserAuth } from "types"; -const tabsList = [ +type Props = { + memberRole: UserAuth; +}; + +const viewerTabs = [ { route: "", label: "Overview", selected: "/[workspaceSlug]/profile/[userId]", }, +]; + +const adminTabs = [ { route: "assigned", label: "Assigned", @@ -27,12 +38,17 @@ const tabsList = [ }, ]; -export const ProfileNavbar = () => { +export const ProfileNavbar: React.FC = ({ memberRole }) => { const router = useRouter(); const { workspaceSlug, userId } = router.query; + const tabsList = + memberRole.isOwner || memberRole.isMember || memberRole.isViewer + ? [...viewerTabs, ...adminTabs] + : viewerTabs; + return ( -
+
{tabsList.map((tab) => ( diff --git a/apps/app/components/profile/overview/activity.tsx b/apps/app/components/profile/overview/activity.tsx new file mode 100644 index 000000000..28801ed8f --- /dev/null +++ b/apps/app/components/profile/overview/activity.tsx @@ -0,0 +1,91 @@ +import { useRouter } from "next/router"; +import Link from "next/link"; + +import useSWR from "swr"; + +// services +import userService from "services/user.service"; +// ui +import { Icon, Loader } from "components/ui"; +// helpers +import { activityDetails } from "helpers/activity.helper"; +import { timeAgo } from "helpers/date-time.helper"; +// fetch-keys +import { USER_PROFILE_ACTIVITY } from "constants/fetch-keys"; + +export const ProfileActivity = () => { + const router = useRouter(); + const { workspaceSlug, userId } = router.query; + + const { data: userProfileActivity } = useSWR( + workspaceSlug && userId + ? USER_PROFILE_ACTIVITY(workspaceSlug.toString(), userId.toString()) + : null, + workspaceSlug && userId + ? () => userService.getUserProfileActivity(workspaceSlug.toString(), userId.toString()) + : null + ); + + return ( +
+

Recent Activity

+
+ {userProfileActivity ? ( +
+ {userProfileActivity.results.map((activity) => ( +
+
+ {activity.actor_detail.avatar && activity.actor_detail.avatar !== "" ? ( + {activity.actor_detail.first_name} + ) : ( +
+ {activity.actor_detail.first_name.charAt(0)} +
+ )} +
+
+

+ + {activity.actor_detail.first_name} {activity.actor_detail.last_name}{" "} + + {activity.field ? ( + activityDetails[activity.field]?.message(activity as any) + ) : ( + + created this{" "} + + Issue + + + + )} +

+

{timeAgo(activity.created_at)}

+
+
+ ))} +
+ ) : ( + + + + + + + + )} +
+
+ ); +}; diff --git a/apps/app/components/profile/overview/index.ts b/apps/app/components/profile/overview/index.ts index 9aabc6d35..0c69a50de 100644 --- a/apps/app/components/profile/overview/index.ts +++ b/apps/app/components/profile/overview/index.ts @@ -1,3 +1,4 @@ +export * from "./activity"; export * from "./priority-distribution"; export * from "./state-distribution"; export * from "./stats"; diff --git a/apps/app/components/profile/overview/workload.tsx b/apps/app/components/profile/overview/workload.tsx index f1785c0ce..18c902e2d 100644 --- a/apps/app/components/profile/overview/workload.tsx +++ b/apps/app/components/profile/overview/workload.tsx @@ -10,7 +10,7 @@ type Props = { export const ProfileWorkload: React.FC = ({ stateDistribution }) => (

Workload

-
+
{stateDistribution.map((group) => (
@@ -21,7 +21,13 @@ export const ProfileWorkload: React.FC = ({ stateDistribution }) => ( }} />
-

{group.state_group}

+

+ {group.state_group === "unstarted" + ? "Not Started" + : group.state_group === "started" + ? "Working on" + : group.state_group} +

{group.state_count}

diff --git a/apps/app/components/profile/profile-issues-view-options.tsx b/apps/app/components/profile/profile-issues-view-options.tsx index e68a821e9..0441987eb 100644 --- a/apps/app/components/profile/profile-issues-view-options.tsx +++ b/apps/app/components/profile/profile-issues-view-options.tsx @@ -10,7 +10,7 @@ import useEstimateOption from "hooks/use-estimate-option"; // components import { MyIssuesSelectFilters } from "components/issues"; // ui -import { CustomMenu, ToggleSwitch, Tooltip } from "components/ui"; +import { CustomMenu, CustomSearchSelect, ToggleSwitch, Tooltip } from "components/ui"; // icons import { ChevronDownIcon } from "@heroicons/react/24/outline"; import { FormatListBulletedOutlined, GridViewOutlined } from "@mui/icons-material"; @@ -21,6 +21,7 @@ import { checkIfArraysHaveSameElements } from "helpers/array.helper"; import { Properties, TIssueViewOptions } from "types"; // constants import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue"; +import useProjects from "hooks/use-projects"; const issueViewOptions: { type: TIssueViewOptions; Icon: any }[] = [ { @@ -37,6 +38,8 @@ export const ProfileIssuesViewOptions: React.FC = () => { const router = useRouter(); const { workspaceSlug, userId } = router.query; + const { projects } = useProjects(); + const { issueView, setIssueView, @@ -54,12 +57,28 @@ export const ProfileIssuesViewOptions: React.FC = () => { const { isEstimateActive } = useEstimateOption(); + const options = projects?.map((project) => ({ + value: project.id, + query: project.name + " " + project.identifier, + content: project.name, + })); + if ( !router.pathname.includes("assigned") && !router.pathname.includes("created") && !router.pathname.includes("subscribed") ) return null; + // return ( + // console.log(val)} + // label="Filters" + // options={options} + // position="right" + // multiple + // /> + // ); return (
diff --git a/apps/app/components/profile/sidebar.tsx b/apps/app/components/profile/sidebar.tsx index df2cf2642..90945fb89 100644 --- a/apps/app/components/profile/sidebar.tsx +++ b/apps/app/components/profile/sidebar.tsx @@ -1,4 +1,5 @@ import { useRouter } from "next/router"; +import Link from "next/link"; import useSWR from "swr"; @@ -8,10 +9,14 @@ import { useTheme } from "next-themes"; import { Disclosure, Transition } from "@headlessui/react"; // services import userService from "services/user.service"; +// hooks +import useUser from "hooks/use-user"; // ui -import { Icon, Loader } from "components/ui"; +import { Icon, Loader, Tooltip } from "components/ui"; +// icons +import { EditOutlined } from "@mui/icons-material"; // helpers -import { renderLongDetailDateFormat } from "helpers/date-time.helper"; +import { render12HourFormatTime, renderLongDetailDateFormat } from "helpers/date-time.helper"; import { renderEmoji } from "helpers/emoji.helper"; // fetch-keys import { USER_PROFILE_PROJECT_SEGREGATION } from "constants/fetch-keys"; @@ -22,6 +27,8 @@ export const ProfileSidebar = () => { const { theme } = useTheme(); + const { user } = useUser(); + const { data: userProjectsData } = useSWR( workspaceSlug && userId ? USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString()) @@ -33,27 +40,24 @@ export const ProfileSidebar = () => { ); const userDetails = [ - { - label: "Username", - value: "", - }, { label: "Joined on", value: renderLongDetailDateFormat(userProjectsData?.user_data.date_joined ?? ""), }, { label: "Timezone", - value: userProjectsData?.user_data.user_timezone, - }, - { - label: "Status", - value: "Online", + value: ( + + {render12HourFormatTime(new Date())}{" "} + {userProjectsData?.user_data.user_timezone} + + ), }, ]; return (
{ {userProjectsData ? ( <>
+ {user?.id === userId && ( +
+ + + + + +
+ )} {userProjectsData.user_data.first_name} {
{userDetails.map((detail) => (
-
{detail.label}
-
{detail.value}
+
{detail.label}
+
{detail.value}
))}
@@ -143,17 +160,19 @@ export const ProfileSidebar = () => {
-
- {completedIssuePercentage}% -
+ +
+ {completedIssuePercentage}% +
+
diff --git a/apps/app/components/ui/dropdowns/custom-search-select.tsx b/apps/app/components/ui/dropdowns/custom-search-select.tsx index e40976d13..afbd27e4a 100644 --- a/apps/app/components/ui/dropdowns/custom-search-select.tsx +++ b/apps/app/components/ui/dropdowns/custom-search-select.tsx @@ -22,7 +22,7 @@ export type CustomSearchSelectProps = DropdownProps & { | { multiple?: false; value: any } // if multiple is false, value can be anything | { multiple?: true; - value: any[]; // if multiple is true, value should be an array + value: any[] | null; // if multiple is true, value should be an array } ); @@ -68,7 +68,7 @@ export const CustomSearchSelect = ({ className={`${selfPositioned ? "" : "relative"} flex-shrink-0 text-left ${className}`} {...props} > - {({ open }: any) => { + {({ open }: { open: boolean }) => { if (open && onOpen) onOpen(); return ( diff --git a/apps/app/hooks/use-profile-issues.tsx b/apps/app/hooks/use-profile-issues.tsx index 07bff53ba..0a8e9f877 100644 --- a/apps/app/hooks/use-profile-issues.tsx +++ b/apps/app/hooks/use-profile-issues.tsx @@ -73,8 +73,6 @@ const useProfileIssues = (workspaceSlug: string | undefined, userId: string | un useEffect(() => { if (!userId || !filters) return; - console.log("Triggered"); - if ( router.pathname.includes("assigned") && (!filters.assignees || !filters.assignees.includes(userId)) diff --git a/apps/app/hooks/use-projects.tsx b/apps/app/hooks/use-projects.tsx index c537e3a62..3363ff278 100644 --- a/apps/app/hooks/use-projects.tsx +++ b/apps/app/hooks/use-projects.tsx @@ -11,13 +11,17 @@ import { IProject } from "types"; // fetch-keys import { PROJECTS_LIST } from "constants/fetch-keys"; -const useProjects = (type?: "all" | boolean) => { +const useProjects = (type?: "all" | boolean, fetchCondition?: boolean) => { + fetchCondition = fetchCondition ?? true; + const router = useRouter(); const { workspaceSlug } = router.query; const { data: projects, mutate: mutateProjects } = useSWR( - workspaceSlug ? PROJECTS_LIST(workspaceSlug as string, { is_favorite: type ?? "all" }) : null, - workspaceSlug + workspaceSlug && fetchCondition + ? PROJECTS_LIST(workspaceSlug as string, { is_favorite: type ?? "all" }) + : null, + workspaceSlug && fetchCondition ? () => projectService.getProjects(workspaceSlug as string, { is_favorite: type ?? "all" }) : null ); diff --git a/apps/app/layouts/profile-layout.tsx b/apps/app/layouts/profile-layout.tsx new file mode 100644 index 000000000..10c38bb10 --- /dev/null +++ b/apps/app/layouts/profile-layout.tsx @@ -0,0 +1,53 @@ +import { useRouter } from "next/router"; + +// hooks +import { useWorkspaceMyMembership } from "contexts/workspace-member.context"; +// layouts +import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; +// components +import { ProfileNavbar, ProfileSidebar } from "components/profile"; +// ui +import { Breadcrumbs, BreadcrumbItem } from "components/breadcrumbs"; + +type Props = { + children: React.ReactNode; + className?: string; +}; + +export const ProfileAuthWrapper = (props: Props) => { + const router = useRouter(); + const { workspaceSlug } = router.query; + + return ( + + + + + } + > + + + ); +}; + +const ProfileLayout: React.FC = ({ children, className }) => { + const { memberRole } = useWorkspaceMyMembership(); + + return ( +
+ +
+ + {memberRole.isOwner || memberRole.isMember || memberRole.isViewer ? ( +
{children}
+ ) : ( +
+ You do not have the permission to access this page. +
+ )} +
+
+ ); +}; diff --git a/apps/app/pages/[workspaceSlug]/profile/[userId]/assigned.tsx b/apps/app/pages/[workspaceSlug]/profile/[userId]/assigned.tsx index 0f0c31829..a60116503 100644 --- a/apps/app/pages/[workspaceSlug]/profile/[userId]/assigned.tsx +++ b/apps/app/pages/[workspaceSlug]/profile/[userId]/assigned.tsx @@ -1,48 +1,19 @@ import React from "react"; -import { useRouter } from "next/router"; - // contexts import { ProfileIssuesContextProvider } from "contexts/profile-issues-context"; -// hooks -import useUser from "hooks/use-user"; -// layouts -import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; +import { ProfileAuthWrapper } from "layouts/profile-layout"; // components -import { ProfileIssuesView, ProfileNavbar, ProfileSidebar } from "components/profile"; -// ui -import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; +import { ProfileIssuesView } from "components/profile"; // types import type { NextPage } from "next"; -const ProfileAssignedIssues: NextPage = () => { - const router = useRouter(); - const { workspaceSlug } = router.query; - - const { user } = useUser(); - - return ( - - - - - - } - > -
-
- -
- -
-
- -
-
-
- ); -}; +const ProfileAssignedIssues: NextPage = () => ( + + + + + +); export default ProfileAssignedIssues; diff --git a/apps/app/pages/[workspaceSlug]/profile/[userId]/created.tsx b/apps/app/pages/[workspaceSlug]/profile/[userId]/created.tsx index 73113eeb2..1cac31e62 100644 --- a/apps/app/pages/[workspaceSlug]/profile/[userId]/created.tsx +++ b/apps/app/pages/[workspaceSlug]/profile/[userId]/created.tsx @@ -1,48 +1,20 @@ import React from "react"; -import { useRouter } from "next/router"; - // contexts import { ProfileIssuesContextProvider } from "contexts/profile-issues-context"; -// hooks -import useUser from "hooks/use-user"; // layouts -import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; +import { ProfileAuthWrapper } from "layouts/profile-layout"; // components -import { ProfileIssuesView, ProfileNavbar, ProfileSidebar } from "components/profile"; -// ui -import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; +import { ProfileIssuesView } from "components/profile"; // types import type { NextPage } from "next"; -const ProfileCreatedIssues: NextPage = () => { - const router = useRouter(); - const { workspaceSlug } = router.query; - - const { user } = useUser(); - - return ( - - - - - - } - > -
-
- -
- -
-
- -
-
-
- ); -}; +const ProfileCreatedIssues: NextPage = () => ( + + + + + +); export default ProfileCreatedIssues; diff --git a/apps/app/pages/[workspaceSlug]/profile/[userId]/index.tsx b/apps/app/pages/[workspaceSlug]/profile/[userId]/index.tsx index c29c78490..0cf54bb0d 100644 --- a/apps/app/pages/[workspaceSlug]/profile/[userId]/index.tsx +++ b/apps/app/pages/[workspaceSlug]/profile/[userId]/index.tsx @@ -1,34 +1,26 @@ import React from "react"; import { useRouter } from "next/router"; -import Link from "next/link"; import useSWR from "swr"; -// layouts -import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; // services import userService from "services/user.service"; +// layouts +import { ProfileAuthWrapper } from "layouts/profile-layout"; // components import { - ProfileNavbar, + ProfileActivity, ProfilePriorityDistribution, - ProfileSidebar, ProfileStateDistribution, ProfileStats, ProfileWorkload, } from "components/profile"; -// ui -import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; -import { Icon, Loader } from "components/ui"; -// helpers -import { activityDetails } from "helpers/activity.helper"; -import { timeAgo } from "helpers/date-time.helper"; // types import type { NextPage } from "next"; import { IUserStateDistribution, TStateGroups } from "types"; // constants -import { USER_PROFILE_DATA, USER_PROFILE_ACTIVITY } from "constants/fetch-keys"; +import { USER_PROFILE_DATA } from "constants/fetch-keys"; import { GROUP_CHOICES } from "constants/project"; const ProfileOverview: NextPage = () => { @@ -42,15 +34,6 @@ const ProfileOverview: NextPage = () => { : null ); - const { data: userProfileActivity } = useSWR( - workspaceSlug && userId - ? USER_PROFILE_ACTIVITY(workspaceSlug.toString(), userId.toString()) - : null, - workspaceSlug && userId - ? () => userService.getUserProfileActivity(workspaceSlug.toString(), userId.toString()) - : null - ); - const stateDistribution: IUserStateDistribution[] = Object.keys(GROUP_CHOICES).map((key) => { const group = userProfile?.state_distribution.find((g) => g.state_group === key); @@ -59,93 +42,20 @@ const ProfileOverview: NextPage = () => { }); return ( - - - - - } - > -
-
- -
- - -
- - -
-
-

Recent Activity

-
- {userProfileActivity ? ( -
- {userProfileActivity.results.map((activity) => ( -
-
- {activity.actor_detail.avatar && activity.actor_detail.avatar !== "" ? ( - {activity.actor_detail.first_name} - ) : ( -
- {activity.actor_detail.first_name.charAt(0)} -
- )} -
-
-

- - {activity.actor_detail.first_name} {activity.actor_detail.last_name}{" "} - - {activity.field ? ( - activityDetails[activity.field]?.message(activity as any) - ) : ( - - created this{" "} - - - Issue - - - - - )} -

-

- {timeAgo(activity.created_at)} -

-
-
- ))} -
- ) : ( - - - - - - - - )} -
-
-
+ +
+ + +
+ +
- +
- +
); }; diff --git a/apps/app/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx b/apps/app/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx index c04681990..3a1ca01ee 100644 --- a/apps/app/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx +++ b/apps/app/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx @@ -1,48 +1,20 @@ import React from "react"; -import { useRouter } from "next/router"; - // contexts import { ProfileIssuesContextProvider } from "contexts/profile-issues-context"; -// hooks -import useUser from "hooks/use-user"; // layouts -import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; +import { ProfileAuthWrapper } from "layouts/profile-layout"; // components -import { ProfileIssuesView, ProfileNavbar, ProfileSidebar } from "components/profile"; -// ui -import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; +import { ProfileIssuesView } from "components/profile"; // types import type { NextPage } from "next"; -const ProfileSubscribedIssues: NextPage = () => { - const router = useRouter(); - const { workspaceSlug } = router.query; - - const { user } = useUser(); - - return ( - - - - - - } - > -
-
- -
- -
-
- -
-
-
- ); -}; +const ProfileSubscribedIssues: NextPage = () => ( + + + + + +); export default ProfileSubscribedIssues; diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx index 61e285542..0a5897013 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { useRouter } from "next/router"; +import Link from "next/link"; import useSWR from "swr"; @@ -187,9 +188,17 @@ const MembersSettings: NextPage = () => { )}
-

- {member.first_name} {member.last_name} -

+ {member.member ? ( + + + {member.first_name} {member.last_name} + + + ) : ( +

+ {member.first_name} {member.last_name} +

+ )}

{member.email}

diff --git a/apps/app/pages/[workspaceSlug]/settings/index.tsx b/apps/app/pages/[workspaceSlug]/settings/index.tsx index 66284382a..8c76d6e6e 100644 --- a/apps/app/pages/[workspaceSlug]/settings/index.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/index.tsx @@ -14,7 +14,6 @@ import useToast from "hooks/use-toast"; import useUserAuth from "hooks/use-user-auth"; // layouts import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; -import SettingsNavbar from "layouts/settings-navbar"; // components import { ImageUploadModal } from "components/core"; import { DeleteWorkspaceModal, SettingsHeader } from "components/workspace"; diff --git a/apps/app/pages/[workspaceSlug]/settings/members.tsx b/apps/app/pages/[workspaceSlug]/settings/members.tsx index 35d7a03bd..2f6167277 100644 --- a/apps/app/pages/[workspaceSlug]/settings/members.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/members.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import Image from "next/image"; +import Link from "next/link"; import { useRouter } from "next/router"; import useSWR from "swr"; @@ -187,9 +187,17 @@ const MembersSettings: NextPage = () => { )}
-

- {member.first_name} {member.last_name} -

+ {member.member ? ( + + + {member.first_name} {member.last_name} + + + ) : ( +

+ {member.first_name} {member.last_name} +

+ )}

{member.email}

From c9498fa54df66a69f71f1e81130c2bf6fb888ea8 Mon Sep 17 00:00:00 2001 From: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Date: Mon, 31 Jul 2023 16:58:45 +0530 Subject: [PATCH 02/12] fix: workspace member invitation mutate (#1721) * fix: workspace member invitation mutate * fix: mutate on finally --- .../workspace/send-workspace-invitation-modal.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/app/components/workspace/send-workspace-invitation-modal.tsx b/apps/app/components/workspace/send-workspace-invitation-modal.tsx index a5ff08652..5bffa3264 100644 --- a/apps/app/components/workspace/send-workspace-invitation-modal.tsx +++ b/apps/app/components/workspace/send-workspace-invitation-modal.tsx @@ -1,5 +1,7 @@ import React, { useEffect } from "react"; +// swr +import { mutate } from "swr"; // react-hook-form import { Controller, useFieldArray, useForm } from "react-hook-form"; // headless @@ -13,9 +15,10 @@ import { CustomSelect, Input, PrimaryButton, SecondaryButton } from "components/ // icons import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline"; // types -import { ICurrentUserResponse, IWorkspace, IWorkspaceMemberInvitation } from "types"; +import { ICurrentUserResponse } from "types"; // constants import { ROLE } from "constants/workspace"; +import { WORKSPACE_INVITATIONS } from "constants/fetch-keys"; type Props = { isOpen: boolean; @@ -94,7 +97,10 @@ const SendWorkspaceInvitationModal: React.FC = ({ }); console.log(err); }) - .finally(() => reset(defaultValues)); + .finally(() => { + reset(defaultValues); + mutate(WORKSPACE_INVITATIONS); + }); }; const appendField = () => { From 98d9763f8e7554298d41671d95e6dfa8f7345d8e Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Mon, 31 Jul 2023 17:10:23 +0530 Subject: [PATCH 03/12] feat: sidebar project ordering functionality added (#1725) --- apps/app/components/project/sidebar-list.tsx | 162 ++++++++++++++---- .../project/single-sidebar-project.tsx | 24 ++- apps/app/services/project.service.ts | 1 + apps/app/types/projects.d.ts | 1 + 4 files changed, 154 insertions(+), 34 deletions(-) diff --git a/apps/app/components/project/sidebar-list.tsx b/apps/app/components/project/sidebar-list.tsx index 477cb49b1..5a35d5625 100644 --- a/apps/app/components/project/sidebar-list.tsx +++ b/apps/app/components/project/sidebar-list.tsx @@ -1,7 +1,10 @@ import React, { useState, FC } from "react"; import { useRouter } from "next/router"; +import { mutate } from "swr"; +// react-beautiful-dnd +import { DragDropContext, Draggable, DropResult, Droppable } from "react-beautiful-dnd"; // hooks import useToast from "hooks/use-toast"; import useTheme from "hooks/use-theme"; @@ -9,12 +12,17 @@ import useUserAuth from "hooks/use-user-auth"; import useProjects from "hooks/use-projects"; // components import { DeleteProjectModal, SingleSidebarProject } from "components/project"; +// services +import projectService from "services/project.service"; // icons import { PlusIcon } from "@heroicons/react/24/outline"; // helpers import { copyTextToClipboard } from "helpers/string.helper"; +import { orderArrayBy } from "helpers/array.helper"; // types import { IProject } from "types"; +// fetch-keys +import { PROJECTS_LIST } from "constants/fetch-keys"; export const ProjectSidebarList: FC = () => { const [deleteProjectModal, setDeleteProjectModal] = useState(false); @@ -32,6 +40,14 @@ export const ProjectSidebarList: FC = () => { const { projects: allProjects } = useProjects(); const favoriteProjects = allProjects?.filter((p) => p.is_favorite); + const orderedAllProjects = allProjects + ? orderArrayBy(allProjects, "sort_order", "ascending") + : []; + + const orderedFavProjects = favoriteProjects + ? orderArrayBy(favoriteProjects, "sort_order", "ascending") + : []; + const handleDeleteProject = (project: IProject) => { setProjectToDelete(project); setDeleteProjectModal(true); @@ -49,6 +65,54 @@ export const ProjectSidebarList: FC = () => { }); }; + const onDragEnd = async (result: DropResult) => { + const { source, destination, draggableId } = result; + + if (!destination || !workspaceSlug) return; + if (source.index === destination.index) return; + + const projectList = + destination.droppableId === "all-projects" ? orderedAllProjects : orderedFavProjects; + + let updatedSortOrder = projectList[source.index].sort_order; + if (destination.index === 0) { + updatedSortOrder = projectList[0].sort_order - 1000; + } else if (destination.index === projectList.length - 1) { + updatedSortOrder = projectList[projectList.length - 1].sort_order + 1000; + } else { + const destinationSortingOrder = projectList[destination.index].sort_order; + const relativeDestinationSortingOrder = + source.index < destination.index + ? projectList[destination.index + 1].sort_order + : projectList[destination.index - 1].sort_order; + + updatedSortOrder = Math.round( + (destinationSortingOrder + relativeDestinationSortingOrder) / 2 + ); + } + + mutate( + PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }), + (prevData) => { + if (!prevData) return prevData; + return prevData.map((p) => + p.id === draggableId ? { ...p, sort_order: updatedSortOrder } : p + ); + }, + false + ); + + await projectService + .setProjectView(workspaceSlug as string, draggableId, { sort_order: updatedSortOrder }) + .catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "Something went wrong. Please try again.", + }); + }); + }; + return ( <> { user={user} />
- {favoriteProjects && favoriteProjects.length > 0 && ( -
- {!sidebarCollapse && ( -
Favorites
+ + + {(provided) => ( +
+ {orderedFavProjects && orderedFavProjects.length > 0 && ( +
+ {!sidebarCollapse && ( +
+ Favorites +
+ )} + {orderedFavProjects.map((project, index) => ( + + {(provided, snapshot) => ( +
+ handleDeleteProject(project)} + handleCopyText={() => handleCopyText(project.id)} + shortContextMenu + /> +
+ )} +
+ ))} + {provided.placeholder} +
+ )} +
)} - {favoriteProjects.map((project) => ( - handleDeleteProject(project)} - handleCopyText={() => handleCopyText(project.id)} - shortContextMenu - /> - ))} -
- )} - {allProjects && allProjects.length > 0 && ( -
- {!sidebarCollapse && ( -
Projects
+ + + + + {(provided) => ( +
+ {orderedAllProjects && orderedAllProjects.length > 0 && ( +
+ {!sidebarCollapse && ( +
Projects
+ )} + {orderedAllProjects.map((project, index) => ( + + {(provided, snapshot) => ( +
+ handleDeleteProject(project)} + handleCopyText={() => handleCopyText(project.id)} + /> +
+ )} +
+ ))} + {provided.placeholder} +
+ )} +
)} - {allProjects.map((project) => ( - handleDeleteProject(project)} - handleCopyText={() => handleCopyText(project.id)} - /> - ))} -
- )} + + {allProjects && allProjects.length === 0 && ( = ({ > diff --git a/apps/app/services/project.service.ts b/apps/app/services/project.service.ts index 03e1b9eed..d25bcd9a6 100644 --- a/apps/app/services/project.service.ts +++ b/apps/app/services/project.service.ts @@ -254,6 +254,7 @@ class ProjectServices extends APIService { view_props?: ProjectViewTheme; default_props?: ProjectViewTheme; preferences?: ProjectPreferences; + sort_order?: number; } ): Promise { await this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/project-views/`, data) diff --git a/apps/app/types/projects.d.ts b/apps/app/types/projects.d.ts index 0ac3b1d91..6ea201391 100644 --- a/apps/app/types/projects.d.ts +++ b/apps/app/types/projects.d.ts @@ -45,6 +45,7 @@ export interface IProject { network: number; page_view: boolean; project_lead: IUserLite | string | null; + sort_order: number; slug: string; total_cycles: number; total_members: number; From 81b14054480d86746a69a4aaad0acb378845a8c5 Mon Sep 17 00:00:00 2001 From: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Date: Mon, 31 Jul 2023 17:21:37 +0530 Subject: [PATCH 04/12] refactor: moved mutate to 'finally' block (#1722) * refactor: moved mutate to 'finally' block fix: content for success * fix: invited -> added --- apps/app/components/project/send-project-invitation-modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/components/project/send-project-invitation-modal.tsx b/apps/app/components/project/send-project-invitation-modal.tsx index c2c1f4eda..b74255a2f 100644 --- a/apps/app/components/project/send-project-invitation-modal.tsx +++ b/apps/app/components/project/send-project-invitation-modal.tsx @@ -93,7 +93,6 @@ const SendProjectInvitationModal: React.FC = ({ isOpen, setIsOpen, member .inviteProject(workspaceSlug as string, projectId as string, payload, user) .then(() => { setIsOpen(false); - mutate(PROJECT_MEMBERS(projectId as string)); setToastAlert({ title: "Success", type: "success", @@ -105,6 +104,7 @@ const SendProjectInvitationModal: React.FC = ({ isOpen, setIsOpen, member }) .finally(() => { reset(defaultValues); + mutate(PROJECT_MEMBERS(projectId.toString())); }); }; From e8f748a67d30e418160e52cb8c6d883739813c5c Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Mon, 31 Jul 2023 17:22:48 +0530 Subject: [PATCH 05/12] style: responsive title (#1683) * style: responsive issue title added * style: responsive breadcrumbs and app-header layout * style: breadcrumbs styling * fix: app header dropdown issue and limit app header title to 32 characters --- apps/app/components/breadcrumbs/index.tsx | 27 ++++++++++++++----- .../core/views/list-view/single-issue.tsx | 16 +++++------ .../issues/view-select/due-date.tsx | 2 +- apps/app/layouts/app-layout/app-header.tsx | 8 +++--- .../archived-issues/[archivedIssueId].tsx | 6 ++++- .../[projectId]/archived-issues/index.tsx | 6 ++--- .../projects/[projectId]/cycles/[cycleId].tsx | 7 ++--- .../projects/[projectId]/cycles/index.tsx | 4 ++- .../projects/[projectId]/inbox/[inboxId].tsx | 2 +- .../projects/[projectId]/issues/[issueId].tsx | 6 ++++- .../projects/[projectId]/issues/index.tsx | 2 +- .../[projectId]/modules/[moduleId].tsx | 3 ++- .../projects/[projectId]/modules/index.tsx | 4 ++- .../projects/[projectId]/pages/[pageId].tsx | 4 +-- .../projects/[projectId]/pages/index.tsx | 8 ++++-- .../[projectId]/settings/automations.tsx | 7 +++-- .../projects/[projectId]/settings/control.tsx | 7 +++-- .../[projectId]/settings/estimates.tsx | 7 +++-- .../[projectId]/settings/features.tsx | 7 +++-- .../projects/[projectId]/settings/index.tsx | 6 +++-- .../[projectId]/settings/integrations.tsx | 7 +++-- .../projects/[projectId]/settings/labels.tsx | 7 +++-- .../projects/[projectId]/settings/members.tsx | 7 +++-- .../projects/[projectId]/settings/states.tsx | 6 +++-- .../pages/[workspaceSlug]/projects/index.tsx | 7 ++++- .../[workspaceSlug]/settings/billing.tsx | 7 +++-- .../settings/import-export.tsx | 23 +++++++++++++--- .../pages/[workspaceSlug]/settings/index.tsx | 6 +++-- .../[workspaceSlug]/settings/integrations.tsx | 7 +++-- .../[workspaceSlug]/settings/members.tsx | 7 +++-- 30 files changed, 155 insertions(+), 68 deletions(-) diff --git a/apps/app/components/breadcrumbs/index.tsx b/apps/app/components/breadcrumbs/index.tsx index 8a67c92b9..0e5cfb9c4 100644 --- a/apps/app/components/breadcrumbs/index.tsx +++ b/apps/app/components/breadcrumbs/index.tsx @@ -2,7 +2,6 @@ import * as React from "react"; import { useRouter } from "next/router"; import Link from "next/link"; // icons -import { ArrowLeftIcon } from "@heroicons/react/24/outline"; import { Icon } from "components/ui"; type BreadcrumbsProps = { @@ -14,7 +13,7 @@ const Breadcrumbs = ({ children }: BreadcrumbsProps) => { return ( <> -
+
{breadcrumbs} - {left} +
{left}
- {right} +
{right}
); diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx index d66575282..a48207db3 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx @@ -23,6 +23,8 @@ import { IIssue } from "types"; import type { NextPage } from "next"; // fetch-keys import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const defaultValues = { name: "", @@ -146,13 +148,15 @@ const ArchivedIssueDetailsPage: NextPage = () => { breadcrumbs={ } diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/index.tsx index e944301d3..b2c0ab428 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/index.tsx @@ -4,6 +4,8 @@ import useSWR from "swr"; // services import projectService from "services/project.service"; +// hooks +import useIssuesView from "hooks/use-issues-view"; // layouts import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; // contexts @@ -21,8 +23,6 @@ import { XMarkIcon } from "@heroicons/react/24/outline"; import type { NextPage } from "next"; // fetch-keys import { PROJECT_DETAILS } from "constants/fetch-keys"; -import useIssuesView from "hooks/use-issues-view"; -import { useEffect } from "react"; const ProjectArchivedIssues: NextPage = () => { const router = useRouter(); @@ -44,7 +44,7 @@ const ProjectArchivedIssues: NextPage = () => { } diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx index 73d6b4f80..0da8f797c 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx @@ -109,8 +109,9 @@ const SingleCycle: React.FC = () => { breadcrumbs={ } @@ -122,7 +123,7 @@ const SingleCycle: React.FC = () => { {cycleDetails?.name && truncateText(cycleDetails.name, 40)} } - className="ml-1.5" + className="ml-1.5 flex-shrink-0" width="auto" > {cycles?.map((cycle) => ( @@ -137,7 +138,7 @@ const SingleCycle: React.FC = () => { } right={ -
+
setAnalyticsModal(true)} diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx index 744d636fa..0ea9e73e3 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx @@ -29,6 +29,8 @@ import emptyCycle from "public/empty-state/cycle.svg"; // types import { SelectCycleType } from "types"; import type { NextPage } from "next"; +// helper +import { truncateText } from "helpers/string.helper"; const tabsList = ["All", "Active", "Upcoming", "Completed", "Drafts"]; @@ -91,7 +93,7 @@ const ProjectCycles: NextPage = () => { breadcrumbs={ - + } right={ diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx index cad3c3830..7a82ac61f 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx @@ -31,7 +31,7 @@ const ProjectInbox: NextPage = () => { } diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index 0c70ff2e9..d5f7c8ec6 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -22,6 +22,8 @@ import { IIssue } from "types"; import type { NextPage } from "next"; // fetch-keys import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const defaultValues = { name: "", @@ -110,13 +112,15 @@ const IssueDetailsPage: NextPage = () => { breadcrumbs={ } diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx index 63d7ffb51..91da78757 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx @@ -54,7 +54,7 @@ const ProjectIssues: NextPage = () => { } diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx index 44f6d5d23..71c3bb655 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx @@ -112,8 +112,9 @@ const SingleModule: React.FC = () => { breadcrumbs={ } diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx index ba952be7a..b5b1e3806 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx @@ -29,6 +29,8 @@ import { IModule, SelectModuleType } from "types/modules"; import type { NextPage } from "next"; // fetch-keys import { MODULE_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const ProjectModules: NextPage = () => { const [selectedModule, setSelectedModule] = useState(); @@ -73,7 +75,7 @@ const ProjectModules: NextPage = () => { breadcrumbs={ - + } right={ diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index f15b9aa1d..b26be661e 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -43,7 +43,7 @@ import { import { ColorPalletteIcon, ClipboardIcon } from "components/icons"; // helpers import { render24HourFormatTime, renderShortDate } from "helpers/date-time.helper"; -import { copyTextToClipboard } from "helpers/string.helper"; +import { copyTextToClipboard, truncateText } from "helpers/string.helper"; import { orderArrayBy } from "helpers/array.helper"; // types import type { NextPage } from "next"; @@ -346,7 +346,7 @@ const SinglePage: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx index a07bcce26..f6f8d3d86 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx @@ -11,6 +11,7 @@ import { Tab } from "@headlessui/react"; import projectService from "services/project.service"; // hooks import useLocalStorage from "hooks/use-local-storage"; +import useUserAuth from "hooks/use-user-auth"; // icons import { PlusIcon } from "components/icons"; // layouts @@ -27,7 +28,8 @@ import { TPageViewProps } from "types"; import type { NextPage } from "next"; // fetch-keys import { PROJECT_DETAILS } from "constants/fetch-keys"; -import useUserAuth from "hooks/use-user-auth"; +// helper +import { truncateText } from "helpers/string.helper"; const AllPagesList = dynamic( () => import("components/pages").then((a) => a.AllPagesList), @@ -107,7 +109,9 @@ const ProjectPages: NextPage = () => { breadcrumbs={ - + } right={ diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx index ca4ac770d..a65222af5 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx @@ -22,6 +22,8 @@ import type { NextPage } from "next"; import { IProject } from "types"; // constant import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const AutomationsSettings: NextPage = () => { const router = useRouter(); @@ -65,10 +67,11 @@ const AutomationsSettings: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/control.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/control.tsx index d0a102e05..9e8d437e8 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/control.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/control.tsx @@ -23,6 +23,8 @@ import { IProject, IUserLite, IWorkspace } from "types"; import type { NextPage } from "next"; // fetch-keys import { PROJECTS_LIST, PROJECT_DETAILS, PROJECT_MEMBERS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const defaultValues: Partial = { project_lead: null, @@ -103,10 +105,11 @@ const ControlSettings: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx index 39efc12c3..7c82f7dd4 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx @@ -29,6 +29,8 @@ import { IEstimate, IProject } from "types"; import type { NextPage } from "next"; // fetch-keys import { ESTIMATES_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const EstimatesSettings: NextPage = () => { const [estimateFormOpen, setEstimateFormOpen] = useState(false); @@ -115,10 +117,11 @@ const EstimatesSettings: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx index 517004dac..be0c2198a 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx @@ -25,6 +25,8 @@ import { IProject } from "types"; import type { NextPage } from "next"; // fetch-keys import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const featuresList = [ { @@ -139,10 +141,11 @@ const FeaturesSettings: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx index 390dc206b..7de91c823 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx @@ -29,6 +29,7 @@ import { import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; // helpers import { renderEmoji } from "helpers/emoji.helper"; +import { truncateText } from "helpers/string.helper"; // types import { IProject, IWorkspace } from "types"; import type { NextPage } from "next"; @@ -161,10 +162,11 @@ const GeneralSettings: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx index 7726df02a..0ede7d216 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx @@ -23,6 +23,8 @@ import { IProject } from "types"; import type { NextPage } from "next"; // fetch-keys import { PROJECT_DETAILS, WORKSPACE_INTEGRATIONS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const ProjectIntegrations: NextPage = () => { const router = useRouter(); @@ -48,10 +50,11 @@ const ProjectIntegrations: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx index 228c8ed44..d8c425ee5 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx @@ -32,6 +32,8 @@ import { IIssueLabels } from "types"; import type { NextPage } from "next"; // fetch-keys import { PROJECT_DETAILS, PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const LabelsSettings: NextPage = () => { // create/edit label form @@ -103,10 +105,11 @@ const LabelsSettings: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx index 0a5897013..d828b3912 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx @@ -29,6 +29,8 @@ import type { NextPage } from "next"; import { PROJECT_INVITATIONS, PROJECT_MEMBERS, WORKSPACE_DETAILS } from "constants/fetch-keys"; // constants import { ROLE } from "constants/workspace"; +// helper +import { truncateText } from "helpers/string.helper"; const MembersSettings: NextPage = () => { const [inviteModal, setInviteModal] = useState(false); @@ -94,10 +96,11 @@ const MembersSettings: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx index 0b7b0fb72..73337c6bb 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx @@ -26,6 +26,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { PlusIcon } from "@heroicons/react/24/outline"; // helpers import { getStatesList, orderStateGroups } from "helpers/state.helper"; +import { truncateText } from "helpers/string.helper"; // types import type { NextPage } from "next"; // fetch-keys @@ -64,10 +65,11 @@ const StatesSettings: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/index.tsx b/apps/app/pages/[workspaceSlug]/projects/index.tsx index e6bf7a28a..5f3fbcc07 100644 --- a/apps/app/pages/[workspaceSlug]/projects/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/index.tsx @@ -26,6 +26,8 @@ import emptyProject from "public/empty-state/project.svg"; import type { NextPage } from "next"; // fetch-keys import { PROJECT_MEMBERS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const ProjectsPage: NextPage = () => { // router @@ -44,7 +46,10 @@ const ProjectsPage: NextPage = () => { - + } right={ diff --git a/apps/app/pages/[workspaceSlug]/settings/billing.tsx b/apps/app/pages/[workspaceSlug]/settings/billing.tsx index 2367b94b4..6731cd33f 100644 --- a/apps/app/pages/[workspaceSlug]/settings/billing.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/billing.tsx @@ -16,6 +16,8 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import type { NextPage } from "next"; // fetch-keys import { WORKSPACE_DETAILS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const BillingSettings: NextPage = () => { const { @@ -32,10 +34,11 @@ const BillingSettings: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/settings/import-export.tsx b/apps/app/pages/[workspaceSlug]/settings/import-export.tsx index efd72af24..ae31aa9f8 100644 --- a/apps/app/pages/[workspaceSlug]/settings/import-export.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/import-export.tsx @@ -1,26 +1,43 @@ import { useRouter } from "next/router"; +import useSWR from "swr"; + +// services +import workspaceService from "services/workspace.service"; // layouts import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { SettingsHeader } from "components/workspace"; // components import IntegrationGuide from "components/integration/guide"; +import { IntegrationAndImportExportBanner } from "components/ui"; // ui import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; // types import type { NextPage } from "next"; -import { IntegrationAndImportExportBanner } from "components/ui"; +// fetch-keys +import { WORKSPACE_DETAILS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const ImportExport: NextPage = () => { const router = useRouter(); const { workspaceSlug } = router.query; + const { data: activeWorkspace } = useSWR( + workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, + () => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null) + ); + return ( - - + + } > diff --git a/apps/app/pages/[workspaceSlug]/settings/index.tsx b/apps/app/pages/[workspaceSlug]/settings/index.tsx index 8c76d6e6e..af46e0884 100644 --- a/apps/app/pages/[workspaceSlug]/settings/index.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/index.tsx @@ -23,7 +23,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; // icons import { LinkIcon } from "@heroicons/react/24/outline"; // helpers -import { copyTextToClipboard } from "helpers/string.helper"; +import { copyTextToClipboard, truncateText } from "helpers/string.helper"; // types import type { IWorkspace } from "types"; import type { NextPage } from "next"; @@ -146,7 +146,9 @@ const WorkspaceSettings: NextPage = () => { - + } > diff --git a/apps/app/pages/[workspaceSlug]/settings/integrations.tsx b/apps/app/pages/[workspaceSlug]/settings/integrations.tsx index b94057bea..1b7a540c4 100644 --- a/apps/app/pages/[workspaceSlug]/settings/integrations.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/integrations.tsx @@ -19,6 +19,8 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import type { NextPage } from "next"; // fetch-keys import { WORKSPACE_DETAILS, APP_INTEGRATIONS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const WorkspaceIntegrations: NextPage = () => { const router = useRouter(); @@ -38,10 +40,11 @@ const WorkspaceIntegrations: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/settings/members.tsx b/apps/app/pages/[workspaceSlug]/settings/members.tsx index 2f6167277..c9c4a2eac 100644 --- a/apps/app/pages/[workspaceSlug]/settings/members.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/members.tsx @@ -27,6 +27,8 @@ import type { NextPage } from "next"; import { WORKSPACE_DETAILS, WORKSPACE_INVITATIONS, WORKSPACE_MEMBERS } from "constants/fetch-keys"; // constants import { ROLE } from "constants/workspace"; +// helper +import { truncateText } from "helpers/string.helper"; const MembersSettings: NextPage = () => { const [selectedRemoveMember, setSelectedRemoveMember] = useState(null); @@ -89,10 +91,11 @@ const MembersSettings: NextPage = () => { breadcrumbs={ - + } > From 7ad0466d6505816b69dc69bd66e53dd4f77fd5b3 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Mon, 31 Jul 2023 17:23:49 +0530 Subject: [PATCH 06/12] refactor: new onboarding workflow (#1724) * refactor: new onboarding workflow * refactor: new onboarding workflow --- .../components/onboarding/invite-members.tsx | 49 +++------- .../components/onboarding/join-workspaces.tsx | 47 +++++----- apps/app/components/onboarding/workspace.tsx | 37 ++++++-- apps/app/pages/onboarding.tsx | 90 +++++++++++++------ apps/app/types/users.d.ts | 4 +- 5 files changed, 125 insertions(+), 102 deletions(-) diff --git a/apps/app/components/onboarding/invite-members.tsx b/apps/app/components/onboarding/invite-members.tsx index 9bd2a82e8..fee1cb252 100644 --- a/apps/app/components/onboarding/invite-members.tsx +++ b/apps/app/components/onboarding/invite-members.tsx @@ -1,12 +1,9 @@ import React, { useEffect } from "react"; -import useSWR, { mutate } from "swr"; - // react-hook-form import { Controller, useFieldArray, useForm } from "react-hook-form"; // services import workspaceService from "services/workspace.service"; -import userService from "services/user.service"; // hooks import useToast from "hooks/use-toast"; // ui @@ -14,16 +11,15 @@ import { CustomSelect, Input, PrimaryButton, SecondaryButton } from "components/ // icons import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline"; // types -import { ICurrentUserResponse, IWorkspace, OnboardingSteps } from "types"; -// fetch-keys -import { CURRENT_USER, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys"; +import { ICurrentUserResponse, IWorkspace, TOnboardingSteps } from "types"; // constants import { ROLE } from "constants/workspace"; type Props = { - workspace: IWorkspace | undefined; + finishOnboarding: () => Promise; + stepChange: (steps: Partial) => Promise; user: ICurrentUserResponse | undefined; - stepChange: (steps: Partial) => Promise; + workspace: IWorkspace | undefined; }; type EmailRole = { @@ -35,7 +31,12 @@ type FormValues = { emails: EmailRole[]; }; -export const InviteMembers: React.FC = ({ workspace, user, stepChange }) => { +export const InviteMembers: React.FC = ({ + finishOnboarding, + stepChange, + user, + workspace, +}) => { const { setToastAlert } = useToast(); const { @@ -49,38 +50,14 @@ export const InviteMembers: React.FC = ({ workspace, user, stepChange }) name: "emails", }); - const { data: invitations } = useSWR(USER_WORKSPACE_INVITATIONS, () => - workspaceService.userWorkspaceInvitations() - ); - const nextStep = async () => { - if (!user || !invitations) return; - - const payload: Partial = { + const payload: Partial = { workspace_invite: true, + workspace_join: true, }; - // update onboarding status from this step if no invitations are present - if (invitations.length === 0) { - payload.workspace_join = true; - - mutate( - CURRENT_USER, - (prevData) => { - if (!prevData) return prevData; - - return { - ...prevData, - is_onboarded: true, - }; - }, - false - ); - - await userService.updateUserOnBoard({ userRole: user.role }, user); - } - await stepChange(payload); + await finishOnboarding(); }; const onSubmit = async (formData: FormValues) => { diff --git a/apps/app/components/onboarding/join-workspaces.tsx b/apps/app/components/onboarding/join-workspaces.tsx index 2441542d6..84b5bdfc9 100644 --- a/apps/app/components/onboarding/join-workspaces.tsx +++ b/apps/app/components/onboarding/join-workspaces.tsx @@ -4,7 +4,6 @@ import useSWR, { mutate } from "swr"; // services import workspaceService from "services/workspace.service"; -import userService from "services/user.service"; // hooks import useUser from "hooks/use-user"; // ui @@ -14,17 +13,23 @@ import { CheckCircleIcon } from "@heroicons/react/24/outline"; // helpers import { truncateText } from "helpers/string.helper"; // types -import { ICurrentUserResponse, IUser, IWorkspaceMemberInvitation, OnboardingSteps } from "types"; +import { IWorkspaceMemberInvitation, TOnboardingSteps } from "types"; // fetch-keys -import { CURRENT_USER, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys"; +import { USER_WORKSPACES, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys"; // constants import { ROLE } from "constants/workspace"; type Props = { - stepChange: (steps: Partial) => Promise; + finishOnboarding: () => Promise; + stepChange: (steps: Partial) => Promise; + updateLastWorkspace: () => Promise; }; -export const JoinWorkspaces: React.FC = ({ stepChange }) => { +export const JoinWorkspaces: React.FC = ({ + finishOnboarding, + stepChange, + updateLastWorkspace, +}) => { const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false); const [invitationsRespond, setInvitationsRespond] = useState([]); @@ -47,25 +52,13 @@ export const JoinWorkspaces: React.FC = ({ stepChange }) => { } }; - // complete onboarding - const finishOnboarding = async () => { + const handleNextStep = async () => { if (!user) return; - mutate( - CURRENT_USER, - (prevData) => { - if (!prevData) return prevData; - - return { - ...prevData, - is_onboarded: true, - }; - }, - false - ); - - await userService.updateUserOnBoard({ userRole: user.role }, user); await stepChange({ workspace_join: true }); + + if (user.onboarding_step.workspace_create && user.onboarding_step.workspace_invite) + await finishOnboarding(); }; const submitInvitations = async () => { @@ -77,11 +70,12 @@ export const JoinWorkspaces: React.FC = ({ stepChange }) => { .joinWorkspaces({ invitations: invitationsRespond }) .then(async () => { await mutateInvitations(); - await finishOnboarding(); + await mutate(USER_WORKSPACES); + await updateLastWorkspace(); - setIsJoiningWorkspaces(false); + await handleNextStep(); }) - .catch(() => setIsJoiningWorkspaces(false)); + .finally(() => setIsJoiningWorkspaces(false)); }; return ( @@ -142,14 +136,15 @@ export const JoinWorkspaces: React.FC = ({ stepChange }) => { type="submit" size="md" onClick={submitInvitations} - disabled={isJoiningWorkspaces || invitationsRespond.length === 0} + disabled={invitationsRespond.length === 0} + loading={isJoiningWorkspaces} > Accept & Join Skip for now diff --git a/apps/app/components/onboarding/workspace.tsx b/apps/app/components/onboarding/workspace.tsx index b401585dd..8f7442432 100644 --- a/apps/app/components/onboarding/workspace.tsx +++ b/apps/app/components/onboarding/workspace.tsx @@ -3,17 +3,25 @@ import { useState } from "react"; // ui import { SecondaryButton } from "components/ui"; // types -import { ICurrentUserResponse, OnboardingSteps } from "types"; +import { ICurrentUserResponse, IWorkspace, TOnboardingSteps } from "types"; // constants import { CreateWorkspaceForm } from "components/workspace"; type Props = { - user: ICurrentUserResponse | undefined; + finishOnboarding: () => Promise; + stepChange: (steps: Partial) => Promise; updateLastWorkspace: () => Promise; - stepChange: (steps: Partial) => Promise; + user: ICurrentUserResponse | undefined; + workspaces: IWorkspace[] | undefined; }; -export const Workspace: React.FC = ({ user, updateLastWorkspace, stepChange }) => { +export const Workspace: React.FC = ({ + finishOnboarding, + stepChange, + updateLastWorkspace, + user, + workspaces, +}) => { const [defaultValues, setDefaultValues] = useState({ name: "", slug: "", @@ -23,12 +31,21 @@ export const Workspace: React.FC = ({ user, updateLastWorkspace, stepChan const completeStep = async () => { if (!user) return; - await stepChange({ + const payload: Partial = { workspace_create: true, - }); + }; + + await stepChange(payload); await updateLastWorkspace(); }; + const secondaryButtonAction = async () => { + if (workspaces && workspaces.length > 0) { + await stepChange({ workspace_create: true, workspace_invite: true, workspace_join: true }); + await finishOnboarding(); + } else await stepChange({ profile_complete: false, workspace_join: false }); + }; + return (

Create your workspace

@@ -43,9 +60,11 @@ export const Workspace: React.FC = ({ user, updateLastWorkspace, stepChan default: "Continue", }} secondaryButton={ - stepChange({ profile_complete: false })}> - Back - + workspaces ? ( + + {workspaces.length > 0 ? "Skip & continue" : "Back"} + + ) : undefined } />
diff --git a/apps/app/pages/onboarding.tsx b/apps/app/pages/onboarding.tsx index 57b121648..217584dd0 100644 --- a/apps/app/pages/onboarding.tsx +++ b/apps/app/pages/onboarding.tsx @@ -24,7 +24,7 @@ import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-logo.svg"; import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.svg"; // types -import { ICurrentUserResponse, IUser, OnboardingSteps } from "types"; +import { ICurrentUserResponse, IUser, TOnboardingSteps } from "types"; import type { NextPage } from "next"; // fetch-keys import { CURRENT_USER, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys"; @@ -43,33 +43,35 @@ const Onboarding: NextPage = () => { workspaceService.userWorkspaceInvitations() ); + // update last active workspace details const updateLastWorkspace = async () => { - if (!userWorkspaces) return; + if (!workspaces) return; - mutate( + await mutate( CURRENT_USER, (prevData) => { if (!prevData) return prevData; return { ...prevData, - last_workspace_id: userWorkspaces[0]?.id, + last_workspace_id: workspaces[0]?.id, workspace: { ...prevData.workspace, - fallback_workspace_id: userWorkspaces[0]?.id, - fallback_workspace_slug: userWorkspaces[0]?.slug, - last_workspace_id: userWorkspaces[0]?.id, - last_workspace_slug: userWorkspaces[0]?.slug, + fallback_workspace_id: workspaces[0]?.id, + fallback_workspace_slug: workspaces[0]?.slug, + last_workspace_id: workspaces[0]?.id, + last_workspace_slug: workspaces[0]?.slug, }, }; }, false ); - await userService.updateUser({ last_workspace_id: userWorkspaces?.[0]?.id }); + await userService.updateUser({ last_workspace_id: workspaces?.[0]?.id }); }; - const stepChange = async (steps: Partial) => { + // handle step change + const stepChange = async (steps: Partial) => { if (!user) return; const payload: Partial = { @@ -95,16 +97,44 @@ const Onboarding: NextPage = () => { await userService.updateUser(payload); }; + // complete onboarding + const finishOnboarding = async () => { + if (!user) return; + + mutate( + CURRENT_USER, + (prevData) => { + if (!prevData) return prevData; + + return { + ...prevData, + is_onboarded: true, + }; + }, + false + ); + + await userService.updateUserOnBoard({ userRole: user.role }, user); + }; + useEffect(() => { const handleStepChange = async () => { - if (!user || !userWorkspaces || !invitations) return; + if (!user || !invitations) return; const onboardingStep = user.onboarding_step; if (!onboardingStep.profile_complete && step !== 1) setStep(1); - if (onboardingStep.profile_complete && !onboardingStep.workspace_create && step !== 2) - setStep(2); + if (onboardingStep.profile_complete) { + if (!onboardingStep.workspace_join && invitations.length > 0 && step !== 2 && step !== 4) + setStep(4); + else if ( + !onboardingStep.workspace_create && + (step !== 4 || onboardingStep.workspace_join) && + step !== 2 + ) + setStep(2); + } if ( onboardingStep.profile_complete && @@ -113,21 +143,10 @@ const Onboarding: NextPage = () => { step !== 3 ) setStep(3); - - if ( - onboardingStep.profile_complete && - onboardingStep.workspace_create && - onboardingStep.workspace_invite && - !onboardingStep.workspace_join && - step !== 4 - ) { - if (invitations.length > 0) setStep(4); - else await Router.push("/"); - } }; handleStepChange(); - }, [user, invitations, userWorkspaces, step]); + }, [user, invitations, step]); if (userLoading || step === null) return ( @@ -167,14 +186,27 @@ const Onboarding: NextPage = () => { ) : step === 2 ? ( ) : step === 3 ? ( - + ) : ( - step === 4 && + step === 4 && ( + + ) )}
{step !== 4 && ( diff --git a/apps/app/types/users.d.ts b/apps/app/types/users.d.ts index 3d72f3300..8731d29ad 100644 --- a/apps/app/types/users.d.ts +++ b/apps/app/types/users.d.ts @@ -27,7 +27,7 @@ export interface IUser { properties: Properties; groupBy: NestedKeyOf | null; } | null; - onboarding_step: OnboardingSteps; + onboarding_step: TOnboardingSteps; role: string; token: string; theme: ICustomTheme; @@ -140,7 +140,7 @@ export type UserAuth = { isGuest: boolean; }; -export type OnboardingSteps = { +export type TOnboardingSteps = { profile_complete: boolean; workspace_create: boolean; workspace_invite: boolean; From f42f2465a99b3efef6049f3072d7c72ce3faa772 Mon Sep 17 00:00:00 2001 From: Sai Phanindra <29685411+ph4ni@users.noreply.github.com> Date: Mon, 31 Jul 2023 17:25:37 +0530 Subject: [PATCH 07/12] Changed alt text for badges in Readme (#1686) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8141bf588..20e34b673 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@

-Discord +Discord online members -Discord +Commit activity per month

From ff3f1897bc8bfaf9f2c0636c9bd76251b10c62aa Mon Sep 17 00:00:00 2001 From: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Date: Mon, 31 Jul 2023 17:49:07 +0530 Subject: [PATCH 08/12] feat: user cycle stats (#1723) * feat: user cycle stats * dev: revert capture exception --- apiserver/plane/api/views/workspace.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index 3404bbf19..a9822aa0b 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -73,7 +73,7 @@ from plane.db.models import ( IssueSubscriber, Project, Label, - State, + CycleIssue, ) from plane.api.permissions import ( WorkSpaceBasePermission, @@ -1140,6 +1140,19 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView): .count() ) + upcoming_cycles = CycleIssue.objects.filter( + workspace__slug=slug, + cycle__start_date__gt=timezone.now().date(), + issue__assignees__in=[user_id,] + ).values("cycle__name", "cycle__id", "cycle__project_id") + + present_cycle = CycleIssue.objects.filter( + workspace__slug=slug, + cycle__start_date__lt=timezone.now().date(), + cycle__end_date__gt=timezone.now().date(), + issue__assignees__in=[user_id,] + ).values("cycle__name", "cycle__id", "cycle__project_id") + return Response( { "state_distribution": state_distribution, @@ -1149,6 +1162,8 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView): "completed_issues": completed_issues_count, "pending_issues": pending_issues_count, "subscribed_issues": subscribed_issues_count, + "present_cycles": present_cycle, + "upcoming_cycles": upcoming_cycles, } ) except Exception as e: @@ -1394,6 +1409,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView): status=status.HTTP_400_BAD_REQUEST, ) + class WorkspaceLabelsEndpoint(BaseAPIView): permission_classes = [ WorkspaceEntityPermission, From 1ae78e55c94938081f08e98e0282a9709ab4279b Mon Sep 17 00:00:00 2001 From: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Date: Mon, 31 Jul 2023 18:04:01 +0530 Subject: [PATCH 09/12] chore: profile page permission (#1728) * chore: profile page permission * dev: change the default type --- apiserver/plane/api/permissions/__init__.py | 2 +- apiserver/plane/api/permissions/workspace.py | 10 ++ apiserver/plane/api/views/workspace.py | 110 ++++++++++--------- 3 files changed, 68 insertions(+), 54 deletions(-) diff --git a/apiserver/plane/api/permissions/__init__.py b/apiserver/plane/api/permissions/__init__.py index 91b3aea35..8b15a9373 100644 --- a/apiserver/plane/api/permissions/__init__.py +++ b/apiserver/plane/api/permissions/__init__.py @@ -1,2 +1,2 @@ -from .workspace import WorkSpaceBasePermission, WorkSpaceAdminPermission, WorkspaceEntityPermission +from .workspace import WorkSpaceBasePermission, WorkSpaceAdminPermission, WorkspaceEntityPermission, WorkspaceViewerPermission from .project import ProjectBasePermission, ProjectEntityPermission, ProjectMemberPermission, ProjectLitePermission diff --git a/apiserver/plane/api/permissions/workspace.py b/apiserver/plane/api/permissions/workspace.py index 7fccc455e..d01b545ee 100644 --- a/apiserver/plane/api/permissions/workspace.py +++ b/apiserver/plane/api/permissions/workspace.py @@ -61,3 +61,13 @@ class WorkspaceEntityPermission(BasePermission): return WorkspaceMember.objects.filter( member=request.user, workspace__slug=view.workspace_slug ).exists() + + +class WorkspaceViewerPermission(BasePermission): + def has_permission(self, request, view): + if request.user.is_anonymous: + return False + + return WorkspaceMember.objects.filter( + member=request.user, workspace__slug=view.workspace_slug, role__gte=10 + ).exists() diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index a9822aa0b..10428b53d 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -73,12 +73,14 @@ from plane.db.models import ( IssueSubscriber, Project, Label, + WorkspaceMember, CycleIssue, ) from plane.api.permissions import ( WorkSpaceBasePermission, WorkSpaceAdminPermission, WorkspaceEntityPermission, + WorkspaceViewerPermission, ) from plane.bgtasks.workspace_invitation_task import workspace_invitation from plane.utils.issue_filters import issue_filters @@ -1209,64 +1211,64 @@ class WorkspaceUserActivityEndpoint(BaseAPIView): class WorkspaceUserProfileEndpoint(BaseAPIView): - permission_classes = [ - WorkspaceEntityPermission, - ] def get(self, request, slug, user_id): try: user_data = User.objects.get(pk=user_id) - projects = ( - Project.objects.filter( - workspace__slug=slug, - project_projectmember__member=request.user, - ) - .annotate( - created_issues=Count( - "project_issue", filter=Q(project_issue__created_by_id=user_id) + requesting_workspace_member = WorkspaceMember.objects.get(workspace__slug=slug, member=request.user) + projects = [] + if requesting_workspace_member.role >= 10: + projects = ( + Project.objects.filter( + workspace__slug=slug, + project_projectmember__member=request.user, + ) + .annotate( + created_issues=Count( + "project_issue", filter=Q(project_issue__created_by_id=user_id) + ) + ) + .annotate( + assigned_issues=Count( + "project_issue", + filter=Q(project_issue__assignees__in=[user_id]), + ) + ) + .annotate( + completed_issues=Count( + "project_issue", + filter=Q( + project_issue__completed_at__isnull=False, + project_issue__assignees__in=[user_id], + ), + ) + ) + .annotate( + pending_issues=Count( + "project_issue", + filter=Q( + project_issue__state__group__in=[ + "backlog", + "unstarted", + "started", + ], + project_issue__assignees__in=[user_id], + ), + ) + ) + .values( + "id", + "name", + "identifier", + "emoji", + "icon_prop", + "created_issues", + "assigned_issues", + "completed_issues", + "pending_issues", ) ) - .annotate( - assigned_issues=Count( - "project_issue", - filter=Q(project_issue__assignees__in=[user_id]), - ) - ) - .annotate( - completed_issues=Count( - "project_issue", - filter=Q( - project_issue__completed_at__isnull=False, - project_issue__assignees__in=[user_id], - ), - ) - ) - .annotate( - pending_issues=Count( - "project_issue", - filter=Q( - project_issue__state__group__in=[ - "backlog", - "unstarted", - "started", - ], - project_issue__assignees__in=[user_id], - ), - ) - ) - .values( - "id", - "name", - "identifier", - "emoji", - "icon_prop", - "created_issues", - "assigned_issues", - "completed_issues", - "pending_issues", - ) - ) return Response( { @@ -1283,6 +1285,8 @@ class WorkspaceUserProfileEndpoint(BaseAPIView): }, status=status.HTTP_200_OK, ) + except WorkspaceMember.DoesNotExist: + return Response({"error": "Forbidden"}, status=status.HTTP_403_FORBIDDEN) except Exception as e: capture_exception(e) return Response( @@ -1293,7 +1297,7 @@ class WorkspaceUserProfileEndpoint(BaseAPIView): class WorkspaceUserProfileIssuesEndpoint(BaseAPIView): permission_classes = [ - WorkspaceEntityPermission, + WorkspaceViewerPermission, ] def get(self, request, slug, user_id): @@ -1412,7 +1416,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView): class WorkspaceLabelsEndpoint(BaseAPIView): permission_classes = [ - WorkspaceEntityPermission, + WorkspaceViewerPermission, ] def get(self, request, slug): From d733fb92cd0f7c9678732692796efe3269c249cd Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Mon, 31 Jul 2023 18:11:25 +0530 Subject: [PATCH 10/12] fix: show project drag handle only when uncollapsed (#1727) * fix: show project drag handle only when uncollapsed * chore: fetch profile issues auth * chore: update theme colors * chore: update profile page redirection --- .../project/single-sidebar-project.tsx | 8 +- .../components/workspace/sidebar-dropdown.tsx | 8 +- apps/app/hooks/use-profile-issues.tsx | 9 +- apps/app/layouts/profile-layout.tsx | 30 +-- apps/app/styles/globals.css | 226 +++++++++--------- 5 files changed, 139 insertions(+), 142 deletions(-) diff --git a/apps/app/components/project/single-sidebar-project.tsx b/apps/app/components/project/single-sidebar-project.tsx index f57c45c87..122012365 100644 --- a/apps/app/components/project/single-sidebar-project.tsx +++ b/apps/app/components/project/single-sidebar-project.tsx @@ -143,7 +143,9 @@ export const SingleSidebarProject: React.FC = ({ >