mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
fix: breaking cycle issues and replacing router.push with Links (#3330)
* fix cycle creation and active cycle map * minor fix in cycle store * create cycle breaking fix * replace last possible bits of router.push with Link --------- Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
This commit is contained in:
parent
a71491ecb9
commit
eccb1f5d10
@ -2,6 +2,7 @@ import { useRouter } from "next/router";
|
|||||||
import { Command } from "cmdk";
|
import { Command } from "cmdk";
|
||||||
// icons
|
// icons
|
||||||
import { SettingIcon } from "components/icons";
|
import { SettingIcon } from "components/icons";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
closePalette: () => void;
|
closePalette: () => void;
|
||||||
@ -13,48 +14,55 @@ export const CommandPaletteWorkspaceSettingsActions: React.FC<Props> = (props) =
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
const redirect = (path: string) => {
|
|
||||||
closePalette();
|
|
||||||
router.push(path);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Command.Item onSelect={() => redirect(`/${workspaceSlug}/settings`)} className="focus:outline-none">
|
<Command.Item onSelect={closePalette} className="focus:outline-none">
|
||||||
|
<Link href={`/${workspaceSlug}/settings`}>
|
||||||
<div className="flex items-center gap-2 text-custom-text-200">
|
<div className="flex items-center gap-2 text-custom-text-200">
|
||||||
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
||||||
General
|
General
|
||||||
</div>
|
</div>
|
||||||
|
</Link>
|
||||||
</Command.Item>
|
</Command.Item>
|
||||||
<Command.Item onSelect={() => redirect(`/${workspaceSlug}/settings/members`)} className="focus:outline-none">
|
<Command.Item onSelect={closePalette} className="focus:outline-none">
|
||||||
|
<Link href={`/${workspaceSlug}/settings/members`}>
|
||||||
<div className="flex items-center gap-2 text-custom-text-200">
|
<div className="flex items-center gap-2 text-custom-text-200">
|
||||||
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
||||||
Members
|
Members
|
||||||
</div>
|
</div>
|
||||||
|
</Link>
|
||||||
</Command.Item>
|
</Command.Item>
|
||||||
<Command.Item onSelect={() => redirect(`/${workspaceSlug}/settings/billing`)} className="focus:outline-none">
|
<Command.Item onSelect={closePalette} className="focus:outline-none">
|
||||||
|
<Link href={`/${workspaceSlug}/settings/billing`}>
|
||||||
<div className="flex items-center gap-2 text-custom-text-200">
|
<div className="flex items-center gap-2 text-custom-text-200">
|
||||||
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
||||||
Billing and Plans
|
Billing and Plans
|
||||||
</div>
|
</div>
|
||||||
|
</Link>
|
||||||
</Command.Item>
|
</Command.Item>
|
||||||
<Command.Item onSelect={() => redirect(`/${workspaceSlug}/settings/integrations`)} className="focus:outline-none">
|
<Command.Item onSelect={closePalette} className="focus:outline-none">
|
||||||
|
<Link href={`/${workspaceSlug}/settings/integrations`}>
|
||||||
<div className="flex items-center gap-2 text-custom-text-200">
|
<div className="flex items-center gap-2 text-custom-text-200">
|
||||||
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
||||||
Integrations
|
Integrations
|
||||||
</div>
|
</div>
|
||||||
|
</Link>
|
||||||
</Command.Item>
|
</Command.Item>
|
||||||
<Command.Item onSelect={() => redirect(`/${workspaceSlug}/settings/imports`)} className="focus:outline-none">
|
<Command.Item onSelect={closePalette} className="focus:outline-none">
|
||||||
|
<Link href={`/${workspaceSlug}/settings/imports`}>
|
||||||
<div className="flex items-center gap-2 text-custom-text-200">
|
<div className="flex items-center gap-2 text-custom-text-200">
|
||||||
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
||||||
Import
|
Import
|
||||||
</div>
|
</div>
|
||||||
|
</Link>
|
||||||
</Command.Item>
|
</Command.Item>
|
||||||
<Command.Item onSelect={() => redirect(`/${workspaceSlug}/settings/exports`)} className="focus:outline-none">
|
<Command.Item onSelect={closePalette} className="focus:outline-none">
|
||||||
|
<Link href={`/${workspaceSlug}/settings/exports`}>
|
||||||
<div className="flex items-center gap-2 text-custom-text-200">
|
<div className="flex items-center gap-2 text-custom-text-200">
|
||||||
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
||||||
Export
|
Export
|
||||||
</div>
|
</div>
|
||||||
|
</Link>
|
||||||
</Command.Item>
|
</Command.Item>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { eachDayOfInterval } from "date-fns";
|
import { eachDayOfInterval, isValid } from "date-fns";
|
||||||
// ui
|
// ui
|
||||||
import { LineGraph } from "components/ui";
|
import { LineGraph } from "components/ui";
|
||||||
// helpers
|
// helpers
|
||||||
@ -47,7 +47,13 @@ const ProgressChart: React.FC<Props> = ({ distribution, startDate, endDate, tota
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const generateXAxisTickValues = () => {
|
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 maxDates = 4;
|
||||||
const totalDates = dates.length;
|
const totalDates = dates.length;
|
||||||
|
@ -33,6 +33,7 @@ import { truncateText } from "helpers/string.helper";
|
|||||||
import { ICycle } from "@plane/types";
|
import { ICycle } from "@plane/types";
|
||||||
import { EIssuesStoreType } from "constants/issue";
|
import { EIssuesStoreType } from "constants/issue";
|
||||||
import { ACTIVE_CYCLE_ISSUES } from "store/issue/cycle";
|
import { ACTIVE_CYCLE_ISSUES } from "store/issue/cycle";
|
||||||
|
import { CYCLE_ISSUES_WITH_PARAMS } from "constants/fetch-keys";
|
||||||
|
|
||||||
const stateGroups = [
|
const stateGroups = [
|
||||||
{
|
{
|
||||||
@ -73,7 +74,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
|||||||
const { workspaceSlug, projectId } = props;
|
const { workspaceSlug, projectId } = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
issues: { issues },
|
issues: { issues, fetchActiveCycleIssues },
|
||||||
issueMap,
|
issueMap,
|
||||||
} = useIssues(EIssuesStoreType.CYCLE);
|
} = useIssues(EIssuesStoreType.CYCLE);
|
||||||
// store hooks
|
// store hooks
|
||||||
@ -99,13 +100,14 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
|||||||
const activeCycle = currentProjectActiveCycleId ? getActiveCycleById(currentProjectActiveCycleId) : null;
|
const activeCycle = currentProjectActiveCycleId ? getActiveCycleById(currentProjectActiveCycleId) : null;
|
||||||
const issueIds = issues?.[ACTIVE_CYCLE_ISSUES];
|
const issueIds = issues?.[ACTIVE_CYCLE_ISSUES];
|
||||||
|
|
||||||
// useSWR(
|
useSWR(
|
||||||
// workspaceSlug && projectId && cycleId ? CYCLE_ISSUES_WITH_PARAMS(cycleId, { priority: "urgent,high" }) : null,
|
workspaceSlug && projectId && currentProjectActiveCycleId
|
||||||
// workspaceSlug && projectId && cycleId
|
? CYCLE_ISSUES_WITH_PARAMS(currentProjectActiveCycleId, { priority: "urgent,high" })
|
||||||
// ? () =>
|
: null,
|
||||||
// fetchActiveCycleIssues(workspaceSlug, projectId, )
|
workspaceSlug && projectId && currentProjectActiveCycleId
|
||||||
// : null
|
? () => fetchActiveCycleIssues(workspaceSlug, projectId, currentProjectActiveCycleId)
|
||||||
// );
|
: null
|
||||||
|
);
|
||||||
|
|
||||||
if (!activeCycle && isLoading)
|
if (!activeCycle && isLoading)
|
||||||
return (
|
return (
|
||||||
@ -382,9 +384,9 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
|||||||
{issueIds ? (
|
{issueIds ? (
|
||||||
issueIds.length > 0 ? (
|
issueIds.length > 0 ? (
|
||||||
issueIds.map((issue: any) => (
|
issueIds.map((issue: any) => (
|
||||||
<div
|
<Link
|
||||||
key={issue.id}
|
key={issue.id}
|
||||||
onClick={() => 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"
|
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"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
@ -427,7 +429,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Link>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="grid place-items-center text-center text-sm text-custom-text-200">
|
<div className="grid place-items-center text-center text-sm text-custom-text-200">
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import Link from "next/link";
|
||||||
// hooks
|
// hooks
|
||||||
import {
|
import {
|
||||||
useApplication,
|
useApplication,
|
||||||
@ -41,14 +42,11 @@ const CycleDropdownOption: React.FC<{ cycleId: string }> = ({ cycleId }) => {
|
|||||||
if (!cycle) return null;
|
if (!cycle) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem key={cycle.id}>
|
||||||
key={cycle.id}
|
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`} className="flex items-center gap-1.5">
|
||||||
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
<ContrastIcon className="h-3 w-3" />
|
<ContrastIcon className="h-3 w-3" />
|
||||||
{truncateText(cycle.name, 40)}
|
{truncateText(cycle.name, 40)}
|
||||||
</div>
|
</Link>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import Link from "next/link";
|
||||||
// hooks
|
// hooks
|
||||||
import {
|
import {
|
||||||
useApplication,
|
useApplication,
|
||||||
@ -41,14 +42,14 @@ const ModuleDropdownOption: React.FC<{ moduleId: string }> = ({ moduleId }) => {
|
|||||||
if (!moduleDetail) return null;
|
if (!moduleDetail) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem key={moduleDetail.id}>
|
||||||
key={moduleDetail.id}
|
<Link
|
||||||
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}/modules/${moduleDetail.id}`)}
|
href={`/${workspaceSlug}/projects/${projectId}/modules/${moduleDetail.id}`}
|
||||||
|
className="flex items-center gap-1.5"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
<DiceIcon className="h-3 w-3" />
|
<DiceIcon className="h-3 w-3" />
|
||||||
{truncateText(moduleDetail.name, 40)}
|
{truncateText(moduleDetail.name, 40)}
|
||||||
</div>
|
</Link>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@ import { useCallback } from "react";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
// hooks
|
// hooks
|
||||||
import {
|
import {
|
||||||
useApplication,
|
useApplication,
|
||||||
@ -154,14 +155,14 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
|||||||
if (!view) return;
|
if (!view) return;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem key={viewId}>
|
||||||
key={viewId}
|
<Link
|
||||||
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}/views/${viewId}`)}
|
href={`/${workspaceSlug}/projects/${projectId}/views/${viewId}`}
|
||||||
|
className="flex items-center gap-1.5"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
<PhotoFilterIcon height={12} width={12} />
|
<PhotoFilterIcon height={12} width={12} />
|
||||||
{truncateText(view.name, 40)}
|
{truncateText(view.name, 40)}
|
||||||
</div>
|
</Link>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { ArchiveRestore, Clock, MessageSquare, User2 } from "lucide-react";
|
import { ArchiveRestore, Clock, MessageSquare, User2 } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// icons
|
// icons
|
||||||
@ -10,17 +11,14 @@ import { ArchiveIcon, CustomMenu, Tooltip } from "@plane/ui";
|
|||||||
import { snoozeOptions } from "constants/notification";
|
import { snoozeOptions } from "constants/notification";
|
||||||
// helper
|
// helper
|
||||||
import { replaceUnderscoreIfSnakeCase, truncateText, stripAndTruncateHTML } from "helpers/string.helper";
|
import { replaceUnderscoreIfSnakeCase, truncateText, stripAndTruncateHTML } from "helpers/string.helper";
|
||||||
import {
|
import { calculateTimeAgo, renderFormattedTime, renderFormattedDate } from "helpers/date-time.helper";
|
||||||
calculateTimeAgo,
|
|
||||||
renderFormattedTime,
|
|
||||||
renderFormattedDate,
|
|
||||||
} from "helpers/date-time.helper";
|
|
||||||
// type
|
// type
|
||||||
import type { IUserNotification } from "@plane/types";
|
import type { IUserNotification } from "@plane/types";
|
||||||
|
|
||||||
type NotificationCardProps = {
|
type NotificationCardProps = {
|
||||||
notification: IUserNotification;
|
notification: IUserNotification;
|
||||||
isSnoozedTabOpen: boolean;
|
isSnoozedTabOpen: boolean;
|
||||||
|
closePopover: () => void;
|
||||||
markNotificationReadStatus: (notificationId: string) => Promise<void>;
|
markNotificationReadStatus: (notificationId: string) => Promise<void>;
|
||||||
markNotificationReadStatusToggle: (notificationId: string) => Promise<void>;
|
markNotificationReadStatusToggle: (notificationId: string) => Promise<void>;
|
||||||
markNotificationArchivedStatus: (notificationId: string) => Promise<void>;
|
markNotificationArchivedStatus: (notificationId: string) => Promise<void>;
|
||||||
@ -32,6 +30,7 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
const {
|
const {
|
||||||
notification,
|
notification,
|
||||||
isSnoozedTabOpen,
|
isSnoozedTabOpen,
|
||||||
|
closePopover,
|
||||||
markNotificationReadStatus,
|
markNotificationReadStatus,
|
||||||
markNotificationReadStatusToggle,
|
markNotificationReadStatusToggle,
|
||||||
markNotificationArchivedStatus,
|
markNotificationArchivedStatus,
|
||||||
@ -47,15 +46,14 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
if (isSnoozedTabOpen && new Date(notification.snoozed_till!) < new Date()) return null;
|
if (isSnoozedTabOpen && new Date(notification.snoozed_till!) < new Date()) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Link
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
markNotificationReadStatus(notification.id);
|
markNotificationReadStatus(notification.id);
|
||||||
router.push(
|
closePopover();
|
||||||
`/${workspaceSlug}/projects/${notification.project}/${
|
|
||||||
notification.data.issue_activity.field === "archived_at" ? "archived-issues" : "issues"
|
|
||||||
}/${notification.data.issue.id}`
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
|
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 ${
|
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"
|
notification.read_at === null ? "bg-custom-primary-70/5" : "hover:bg-custom-background-200"
|
||||||
}`}
|
}`}
|
||||||
@ -149,7 +147,8 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
<p className="flex flex-shrink-0 items-center justify-end gap-x-1 text-custom-text-300">
|
<p className="flex flex-shrink-0 items-center justify-end gap-x-1 text-custom-text-300">
|
||||||
<Clock className="h-4 w-4" />
|
<Clock className="h-4 w-4" />
|
||||||
<span>
|
<span>
|
||||||
Till {renderFormattedDate(notification.snoozed_till)}, {renderFormattedTime(notification.snoozed_till, '12-hour')}
|
Till {renderFormattedDate(notification.snoozed_till)},{" "}
|
||||||
|
{renderFormattedTime(notification.snoozed_till, "12-hour")}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
@ -195,6 +194,8 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
item.onClick();
|
item.onClick();
|
||||||
}}
|
}}
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@ -204,7 +205,6 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<Tooltip tooltipContent="Snooze">
|
<Tooltip tooltipContent="Snooze">
|
||||||
<CustomMenu
|
<CustomMenu
|
||||||
className="flex items-center"
|
className="flex items-center"
|
||||||
@ -223,6 +223,7 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
key={item.label}
|
key={item.label}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
if (!item.value) {
|
if (!item.value) {
|
||||||
setSelectedNotificationForSnooze(notification.id);
|
setSelectedNotificationForSnooze(notification.id);
|
||||||
@ -243,6 +244,6 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -119,6 +119,7 @@ export const NotificationPopover = observer(() => {
|
|||||||
<NotificationCard
|
<NotificationCard
|
||||||
key={notification.id}
|
key={notification.id}
|
||||||
isSnoozedTabOpen={snoozed}
|
isSnoozedTabOpen={snoozed}
|
||||||
|
closePopover={closePopover}
|
||||||
notification={notification}
|
notification={notification}
|
||||||
markNotificationArchivedStatus={markNotificationArchivedStatus}
|
markNotificationArchivedStatus={markNotificationArchivedStatus}
|
||||||
markNotificationReadStatus={markNotificationAsRead}
|
markNotificationReadStatus={markNotificationAsRead}
|
||||||
|
@ -2,6 +2,7 @@ import React, { useState } from "react";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { LinkIcon, Lock, Pencil, Star } from "lucide-react";
|
import { LinkIcon, Lock, Pencil, Star } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProject } from "hooks/store";
|
import { useProject } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
@ -74,7 +75,7 @@ export const ProjectCard: React.FC<ProjectCardProps> = observer((props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const projectMembersIds = project.members.map((member) => member.member_id);
|
const projectMembersIds = project.members?.map((member) => member.member_id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -178,7 +179,7 @@ export const ProjectCard: React.FC<ProjectCardProps> = observer((props) => {
|
|||||||
}
|
}
|
||||||
position="top"
|
position="top"
|
||||||
>
|
>
|
||||||
{projectMembersIds.length > 0 ? (
|
{projectMembersIds && projectMembersIds.length > 0 ? (
|
||||||
<div className="flex cursor-pointer items-center gap-2 text-custom-text-200">
|
<div className="flex cursor-pointer items-center gap-2 text-custom-text-200">
|
||||||
<AvatarGroup showTooltip={false}>
|
<AvatarGroup showTooltip={false}>
|
||||||
{projectMembersIds.map((memberId) => {
|
{projectMembersIds.map((memberId) => {
|
||||||
@ -195,17 +196,15 @@ export const ProjectCard: React.FC<ProjectCardProps> = observer((props) => {
|
|||||||
)}
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{(isOwner || isMember) && (
|
{(isOwner || isMember) && (
|
||||||
<button
|
<Link
|
||||||
className="flex items-center justify-center rounded p-1 text-custom-text-400 hover:bg-custom-background-80 hover:text-custom-text-200"
|
className="flex items-center justify-center rounded p-1 text-custom-text-400 hover:bg-custom-background-80 hover:text-custom-text-200"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
router.push(`/${workspaceSlug}/projects/${project.id}/settings`);
|
|
||||||
}}
|
}}
|
||||||
|
href={`/${workspaceSlug}/projects/${project.id}/settings`}
|
||||||
>
|
>
|
||||||
<Pencil className="h-3.5 w-3.5" />
|
<Pencil className="h-3.5 w-3.5" />
|
||||||
</button>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!project.is_member ? (
|
{!project.is_member ? (
|
||||||
|
@ -45,12 +45,12 @@ type EmptySpaceItemProps = {
|
|||||||
title: string;
|
title: string;
|
||||||
description?: React.ReactNode | string;
|
description?: React.ReactNode | string;
|
||||||
Icon: any;
|
Icon: any;
|
||||||
action: () => void;
|
action?: () => void;
|
||||||
|
href?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const EmptySpaceItem: React.FC<EmptySpaceItemProps> = ({ title, description, Icon, action }) => (
|
const EmptySpaceItem: React.FC<EmptySpaceItemProps> = ({ title, description, Icon, action, href }) => {
|
||||||
<>
|
let spaceItem = (
|
||||||
<li className="cursor-pointer" onClick={action} role="button">
|
|
||||||
<div className={`group relative flex ${description ? "items-start" : "items-center"} space-x-3 py-4`}>
|
<div className={`group relative flex ${description ? "items-start" : "items-center"} space-x-3 py-4`}>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<span className="inline-flex h-10 w-10 items-center justify-center rounded-lg bg-custom-primary">
|
<span className="inline-flex h-10 w-10 items-center justify-center rounded-lg bg-custom-primary">
|
||||||
@ -65,8 +65,19 @@ const EmptySpaceItem: React.FC<EmptySpaceItemProps> = ({ title, description, Ico
|
|||||||
<ChevronRight className="h-5 w-5 text-custom-text-200 group-hover:text-custom-text-100" aria-hidden="true" />
|
<ChevronRight className="h-5 w-5 text-custom-text-200 group-hover:text-custom-text-100" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (href) {
|
||||||
|
spaceItem = <Link href={href}>{spaceItem}</Link>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<li className="cursor-pointer" onClick={action} role="button">
|
||||||
|
{spaceItem}
|
||||||
</li>
|
</li>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export { EmptySpace, EmptySpaceItem };
|
export { EmptySpace, EmptySpaceItem };
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { FC, ReactNode } from "react";
|
import { FC, ReactNode } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import Link from "next/link";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "hooks/store";
|
import { useUser } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
@ -31,14 +32,12 @@ export const ProjectSettingLayout: FC<IProjectSettingLayout> = observer((props)
|
|||||||
<NotAuthorizedView
|
<NotAuthorizedView
|
||||||
type="project"
|
type="project"
|
||||||
actionButton={
|
actionButton={
|
||||||
<Button
|
//TODO: Create a new component called Button Link to handle such scenarios
|
||||||
variant="primary"
|
<Link href={`/${workspaceSlug}/projects/${projectId}/issues`}>
|
||||||
size="md"
|
<Button variant="primary" size="md" prependIcon={<LayersIcon />}>
|
||||||
prependIcon={<LayersIcon />}
|
|
||||||
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}/issues`)}
|
|
||||||
>
|
|
||||||
Go to issues
|
Go to issues
|
||||||
</Button>
|
</Button>
|
||||||
|
</Link>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
@ -3,6 +3,7 @@ import { useRouter } from "next/router";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import Link from "next/link";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "hooks/store";
|
import { useUser } from "hooks/store";
|
||||||
// layouts
|
// layouts
|
||||||
@ -39,9 +40,9 @@ const CreateWorkspacePage: NextPageWithLayout = observer(() => {
|
|||||||
<div className="flex h-full flex-col gap-y-2 overflow-hidden sm:flex-row sm:gap-y-0">
|
<div className="flex h-full flex-col gap-y-2 overflow-hidden sm:flex-row sm:gap-y-0">
|
||||||
<div className="relative h-1/6 flex-shrink-0 sm:w-2/12 md:w-3/12 lg:w-1/5">
|
<div className="relative h-1/6 flex-shrink-0 sm:w-2/12 md:w-3/12 lg:w-1/5">
|
||||||
<div className="absolute left-0 top-1/2 h-[0.5px] w-full -translate-y-1/2 border-b-[0.5px] border-custom-border-200 sm:left-1/2 sm:top-0 sm:h-screen sm:w-[0.5px] sm:-translate-x-1/2 sm:translate-y-0 sm:border-r-[0.5px] md:left-1/3" />
|
<div className="absolute left-0 top-1/2 h-[0.5px] w-full -translate-y-1/2 border-b-[0.5px] border-custom-border-200 sm:left-1/2 sm:top-0 sm:h-screen sm:w-[0.5px] sm:-translate-x-1/2 sm:translate-y-0 sm:border-r-[0.5px] md:left-1/3" />
|
||||||
<button
|
<Link
|
||||||
className="absolute left-5 top-1/2 grid -translate-y-1/2 place-items-center bg-custom-background-100 px-3 sm:left-1/2 sm:top-12 sm:-translate-x-[15px] sm:translate-y-0 sm:px-0 sm:py-5 md:left-1/3"
|
className="absolute left-5 top-1/2 grid -translate-y-1/2 place-items-center bg-custom-background-100 px-3 sm:left-1/2 sm:top-12 sm:-translate-x-[15px] sm:translate-y-0 sm:px-0 sm:py-5 md:left-1/3"
|
||||||
onClick={() => router.push("/")}
|
href="/"
|
||||||
>
|
>
|
||||||
<div className="h-[30px] w-[133px]">
|
<div className="h-[30px] w-[133px]">
|
||||||
{theme === "light" ? (
|
{theme === "light" ? (
|
||||||
@ -50,7 +51,7 @@ const CreateWorkspacePage: NextPageWithLayout = observer(() => {
|
|||||||
<Image src={WhiteHorizontalLogo} alt="Plane white logo" />
|
<Image src={WhiteHorizontalLogo} alt="Plane white logo" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</Link>
|
||||||
<div className="absolute right-4 top-1/4 -translate-y-1/2 text-sm text-custom-text-100 sm:fixed sm:right-16 sm:top-12 sm:translate-y-0 sm:py-5">
|
<div className="absolute right-4 top-1/4 -translate-y-1/2 text-sm text-custom-text-100 sm:fixed sm:right-16 sm:top-12 sm:translate-y-0 sm:py-5">
|
||||||
{currentUser?.email}
|
{currentUser?.email}
|
||||||
</div>
|
</div>
|
||||||
|
@ -81,7 +81,7 @@ const WorkspaceInvitationPage: NextPageWithLayout = observer(() => {
|
|||||||
title={`You are already a member of ${invitationDetail.workspace.name}`}
|
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."
|
description="Your workspace is where you'll create projects, collaborate on your issues, and organize different streams of work in your Plane account."
|
||||||
>
|
>
|
||||||
<EmptySpaceItem Icon={Boxes} title="Continue to Dashboard" action={() => router.push("/")} />
|
<EmptySpaceItem Icon={Boxes} title="Continue to Dashboard" href="/" />
|
||||||
</EmptySpace>
|
</EmptySpace>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@ -103,35 +103,15 @@ const WorkspaceInvitationPage: NextPageWithLayout = observer(() => {
|
|||||||
link={{ text: "Or start from an empty project", href: "/" }}
|
link={{ text: "Or start from an empty project", href: "/" }}
|
||||||
>
|
>
|
||||||
{!currentUser ? (
|
{!currentUser ? (
|
||||||
<EmptySpaceItem
|
<EmptySpaceItem Icon={User2} title="Sign in to continue" href="/" />
|
||||||
Icon={User2}
|
|
||||||
title="Sign in to continue"
|
|
||||||
action={() => {
|
|
||||||
router.push("/");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<EmptySpaceItem
|
<EmptySpaceItem Icon={Boxes} title="Continue to Dashboard" href="/" />
|
||||||
Icon={Boxes}
|
|
||||||
title="Continue to Dashboard"
|
|
||||||
action={() => {
|
|
||||||
router.push("/");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<EmptySpaceItem
|
<EmptySpaceItem Icon={Star} title="Star us on GitHub" href="https://github.com/makeplane" />
|
||||||
Icon={Star}
|
|
||||||
title="Star us on GitHub"
|
|
||||||
action={() => {
|
|
||||||
router.push("https://github.com/makeplane");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<EmptySpaceItem
|
<EmptySpaceItem
|
||||||
Icon={Share2}
|
Icon={Share2}
|
||||||
title="Join our community of active creators"
|
title="Join our community of active creators"
|
||||||
action={() => {
|
href="https://discord.com/invite/8SR2N9PAcJ"
|
||||||
router.push("https://discord.com/invite/8SR2N9PAcJ");
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</EmptySpace>
|
</EmptySpace>
|
||||||
) : (
|
) : (
|
||||||
|
@ -14,7 +14,7 @@ import { CycleService } from "services/cycle.service";
|
|||||||
export interface ICycleStore {
|
export interface ICycleStore {
|
||||||
// observables
|
// observables
|
||||||
cycleMap: Record<string, ICycle>;
|
cycleMap: Record<string, ICycle>;
|
||||||
activeCycleMap: Record<string, ICycle>; // TODO: Merge these two into single map
|
activeCycleIdMap: Record<string, boolean>;
|
||||||
// computed
|
// computed
|
||||||
currentProjectCycleIds: string[] | null;
|
currentProjectCycleIds: string[] | null;
|
||||||
currentProjectCompletedCycleIds: string[] | null;
|
currentProjectCompletedCycleIds: string[] | null;
|
||||||
@ -49,7 +49,7 @@ export interface ICycleStore {
|
|||||||
export class CycleStore implements ICycleStore {
|
export class CycleStore implements ICycleStore {
|
||||||
// observables
|
// observables
|
||||||
cycleMap: Record<string, ICycle> = {};
|
cycleMap: Record<string, ICycle> = {};
|
||||||
activeCycleMap: Record<string, ICycle> = {};
|
activeCycleIdMap: Record<string, boolean> = {};
|
||||||
// root store
|
// root store
|
||||||
rootStore;
|
rootStore;
|
||||||
// services
|
// services
|
||||||
@ -61,7 +61,7 @@ export class CycleStore implements ICycleStore {
|
|||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
// observables
|
// observables
|
||||||
cycleMap: observable,
|
cycleMap: observable,
|
||||||
activeCycleMap: observable,
|
activeCycleIdMap: observable,
|
||||||
// computed
|
// computed
|
||||||
currentProjectCycleIds: computed,
|
currentProjectCycleIds: computed,
|
||||||
currentProjectCompletedCycleIds: computed,
|
currentProjectCompletedCycleIds: computed,
|
||||||
@ -168,8 +168,8 @@ export class CycleStore implements ICycleStore {
|
|||||||
get currentProjectActiveCycleId() {
|
get currentProjectActiveCycleId() {
|
||||||
const projectId = this.rootStore.app.router.projectId;
|
const projectId = this.rootStore.app.router.projectId;
|
||||||
if (!projectId) return null;
|
if (!projectId) return null;
|
||||||
const activeCycle = Object.keys(this.activeCycleMap ?? {}).find(
|
const activeCycle = Object.keys(this.activeCycleIdMap ?? {}).find(
|
||||||
(cycleId) => this.activeCycleMap?.[cycleId]?.project === projectId
|
(cycleId) => this.cycleMap?.[cycleId]?.project === projectId
|
||||||
);
|
);
|
||||||
return activeCycle || null;
|
return activeCycle || null;
|
||||||
}
|
}
|
||||||
@ -186,7 +186,8 @@ export class CycleStore implements ICycleStore {
|
|||||||
* @param cycleId
|
* @param cycleId
|
||||||
* @returns
|
* @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
|
* @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) => {
|
await this.cycleService.getCyclesWithParams(workspaceSlug, projectId, "current").then((response) => {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
response.forEach((cycle) => {
|
response.forEach((cycle) => {
|
||||||
set(this.activeCycleMap, [cycle.id], cycle);
|
set(this.activeCycleIdMap, [cycle.id], true);
|
||||||
|
set(this.cycleMap, [cycle.id], cycle);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
@ -252,7 +254,6 @@ export class CycleStore implements ICycleStore {
|
|||||||
await this.cycleService.getCycleDetails(workspaceSlug, projectId, cycleId).then((response) => {
|
await this.cycleService.getCycleDetails(workspaceSlug, projectId, cycleId).then((response) => {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
set(this.cycleMap, [response.id], { ...this.cycleMap?.[response.id], ...response });
|
set(this.cycleMap, [response.id], { ...this.cycleMap?.[response.id], ...response });
|
||||||
set(this.activeCycleMap, [response.id], { ...this.activeCycleMap?.[response.id], ...response });
|
|
||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
@ -268,7 +269,6 @@ export class CycleStore implements ICycleStore {
|
|||||||
await this.cycleService.createCycle(workspaceSlug, projectId, data).then((response) => {
|
await this.cycleService.createCycle(workspaceSlug, projectId, data).then((response) => {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
set(this.cycleMap, [response.id], response);
|
set(this.cycleMap, [response.id], response);
|
||||||
set(this.activeCycleMap, [response.id], response);
|
|
||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
@ -285,7 +285,6 @@ export class CycleStore implements ICycleStore {
|
|||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
set(this.cycleMap, [cycleId], { ...this.cycleMap?.[cycleId], ...data });
|
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);
|
const response = await this.cycleService.patchCycle(workspaceSlug, projectId, cycleId, data);
|
||||||
return response;
|
return response;
|
||||||
@ -307,7 +306,7 @@ export class CycleStore implements ICycleStore {
|
|||||||
await this.cycleService.deleteCycle(workspaceSlug, projectId, cycleId).then(() => {
|
await this.cycleService.deleteCycle(workspaceSlug, projectId, cycleId).then(() => {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
delete this.cycleMap[cycleId];
|
delete this.cycleMap[cycleId];
|
||||||
delete this.activeCycleMap[cycleId];
|
delete this.activeCycleIdMap[cycleId];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -324,7 +323,6 @@ export class CycleStore implements ICycleStore {
|
|||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
if (currentCycle) set(this.cycleMap, [cycleId, "is_favorite"], true);
|
if (currentCycle) set(this.cycleMap, [cycleId, "is_favorite"], true);
|
||||||
if (currentActiveCycle) set(this.activeCycleMap, [cycleId, "is_favorite"], true);
|
|
||||||
});
|
});
|
||||||
// updating through api.
|
// updating through api.
|
||||||
const response = await this.cycleService.addCycleToFavorites(workspaceSlug, projectId, { cycle: cycleId });
|
const response = await this.cycleService.addCycleToFavorites(workspaceSlug, projectId, { cycle: cycleId });
|
||||||
@ -332,7 +330,6 @@ export class CycleStore implements ICycleStore {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
if (currentCycle) set(this.cycleMap, [cycleId, "is_favorite"], false);
|
if (currentCycle) set(this.cycleMap, [cycleId, "is_favorite"], false);
|
||||||
if (currentActiveCycle) set(this.activeCycleMap, [cycleId, "is_favorite"], false);
|
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@ -351,14 +348,12 @@ export class CycleStore implements ICycleStore {
|
|||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
if (currentCycle) set(this.cycleMap, [cycleId, "is_favorite"], false);
|
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);
|
const response = await this.cycleService.removeCycleFromFavorites(workspaceSlug, projectId, cycleId);
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
if (currentCycle) set(this.cycleMap, [cycleId, "is_favorite"], true);
|
if (currentCycle) set(this.cycleMap, [cycleId, "is_favorite"], true);
|
||||||
if (currentActiveCycle) set(this.activeCycleMap, [cycleId, "is_favorite"], true);
|
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user