From 605336cc88b0a7b05e7304a29367659220d15b12 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 22 Nov 2023 18:36:45 +0530 Subject: [PATCH 1/4] dev: deactivate user option added --- ...modal.tsx => deactivate-account-modal.tsx} | 85 ++++++----- web/components/account/index.ts | 1 + web/components/issues/delete-issue-modal.tsx | 4 +- .../project/confirm-project-member-remove.tsx | 37 +++-- web/components/project/join-project-modal.tsx | 2 +- web/components/project/member-list-item.tsx | 141 +++++++----------- web/components/project/member-list.tsx | 64 ++------ .../project/send-project-invitation-modal.tsx | 49 +++--- .../confirm-workspace-member-remove.tsx | 18 ++- .../workspace/settings/members-list-item.tsx | 97 +++++++----- .../workspace/settings/members-list.tsx | 8 +- web/components/workspace/sidebar-dropdown.tsx | 6 +- .../[workspaceSlug]/me/profile/index.tsx | 61 ++++++-- .../[workspaceSlug]/settings/members.tsx | 15 +- web/pages/onboarding/index.tsx | 11 +- .../project/project-member.service.ts | 24 --- web/services/project/project.service.ts | 16 -- web/services/user.service.ts | 29 +++- web/store/project/project-members.store.ts | 13 +- web/store/project/project.store.ts | 54 ------- web/store/user.store.ts | 74 ++++++++- web/store/workspace/workspace-member.store.ts | 12 +- 22 files changed, 420 insertions(+), 401 deletions(-) rename web/components/account/{delete-account-modal.tsx => deactivate-account-modal.tsx} (69%) diff --git a/web/components/account/delete-account-modal.tsx b/web/components/account/deactivate-account-modal.tsx similarity index 69% rename from web/components/account/delete-account-modal.tsx rename to web/components/account/deactivate-account-modal.tsx index 844c3837e36..108ff45a728 100644 --- a/web/components/account/delete-account-modal.tsx +++ b/web/components/account/deactivate-account-modal.tsx @@ -1,18 +1,15 @@ -// react import React, { useState } from "react"; -// next import { useRouter } from "next/router"; -// components +import { Dialog, Transition } from "@headlessui/react"; +import { AlertTriangle } from "lucide-react"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +// ui import { Button } from "@plane/ui"; // hooks import useToast from "hooks/use-toast"; // services import { AuthService } from "services/auth.service"; -// headless ui -import { Dialog, Transition } from "@headlessui/react"; -// icons -import { AlertTriangle } from "lucide-react"; -import { UserService } from "services/user.service"; type Props = { isOpen: boolean; @@ -20,19 +17,36 @@ type Props = { }; const authService = new AuthService(); -const userService = new UserService(); -const DeleteAccountModal: React.FC = (props) => { +export const DeactivateAccountModal: React.FC = (props) => { const { isOpen, onClose } = props; - const [isDeleteLoading, setIsDeleteLoading] = useState(false); + + // states + const [switchingAccount, setSwitchingAccount] = useState(false); + const [isDeactivating, setIsDeactivating] = useState(false); + + const { + user: { deactivateAccount }, + } = useMobxStore(); + const router = useRouter(); + const { setToastAlert } = useToast(); - const handleSignOut = async () => { + const handleClose = () => { + setSwitchingAccount(false); + setIsDeactivating(false); + onClose(); + }; + + const handleSwitchAccount = async () => { + setSwitchingAccount(true); + await authService .signOut() .then(() => { router.push("/"); + handleClose(); }) .catch(() => setToastAlert({ @@ -40,33 +54,31 @@ const DeleteAccountModal: React.FC = (props) => { title: "Error!", message: "Failed to sign out. Please try again.", }) - ); + ) + .finally(() => setSwitchingAccount(false)); }; const handleDeleteAccount = async () => { - setIsDeleteLoading(true); - await userService - .deleteAccount() + setIsDeactivating(true); + + await deactivateAccount() .then(() => { setToastAlert({ type: "success", title: "Success!", message: "Account deleted successfully.", }); + handleClose(); router.push("/"); }) .catch((err) => setToastAlert({ type: "error", title: "Error!", - message: err?.data?.error, + message: err?.error, }) - ); - setIsDeleteLoading(false); - }; - - const handleClose = () => { - onClose(); + ) + .finally(() => setIsDeactivating(false)); }; return ( @@ -99,32 +111,29 @@ const DeleteAccountModal: React.FC = (props) => {
-
+
- Not the right workspace? + Deactivate account?
    -
  • Delete this account if you have another and won’t use this account.
  • -
  • Switch to another account if you’d like to come back to this account another time.
  • +
  • Deactivate this account if you have another and won{"'"}t use this account.
  • +
  • Switch to another account if you{"'"}d like to come back to this account another time.
