diff --git a/web/components/command-palette/actions/workspace-settings-actions.tsx b/web/components/command-palette/actions/workspace-settings-actions.tsx index 84e62593a..7503343ee 100644 --- a/web/components/command-palette/actions/workspace-settings-actions.tsx +++ b/web/components/command-palette/actions/workspace-settings-actions.tsx @@ -2,6 +2,7 @@ import { useRouter } from "next/router"; import { Command } from "cmdk"; // icons import { SettingIcon } from "components/icons"; +import Link from "next/link"; type Props = { closePalette: () => void; @@ -13,48 +14,55 @@ export const CommandPaletteWorkspaceSettingsActions: React.FC = (props) = const router = useRouter(); const { workspaceSlug } = router.query; - const redirect = (path: string) => { - closePalette(); - router.push(path); - }; - return ( <> - redirect(`/${workspaceSlug}/settings`)} className="focus:outline-none"> -
- - General -
+ + +
+ + General +
+
- redirect(`/${workspaceSlug}/settings/members`)} className="focus:outline-none"> -
- - Members -
+ + +
+ + Members +
+
- redirect(`/${workspaceSlug}/settings/billing`)} className="focus:outline-none"> -
- - Billing and Plans -
+ + +
+ + Billing and Plans +
+
- redirect(`/${workspaceSlug}/settings/integrations`)} className="focus:outline-none"> -
- - Integrations -
+ + +
+ + Integrations +
+
- redirect(`/${workspaceSlug}/settings/imports`)} className="focus:outline-none"> -
- - Import -
+ + +
+ + Import +
+
- redirect(`/${workspaceSlug}/settings/exports`)} className="focus:outline-none"> -
- - Export -
+ + +
+ + Export +
+
); diff --git a/web/components/core/sidebar/progress-chart.tsx b/web/components/core/sidebar/progress-chart.tsx index 9e9a4bac8..274e5da0c 100644 --- a/web/components/core/sidebar/progress-chart.tsx +++ b/web/components/core/sidebar/progress-chart.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { eachDayOfInterval } from "date-fns"; +import { eachDayOfInterval, isValid } from "date-fns"; // ui import { LineGraph } from "components/ui"; // helpers @@ -47,7 +47,13 @@ const ProgressChart: React.FC = ({ distribution, startDate, endDate, tota })); const generateXAxisTickValues = () => { - const dates = eachDayOfInterval({ start: new Date(startDate), end: new Date(endDate) }); + const start = new Date(startDate); + const end = new Date(endDate); + + let dates: Date[] = []; + if (isValid(start) && isValid(end)) { + dates = eachDayOfInterval({ start, end }); + } const maxDates = 4; const totalDates = dates.length; diff --git a/web/components/cycles/active-cycle-details.tsx b/web/components/cycles/active-cycle-details.tsx index 5ef912572..eb06c693a 100644 --- a/web/components/cycles/active-cycle-details.tsx +++ b/web/components/cycles/active-cycle-details.tsx @@ -33,6 +33,7 @@ import { truncateText } from "helpers/string.helper"; import { ICycle } from "@plane/types"; import { EIssuesStoreType } from "constants/issue"; import { ACTIVE_CYCLE_ISSUES } from "store/issue/cycle"; +import { CYCLE_ISSUES_WITH_PARAMS } from "constants/fetch-keys"; const stateGroups = [ { @@ -73,7 +74,7 @@ export const ActiveCycleDetails: React.FC = observer((props const { workspaceSlug, projectId } = props; const { - issues: { issues }, + issues: { issues, fetchActiveCycleIssues }, issueMap, } = useIssues(EIssuesStoreType.CYCLE); // store hooks @@ -99,13 +100,14 @@ export const ActiveCycleDetails: React.FC = observer((props const activeCycle = currentProjectActiveCycleId ? getActiveCycleById(currentProjectActiveCycleId) : null; const issueIds = issues?.[ACTIVE_CYCLE_ISSUES]; - // useSWR( - // workspaceSlug && projectId && cycleId ? CYCLE_ISSUES_WITH_PARAMS(cycleId, { priority: "urgent,high" }) : null, - // workspaceSlug && projectId && cycleId - // ? () => - // fetchActiveCycleIssues(workspaceSlug, projectId, ) - // : null - // ); + useSWR( + workspaceSlug && projectId && currentProjectActiveCycleId + ? CYCLE_ISSUES_WITH_PARAMS(currentProjectActiveCycleId, { priority: "urgent,high" }) + : null, + workspaceSlug && projectId && currentProjectActiveCycleId + ? () => fetchActiveCycleIssues(workspaceSlug, projectId, currentProjectActiveCycleId) + : null + ); if (!activeCycle && isLoading) return ( @@ -382,9 +384,9 @@ export const ActiveCycleDetails: React.FC = observer((props {issueIds ? ( issueIds.length > 0 ? ( issueIds.map((issue: any) => ( -
router.push(`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`)} + href={`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`} className="flex cursor-pointer flex-wrap items-center justify-between gap-2 rounded-md border border-custom-border-200 bg-custom-background-90 px-3 py-1.5" >
@@ -427,7 +429,7 @@ export const ActiveCycleDetails: React.FC = observer((props )}
- + )) ) : (
diff --git a/web/components/headers/cycle-issues.tsx b/web/components/headers/cycle-issues.tsx index 7873ea691..a616e4470 100644 --- a/web/components/headers/cycle-issues.tsx +++ b/web/components/headers/cycle-issues.tsx @@ -1,6 +1,7 @@ import { useCallback, useState } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; +import Link from "next/link"; // hooks import { useApplication, @@ -41,14 +42,11 @@ const CycleDropdownOption: React.FC<{ cycleId: string }> = ({ cycleId }) => { if (!cycle) return null; return ( - router.push(`/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`)} - > -
+ + {truncateText(cycle.name, 40)} -
+
); }; diff --git a/web/components/headers/module-issues.tsx b/web/components/headers/module-issues.tsx index 3a607a4df..9cfc9c1b3 100644 --- a/web/components/headers/module-issues.tsx +++ b/web/components/headers/module-issues.tsx @@ -1,6 +1,7 @@ import { useCallback, useState } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; +import Link from "next/link"; // hooks import { useApplication, @@ -41,14 +42,14 @@ const ModuleDropdownOption: React.FC<{ moduleId: string }> = ({ moduleId }) => { if (!moduleDetail) return null; return ( - router.push(`/${workspaceSlug}/projects/${projectId}/modules/${moduleDetail.id}`)} - > -
+ + {truncateText(moduleDetail.name, 40)} -
+
); }; diff --git a/web/components/headers/project-view-issues.tsx b/web/components/headers/project-view-issues.tsx index d53880a53..48e38491b 100644 --- a/web/components/headers/project-view-issues.tsx +++ b/web/components/headers/project-view-issues.tsx @@ -2,6 +2,7 @@ import { useCallback } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { Plus } from "lucide-react"; +import Link from "next/link"; // hooks import { useApplication, @@ -154,14 +155,14 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => { if (!view) return; return ( - router.push(`/${workspaceSlug}/projects/${projectId}/views/${viewId}`)} - > -
+ + {truncateText(view.name, 40)} -
+
); })} diff --git a/web/components/notifications/notification-card.tsx b/web/components/notifications/notification-card.tsx index 6e20da193..58b79ac3e 100644 --- a/web/components/notifications/notification-card.tsx +++ b/web/components/notifications/notification-card.tsx @@ -2,6 +2,7 @@ import React from "react"; import Image from "next/image"; import { useRouter } from "next/router"; import { ArchiveRestore, Clock, MessageSquare, User2 } from "lucide-react"; +import Link from "next/link"; // hooks import useToast from "hooks/use-toast"; // icons @@ -10,17 +11,14 @@ import { ArchiveIcon, CustomMenu, Tooltip } from "@plane/ui"; import { snoozeOptions } from "constants/notification"; // helper import { replaceUnderscoreIfSnakeCase, truncateText, stripAndTruncateHTML } from "helpers/string.helper"; -import { - calculateTimeAgo, - renderFormattedTime, - renderFormattedDate, -} from "helpers/date-time.helper"; +import { calculateTimeAgo, renderFormattedTime, renderFormattedDate } from "helpers/date-time.helper"; // type import type { IUserNotification } from "@plane/types"; type NotificationCardProps = { notification: IUserNotification; isSnoozedTabOpen: boolean; + closePopover: () => void; markNotificationReadStatus: (notificationId: string) => Promise; markNotificationReadStatusToggle: (notificationId: string) => Promise; markNotificationArchivedStatus: (notificationId: string) => Promise; @@ -32,6 +30,7 @@ export const NotificationCard: React.FC = (props) => { const { notification, isSnoozedTabOpen, + closePopover, markNotificationReadStatus, markNotificationReadStatusToggle, markNotificationArchivedStatus, @@ -47,15 +46,14 @@ export const NotificationCard: React.FC = (props) => { if (isSnoozedTabOpen && new Date(notification.snoozed_till!) < new Date()) return null; return ( -
{ markNotificationReadStatus(notification.id); - router.push( - `/${workspaceSlug}/projects/${notification.project}/${ - notification.data.issue_activity.field === "archived_at" ? "archived-issues" : "issues" - }/${notification.data.issue.id}` - ); + closePopover(); }} + href={`/${workspaceSlug}/projects/${notification.project}/${ + notification.data.issue_activity.field === "archived_at" ? "archived-issues" : "issues" + }/${notification.data.issue.id}`} className={`group relative flex w-full cursor-pointer items-center gap-4 p-3 pl-6 ${ notification.read_at === null ? "bg-custom-primary-70/5" : "hover:bg-custom-background-200" }`} @@ -149,7 +147,8 @@ export const NotificationCard: React.FC = (props) => {

- Till {renderFormattedDate(notification.snoozed_till)}, {renderFormattedTime(notification.snoozed_till, '12-hour')} + Till {renderFormattedDate(notification.snoozed_till)},{" "} + {renderFormattedTime(notification.snoozed_till, "12-hour")}

) : ( @@ -195,6 +194,8 @@ export const NotificationCard: React.FC = (props) => { type="button" onClick={(e) => { e.stopPropagation(); + e.preventDefault(); + item.onClick(); }} key={item.id} @@ -204,7 +205,6 @@ export const NotificationCard: React.FC = (props) => { ))} - = (props) => { key={item.label} onClick={(e) => { e.stopPropagation(); + e.preventDefault(); if (!item.value) { setSelectedNotificationForSnooze(notification.id); @@ -243,6 +244,6 @@ export const NotificationCard: React.FC = (props) => {
-
+ ); }; diff --git a/web/components/notifications/notification-popover.tsx b/web/components/notifications/notification-popover.tsx index d3dc541b3..4b55ea4cb 100644 --- a/web/components/notifications/notification-popover.tsx +++ b/web/components/notifications/notification-popover.tsx @@ -119,6 +119,7 @@ export const NotificationPopover = observer(() => { = observer((props) => { }); }; - const projectMembersIds = project.members.map((member) => member.member_id); + const projectMembersIds = project.members?.map((member) => member.member_id); return ( <> @@ -178,7 +179,7 @@ export const ProjectCard: React.FC = observer((props) => { } position="top" > - {projectMembersIds.length > 0 ? ( + {projectMembersIds && projectMembersIds.length > 0 ? (
{projectMembersIds.map((memberId) => { @@ -195,17 +196,15 @@ export const ProjectCard: React.FC = observer((props) => { )} {(isOwner || isMember) && ( - + )} {!project.is_member ? ( diff --git a/web/components/ui/empty-space.tsx b/web/components/ui/empty-space.tsx index 4c5e94736..4b70bbb15 100644 --- a/web/components/ui/empty-space.tsx +++ b/web/components/ui/empty-space.tsx @@ -45,28 +45,39 @@ type EmptySpaceItemProps = { title: string; description?: React.ReactNode | string; Icon: any; - action: () => void; + action?: () => void; + href?: string; }; -const EmptySpaceItem: React.FC = ({ title, description, Icon, action }) => ( - <> -
  • -
    -
    - - -
    -
    -
    {title}
    - {description ?
    {description}
    : null} -
    -
    -
    +const EmptySpaceItem: React.FC = ({ title, description, Icon, action, href }) => { + let spaceItem = ( +
    +
    + +
    -
  • - -); +
    +
    {title}
    + {description ?
    {description}
    : null} +
    +
    +
    +
    + ); + + if (href) { + spaceItem = {spaceItem}; + } + + return ( + <> +
  • + {spaceItem} +
  • + + ); +}; export { EmptySpace, EmptySpaceItem }; diff --git a/web/layouts/settings-layout/project/layout.tsx b/web/layouts/settings-layout/project/layout.tsx index 2abeb1976..c029643bf 100644 --- a/web/layouts/settings-layout/project/layout.tsx +++ b/web/layouts/settings-layout/project/layout.tsx @@ -1,6 +1,7 @@ import { FC, ReactNode } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; +import Link from "next/link"; // hooks import { useUser } from "hooks/store"; // components @@ -31,14 +32,12 @@ export const ProjectSettingLayout: FC = observer((props) } - onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}/issues`)} - > - Go to issues - + //TODO: Create a new component called Button Link to handle such scenarios + + + } /> ) : ( diff --git a/web/pages/create-workspace.tsx b/web/pages/create-workspace.tsx index 458a595cc..10eb11f55 100644 --- a/web/pages/create-workspace.tsx +++ b/web/pages/create-workspace.tsx @@ -3,6 +3,7 @@ import { useRouter } from "next/router"; import Image from "next/image"; import { useTheme } from "next-themes"; import { observer } from "mobx-react-lite"; +import Link from "next/link"; // hooks import { useUser } from "hooks/store"; // layouts @@ -39,9 +40,9 @@ const CreateWorkspacePage: NextPageWithLayout = observer(() => {
    - +
    {currentUser?.email}
    diff --git a/web/pages/workspace-invitations/index.tsx b/web/pages/workspace-invitations/index.tsx index 331441935..aa95d0a38 100644 --- a/web/pages/workspace-invitations/index.tsx +++ b/web/pages/workspace-invitations/index.tsx @@ -81,7 +81,7 @@ const WorkspaceInvitationPage: NextPageWithLayout = observer(() => { title={`You are already a member of ${invitationDetail.workspace.name}`} description="Your workspace is where you'll create projects, collaborate on your issues, and organize different streams of work in your Plane account." > - router.push("/")} /> + ) : ( @@ -103,35 +103,15 @@ const WorkspaceInvitationPage: NextPageWithLayout = observer(() => { link={{ text: "Or start from an empty project", href: "/" }} > {!currentUser ? ( - { - router.push("/"); - }} - /> + ) : ( - { - router.push("/"); - }} - /> + )} - { - router.push("https://github.com/makeplane"); - }} - /> + { - router.push("https://discord.com/invite/8SR2N9PAcJ"); - }} + href="https://discord.com/invite/8SR2N9PAcJ" /> ) : ( diff --git a/web/store/cycle.store.ts b/web/store/cycle.store.ts index 53c3bce60..5b663f79b 100644 --- a/web/store/cycle.store.ts +++ b/web/store/cycle.store.ts @@ -14,7 +14,7 @@ import { CycleService } from "services/cycle.service"; export interface ICycleStore { // observables cycleMap: Record; - activeCycleMap: Record; // TODO: Merge these two into single map + activeCycleIdMap: Record; // computed currentProjectCycleIds: string[] | null; currentProjectCompletedCycleIds: string[] | null; @@ -49,7 +49,7 @@ export interface ICycleStore { export class CycleStore implements ICycleStore { // observables cycleMap: Record = {}; - activeCycleMap: Record = {}; + activeCycleIdMap: Record = {}; // root store rootStore; // services @@ -61,7 +61,7 @@ export class CycleStore implements ICycleStore { makeObservable(this, { // observables cycleMap: observable, - activeCycleMap: observable, + activeCycleIdMap: observable, // computed currentProjectCycleIds: computed, currentProjectCompletedCycleIds: computed, @@ -168,8 +168,8 @@ export class CycleStore implements ICycleStore { get currentProjectActiveCycleId() { const projectId = this.rootStore.app.router.projectId; if (!projectId) return null; - const activeCycle = Object.keys(this.activeCycleMap ?? {}).find( - (cycleId) => this.activeCycleMap?.[cycleId]?.project === projectId + const activeCycle = Object.keys(this.activeCycleIdMap ?? {}).find( + (cycleId) => this.cycleMap?.[cycleId]?.project === projectId ); return activeCycle || null; } @@ -186,7 +186,8 @@ export class CycleStore implements ICycleStore { * @param cycleId * @returns */ - getActiveCycleById = (cycleId: string): ICycle | null => this.activeCycleMap?.[cycleId] ?? null; + getActiveCycleById = (cycleId: string): ICycle | null => + this.activeCycleIdMap?.[cycleId] && this.cycleMap?.[cycleId] ? this.cycleMap?.[cycleId] : null; /** * @description returns list of cycle ids of the project id passed as argument @@ -235,7 +236,8 @@ export class CycleStore implements ICycleStore { await this.cycleService.getCyclesWithParams(workspaceSlug, projectId, "current").then((response) => { runInAction(() => { response.forEach((cycle) => { - set(this.activeCycleMap, [cycle.id], cycle); + set(this.activeCycleIdMap, [cycle.id], true); + set(this.cycleMap, [cycle.id], cycle); }); }); return response; @@ -252,7 +254,6 @@ export class CycleStore implements ICycleStore { await this.cycleService.getCycleDetails(workspaceSlug, projectId, cycleId).then((response) => { runInAction(() => { set(this.cycleMap, [response.id], { ...this.cycleMap?.[response.id], ...response }); - set(this.activeCycleMap, [response.id], { ...this.activeCycleMap?.[response.id], ...response }); }); return response; }); @@ -268,7 +269,6 @@ export class CycleStore implements ICycleStore { await this.cycleService.createCycle(workspaceSlug, projectId, data).then((response) => { runInAction(() => { set(this.cycleMap, [response.id], response); - set(this.activeCycleMap, [response.id], response); }); return response; }); @@ -285,7 +285,6 @@ export class CycleStore implements ICycleStore { try { runInAction(() => { set(this.cycleMap, [cycleId], { ...this.cycleMap?.[cycleId], ...data }); - set(this.activeCycleMap, [cycleId], { ...this.activeCycleMap?.[cycleId], ...data }); }); const response = await this.cycleService.patchCycle(workspaceSlug, projectId, cycleId, data); return response; @@ -307,7 +306,7 @@ export class CycleStore implements ICycleStore { await this.cycleService.deleteCycle(workspaceSlug, projectId, cycleId).then(() => { runInAction(() => { delete this.cycleMap[cycleId]; - delete this.activeCycleMap[cycleId]; + delete this.activeCycleIdMap[cycleId]; }); }); @@ -324,7 +323,6 @@ export class CycleStore implements ICycleStore { try { runInAction(() => { if (currentCycle) set(this.cycleMap, [cycleId, "is_favorite"], true); - if (currentActiveCycle) set(this.activeCycleMap, [cycleId, "is_favorite"], true); }); // updating through api. const response = await this.cycleService.addCycleToFavorites(workspaceSlug, projectId, { cycle: cycleId }); @@ -332,7 +330,6 @@ export class CycleStore implements ICycleStore { } catch (error) { runInAction(() => { if (currentCycle) set(this.cycleMap, [cycleId, "is_favorite"], false); - if (currentActiveCycle) set(this.activeCycleMap, [cycleId, "is_favorite"], false); }); throw error; } @@ -351,14 +348,12 @@ export class CycleStore implements ICycleStore { try { runInAction(() => { if (currentCycle) set(this.cycleMap, [cycleId, "is_favorite"], false); - if (currentActiveCycle) set(this.activeCycleMap, [cycleId, "is_favorite"], false); }); const response = await this.cycleService.removeCycleFromFavorites(workspaceSlug, projectId, cycleId); return response; } catch (error) { runInAction(() => { if (currentCycle) set(this.cycleMap, [cycleId, "is_favorite"], true); - if (currentActiveCycle) set(this.activeCycleMap, [cycleId, "is_favorite"], true); }); throw error; }