From c6d6b9a0e938a003364dd7615a29806d14988387 Mon Sep 17 00:00:00 2001 From: Ramesh Kumar Chandra <31303617+rameshkumarchandra@users.noreply.github.com> Date: Tue, 30 Jan 2024 15:22:24 +0530 Subject: [PATCH 1/8] style/responsive sidebar (#3505) * style: added sidebar toggle in all the screens for mobile responsive * chore: close sidebar on click of empty space when opened * chore: setting the sidebar collapsed in smaller screens --- .../sidebar/sidebar-menu-hamburger-toggle.tsx | 16 ++++++ .../dashboard/widgets/overview-stats.tsx | 49 ++++++++----------- web/components/headers/cycle-issues.tsx | 2 + web/components/headers/cycles.tsx | 3 ++ web/components/headers/global-issues.tsx | 4 +- web/components/headers/module-issues.tsx | 2 + web/components/headers/modules-list.tsx | 3 ++ web/components/headers/page-details.tsx | 3 ++ web/components/headers/pages.tsx | 3 ++ .../project-archived-issue-details.tsx | 3 ++ .../headers/project-archived-issues.tsx | 2 + .../headers/project-draft-issues.tsx | 2 + web/components/headers/project-inbox.tsx | 2 + .../headers/project-issue-details.tsx | 3 ++ web/components/headers/project-issues.tsx | 2 + web/components/headers/project-settings.tsx | 3 ++ .../headers/project-view-issues.tsx | 2 + web/components/headers/project-views.tsx | 2 + web/components/headers/projects.tsx | 3 ++ web/components/headers/user-profile.tsx | 3 ++ .../headers/workspace-active-cycles.tsx | 2 + .../headers/workspace-analytics.tsx | 3 ++ .../headers/workspace-dashboard.tsx | 4 +- web/components/headers/workspace-settings.tsx | 6 +-- web/components/project/sidebar-list-item.tsx | 45 +++++++++-------- web/components/workspace/sidebar-dropdown.tsx | 47 ++++++++++-------- web/components/workspace/sidebar-menu.tsx | 20 +++++--- web/layouts/app-layout/sidebar.tsx | 45 ++++++++++++++--- 28 files changed, 195 insertions(+), 89 deletions(-) create mode 100644 web/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx diff --git a/web/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx b/web/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx new file mode 100644 index 000000000..0e34eac2c --- /dev/null +++ b/web/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx @@ -0,0 +1,16 @@ +import { FC } from "react"; +import { Menu } from "lucide-react"; +import { useApplication } from "hooks/store"; +import { observer } from "mobx-react"; + +export const SidebarHamburgerToggle: FC = observer (() => { + const { theme: themStore } = useApplication(); + return ( +
themStore.toggleSidebar()} + > + +
+ ); +}); diff --git a/web/components/dashboard/widgets/overview-stats.tsx b/web/components/dashboard/widgets/overview-stats.tsx index 3af080dc3..418f0c63f 100644 --- a/web/components/dashboard/widgets/overview-stats.tsx +++ b/web/components/dashboard/widgets/overview-stats.tsx @@ -63,34 +63,27 @@ export const OverviewStatsWidget: React.FC = observer((props) => { if (!widgetStats) return ; return ( -
- {STATS_LIST.map((stat, index) => { - const isFirst = index === 0; - const isLast = index === STATS_LIST.length - 1; - const isMiddle = !isFirst && !isLast; - - return ( -
- {!isLast && ( -
- )} - -
{stat.count}
-

{stat.title}

- -
- ); - })} +
+ {STATS_LIST.map((stat) => ( +
+ +
+
+
{stat.count}
+

{stat.title}

+
+
+ +
+ ))}
); }); diff --git a/web/components/headers/cycle-issues.tsx b/web/components/headers/cycle-issues.tsx index 949c192fa..fc0075030 100644 --- a/web/components/headers/cycle-issues.tsx +++ b/web/components/headers/cycle-issues.tsx @@ -17,6 +17,7 @@ import useLocalStorage from "hooks/use-local-storage"; // components import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues"; import { ProjectAnalyticsModal } from "components/analytics"; +import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle"; // ui import { Breadcrumbs, Button, ContrastIcon, CustomMenu } from "@plane/ui"; // icons @@ -146,6 +147,7 @@ export const CycleIssuesHeader: React.FC = observer(() => { />
+ { // router @@ -30,6 +32,7 @@ export const CyclesHeader: FC = observer(() => { return (
+
= observer((props) => { <> setCreateViewModal(false)} />
-
+
+ { />
+ { // router @@ -31,6 +33,7 @@ export const ModulesListHeader: React.FC = observer(() => { return (
+
= observer((props) => { return (
+
{ // router @@ -29,6 +31,7 @@ export const PagesHeader = observer(() => { return (
+
{ return (
+
{ return (
+
+ ) : ( + + )} + + {isOpen && ( + +
+
+ + setQuery(e.target.value)} + placeholder="Search" + displayValue={(moduleIds: any) => { + const displayValueOptions: TModuleSelectDropdownOption[] | undefined = options?.filter((_module) => + moduleIds.includes(_module.value) + ); + return displayValueOptions?.map((_option) => _option.query).join(", ") || "Select Module"; + }} + /> +
+
+ {filteredOptions ? ( + filteredOptions.length > 0 ? ( + filteredOptions.map((option) => ( + + `w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none ${ + active ? "bg-custom-background-80" : "" + } ${selected ? "text-custom-text-100" : "text-custom-text-200"}` + } + onClick={() => !multiple && closeDropdown()} + > + {({ selected }) => ( + <> + {option.content} + {selected && } + + )} + + )) + ) : ( +

No matching results

+ ) + ) : ( +

Loading...

+ )} +
+
+
+ )} + + ); +}); diff --git a/web/components/dropdowns/module-select/types.d.ts b/web/components/dropdowns/module-select/types.d.ts new file mode 100644 index 000000000..b1c10eedb --- /dev/null +++ b/web/components/dropdowns/module-select/types.d.ts @@ -0,0 +1,50 @@ +import { ReactNode } from "react"; +import { Placement } from "@popperjs/core"; +import { TDropdownProps, TButtonVariants } from "../types"; + +type TModuleSelectDropdownRoot = Omit< + TDropdownProps, + "buttonClassName", + "buttonContainerClassName", + "buttonContainerClassName", + "className", + "disabled", + "hideIcon", + "placeholder", + "placement", + "tabIndex", + "tooltip" +>; + +export type TModuleSelectDropdownBase = { + value: string | string[] | undefined; + onChange: (moduleIds: undefined | string | (string | undefined)[]) => void; + placeholder?: string; + disabled?: boolean; + buttonClassName?: string; + buttonVariant?: TButtonVariants; + hideIcon?: boolean; + dropdownArrow?: boolean; + dropdownArrowClassName?: string; + showTooltip?: boolean; + showCount?: boolean; +}; + +export type TModuleSelectButton = TModuleSelectDropdownBase & { hideText?: boolean }; + +export type TModuleSelectDropdown = TModuleSelectDropdownBase & { + workspaceSlug: string; + projectId: string; + multiple?: boolean; + className?: string; + buttonContainerClassName?: string; + placement?: Placement; + tabIndex?: number; + button?: ReactNode; +}; + +export type TModuleSelectDropdownOption = { + value: string | undefined; + query: string; + content: JSX.Element; +}; diff --git a/web/components/issues/draft-issue-form.tsx b/web/components/issues/draft-issue-form.tsx index fdbfb30f7..a5b0ef149 100644 --- a/web/components/issues/draft-issue-form.tsx +++ b/web/components/issues/draft-issue-form.tsx @@ -21,7 +21,7 @@ import { CycleDropdown, DateDropdown, EstimateDropdown, - ModuleDropdown, + ModuleSelectDropdown, PriorityDropdown, ProjectDropdown, ProjectMemberDropdown, @@ -152,7 +152,7 @@ export const DraftIssueForm: FC = observer((props) => { project_id: watch("project_id"), parent_id: watch("parent_id"), cycle_id: watch("cycle_id"), - module_id: watch("module_id"), + module_ids: watch("module_ids"), }; useEffect(() => { @@ -570,15 +570,17 @@ export const DraftIssueForm: FC = observer((props) => { )} /> )} - {projectDetails?.module_view && ( + + {projectDetails?.module_view && workspaceSlug && ( (
- onChange(moduleId)} buttonVariant="border-with-text" /> @@ -586,6 +588,7 @@ export const DraftIssueForm: FC = observer((props) => { )} /> )} + {(fieldsToShow.includes("all") || fieldsToShow.includes("estimate")) && areEstimatesEnabledForProject(projectId) && ( = observer( cycle: cycleId.toString(), })); } - if (moduleId && !prePopulateDataProps?.module_id) { + if (moduleId && !prePopulateDataProps?.module_ids) { setPreloadedData((prevData) => ({ ...(prevData ?? {}), ...prePopulateDataProps, @@ -123,7 +123,7 @@ export const CreateUpdateDraftIssueModal: React.FC = observer( cycle: cycleId.toString(), })); } - if (moduleId && !prePopulateDataProps?.module_id) { + if (moduleId && !prePopulateDataProps?.module_ids) { setPreloadedData((prevData) => ({ ...(prevData ?? {}), ...prePopulateDataProps, @@ -233,11 +233,11 @@ export const CreateUpdateDraftIssueModal: React.FC = observer( }); }; - const addIssueToModule = async (issueId: string, moduleId: string) => { + const addIssueToModule = async (issueId: string, moduleIds: string[]) => { if (!workspaceSlug || !activeProject) return; - await moduleService.addIssuesToModule(workspaceSlug as string, activeProject ?? "", moduleId as string, { - issues: [issueId], + await moduleService.addModulesToIssue(workspaceSlug as string, activeProject ?? "", issueId as string, { + modules: moduleIds, }); }; @@ -248,7 +248,7 @@ export const CreateUpdateDraftIssueModal: React.FC = observer( .createIssue(workspaceSlug.toString(), activeProject, payload) .then(async (res) => { if (payload.cycle_id && payload.cycle_id !== "") await addIssueToCycle(res.id, payload.cycle_id); - if (payload.module_id && payload.module_id !== "") await addIssueToModule(res.id, payload.module_id); + if (payload.module_ids && payload.module_ids.length > 0) await addIssueToModule(res.id, payload.module_ids); setToastAlert({ type: "success", diff --git a/web/components/issues/issue-detail/issue-activity/activity/actions/module.tsx b/web/components/issues/issue-detail/issue-activity/activity/actions/module.tsx index e092ab08b..c8089d233 100644 --- a/web/components/issues/issue-detail/issue-activity/activity/actions/module.tsx +++ b/web/components/issues/issue-detail/issue-activity/activity/actions/module.tsx @@ -59,7 +59,7 @@ export const IssueModuleActivity: FC = observer((props) => rel="noopener noreferrer" className="inline-flex items-center gap-1 truncate font-medium text-custom-text-100 hover:underline" > - {activity.new_value} + {activity.old_value} )} diff --git a/web/components/issues/issue-detail/module-select.tsx b/web/components/issues/issue-detail/module-select.tsx index 06853a799..82ff4ed32 100644 --- a/web/components/issues/issue-detail/module-select.tsx +++ b/web/components/issues/issue-detail/module-select.tsx @@ -1,9 +1,10 @@ import React, { useState } from "react"; import { observer } from "mobx-react-lite"; +import xor from "lodash/xor"; // hooks import { useIssueDetail } from "hooks/store"; // components -import { ModuleDropdown } from "components/dropdowns"; +import { ModuleSelectDropdown } from "components/dropdowns"; // ui import { Spinner } from "@plane/ui"; // helpers @@ -32,58 +33,56 @@ export const IssueModuleSelect: React.FC = observer((props) const issue = getIssueById(issueId); const disableSelect = disabled || isUpdating; - const handleIssueModuleChange = async (moduleId: string | null) => { - if (!issue || issue.module_id === moduleId) return; + const handleIssueModuleChange = async (moduleIds: undefined | string | (string | undefined)[]) => { + if (!issue) return; + setIsUpdating(true); - if (moduleId) await issueOperations.addIssueToModule?.(workspaceSlug, projectId, moduleId, [issueId]); - else await issueOperations.removeIssueFromModule?.(workspaceSlug, projectId, issue.module_id ?? "", issueId); + if (moduleIds === undefined && issue?.module_ids && issue?.module_ids.length > 0) + await issueOperations.removeModulesFromIssue?.(workspaceSlug, projectId, issueId, issue?.module_ids); + + if (typeof moduleIds === "string" && moduleIds) + await issueOperations.removeModulesFromIssue?.(workspaceSlug, projectId, issueId, [moduleIds]); + + if (Array.isArray(moduleIds)) { + if (moduleIds.includes(undefined)) { + await issueOperations.removeModulesFromIssue?.( + workspaceSlug, + projectId, + issueId, + moduleIds.filter((x) => x != undefined) as string[] + ); + } else { + const _moduleIds = xor(issue?.module_ids, moduleIds)[0]; + if (_moduleIds) { + if (issue?.module_ids?.includes(_moduleIds)) + await issueOperations.removeModulesFromIssue?.(workspaceSlug, projectId, issueId, [_moduleIds]); + else await issueOperations.addModulesToIssue?.(workspaceSlug, projectId, issueId, [_moduleIds]); + } + } + } setIsUpdating(false); }; return ( -
- + - {/* handleIssueModuleChange(value)} - options={options} - customButton={ -
- - - -
- } - noChevron disabled={disableSelect} - /> */} + className={`w-full h-full group`} + buttonContainerClassName="w-full" + buttonClassName={`min-h-8 ${issue?.module_ids?.length ? `` : `text-custom-text-400`}`} + buttonVariant="transparent-with-text" + hideIcon={false} + dropdownArrow={true} + dropdownArrowClassName="h-3.5 w-3.5 hidden group-hover:inline" + showTooltip={true} + /> + {isUpdating && }
); diff --git a/web/components/issues/issue-detail/root.tsx b/web/components/issues/issue-detail/root.tsx index 58a52cc97..9a16dcbf0 100644 --- a/web/components/issues/issue-detail/root.tsx +++ b/web/components/issues/issue-detail/root.tsx @@ -29,13 +29,19 @@ export type TIssueOperations = { remove: (workspaceSlug: string, projectId: string, issueId: string) => Promise; addIssueToCycle?: (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => Promise; removeIssueFromCycle?: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise; - addIssueToModule?: (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => Promise; + addModulesToIssue?: (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => Promise; removeIssueFromModule?: ( workspaceSlug: string, projectId: string, moduleId: string, issueId: string ) => Promise; + removeModulesFromIssue?: ( + workspaceSlug: string, + projectId: string, + issueId: string, + moduleIds: string[] + ) => Promise; }; export type TIssueDetailRoot = { @@ -57,8 +63,9 @@ export const IssueDetailRoot: FC = (props) => { removeIssue, addIssueToCycle, removeIssueFromCycle, - addIssueToModule, + addModulesToIssue, removeIssueFromModule, + removeModulesFromIssue, } = useIssueDetail(); const { issues: { removeIssue: removeArchivedIssue }, @@ -150,9 +157,9 @@ export const IssueDetailRoot: FC = (props) => { }); } }, - addIssueToModule: async (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => { + addModulesToIssue: async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => { try { - await addIssueToModule(workspaceSlug, projectId, moduleId, issueIds); + await addModulesToIssue(workspaceSlug, projectId, issueId, moduleIds); setToastAlert({ title: "Module added to issue successfully", type: "success", @@ -182,6 +189,27 @@ export const IssueDetailRoot: FC = (props) => { }); } }, + removeModulesFromIssue: async ( + workspaceSlug: string, + projectId: string, + issueId: string, + moduleIds: string[] + ) => { + try { + await removeModulesFromIssue(workspaceSlug, projectId, issueId, moduleIds); + setToastAlert({ + title: "Module removed from issue successfully", + type: "success", + message: "Module removed from issue successfully", + }); + } catch (error) { + setToastAlert({ + title: "Module remove from issue failed", + type: "error", + message: "Module remove from issue failed", + }); + } + }, }), [ is_archived, @@ -191,8 +219,9 @@ export const IssueDetailRoot: FC = (props) => { removeArchivedIssue, addIssueToCycle, removeIssueFromCycle, - addIssueToModule, + addModulesToIssue, removeIssueFromModule, + removeModulesFromIssue, setToastAlert, ] ); diff --git a/web/components/issues/issue-detail/sidebar.tsx b/web/components/issues/issue-detail/sidebar.tsx index 0a38c3017..f2ee876b9 100644 --- a/web/components/issues/issue-detail/sidebar.tsx +++ b/web/components/issues/issue-detail/sidebar.tsx @@ -286,7 +286,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { )} {projectDetails?.module_view && ( -
+
Module diff --git a/web/components/issues/issue-layouts/empty-states/module.tsx b/web/components/issues/issue-layouts/empty-states/module.tsx index a5b6d7255..aa3a8dc19 100644 --- a/web/components/issues/issue-layouts/empty-states/module.tsx +++ b/web/components/issues/issue-layouts/empty-states/module.tsx @@ -46,11 +46,7 @@ export const ModuleEmptyState: React.FC = observer((props) => { const issueIds = data.map((i) => i.id); await issues - .addIssueToModule(workspaceSlug.toString(), projectId?.toString(), moduleId.toString(), issueIds) - .then((res) => { - updateIssue(workspaceSlug, projectId, res.id, res); - fetchIssue(workspaceSlug, projectId, res.id); - }) + .addIssuesToModule(workspaceSlug.toString(), projectId?.toString(), moduleId.toString(), issueIds) .catch(() => setToastAlert({ type: "error", @@ -69,7 +65,7 @@ export const ModuleEmptyState: React.FC = observer((props) => { projectId={projectId} isOpen={moduleIssuesListModal} handleClose={() => setModuleIssuesListModal(false)} - searchParams={{ module: true }} + searchParams={{ module: moduleId != undefined ? [moduleId.toString()] : [] }} handleOnSubmit={handleAddIssuesToModule} />
diff --git a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx index 5a94b6bac..eb7005cbd 100644 --- a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx +++ b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx @@ -44,7 +44,7 @@ export interface IBaseKanBanLayout { showLoader?: boolean; viewId?: string; storeType?: TCreateModalStoreTypes; - addIssuesToView?: (issueIds: string[]) => Promise; + addIssuesToView?: (issueIds: string[]) => Promise; canEditPropertiesBasedOnProject?: (projectId: string) => boolean; } diff --git a/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx b/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx index 05c2b5d45..713a6644a 100644 --- a/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx @@ -56,7 +56,7 @@ export const HeaderGroupByCard: FC = observer((props) => { const { setToastAlert } = useToast(); const renderExistingIssueModal = moduleId || cycleId; - const ExistingIssuesListModalPayload = moduleId ? { module: true } : { cycle: true }; + const ExistingIssuesListModalPayload = moduleId ? { module: [moduleId.toString()] } : { cycle: true }; const handleAddIssuesToView = async (data: ISearchIssueResponse[]) => { if (!workspaceSlug || !projectId) return; diff --git a/web/components/issues/issue-layouts/kanban/roots/module-root.tsx b/web/components/issues/issue-layouts/kanban/roots/module-root.tsx index 89f4683af..c3af69e6e 100644 --- a/web/components/issues/issue-layouts/kanban/roots/module-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/module-root.tsx @@ -53,7 +53,7 @@ export const ModuleKanBanLayout: React.FC = observer(() => { storeType={EIssuesStoreType.MODULE} addIssuesToView={(issueIds: string[]) => { if (!workspaceSlug || !projectId || !moduleId) throw new Error(); - return issues.addIssueToModule(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), issueIds); + return issues.addIssuesToModule(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), issueIds); }} /> ); diff --git a/web/components/issues/issue-layouts/list/base-list-root.tsx b/web/components/issues/issue-layouts/list/base-list-root.tsx index b718269b6..10f3582f1 100644 --- a/web/components/issues/issue-layouts/list/base-list-root.tsx +++ b/web/components/issues/issue-layouts/list/base-list-root.tsx @@ -49,7 +49,7 @@ interface IBaseListRoot { }; viewId?: string; storeType: TCreateModalStoreTypes; - addIssuesToView?: (issueIds: string[]) => Promise; + addIssuesToView?: (issueIds: string[]) => Promise; canEditPropertiesBasedOnProject?: (projectId: string) => boolean; } diff --git a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx index cf56d6b5d..7a7a2d1ab 100644 --- a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx +++ b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx @@ -37,7 +37,7 @@ export const HeaderGroupByCard = observer( const { setToastAlert } = useToast(); const renderExistingIssueModal = moduleId || cycleId; - const ExistingIssuesListModalPayload = moduleId ? { module: true } : { cycle: true }; + const ExistingIssuesListModalPayload = moduleId ? { module: [moduleId.toString()] } : { cycle: true }; const handleAddIssuesToView = async (data: ISearchIssueResponse[]) => { if (!workspaceSlug || !projectId) return; diff --git a/web/components/issues/issue-layouts/list/roots/module-root.tsx b/web/components/issues/issue-layouts/list/roots/module-root.tsx index fb874b8f6..520a2da32 100644 --- a/web/components/issues/issue-layouts/list/roots/module-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/module-root.tsx @@ -51,7 +51,7 @@ export const ModuleListLayout: React.FC = observer(() => { storeType={EIssuesStoreType.MODULE} addIssuesToView={(issueIds: string[]) => { if (!workspaceSlug || !projectId || !moduleId) throw new Error(); - return issues.addIssueToModule(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), issueIds); + return issues.addIssuesToModule(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), issueIds); }} /> ); diff --git a/web/components/issues/issue-modal/form.tsx b/web/components/issues/issue-modal/form.tsx index dbae7e2d5..f1f5d873f 100644 --- a/web/components/issues/issue-modal/form.tsx +++ b/web/components/issues/issue-modal/form.tsx @@ -20,7 +20,7 @@ import { CycleDropdown, DateDropdown, EstimateDropdown, - ModuleDropdown, + ModuleSelectDropdown, PriorityDropdown, ProjectDropdown, ProjectMemberDropdown, @@ -44,7 +44,7 @@ const defaultValues: Partial = { assignee_ids: [], label_ids: [], cycle_id: null, - module_id: null, + module_ids: null, start_date: null, target_date: null, }; @@ -541,21 +541,24 @@ export const IssueFormRoot: FC = observer((props) => { )} /> )} - {projectDetails?.module_view && ( + {projectDetails?.module_view && workspaceSlug && ( (
- { onChange(moduleId); handleFormChange(); }} buttonVariant="border-with-text" tabIndex={13} + multiple={true} + showCount={true} />
)} diff --git a/web/components/issues/issue-modal/modal.tsx b/web/components/issues/issue-modal/modal.tsx index c3c38c572..da13e6353 100644 --- a/web/components/issues/issue-modal/modal.tsx +++ b/web/components/issues/issue-modal/modal.tsx @@ -108,11 +108,11 @@ export const CreateUpdateIssueModal: React.FC = observer((prop fetchCycleDetails(workspaceSlug, activeProjectId, cycleId); }; - const addIssueToModule = async (issue: TIssue, moduleId: string) => { + const addIssueToModule = async (issue: TIssue, moduleIds: string[]) => { if (!workspaceSlug || !activeProjectId) return; - await moduleIssues.addIssueToModule(workspaceSlug, activeProjectId, moduleId, [issue.id]); - fetchModuleDetails(workspaceSlug, activeProjectId, moduleId); + await moduleIssues.addModulesToIssue(workspaceSlug, activeProjectId, issue.id, moduleIds); + moduleIds.forEach((moduleId) => fetchModuleDetails(workspaceSlug, activeProjectId, moduleId)); }; const handleCreateMoreToggleChange = (value: boolean) => { @@ -139,8 +139,8 @@ export const CreateUpdateIssueModal: React.FC = observer((prop if (payload.cycle_id && payload.cycle_id !== "" && storeType !== EIssuesStoreType.CYCLE) await addIssueToCycle(response, payload.cycle_id); - if (payload.module_id && payload.module_id !== "" && storeType !== EIssuesStoreType.MODULE) - await addIssueToModule(response, payload.module_id); + if (payload.module_ids && payload.module_ids.length > 0 && storeType !== EIssuesStoreType.MODULE) + await addIssueToModule(response, payload.module_ids); setToastAlert({ type: "success", @@ -278,7 +278,7 @@ export const CreateUpdateIssueModal: React.FC = observer((prop data={{ ...data, cycle_id: data?.cycle_id ? data?.cycle_id : cycleId ? cycleId : null, - module_id: data?.module_id ? data?.module_id : moduleId ? moduleId : null, + module_ids: data?.module_ids ? data?.module_ids : moduleId ? [moduleId] : null, }} onChange={handleFormChange} onClose={handleClose} @@ -292,7 +292,7 @@ export const CreateUpdateIssueModal: React.FC = observer((prop data={{ ...data, cycle_id: data?.cycle_id ? data?.cycle_id : cycleId ? cycleId : null, - module_id: data?.module_id ? data?.module_id : moduleId ? moduleId : null, + module_ids: data?.module_ids ? data?.module_ids : moduleId ? [moduleId] : null, }} onClose={() => handleClose(false)} isCreateMoreToggleEnabled={createMore} diff --git a/web/components/issues/peek-overview/properties.tsx b/web/components/issues/peek-overview/properties.tsx index 6aee23a23..ea00b845a 100644 --- a/web/components/issues/peek-overview/properties.tsx +++ b/web/components/issues/peek-overview/properties.tsx @@ -203,7 +203,7 @@ export const PeekOverviewProperties: FC = observer((pro )} {projectDetails?.module_view && ( -
+
Module diff --git a/web/components/issues/peek-overview/root.tsx b/web/components/issues/peek-overview/root.tsx index 89a659fb3..5041e5d2a 100644 --- a/web/components/issues/peek-overview/root.tsx +++ b/web/components/issues/peek-overview/root.tsx @@ -28,8 +28,19 @@ export type TIssuePeekOperations = { remove: (workspaceSlug: string, projectId: string, issueId: string) => Promise; addIssueToCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => Promise; removeIssueFromCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise; - addIssueToModule: (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => Promise; - removeIssueFromModule: (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => Promise; + addModulesToIssue?: (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => Promise; + removeIssueFromModule?: ( + workspaceSlug: string, + projectId: string, + moduleId: string, + issueId: string + ) => Promise; + removeModulesFromIssue?: ( + workspaceSlug: string, + projectId: string, + issueId: string, + moduleIds: string[] + ) => Promise; }; export const IssuePeekOverview: FC = observer((props) => { @@ -48,7 +59,8 @@ export const IssuePeekOverview: FC = observer((props) => { removeIssue, issue: { getIssueById, fetchIssue }, } = useIssueDetail(); - const { addIssueToCycle, removeIssueFromCycle, addIssueToModule, removeIssueFromModule } = useIssueDetail(); + const { addIssueToCycle, removeIssueFromCycle, addModulesToIssue, removeIssueFromModule, removeModulesFromIssue } = + useIssueDetail(); // state const [loader, setLoader] = useState(false); @@ -143,9 +155,9 @@ export const IssuePeekOverview: FC = observer((props) => { }); } }, - addIssueToModule: async (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => { + addModulesToIssue: async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => { try { - await addIssueToModule(workspaceSlug, projectId, moduleId, issueIds); + await addModulesToIssue(workspaceSlug, projectId, issueId, moduleIds); setToastAlert({ title: "Module added to issue successfully", type: "success", @@ -175,6 +187,27 @@ export const IssuePeekOverview: FC = observer((props) => { }); } }, + removeModulesFromIssue: async ( + workspaceSlug: string, + projectId: string, + issueId: string, + moduleIds: string[] + ) => { + try { + await removeModulesFromIssue(workspaceSlug, projectId, issueId, moduleIds); + setToastAlert({ + title: "Module removed from issue successfully", + type: "success", + message: "Module removed from issue successfully", + }); + } catch (error) { + setToastAlert({ + title: "Module remove from issue failed", + type: "error", + message: "Module remove from issue failed", + }); + } + }, }), [ is_archived, @@ -184,8 +217,9 @@ export const IssuePeekOverview: FC = observer((props) => { removeArchivedIssue, addIssueToCycle, removeIssueFromCycle, - addIssueToModule, + addModulesToIssue, removeIssueFromModule, + removeModulesFromIssue, setToastAlert, onIssueUpdate, ] diff --git a/web/lib/app-provider.tsx b/web/lib/app-provider.tsx index dad6253c9..027800cd8 100644 --- a/web/lib/app-provider.tsx +++ b/web/lib/app-provider.tsx @@ -47,7 +47,7 @@ export const AppProvider: FC = observer((props) => { - = observer((props) => { posthogHost={envConfig?.posthog_host || null} > {children} - + */} + {children} diff --git a/web/services/module.service.ts b/web/services/module.service.ts index 4638f6ab2..ebddfb055 100644 --- a/web/services/module.service.ts +++ b/web/services/module.service.ts @@ -1,7 +1,7 @@ // services import { APIService } from "services/api.service"; // types -import type { IModule, TIssue, ILinkDetails, ModuleLink, TIssueMap } from "@plane/types"; +import type { IModule, TIssue, ILinkDetails, ModuleLink } from "@plane/types"; import { API_BASE_URL } from "helpers/common.helper"; export class ModuleService extends APIService { @@ -63,22 +63,7 @@ export class ModuleService extends APIService { } async getModuleIssues(workspaceSlug: string, projectId: string, moduleId: string, queries?: any): Promise { - return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-issues/`, { - params: queries, - }) - .then((response) => response?.data) - .catch((error) => { - throw error?.response?.data; - }); - } - - async getModuleIssuesWithParams( - workspaceSlug: string, - projectId: string, - moduleId: string, - queries?: any - ): Promise { - return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-issues/`, { + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/`, { params: queries, }) .then((response) => response?.data) @@ -92,15 +77,21 @@ export class ModuleService extends APIService { projectId: string, moduleId: string, data: { issues: string[] } - ): Promise< - { - issue: string; - issue_detail: TIssue; - module: string; - module_detail: IModule; - }[] - > { - return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-issues/`, data) + ): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async addModulesToIssue( + workspaceSlug: string, + projectId: string, + issueId: string, + data: { modules: string[] } + ): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/modules/`, data) .then((response) => response?.data) .catch((error) => { throw error?.response?.data; @@ -111,17 +102,53 @@ export class ModuleService extends APIService { workspaceSlug: string, projectId: string, moduleId: string, - bridgeId: string + issueId: string ): Promise { - return this.delete( - `/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-issues/${bridgeId}/` - ) + return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`) .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } + async removeIssuesFromModuleBulk( + workspaceSlug: string, + projectId: string, + moduleId: string, + issueIds: string[] + ): Promise { + const promiseDataUrls: any = []; + issueIds.forEach((issueId) => { + promiseDataUrls.push( + this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`) + ); + }); + return await Promise.all(promiseDataUrls) + .then((response) => response) + .catch((error) => { + throw error?.response?.data; + }); + } + + async removeModulesFromIssueBulk( + workspaceSlug: string, + projectId: string, + issueId: string, + moduleIds: string[] + ): Promise { + const promiseDataUrls: any = []; + moduleIds.forEach((moduleId) => { + promiseDataUrls.push( + this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`) + ); + }); + return await Promise.all(promiseDataUrls) + .then((response) => response) + .catch((error) => { + throw error?.response?.data; + }); + } + async createModuleLink( workspaceSlug: string, projectId: string, diff --git a/web/store/issue/issue-details/issue.store.ts b/web/store/issue/issue-details/issue.store.ts index be687eab8..46605c771 100644 --- a/web/store/issue/issue-details/issue.store.ts +++ b/web/store/issue/issue-details/issue.store.ts @@ -12,13 +12,14 @@ export interface IIssueStoreActions { removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; addIssueToCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => Promise; removeIssueFromCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise; - addIssueToModule: (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => Promise; - removeIssueFromModule: ( + addModulesToIssue: (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => Promise; + removeModulesFromIssue: ( workspaceSlug: string, projectId: string, - moduleId: string, - issueId: string - ) => Promise; + issueId: string, + moduleIds: string[] + ) => Promise; + removeIssueFromModule: (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => Promise; } export interface IIssueStore extends IIssueStoreActions { @@ -143,15 +144,26 @@ export class IssueStore implements IIssueStore { return cycle; }; - addIssueToModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => { - const _module = await this.rootIssueDetailStore.rootIssueStore.moduleIssues.addIssueToModule( + addModulesToIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => { + const _module = await this.rootIssueDetailStore.rootIssueStore.moduleIssues.addModulesToIssue( workspaceSlug, projectId, - moduleId, - issueIds + issueId, + moduleIds ); - if (issueIds && issueIds.length > 0) - await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueIds[0]); + if (moduleIds && moduleIds.length > 0) + await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId); + return _module; + }; + + removeModulesFromIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => { + const _module = await this.rootIssueDetailStore.rootIssueStore.moduleIssues.removeModulesFromIssue( + workspaceSlug, + projectId, + issueId, + moduleIds + ); + await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId); return _module; }; diff --git a/web/store/issue/issue-details/root.store.ts b/web/store/issue/issue-details/root.store.ts index 9feb728c7..d78add446 100644 --- a/web/store/issue/issue-details/root.store.ts +++ b/web/store/issue/issue-details/root.store.ts @@ -143,8 +143,10 @@ export class IssueDetail implements IIssueDetail { this.issue.addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds); removeIssueFromCycle = async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => this.issue.removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId); - addIssueToModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => - this.issue.addIssueToModule(workspaceSlug, projectId, moduleId, issueIds); + addModulesToIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => + this.issue.addModulesToIssue(workspaceSlug, projectId, issueId, moduleIds); + removeModulesFromIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => + this.issue.removeModulesFromIssue(workspaceSlug, projectId, issueId, moduleIds); removeIssueFromModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => this.issue.removeIssueFromModule(workspaceSlug, projectId, moduleId, issueId); diff --git a/web/store/issue/module/issue.store.ts b/web/store/issue/module/issue.store.ts index e24f03fb6..da2b127c1 100644 --- a/web/store/issue/module/issue.store.ts +++ b/web/store/issue/module/issue.store.ts @@ -52,13 +52,21 @@ export interface IModuleIssues { data: TIssue, moduleId?: string | undefined ) => Promise; - addIssueToModule: (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => Promise; - removeIssueFromModule: ( + addIssuesToModule: (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => Promise; + removeIssuesFromModule: ( workspaceSlug: string, projectId: string, moduleId: string, - issueId: string - ) => Promise; + issueIds: string[] + ) => Promise; + addModulesToIssue: (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => Promise; + removeModulesFromIssue: ( + workspaceSlug: string, + projectId: string, + issueId: string, + moduleIds: string[] + ) => Promise; + removeIssueFromModule: (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => Promise; } export class ModuleIssues extends IssueHelperStore implements IModuleIssues { @@ -90,7 +98,10 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues { updateIssue: action, removeIssue: action, quickAddIssue: action, - addIssueToModule: action, + addIssuesToModule: action, + removeIssuesFromModule: action, + addModulesToIssue: action, + removeModulesFromIssue: action, removeIssueFromModule: action, }); @@ -175,7 +186,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues { if (!moduleId) throw new Error("Module Id is required"); const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data); - await this.addIssueToModule(workspaceSlug, projectId, moduleId, [response.id]); + await this.addIssuesToModule(workspaceSlug, projectId, moduleId, [response.id]); return response; } catch (error) { @@ -253,7 +264,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues { } }; - addIssueToModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => { + addIssuesToModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => { try { const issueToModule = await this.moduleService.addIssuesToModule(workspaceSlug, projectId, moduleId, { issues: issueIds, @@ -261,11 +272,16 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues { runInAction(() => { update(this.issues, moduleId, (moduleIssueIds = []) => { - uniq(concat(moduleIssueIds, issueIds)); + if (!moduleIssueIds) return [...issueIds]; + else return uniq(concat(moduleIssueIds, issueIds)); }); }); + issueIds.forEach((issueId) => { - this.rootStore.issues.updateIssue(issueId, { module_id: moduleId }); + update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) => { + if (issueModuleIds.includes(moduleId)) return issueModuleIds; + else return uniq(concat(issueModuleIds, [moduleId])); + }); }); return issueToModule; @@ -274,14 +290,96 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues { } }; + removeIssuesFromModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => { + try { + runInAction(() => { + issueIds.forEach((issueId) => { + pull(this.issues[moduleId], issueId); + }); + }); + + runInAction(() => { + issueIds.forEach((issueId) => { + update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) => { + if (issueModuleIds.includes(moduleId)) return pull(issueModuleIds, moduleId); + else return uniq(concat(issueModuleIds, [moduleId])); + }); + }); + }); + + const response = await this.moduleService.removeIssuesFromModuleBulk( + workspaceSlug, + projectId, + moduleId, + issueIds + ); + + return response; + } catch (error) { + throw error; + } + }; + + addModulesToIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => { + try { + const issueToModule = await this.moduleService.addModulesToIssue(workspaceSlug, projectId, issueId, { + modules: moduleIds, + }); + + runInAction(() => { + moduleIds.forEach((moduleId) => { + update(this.issues, moduleId, (moduleIssueIds = []) => { + if (moduleIssueIds.includes(issueId)) return moduleIssueIds; + else return uniq(concat(moduleIssueIds, [issueId])); + }); + }); + update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) => + uniq(concat(issueModuleIds, moduleIds)) + ); + }); + + return issueToModule; + } catch (error) { + throw error; + } + }; + + removeModulesFromIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => { + try { + runInAction(() => { + moduleIds.forEach((moduleId) => { + update(this.issues, moduleId, (moduleIssueIds = []) => { + if (moduleIssueIds.includes(issueId)) return moduleIssueIds; + else return uniq(concat(moduleIssueIds, [issueId])); + }); + update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) => + pull(issueModuleIds, moduleId) + ); + }); + }); + + const response = await this.moduleService.removeModulesFromIssueBulk( + workspaceSlug, + projectId, + issueId, + moduleIds + ); + + return response; + } catch (error) { + throw error; + } + }; + removeIssueFromModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => { try { runInAction(() => { pull(this.issues[moduleId], issueId); + update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) => + pull(issueModuleIds, moduleId) + ); }); - this.rootStore.issues.updateIssue(issueId, { module_id: null }); - const response = await this.moduleService.removeIssueFromModule(workspaceSlug, projectId, moduleId, issueId); return response; From c67e097fc28afc81cafee57cfa047a2984c18da1 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Tue, 30 Jan 2024 16:25:13 +0530 Subject: [PATCH 3/8] chore: fix assignee tooltip logic in list and kanban layout for consistency. (#3510) --- .../issues/issue-layouts/properties/all-properties.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/web/components/issues/issue-layouts/properties/all-properties.tsx b/web/components/issues/issue-layouts/properties/all-properties.tsx index b7e1fae4f..c23938a19 100644 --- a/web/components/issues/issue-layouts/properties/all-properties.tsx +++ b/web/components/issues/issue-layouts/properties/all-properties.tsx @@ -155,7 +155,6 @@ export const IssueProperties: React.FC = observer((props) => { multiple buttonVariant={issue.assignee_ids?.length > 0 ? "transparent-without-text" : "border-without-text"} buttonClassName={issue.assignee_ids?.length > 0 ? "hover:bg-transparent px-0" : ""} - tooltip />
From 638c1e21c926e555e21fae19fb1cbb216ce51542 Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:03:49 +0530 Subject: [PATCH 4/8] fix quick add issue creation in cycles (#3511) --- web/store/issue/cycle/issue.store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/store/issue/cycle/issue.store.ts b/web/store/issue/cycle/issue.store.ts index 33cd06d4d..1a8343006 100644 --- a/web/store/issue/cycle/issue.store.ts +++ b/web/store/issue/cycle/issue.store.ts @@ -268,7 +268,7 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues { runInAction(() => { update(this.issues, cycleId, (cycleIssueIds = []) => { - uniq(concat(cycleIssueIds, issueIds)); + return uniq(concat(cycleIssueIds, issueIds)); }); }); issueIds.forEach((issueId) => { From 817737b2c08c79d815b5218291d898596a5f36f8 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Tue, 30 Jan 2024 20:11:16 +0530 Subject: [PATCH 5/8] improvement: add hide/ unhide icon for all password field in the platform. (#3513) --- .../sign-in-forms/optional-set-password.tsx | 38 +++++++++++++------ .../account/sign-in-forms/password.tsx | 34 ++++++++++++----- .../sign-up-forms/optional-set-password.tsx | 36 +++++++++++++----- .../account/sign-up-forms/password.tsx | 37 ++++++++++++------ .../instance/setup-form/sign-in-form.tsx | 35 ++++++++++++----- web/pages/accounts/reset-password.tsx | 37 +++++++++++++----- 6 files changed, 155 insertions(+), 62 deletions(-) diff --git a/web/components/account/sign-in-forms/optional-set-password.tsx b/web/components/account/sign-in-forms/optional-set-password.tsx index 1669811cb..d7a595298 100644 --- a/web/components/account/sign-in-forms/optional-set-password.tsx +++ b/web/components/account/sign-in-forms/optional-set-password.tsx @@ -8,6 +8,8 @@ import useToast from "hooks/use-toast"; import { Button, Input } from "@plane/ui"; // helpers import { checkEmailValidity } from "helpers/string.helper"; +// icons +import { Eye, EyeOff } from "lucide-react"; type Props = { email: string; @@ -31,6 +33,7 @@ export const SignInOptionalSetPasswordForm: React.FC = (props) => { const { email, handleSignInRedirection } = props; // states const [isGoingToWorkspace, setIsGoingToWorkspace] = useState(false); + const [showPassword, setShowPassword] = useState(false); // toast alert const { setToastAlert } = useToast(); // form info @@ -114,17 +117,30 @@ export const SignInOptionalSetPasswordForm: React.FC = (props) => { required: "Password is required", }} render={({ field: { value, onChange, ref } }) => ( - +
+ + {showPassword ? ( + setShowPassword(false)} + /> + ) : ( + setShowPassword(true)} + /> + )} +
)} />

diff --git a/web/components/account/sign-in-forms/password.tsx b/web/components/account/sign-in-forms/password.tsx index fd4ccbf40..fe20d5b10 100644 --- a/web/components/account/sign-in-forms/password.tsx +++ b/web/components/account/sign-in-forms/password.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import Link from "next/link"; import { observer } from "mobx-react-lite"; import { Controller, useForm } from "react-hook-form"; -import { XCircle } from "lucide-react"; +import { Eye, EyeOff, XCircle } from "lucide-react"; // services import { AuthService } from "services/auth.service"; // hooks @@ -40,6 +40,7 @@ export const SignInPasswordForm: React.FC = observer((props) => { const { email, handleStepChange, handleEmailClear, onSubmit } = props; // states const [isSendingUniqueCode, setIsSendingUniqueCode] = useState(false); + const [showPassword, setShowPassword] = useState(false); // toast alert const { setToastAlert } = useToast(); const { @@ -157,15 +158,28 @@ export const SignInPasswordForm: React.FC = observer((props) => { required: "Password is required", }} render={({ field: { value, onChange } }) => ( - +

+ + {showPassword ? ( + setShowPassword(false)} + /> + ) : ( + setShowPassword(true)} + /> + )} +
)} />
diff --git a/web/components/account/sign-up-forms/optional-set-password.tsx b/web/components/account/sign-up-forms/optional-set-password.tsx index 38fdaeca1..db14f0ccb 100644 --- a/web/components/account/sign-up-forms/optional-set-password.tsx +++ b/web/components/account/sign-up-forms/optional-set-password.tsx @@ -10,6 +10,8 @@ import { Button, Input } from "@plane/ui"; import { checkEmailValidity } from "helpers/string.helper"; // constants import { ESignUpSteps } from "components/account"; +// icons +import { Eye, EyeOff } from "lucide-react"; type Props = { email: string; @@ -34,6 +36,7 @@ export const SignUpOptionalSetPasswordForm: React.FC = (props) => { const { email, handleSignInRedirection } = props; // states const [isGoingToWorkspace, setIsGoingToWorkspace] = useState(false); + const [showPassword, setShowPassword] = useState(false); // toast alert const { setToastAlert } = useToast(); // form info @@ -119,16 +122,29 @@ export const SignUpOptionalSetPasswordForm: React.FC = (props) => { required: "Password is required", }} render={({ field: { value, onChange } }) => ( - +
+ + {showPassword ? ( + setShowPassword(false)} + /> + ) : ( + setShowPassword(true)} + /> + )} +
)} />

diff --git a/web/components/account/sign-up-forms/password.tsx b/web/components/account/sign-up-forms/password.tsx index 6ff6753df..293e03ef8 100644 --- a/web/components/account/sign-up-forms/password.tsx +++ b/web/components/account/sign-up-forms/password.tsx @@ -1,8 +1,8 @@ -import React from "react"; +import React, { useState } from "react"; import Link from "next/link"; import { observer } from "mobx-react-lite"; import { Controller, useForm } from "react-hook-form"; -import { XCircle } from "lucide-react"; +import { Eye, EyeOff, XCircle } from "lucide-react"; // services import { AuthService } from "services/auth.service"; // hooks @@ -32,6 +32,8 @@ const authService = new AuthService(); export const SignUpPasswordForm: React.FC = observer((props) => { const { onSubmit } = props; + // states + const [showPassword, setShowPassword] = useState(false); // toast alert const { setToastAlert } = useToast(); // form info @@ -112,15 +114,28 @@ export const SignUpPasswordForm: React.FC = observer((props) => { required: "Password is required", }} render={({ field: { value, onChange } }) => ( - +

+ + {showPassword ? ( + setShowPassword(false)} + /> + ) : ( + setShowPassword(true)} + /> + )} +
)} />

diff --git a/web/components/instance/setup-form/sign-in-form.tsx b/web/components/instance/setup-form/sign-in-form.tsx index f34d807c9..c4a9de6a3 100644 --- a/web/components/instance/setup-form/sign-in-form.tsx +++ b/web/components/instance/setup-form/sign-in-form.tsx @@ -1,6 +1,6 @@ -import { FC } from "react"; +import { FC, useState } from "react"; import { useForm, Controller } from "react-hook-form"; -import { XCircle } from "lucide-react"; +import { Eye, EyeOff, XCircle } from "lucide-react"; // hooks import { useUser } from "hooks/store"; // ui @@ -24,6 +24,8 @@ export interface IInstanceSetupEmailForm { export const InstanceSetupSignInForm: FC = (props) => { const { handleNextStep } = props; + // states + const [showPassword, setShowPassword] = useState(false); // store hooks const { fetchCurrentUser } = useUser(); // form info @@ -107,14 +109,27 @@ export const InstanceSetupSignInForm: FC = (props) => { required: "Password is required", }} render={({ field: { value, onChange } }) => ( - +

+ + {showPassword ? ( + setShowPassword(false)} + /> + ) : ( + setShowPassword(true)} + /> + )} +
)} />

diff --git a/web/pages/accounts/reset-password.tsx b/web/pages/accounts/reset-password.tsx index 2b893d665..9854ec5bb 100644 --- a/web/pages/accounts/reset-password.tsx +++ b/web/pages/accounts/reset-password.tsx @@ -1,4 +1,4 @@ -import { ReactElement } from "react"; +import { ReactElement, useState } from "react"; import Image from "next/image"; import { useRouter } from "next/router"; import { Controller, useForm } from "react-hook-form"; @@ -19,6 +19,8 @@ import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; import { checkEmailValidity } from "helpers/string.helper"; // type import { NextPageWithLayout } from "lib/types"; +// icons +import { Eye, EyeOff } from "lucide-react"; type TResetPasswordFormValues = { email: string; @@ -37,6 +39,8 @@ const ResetPasswordPage: NextPageWithLayout = () => { // router const router = useRouter(); const { uidb64, token, email } = router.query; + // states + const [showPassword, setShowPassword] = useState(false); // toast const { setToastAlert } = useToast(); // sign in redirection hook @@ -117,15 +121,28 @@ const ResetPasswordPage: NextPageWithLayout = () => { required: "Password is required", }} render={({ field: { value, onChange } }) => ( - +

+ + {showPassword ? ( + setShowPassword(false)} + /> + ) : ( + setShowPassword(true)} + /> + )} +
)} />