-
- - Switch account - - +
+ +
@@ -134,5 +143,3 @@ const DeleteAccountModal: React.FC = (props) => { ); }; - -export default DeleteAccountModal; diff --git a/web/components/account/index.ts b/web/components/account/index.ts index 36a204c6203..4633d91b3d8 100644 --- a/web/components/account/index.ts +++ b/web/components/account/index.ts @@ -1,3 +1,4 @@ +export * from "./deactivate-account-modal"; export * from "./email-code-form"; export * from "./email-password-form"; export * from "./email-forgot-password-form"; diff --git a/web/components/issues/delete-issue-modal.tsx b/web/components/issues/delete-issue-modal.tsx index 38d0a33d100..fd9e32f6d20 100644 --- a/web/components/issues/delete-issue-modal.tsx +++ b/web/components/issues/delete-issue-modal.tsx @@ -75,9 +75,9 @@ export const DeleteIssueModal: React.FC = observer((props) => {
- +

Delete Issue

diff --git a/web/components/project/confirm-project-member-remove.tsx b/web/components/project/confirm-project-member-remove.tsx index c084407a2c4..dff0eacd948 100644 --- a/web/components/project/confirm-project-member-remove.tsx +++ b/web/components/project/confirm-project-member-remove.tsx @@ -1,23 +1,32 @@ import React, { useState } from "react"; -// headless ui import { Dialog, Transition } from "@headlessui/react"; +import { observer } from "mobx-react-lite"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; // icons import { AlertTriangle } from "lucide-react"; // ui import { Button } from "@plane/ui"; +// types +import { IUserLite } from "types"; type Props = { + data: IUserLite; + onSubmit: () => Promise; isOpen: boolean; onClose: () => void; - handleDelete: () => void; - data?: any; }; -export const ConfirmProjectMemberRemove: React.FC = (props) => { - const { isOpen, onClose, data, handleDelete } = props; +export const ConfirmProjectMemberRemove: React.FC = observer((props) => { + const { data, onSubmit, isOpen, onClose } = props; + // states const [isDeleteLoading, setIsDeleteLoading] = useState(false); + const { + user: { currentUser }, + } = useMobxStore(); + const handleClose = () => { onClose(); setIsDeleteLoading(false); @@ -25,10 +34,14 @@ export const ConfirmProjectMemberRemove: React.FC = (props) => { const handleDeletion = async () => { setIsDeleteLoading(true); - handleDelete(); + + await onSubmit(); + handleClose(); }; + const isCurrentUser = currentUser?.id === data?.id; + return ( @@ -63,7 +76,7 @@ export const ConfirmProjectMemberRemove: React.FC = (props) => {
- Remove {data?.display_name}? + {isCurrentUser ? "Leave project?" : `Remove ${data?.display_name}?`}

@@ -80,7 +93,13 @@ export const ConfirmProjectMemberRemove: React.FC = (props) => { Cancel

@@ -90,4 +109,4 @@ export const ConfirmProjectMemberRemove: React.FC = (props) => { ); -}; +}); diff --git a/web/components/project/join-project-modal.tsx b/web/components/project/join-project-modal.tsx index 67adc881d33..8a4e6304d94 100644 --- a/web/components/project/join-project-modal.tsx +++ b/web/components/project/join-project-modal.tsx @@ -22,7 +22,7 @@ export const JoinProjectModal: React.FC = (props) => { const [isJoiningLoading, setIsJoiningLoading] = useState(false); // store const { - project: { joinProject }, + user: { joinProject }, } = useMobxStore(); // router const router = useRouter(); diff --git a/web/components/project/member-list-item.tsx b/web/components/project/member-list-item.tsx index 308ca8827fe..df60c75ecae 100644 --- a/web/components/project/member-list-item.tsx +++ b/web/components/project/member-list-item.tsx @@ -1,8 +1,8 @@ import { useState } from "react"; import { useRouter } from "next/router"; import Link from "next/link"; -import useSWR, { mutate } from "swr"; import { observer } from "mobx-react-lite"; +// mobx store import { useMobxStore } from "lib/mobx/store-provider"; // hooks import useToast from "hooks/use-toast"; @@ -15,113 +15,95 @@ import { ChevronDown, Dot, XCircle } from "lucide-react"; // constants import { ROLE } from "constants/workspace"; // types -import { TUserProjectRole } from "types"; +import { IProjectMember, TUserProjectRole } from "types"; type Props = { - member: any; + member: IProjectMember; }; export const ProjectMemberListItem: React.FC = observer((props) => { const { member } = props; + // states + const [removeMemberModal, setRemoveMemberModal] = useState(false); // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; - // states - const [selectedRemoveMember, setSelectedRemoveMember] = useState(null); - const [selectedInviteRemoveMember, setSelectedInviteRemoveMember] = useState(null); + // store const { - user: userStore, - projectMember: { - projectMembers, - fetchProjectMembers, - removeMemberFromProject, - updateMember, - deleteProjectInvitation, - }, + user: { currentUser, currentProjectMemberInfo, currentProjectRole, leaveProject }, + projectMember: { removeMemberFromProject, updateMember }, } = useMobxStore(); // hooks const { setToastAlert } = useToast(); - // fetching project members - useSWR( - workspaceSlug && projectId ? `PROJECT_MEMBERS_${projectId.toString().toUpperCase()}` : null, - workspaceSlug && projectId ? () => fetchProjectMembers(workspaceSlug.toString(), projectId.toString()) : null - ); + // derived values - const user = userStore.currentUser; - const { currentProjectMemberInfo, currentProjectRole } = userStore; const isAdmin = currentProjectRole === 20; - const currentUser = projectMembers?.find((item) => item.member.id === user?.id); + const memberDetails = member.member; + + const handleRemove = async () => { + if (!workspaceSlug || !projectId) return; + + if (memberDetails.id === currentUser?.id) { + await leaveProject(workspaceSlug.toString(), projectId.toString()) + .then(() => router.push(`/${workspaceSlug}/projects`)) + .catch((err) => + setToastAlert({ + type: "error", + title: "Error", + message: err?.error || "Something went wrong. Please try again.", + }) + ); + } else + await removeMemberFromProject(workspaceSlug.toString(), projectId.toString(), member.id).catch((err) => + setToastAlert({ + type: "error", + title: "Error", + message: err?.error || "Something went wrong. Please try again.", + }) + ); + }; return ( <> { - setSelectedRemoveMember(null); - setSelectedInviteRemoveMember(null); - }} - data={selectedRemoveMember ?? selectedInviteRemoveMember} - handleDelete={async () => { - if (!workspaceSlug || !projectId) return; - - // if the user is a member - if (selectedRemoveMember) { - await removeMemberFromProject(workspaceSlug.toString(), projectId.toString(), selectedRemoveMember.id); - } - // if the user is an invite - if (selectedInviteRemoveMember) { - await deleteProjectInvitation( - workspaceSlug.toString(), - projectId.toString(), - selectedInviteRemoveMember.id - ); - mutate(`PROJECT_INVITATIONS_${projectId.toString()}`); - } - - setToastAlert({ - type: "success", - message: "Member removed successfully", - title: "Success", - }); - }} + isOpen={removeMemberModal} + onClose={() => setRemoveMemberModal(false)} + data={member.member} + onSubmit={handleRemove} />
- {member.avatar && member.avatar !== "" ? ( - + {memberDetails.avatar && memberDetails.avatar !== "" ? ( + {member.display_name ) : ( - + - {(member.display_name ?? member.email ?? "?")[0]} + {(memberDetails.display_name ?? memberDetails.email ?? "?")[0]} )}
- {member.member ? ( - - - {member.first_name} {member.last_name} - - - ) : ( -

{member.display_name || member.email}

- )} + + + {memberDetails.first_name} {memberDetails.last_name} + +
-

{member.display_name}

+

{memberDetails.display_name}

{isAdmin && ( <> -

{member.email}

+

{memberDetails.email}

)}
@@ -129,23 +111,17 @@ export const ProjectMemberListItem: React.FC = observer((props) => {
- {!member?.status && ( -
-

Pending

-
- )} - {ROLE[member.role as keyof typeof ROLE]} - {member.memberId !== currentProjectMemberInfo?.id && ( + {memberDetails.id !== currentProjectMemberInfo?.id && ( @@ -170,9 +146,9 @@ export const ProjectMemberListItem: React.FC = observer((props) => { }); }} disabled={ - member.memberId === user?.id || + memberDetails.id === currentUser?.id || !member.member || - (currentUser && currentUser.role !== 20 && currentUser.role < member.role) + (currentProjectRole && currentProjectRole !== 20 && currentProjectRole < member.role) } placement="bottom-end" > @@ -188,14 +164,13 @@ export const ProjectMemberListItem: React.FC = observer((props) => { {isAdmin && (
- {!projectMembers || !projectInvitations ? ( + {!projectMembers ? ( @@ -113,7 +69,7 @@ export const ProjectMemberList: React.FC = observer(() => { ) : (
- {members.length > 0 + {projectMembers.length > 0 ? searchedMembers.map((member) => ) : null} {searchedMembers.length === 0 && ( diff --git a/web/components/project/send-project-invitation-modal.tsx b/web/components/project/send-project-invitation-modal.tsx index 6509a4e4f25..cfcc258668f 100644 --- a/web/components/project/send-project-invitation-modal.tsx +++ b/web/components/project/send-project-invitation-modal.tsx @@ -1,7 +1,6 @@ import React, { useEffect } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; -import useSWR from "swr"; import { useForm, Controller, useFieldArray } from "react-hook-form"; import { Dialog, Transition } from "@headlessui/react"; import { ChevronDown, Plus, X } from "lucide-react"; @@ -11,22 +10,19 @@ import { useMobxStore } from "lib/mobx/store-provider"; import { Avatar, Button, CustomSelect, CustomSearchSelect } from "@plane/ui"; // services import { ProjectMemberService } from "services/project"; -import { WorkspaceService } from "services/workspace.service"; // hooks import useToast from "hooks/use-toast"; +// helpers +import { trackEvent } from "helpers/event-tracker.helper"; // types -import { IUser, TUserProjectRole } from "types"; -// fetch-keys -import { WORKSPACE_MEMBERS } from "constants/fetch-keys"; +import { IProjectMember, TUserProjectRole } from "types"; // constants import { ROLE } from "constants/workspace"; -import { trackEvent } from "helpers/event-tracker.helper"; type Props = { isOpen: boolean; - setIsOpen: React.Dispatch>; - members: any[]; - user: IUser | undefined; + members: IProjectMember[]; + onClose: () => void; onSuccess: () => void; }; @@ -50,23 +46,19 @@ const defaultValues: FormValues = { // services const projectMemberService = new ProjectMemberService(); -const workspaceService = new WorkspaceService(); export const SendProjectInvitationModal: React.FC = observer((props) => { - const { isOpen, setIsOpen, members, onSuccess } = props; + const { isOpen, members, onClose, onSuccess } = props; const router = useRouter(); const { workspaceSlug, projectId } = router.query; const { setToastAlert } = useToast(); - const { user: userStore } = useMobxStore(); - const userRole = userStore.currentProjectRole; - - const { data: people } = useSWR( - workspaceSlug ? WORKSPACE_MEMBERS(workspaceSlug as string) : null, - workspaceSlug ? () => workspaceService.fetchWorkspaceMembers(workspaceSlug as string) : null - ); + const { + user: { currentProjectRole }, + workspaceMember: { workspaceMembers }, + } = useMobxStore(); const { formState: { errors, isSubmitting }, @@ -80,8 +72,8 @@ export const SendProjectInvitationModal: React.FC = observer((props) => { name: "members", }); - const uninvitedPeople = people?.filter((person) => { - const isInvited = members?.find((member) => member.memberId === person.member.id); + const uninvitedPeople = workspaceMembers?.filter((person) => { + const isInvited = members?.find((member) => member.member.id === person.member.id); return !isInvited; }); @@ -93,17 +85,15 @@ export const SendProjectInvitationModal: React.FC = observer((props) => { await projectMemberService .bulkAddMembersToProject(workspaceSlug.toString(), projectId.toString(), payload) - .then((res) => { - setIsOpen(false); - trackEvent( - 'PROJECT_MEMBER_INVITE', - ) + .then(() => { + onSuccess(); + onClose(); + trackEvent("PROJECT_MEMBER_INVITE"); setToastAlert({ title: "Success", type: "success", message: "Member added successfully", }); - onSuccess(); }) .catch((error) => { console.log(error); @@ -114,7 +104,8 @@ export const SendProjectInvitationModal: React.FC = observer((props) => { }; const handleClose = () => { - setIsOpen(false); + onClose(); + const timeout = setTimeout(() => { reset(defaultValues); clearTimeout(timeout); @@ -195,7 +186,7 @@ export const SendProjectInvitationModal: React.FC = observer((props) => { name={`members.${index}.member_id`} rules={{ required: "Please select a member" }} render={({ field: { value, onChange } }) => { - const selectedMember = people?.find((p) => p.member.id === value)?.member; + const selectedMember = workspaceMembers?.find((p) => p.member.id === value)?.member; return ( = observer((props) => { width="w-full" > {Object.entries(ROLE).map(([key, label]) => { - if (parseInt(key) > (userRole ?? 5)) return null; + if (parseInt(key) > (currentProjectRole ?? 5)) return null; return ( diff --git a/web/components/workspace/confirm-workspace-member-remove.tsx b/web/components/workspace/confirm-workspace-member-remove.tsx index 2c1387996c3..5f9d87af4f5 100644 --- a/web/components/workspace/confirm-workspace-member-remove.tsx +++ b/web/components/workspace/confirm-workspace-member-remove.tsx @@ -8,14 +8,14 @@ import { useMobxStore } from "lib/mobx/store-provider"; import { Button } from "@plane/ui"; type Props = { + data?: any; isOpen: boolean; onClose: () => void; onSubmit: () => Promise; - data?: any; }; export const ConfirmWorkspaceMemberRemove: React.FC = observer((props) => { - const { isOpen, onClose, data, onSubmit } = props; + const { data, isOpen, onClose, onSubmit } = props; const [isRemoving, setIsRemoving] = useState(false); @@ -62,8 +62,8 @@ export const ConfirmWorkspaceMemberRemove: React.FC = observer((props) => leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - -
+ +
-
+
diff --git a/web/components/workspace/settings/members-list-item.tsx b/web/components/workspace/settings/members-list-item.tsx index 7c00f4f12e4..5384977f824 100644 --- a/web/components/workspace/settings/members-list-item.tsx +++ b/web/components/workspace/settings/members-list-item.tsx @@ -12,9 +12,10 @@ import { ConfirmWorkspaceMemberRemove } from "components/workspace"; import { CustomSelect, Tooltip } from "@plane/ui"; // icons import { ChevronDown, Dot, XCircle } from "lucide-react"; +// types +import { TUserWorkspaceRole } from "types"; // constants import { ROLE } from "constants/workspace"; -import { TUserWorkspaceRole } from "types"; type Props = { member: { @@ -40,7 +41,7 @@ export const WorkspaceMembersListItem: FC = (props) => { // store const { workspaceMember: { removeMember, updateMember, deleteWorkspaceInvitation }, - user: { currentWorkspaceMemberInfo, currentWorkspaceRole, currentUser, currentUserSettings }, + user: { currentWorkspaceMemberInfo, currentWorkspaceRole, currentUser, currentUserSettings, leaveWorkspace }, } = useMobxStore(); const isAdmin = currentWorkspaceRole === 20; // states @@ -48,49 +49,69 @@ export const WorkspaceMembersListItem: FC = (props) => { // hooks const { setToastAlert } = useToast(); + const handleLeaveWorkspace = async () => { + if (!workspaceSlug || !currentUserSettings) return; + + await leaveWorkspace(workspaceSlug.toString()) + .then(() => { + if (currentUserSettings.workspace?.invites > 0) router.push("/invitations"); + else router.push("/create-workspace"); + }) + .catch((err) => + setToastAlert({ + type: "error", + title: "Error", + message: err?.error || "Something went wrong. Please try again.", + }) + ); + }; + const handleRemoveMember = async () => { if (!workspaceSlug) return; - if (member.member) - await removeMember(workspaceSlug.toString(), member.id) - .then(() => { - const memberId = member.memberId; + await removeMember(workspaceSlug.toString(), member.id).catch((err) => + setToastAlert({ + type: "error", + title: "Error", + message: err?.error || "Something went wrong. Please try again.", + }) + ); + }; + + const handleRemoveInvitation = async () => { + if (!workspaceSlug) return; - if (memberId === currentUser?.id && currentUserSettings) { - if (currentUserSettings.workspace?.invites > 0) router.push("/invitations"); - else router.push("/create-workspace"); - } + await deleteWorkspaceInvitation(workspaceSlug.toString(), member.id) + .then(() => + setToastAlert({ + type: "success", + title: "Success", + message: "Invitation removed successfully.", }) - .catch((err) => { - setToastAlert({ - type: "error", - title: "Error", - message: err?.error || "Something went wrong", - }); - }); - else - await deleteWorkspaceInvitation(workspaceSlug.toString(), member.id) - .then(() => { - setToastAlert({ - type: "success", - title: "Success", - message: "Member removed successfully", - }); + ) + .catch((err) => + setToastAlert({ + type: "error", + title: "Error", + message: err?.error || "Something went wrong. Please try again.", }) - .catch((err) => { - setToastAlert({ - type: "error", - title: "Error", - message: err?.error || "Something went wrong", - }); + ) + .finally(() => + mutate(`WORKSPACE_INVITATIONS_${workspaceSlug.toString()}`, (prevData: any) => { + if (!prevData) return prevData; + + return prevData.filter((item: any) => item.id !== member.id); }) - .finally(() => { - mutate(`WORKSPACE_INVITATIONS_${workspaceSlug.toString()}`, (prevData: any) => { - if (!prevData) return prevData; + ); + }; + + const handleRemove = async () => { + if (member.member) { + const memberId = member.memberId; - return prevData.filter((item: any) => item.id !== member.id); - }); - }); + if (memberId === currentUser?.id) await handleLeaveWorkspace(); + else await handleRemoveMember(); + } else await handleRemoveInvitation(); }; if (!currentWorkspaceMemberInfo) return null; @@ -101,7 +122,7 @@ export const WorkspaceMembersListItem: FC = (props) => { isOpen={removeMemberModal} onClose={() => setRemoveMemberModal(false)} data={member} - onSubmit={handleRemoveMember} + onSubmit={handleRemove} />
diff --git a/web/components/workspace/settings/members-list.tsx b/web/components/workspace/settings/members-list.tsx index 2244c5cadb7..854c3b06974 100644 --- a/web/components/workspace/settings/members-list.tsx +++ b/web/components/workspace/settings/members-list.tsx @@ -33,11 +33,7 @@ export const WorkspaceMembersList: FC<{ searchQuery: string }> = observer(({ sea const displayName = member.display_name.toLowerCase(); const fullName = `${member.first_name} ${member.last_name}`.toLowerCase(); - return ( - displayName.includes(searchQuery.toLowerCase()) || - fullName.includes(searchQuery.toLowerCase()) || - email?.includes(searchQuery.toLowerCase()) - ); + return `${email}${displayName}${fullName}`.includes(searchQuery.toLowerCase()); }); if ( @@ -61,7 +57,7 @@ export const WorkspaceMembersList: FC<{ searchQuery: string }> = observer(({ sea ? searchedMembers?.map((member) => ) : null} {searchedMembers?.length === 0 && ( -

No matching member

+

No matching members

)}
); diff --git a/web/components/workspace/sidebar-dropdown.tsx b/web/components/workspace/sidebar-dropdown.tsx index 09453ac7021..73214c02a76 100644 --- a/web/components/workspace/sidebar-dropdown.tsx +++ b/web/components/workspace/sidebar-dropdown.tsx @@ -94,7 +94,7 @@ export const WorkspaceSidebarDropdown = observer(() => { {({ open }) => ( <> - +
{ {!sidebarCollapsed && ( @@ -251,7 +251,7 @@ export const WorkspaceSidebarDropdown = observer(() => { {!sidebarCollapsed && ( - { const [isRemoving, setIsRemoving] = useState(false); const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false); + const [deactivateAccountModal, setDeactivateAccountModal] = useState(false); // router const router = useRouter(); const { workspaceSlug } = router.query; @@ -143,6 +146,13 @@ const ProfilePage: NextPageWithLayout = () => { content: timeZone.label, })); + if (!myProfile) + return ( +
+ +
+ ); + return ( <> { value={watch("avatar") !== "" ? watch("avatar") : undefined} userImage /> - {myProfile ? ( -
-
+ setDeactivateAccountModal(false)} /> +
+ +
{
- ) : ( -
- -
- )} + + {({ open }) => ( + <> + + Deactivate Account + {/* */} + + + + + +
+ + The danger zone of the profile page is a critical area that requires careful consideration and + attention. When deactivating an account, all of the data and resources within that account will be + permanently removed and cannot be recovered. + +
+ +
+
+
+
+ + )} +
+
); }; diff --git a/web/pages/[workspaceSlug]/settings/members.tsx b/web/pages/[workspaceSlug]/settings/members.tsx index 93aba3db180..56a9baab31d 100644 --- a/web/pages/[workspaceSlug]/settings/members.tsx +++ b/web/pages/[workspaceSlug]/settings/members.tsx @@ -14,10 +14,11 @@ import { SendWorkspaceInvitationModal, WorkspaceMembersList } from "components/w import { Button } from "@plane/ui"; // icons import { Search } from "lucide-react"; +// helpers +import { trackEvent } from "helpers/event-tracker.helper"; // types import { NextPageWithLayout } from "types/app"; import { IWorkspaceBulkInviteFormData } from "types"; -import { trackEvent } from "helpers/event-tracker.helper"; const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => { const router = useRouter(); @@ -36,7 +37,7 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => { if (!workspaceSlug) return; return inviteMembersToWorkspace(workspaceSlug.toString(), data) - .then(async (res) => { + .then(async () => { setInviteModal(false); trackEvent("WORKSPACE_USER_INVITE"); setToastAlert({ @@ -67,14 +68,14 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => {

Members

-
- +
+ setSearchQuery(e.target.value)} + autoFocus />
@@ -96,7 +99,7 @@ export const WorkspaceHelpSection: React.FC = observe className={`hidden md:grid place-items-center rounded-md p-1.5 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-90 outline-none ${ isCollapsed ? "w-full" : "" }`} - onClick={() => themeStore.toggleSidebar()} + onClick={() => toggleSidebar()} > diff --git a/web/components/workspace/sidebar-dropdown.tsx b/web/components/workspace/sidebar-dropdown.tsx index 73214c02a76..4325637fcdc 100644 --- a/web/components/workspace/sidebar-dropdown.tsx +++ b/web/components/workspace/sidebar-dropdown.tsx @@ -40,7 +40,7 @@ const profileLinks = (workspaceSlug: string, userId: string) => [ { name: "Settings", icon: Settings, - link: `/${workspaceSlug}/me/profile`, + link: "/me/profile", }, ]; diff --git a/web/layouts/admin-layout/sidebar.tsx b/web/layouts/admin-layout/sidebar.tsx index d3a9ecfa12e..ce6a302dd9b 100644 --- a/web/layouts/admin-layout/sidebar.tsx +++ b/web/layouts/admin-layout/sidebar.tsx @@ -13,7 +13,6 @@ export const InstanceAdminSidebar: FC = observer(() => { return (
= observer(() => { return (
= observer((props) => { + const { children, header } = props; + + const { + theme: { sidebarCollapsed }, + workspace: { workspaces }, + } = useMobxStore(); + + return ( + <> + + +
+
+
+
+ {SIDEBAR_LINKS.map((link) => ( + + + +
+ {} + {!sidebarCollapsed && link.name} +
+
+
+ + ))} +
+ {workspaces && workspaces.length > 0 && ( +
+ {!sidebarCollapsed && ( +
+ Your workspaces +
+ )} + +
+ )} +
+
+
+ {header} +
+
+ +
+ {children} +
+
+
+
+ + ); +}); diff --git a/web/layouts/settings-layout/profile/sidebar.tsx b/web/layouts/settings-layout/profile/sidebar.tsx new file mode 100644 index 00000000000..1f87a038224 --- /dev/null +++ b/web/layouts/settings-layout/profile/sidebar.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import { useRouter } from "next/router"; +import Link from "next/link"; + +const PROFILE_LINKS: Array<{ + label: string; + href: string; +}> = [ + { + label: "Profile", + href: `/me/profile`, + }, + { + label: "Activity", + href: `/me/profile/activity`, + }, + { + label: "Preferences", + href: `/me/profile/preferences`, + }, +]; + +export const ProfileSettingsSidebar = () => { + const router = useRouter(); + + return ( +
+ My Account +
+ {PROFILE_LINKS.map((link) => ( + + +
+ {link.label} +
+
+ + ))} +
+
+ ); +}; diff --git a/web/layouts/settings-layout/workspace/index.tsx b/web/layouts/settings-layout/project/index.ts similarity index 100% rename from web/layouts/settings-layout/workspace/index.tsx rename to web/layouts/settings-layout/project/index.ts diff --git a/web/layouts/settings-layout/workspace/index.ts b/web/layouts/settings-layout/workspace/index.ts new file mode 100644 index 00000000000..c4bfd4db38a --- /dev/null +++ b/web/layouts/settings-layout/workspace/index.ts @@ -0,0 +1,2 @@ +export * from "./layout"; +export * from "./sidebar"; diff --git a/web/layouts/settings-layout/workspace/sidebar.tsx b/web/layouts/settings-layout/workspace/sidebar.tsx index 831789bbc55..19e9aabd888 100644 --- a/web/layouts/settings-layout/workspace/sidebar.tsx +++ b/web/layouts/settings-layout/workspace/sidebar.tsx @@ -64,31 +64,6 @@ export const WorkspaceSettingsSidebar = () => { }, ]; - const profileLinks: Array<{ - label: string; - href: string; - }> = [ - { - label: "Profile", - href: `/${workspaceSlug}/me/profile`, - }, - { - label: "Activity", - href: `/${workspaceSlug}/me/profile/activity`, - }, - { - label: "Preferences", - href: `/${workspaceSlug}/me/profile/preferences`, - }, - ]; - - function highlightSetting(label: string, link: string): boolean { - if (router.asPath.startsWith(link) && (label === "Imports" || label === "Api tokens")) { - return true; - } - return link === router.asPath; - } - return (
@@ -114,26 +89,6 @@ export const WorkspaceSettingsSidebar = () => { )}
-
- My Account -
- {profileLinks.map((link) => ( - - -
- {link.label} -
-
- - ))} -
-
); }; diff --git a/web/pages/[workspaceSlug]/profile/[userId]/index.tsx b/web/pages/[workspaceSlug]/profile/[userId]/index.tsx index f345f88674f..9f55f1cd90b 100644 --- a/web/pages/[workspaceSlug]/profile/[userId]/index.tsx +++ b/web/pages/[workspaceSlug]/profile/[userId]/index.tsx @@ -56,7 +56,7 @@ const ProfileOverviewPage: NextPageWithLayout = () => { ProfileOverviewPage.getLayout = function getLayout(page: ReactElement) { return ( - }> + }> {page} ); diff --git a/web/pages/[workspaceSlug]/me/profile/activity.tsx b/web/pages/me/profile/activity.tsx similarity index 96% rename from web/pages/[workspaceSlug]/me/profile/activity.tsx rename to web/pages/me/profile/activity.tsx index ac7d0f6975d..57832cdf394 100644 --- a/web/pages/[workspaceSlug]/me/profile/activity.tsx +++ b/web/pages/me/profile/activity.tsx @@ -5,12 +5,11 @@ import Link from "next/link"; // services import { UserService } from "services/user.service"; // layouts -import { WorkspaceSettingLayout } from "layouts/settings-layout"; -import { AppLayout } from "layouts/app-layout"; +import { ProfileSettingsLayout } from "layouts/settings-layout"; // components import { ActivityIcon, ActivityMessage } from "components/core"; import { RichReadOnlyEditor } from "@plane/rich-text-editor"; -import { WorkspaceSettingHeader } from "components/headers"; +import { ProfileSettingsHeader } from "components/headers"; // icons import { History, MessageSquare } from "lucide-react"; // ui @@ -198,9 +197,7 @@ const ProfileActivityPage: NextPageWithLayout = () => { ProfileActivityPage.getLayout = function getLayout(page: ReactElement) { return ( - }> - {page} - + }>{page} ); }; diff --git a/web/pages/[workspaceSlug]/me/profile/index.tsx b/web/pages/me/profile/index.tsx similarity index 92% rename from web/pages/[workspaceSlug]/me/profile/index.tsx rename to web/pages/me/profile/index.tsx index 2545dcb57f2..9dcc13913d6 100644 --- a/web/pages/[workspaceSlug]/me/profile/index.tsx +++ b/web/pages/me/profile/index.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useState, ReactElement } from "react"; -import { useRouter } from "next/router"; import Link from "next/link"; import { Controller, useForm } from "react-hook-form"; import { Disclosure, Transition } from "@headlessui/react"; @@ -10,16 +9,15 @@ import { UserService } from "services/user.service"; import useUserAuth from "hooks/use-user-auth"; import useToast from "hooks/use-toast"; // layouts -import { AppLayout } from "layouts/app-layout"; -import { WorkspaceSettingLayout } from "layouts/settings-layout"; +import { ProfileSettingsLayout } from "layouts/settings-layout"; // components import { ImagePickerPopover, ImageUploadModal } from "components/core"; -import { WorkspaceSettingHeader } from "components/headers"; +import { ProfileSettingsHeader } from "components/headers"; import { DeactivateAccountModal } from "components/account"; // ui import { Button, CustomSelect, CustomSearchSelect, Input, Spinner } from "@plane/ui"; // icons -import { ChevronDown, User2, UserCircle2 } from "lucide-react"; +import { ChevronDown, ExternalLink, User2 } from "lucide-react"; // types import type { IUser } from "types"; import type { NextPageWithLayout } from "types/app"; @@ -32,6 +30,7 @@ const defaultValues: Partial = { cover_image: "", first_name: "", last_name: "", + display_name: "", email: "", role: "Product / Project Manager", user_timezone: "Asia/Kolkata", @@ -40,13 +39,11 @@ const defaultValues: Partial = { const fileService = new FileService(); const userService = new UserService(); -const ProfilePage: NextPageWithLayout = () => { +const ProfileSettingsPage: NextPageWithLayout = () => { const [isRemoving, setIsRemoving] = useState(false); const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false); const [deactivateAccountModal, setDeactivateAccountModal] = useState(false); - // router - const router = useRouter(); - const { workspaceSlug } = router.query; + // form info const { handleSubmit, @@ -205,14 +202,12 @@ const ProfilePage: NextPageWithLayout = () => { ( + render={({ field: { value, onChange } }) => ( { - setValue("cover_image", imageUrl); - }} + onChange={(imageUrl) => onChange(imageUrl)} control={control} - value={watch("cover_image") ?? "https://images.unsplash.com/photo-1506383796573-caf02b4a79ab"} + value={value ?? "https://images.unsplash.com/photo-1506383796573-caf02b4a79ab"} /> )} /> @@ -227,14 +222,12 @@ const ProfilePage: NextPageWithLayout = () => { {watch("email")}
- - - - - - View Profile + {/* + + + Activity Overview - + */}
@@ -439,12 +432,8 @@ const ProfilePage: NextPageWithLayout = () => { ); }; -ProfilePage.getLayout = function getLayout(page: ReactElement) { - return ( - }> - {page} - - ); +ProfileSettingsPage.getLayout = function getLayout(page: ReactElement) { + return }>{page}; }; -export default ProfilePage; +export default ProfileSettingsPage; diff --git a/web/pages/[workspaceSlug]/me/profile/preferences.tsx b/web/pages/me/profile/preferences.tsx similarity index 88% rename from web/pages/[workspaceSlug]/me/profile/preferences.tsx rename to web/pages/me/profile/preferences.tsx index d08b9da3ca3..35ccb30f4dd 100644 --- a/web/pages/[workspaceSlug]/me/profile/preferences.tsx +++ b/web/pages/me/profile/preferences.tsx @@ -5,11 +5,10 @@ import { useTheme } from "next-themes"; import { useMobxStore } from "lib/mobx/store-provider"; import useToast from "hooks/use-toast"; // layouts -import { AppLayout } from "layouts/app-layout"; -import { WorkspaceSettingLayout } from "layouts/settings-layout"; +import { ProfileSettingsLayout } from "layouts/settings-layout"; // components import { CustomThemeSelector, ThemeSwitch } from "components/core"; -import { WorkspaceSettingHeader } from "components/headers"; +import { ProfileSettingsHeader } from "components/headers"; // ui import { Spinner } from "@plane/ui"; // constants @@ -77,9 +76,9 @@ const ProfilePreferencesPage: NextPageWithLayout = observer(() => { ProfilePreferencesPage.getLayout = function getLayout(page: ReactElement) { return ( - }> - {page} - + }> + {page} + ); }; From 177638af8647efcd2f479e10ef9e392117030bf6 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Thu, 23 Nov 2023 12:11:47 +0530 Subject: [PATCH 3/4] fix: build errors --- web/components/auth-screens/project/join-project.tsx | 6 ++++-- web/components/headers/profile-settings.tsx | 4 ++-- web/components/project/leave-project-modal.tsx | 7 ++++--- web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx | 2 +- web/pages/[workspaceSlug]/profile/[userId]/created.tsx | 2 +- web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx | 2 +- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/web/components/auth-screens/project/join-project.tsx b/web/components/auth-screens/project/join-project.tsx index 5713e2ad85e..d5841e43b3a 100644 --- a/web/components/auth-screens/project/join-project.tsx +++ b/web/components/auth-screens/project/join-project.tsx @@ -13,7 +13,9 @@ import JoinProjectImg from "public/auth/project-not-authorized.svg"; export const JoinProject: React.FC = () => { const [isJoiningProject, setIsJoiningProject] = useState(false); - const { project: projectStore } = useMobxStore(); + const { + user: { joinProject }, + } = useMobxStore(); const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -23,7 +25,7 @@ export const JoinProject: React.FC = () => { setIsJoiningProject(true); - projectStore.joinProject(workspaceSlug.toString(), [projectId.toString()]).finally(() => { + joinProject(workspaceSlug.toString(), [projectId.toString()]).finally(() => { setIsJoiningProject(false); }); }; diff --git a/web/components/headers/profile-settings.tsx b/web/components/headers/profile-settings.tsx index d0fe8d440bb..75baa9728d5 100644 --- a/web/components/headers/profile-settings.tsx +++ b/web/components/headers/profile-settings.tsx @@ -3,11 +3,11 @@ import { FC } from "react"; import { Breadcrumbs } from "@plane/ui"; import { Settings } from "lucide-react"; -export interface IWorkspaceSettingHeader { +interface IProfileSettingHeader { title: string; } -export const ProfileSettingsHeader: FC = (props) => { +export const ProfileSettingsHeader: FC = (props) => { const { title } = props; return ( diff --git a/web/components/project/leave-project-modal.tsx b/web/components/project/leave-project-modal.tsx index 421234ab6bc..503c1df740e 100644 --- a/web/components/project/leave-project-modal.tsx +++ b/web/components/project/leave-project-modal.tsx @@ -35,7 +35,9 @@ export const LeaveProjectModal: FC = observer((props) => { const router = useRouter(); const { workspaceSlug } = router.query; // store - const { project: projectStore } = useMobxStore(); + const { + user: { leaveProject }, + } = useMobxStore(); // toast const { setToastAlert } = useToast(); @@ -57,8 +59,7 @@ export const LeaveProjectModal: FC = observer((props) => { if (data) { if (data.projectName === project?.name) { if (data.confirmLeave === "Leave Project") { - return projectStore - .leaveProject(workspaceSlug.toString(), project.id) + return leaveProject(workspaceSlug.toString(), project.id) .then(() => { handleClose(); router.push(`/${workspaceSlug}/projects`); diff --git a/web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx b/web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx index 6d0c6e0d640..738aaecf0df 100644 --- a/web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx +++ b/web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx @@ -65,7 +65,7 @@ const ProfileAssignedIssuesPage: NextPageWithLayout = observer(() => { ProfileAssignedIssuesPage.getLayout = function getLayout(page: ReactElement) { return ( - }> + }> {page} ); diff --git a/web/pages/[workspaceSlug]/profile/[userId]/created.tsx b/web/pages/[workspaceSlug]/profile/[userId]/created.tsx index 7bf21edd992..c8418043e7f 100644 --- a/web/pages/[workspaceSlug]/profile/[userId]/created.tsx +++ b/web/pages/[workspaceSlug]/profile/[userId]/created.tsx @@ -61,7 +61,7 @@ const ProfileCreatedIssuesPage: NextPageWithLayout = () => { ProfileCreatedIssuesPage.getLayout = function getLayout(page: ReactElement) { return ( - }> + }> {page} ); diff --git a/web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx b/web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx index 8900fb3fd61..6862b00b4c4 100644 --- a/web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx +++ b/web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx @@ -61,7 +61,7 @@ const ProfileSubscribedIssuesPage: NextPageWithLayout = () => { ProfileSubscribedIssuesPage.getLayout = function getLayout(page: ReactElement) { return ( - }> + }> {page} ); From ffa48260d5b4958cfff79669e73d6580fa5fa876 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Thu, 23 Nov 2023 13:34:42 +0530 Subject: [PATCH 4/4] fix: user profile activity --- web/components/core/activity.tsx | 97 ++++++------ web/components/headers/profile-settings.tsx | 2 +- web/components/workspace/help-section.tsx | 5 +- web/layouts/settings-layout/profile/index.ts | 1 + .../settings-layout/profile/layout.tsx | 109 +------------- .../profile/settings-sidebar.tsx | 48 ++++++ .../settings-layout/profile/sidebar.tsx | 141 +++++++++++++----- .../index.ts | 0 .../layout.tsx | 0 .../profile/[userId]/assigned.tsx | 2 +- .../profile/[userId]/created.tsx | 2 +- .../profile/[userId]/index.tsx | 2 +- .../profile/[userId]/subscribed.tsx | 2 +- web/pages/me/profile/activity.tsx | 9 +- web/pages/me/profile/index.tsx | 5 +- web/pages/me/profile/preferences.tsx | 6 +- web/services/user.service.ts | 4 +- web/types/issues.d.ts | 1 + web/types/users.d.ts | 2 +- 19 files changed, 227 insertions(+), 211 deletions(-) create mode 100644 web/layouts/settings-layout/profile/settings-sidebar.tsx rename web/layouts/{profile-layout => user-profile-layout}/index.ts (100%) rename web/layouts/{profile-layout => user-profile-layout}/layout.tsx (100%) diff --git a/web/components/core/activity.tsx b/web/components/core/activity.tsx index 6bddae9404d..fb2df649637 100644 --- a/web/components/core/activity.tsx +++ b/web/components/core/activity.tsx @@ -1,11 +1,9 @@ import { useRouter } from "next/router"; - -import useSWR from "swr"; - +import { observer } from "mobx-react-lite"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; // hook import useEstimateOption from "hooks/use-estimate-option"; -// services -import { IssueLabelService } from "services/issue"; // icons import { Tooltip, BlockedIcon, BlockerIcon, RelatedIcon, LayersIcon, DiceIcon } from "@plane/ui"; import { @@ -29,11 +27,7 @@ import { renderShortDateWithYearFormat } from "helpers/date-time.helper"; import { capitalizeFirstLetter } from "helpers/string.helper"; // types import { IIssueActivity } from "types"; -// fetch-keys -import { WORKSPACE_LABELS } from "constants/fetch-keys"; - -// services -const issueLabelService = new IssueLabelService(); +import { useEffect } from "react"; const IssueLink = ({ activity }: { activity: IIssueActivity }) => { const router = useRouter(); @@ -44,7 +38,11 @@ const IssueLink = ({ activity }: { activity: IIssueActivity }) => { { return ( { ); }; -const LabelPill = ({ labelId }: { labelId: string }) => { - const router = useRouter(); - const { workspaceSlug } = router.query; +const LabelPill = observer(({ labelId, workspaceSlug }: { labelId: string; workspaceSlug: string }) => { + const { + workspace: { labels, fetchWorkspaceLabels }, + } = useMobxStore(); - const { data: labels } = useSWR( - workspaceSlug ? WORKSPACE_LABELS(workspaceSlug.toString()) : null, - workspaceSlug ? () => issueLabelService.getWorkspaceIssueLabels(workspaceSlug.toString()) : null - ); + const workspaceLabels = labels[workspaceSlug]; + + useEffect(() => { + if (!workspaceLabels) fetchWorkspaceLabels(workspaceSlug); + }, [fetchWorkspaceLabels, workspaceLabels, workspaceSlug]); return ( l.id === labelId)?.color ?? "#000000", + backgroundColor: workspaceLabels?.find((l) => l.id === labelId)?.color ?? "#000000", }} aria-hidden="true" /> ); -}; +}); const EstimatePoint = ({ point }: { point: string }) => { const { estimateValue, isEstimateActive } = useEstimateOption(Number(point)); @@ -243,24 +245,6 @@ const activityDetails: { }, icon: , }, - relates_to: { - message: (activity) => { - if (activity.old_value === "") - return ( - <> - marked that this issue relates to{" "} - {activity.new_value}. - - ); - else - return ( - <> - removed the relation from {activity.old_value}. - - ); - }, - icon: , - }, cycles: { message: (activity, showIssue, workspaceSlug) => { if (activity.verb === "created") @@ -365,13 +349,13 @@ const activityDetails: { icon:
- +
{header}
@@ -133,4 +32,4 @@ export const ProfileSettingsLayout: FC = observer((props ); -}); +}; diff --git a/web/layouts/settings-layout/profile/settings-sidebar.tsx b/web/layouts/settings-layout/profile/settings-sidebar.tsx new file mode 100644 index 00000000000..1f87a038224 --- /dev/null +++ b/web/layouts/settings-layout/profile/settings-sidebar.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import { useRouter } from "next/router"; +import Link from "next/link"; + +const PROFILE_LINKS: Array<{ + label: string; + href: string; +}> = [ + { + label: "Profile", + href: `/me/profile`, + }, + { + label: "Activity", + href: `/me/profile/activity`, + }, + { + label: "Preferences", + href: `/me/profile/preferences`, + }, +]; + +export const ProfileSettingsSidebar = () => { + const router = useRouter(); + + return ( +
+ My Account +
+ {PROFILE_LINKS.map((link) => ( + + +
+ {link.label} +
+
+ + ))} +
+
+ ); +}; diff --git a/web/layouts/settings-layout/profile/sidebar.tsx b/web/layouts/settings-layout/profile/sidebar.tsx index 1f87a038224..325fd703a09 100644 --- a/web/layouts/settings-layout/profile/sidebar.tsx +++ b/web/layouts/settings-layout/profile/sidebar.tsx @@ -1,48 +1,119 @@ -import React from "react"; -import { useRouter } from "next/router"; import Link from "next/link"; +import { observer } from "mobx-react-lite"; +import { MoveLeft, Plus, UserPlus } from "lucide-react"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +// ui +import { Tooltip } from "@plane/ui"; -const PROFILE_LINKS: Array<{ - label: string; - href: string; -}> = [ +const SIDEBAR_LINKS = [ { - label: "Profile", - href: `/me/profile`, + key: "create-workspace", + Icon: Plus, + name: "Create workspace", + href: "/create-workspace", }, { - label: "Activity", - href: `/me/profile/activity`, - }, - { - label: "Preferences", - href: `/me/profile/preferences`, + key: "invitations", + Icon: UserPlus, + name: "Invitations", + href: "/invitations", }, ]; -export const ProfileSettingsSidebar = () => { - const router = useRouter(); +export const ProfileLayoutSidebar = observer(() => { + const { + theme: { sidebarCollapsed, toggleSidebar }, + workspace: { workspaces }, + } = useMobxStore(); return ( -
- My Account -
- {PROFILE_LINKS.map((link) => ( - - -
- {link.label} -
-
- - ))} +
+
+
+ {SIDEBAR_LINKS.map((link) => ( + + + +
+ {} + {!sidebarCollapsed && link.name} +
+
+
+ + ))} +
+ {workspaces && workspaces.length > 0 && ( + + )} +
+ + +
); -}; +}); diff --git a/web/layouts/profile-layout/index.ts b/web/layouts/user-profile-layout/index.ts similarity index 100% rename from web/layouts/profile-layout/index.ts rename to web/layouts/user-profile-layout/index.ts diff --git a/web/layouts/profile-layout/layout.tsx b/web/layouts/user-profile-layout/layout.tsx similarity index 100% rename from web/layouts/profile-layout/layout.tsx rename to web/layouts/user-profile-layout/layout.tsx diff --git a/web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx b/web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx index 738aaecf0df..2d1bb55348e 100644 --- a/web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx +++ b/web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx @@ -4,7 +4,7 @@ import useSWR from "swr"; import { observer } from "mobx-react-lite"; // layouts import { AppLayout } from "layouts/app-layout"; -import { ProfileAuthWrapper } from "layouts/profile-layout"; +import { ProfileAuthWrapper } from "layouts/user-profile-layout"; // components import { UserProfileHeader } from "components/headers"; import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root"; diff --git a/web/pages/[workspaceSlug]/profile/[userId]/created.tsx b/web/pages/[workspaceSlug]/profile/[userId]/created.tsx index c8418043e7f..a6bbd8521d9 100644 --- a/web/pages/[workspaceSlug]/profile/[userId]/created.tsx +++ b/web/pages/[workspaceSlug]/profile/[userId]/created.tsx @@ -6,7 +6,7 @@ import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; // layouts import { AppLayout } from "layouts/app-layout"; -import { ProfileAuthWrapper } from "layouts/profile-layout"; +import { ProfileAuthWrapper } from "layouts/user-profile-layout"; // components import { UserProfileHeader } from "components/headers"; import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root"; diff --git a/web/pages/[workspaceSlug]/profile/[userId]/index.tsx b/web/pages/[workspaceSlug]/profile/[userId]/index.tsx index 9f55f1cd90b..22fec985b95 100644 --- a/web/pages/[workspaceSlug]/profile/[userId]/index.tsx +++ b/web/pages/[workspaceSlug]/profile/[userId]/index.tsx @@ -5,7 +5,7 @@ import useSWR from "swr"; import { UserService } from "services/user.service"; // layouts import { AppLayout } from "layouts/app-layout"; -import { ProfileAuthWrapper } from "layouts/profile-layout"; +import { ProfileAuthWrapper } from "layouts/user-profile-layout"; // components import { UserProfileHeader } from "components/headers"; import { diff --git a/web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx b/web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx index 6862b00b4c4..bcc9c66a574 100644 --- a/web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx +++ b/web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx @@ -6,7 +6,7 @@ import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; // layouts import { AppLayout } from "layouts/app-layout"; -import { ProfileAuthWrapper } from "layouts/profile-layout"; +import { ProfileAuthWrapper } from "layouts/user-profile-layout"; // components import { UserProfileHeader } from "components/headers"; import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root"; diff --git a/web/pages/me/profile/activity.tsx b/web/pages/me/profile/activity.tsx index 57832cdf394..475ff05bf38 100644 --- a/web/pages/me/profile/activity.tsx +++ b/web/pages/me/profile/activity.tsx @@ -27,10 +27,7 @@ const ProfileActivityPage: NextPageWithLayout = () => { const router = useRouter(); const { workspaceSlug } = router.query; - const { data: userActivity } = useSWR( - workspaceSlug ? USER_ACTIVITY : null, - workspaceSlug ? () => userService.getUserWorkspaceActivity(workspaceSlug.toString()) : null - ); + const { data: userActivity } = useSWR(USER_ACTIVITY, () => userService.getUserActivity()); return ( <> @@ -196,9 +193,7 @@ const ProfileActivityPage: NextPageWithLayout = () => { }; ProfileActivityPage.getLayout = function getLayout(page: ReactElement) { - return ( - }>{page} - ); + return }>{page}; }; export default ProfileActivityPage; diff --git a/web/pages/me/profile/index.tsx b/web/pages/me/profile/index.tsx index 9dcc13913d6..f80bf4da659 100644 --- a/web/pages/me/profile/index.tsx +++ b/web/pages/me/profile/index.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useState, ReactElement } from "react"; -import Link from "next/link"; import { Controller, useForm } from "react-hook-form"; import { Disclosure, Transition } from "@headlessui/react"; // services @@ -17,7 +16,7 @@ import { DeactivateAccountModal } from "components/account"; // ui import { Button, CustomSelect, CustomSearchSelect, Input, Spinner } from "@plane/ui"; // icons -import { ChevronDown, ExternalLink, User2 } from "lucide-react"; +import { ChevronDown, User2 } from "lucide-react"; // types import type { IUser } from "types"; import type { NextPageWithLayout } from "types/app"; @@ -433,7 +432,7 @@ const ProfileSettingsPage: NextPageWithLayout = () => { }; ProfileSettingsPage.getLayout = function getLayout(page: ReactElement) { - return }>{page}; + return }>{page}; }; export default ProfileSettingsPage; diff --git a/web/pages/me/profile/preferences.tsx b/web/pages/me/profile/preferences.tsx index 35ccb30f4dd..57631488ab4 100644 --- a/web/pages/me/profile/preferences.tsx +++ b/web/pages/me/profile/preferences.tsx @@ -75,11 +75,7 @@ const ProfilePreferencesPage: NextPageWithLayout = observer(() => { }); ProfilePreferencesPage.getLayout = function getLayout(page: ReactElement) { - return ( - }> - {page} - - ); + return }>{page}; }; export default ProfilePreferencesPage; diff --git a/web/services/user.service.ts b/web/services/user.service.ts index 22e45cfac21..4b903728727 100644 --- a/web/services/user.service.ts +++ b/web/services/user.service.ts @@ -96,8 +96,8 @@ export class UserService extends APIService { }); } - async getUserWorkspaceActivity(workspaceSlug: string): Promise { - return this.get(`/api/users/workspaces/${workspaceSlug}/activities/`) + async getUserActivity(): Promise { + return this.get(`/api/users/me/activities/`) .then((response) => response?.data) .catch((error) => { throw error?.response?.data; diff --git a/web/types/issues.d.ts b/web/types/issues.d.ts index b04a7e5eff0..fdba7fd4361 100644 --- a/web/types/issues.d.ts +++ b/web/types/issues.d.ts @@ -209,6 +209,7 @@ export interface IIssueActivity { updated_by: string; verb: string; workspace: string; + workspace_detail?: IWorkspaceLite; } export interface IIssueComment extends IIssueActivity { diff --git a/web/types/users.d.ts b/web/types/users.d.ts index b82daa75fdc..0ac13fd3410 100644 --- a/web/types/users.d.ts +++ b/web/types/users.d.ts @@ -27,7 +27,7 @@ export interface IUser { user_timezone: string; username: string; theme: IUserTheme; - use_case? :string; + use_case?: string; } export interface IInstanceAdminStatus {