Merge branch 'sync/ce-ee' of github.com:makeplane/plane-ee into develop

This commit is contained in:
sriram veeraghanta
2024-01-29 20:41:09 +05:30
41 changed files with 337 additions and 134 deletions

View File

@@ -304,6 +304,7 @@ class IssueRelationSerializer(BaseSerializer):
sequence_id = serializers.IntegerField(
source="related_issue.sequence_id", read_only=True
)
name = serializers.CharField(source="related_issue.name", read_only=True)
relation_type = serializers.CharField(read_only=True)
class Meta:
@@ -313,6 +314,7 @@ class IssueRelationSerializer(BaseSerializer):
"project_id",
"sequence_id",
"relation_type",
"name",
]
read_only_fields = [
"workspace",
@@ -328,6 +330,7 @@ class RelatedIssueSerializer(BaseSerializer):
sequence_id = serializers.IntegerField(
source="issue.sequence_id", read_only=True
)
name = serializers.CharField(source="issue.name", read_only=True)
relation_type = serializers.CharField(read_only=True)
class Meta:
@@ -337,6 +340,7 @@ class RelatedIssueSerializer(BaseSerializer):
"project_id",
"sequence_id",
"relation_type",
"name",
]
read_only_fields = [
"workspace",

View File

@@ -2,6 +2,7 @@ import { MouseEvent } from "react";
import Link from "next/link";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
import { useTheme } from "next-themes";
// hooks
import { useCycle, useIssues, useProject, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
@@ -43,6 +44,7 @@ interface IActiveCycleDetails {
export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props) => {
// props
const { workspaceSlug, projectId } = props;
const { resolvedTheme } = useTheme();
// store hooks
const { currentUser } = useUser();
const {
@@ -76,7 +78,9 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
);
const emptyStateDetail = CYCLE_EMPTY_STATE_DETAILS["active"];
const emptyStateImage = getEmptyStateImagePath("cycle", "active", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("cycle", "active", isLightMode);
if (!activeCycle && isLoading)
return (

View File

@@ -1,5 +1,6 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// hooks
import { useUser } from "hooks/store";
// components
@@ -18,11 +19,15 @@ export interface ICyclesBoard {
export const CyclesBoard: FC<ICyclesBoard> = observer((props) => {
const { cycleIds, filter, workspaceSlug, projectId, peekCycle } = props;
// theme
const { resolvedTheme } = useTheme();
// store hooks
const { currentUser } = useUser();
const emptyStateDetail = CYCLE_EMPTY_STATE_DETAILS[filter as keyof typeof CYCLE_EMPTY_STATE_DETAILS];
const emptyStateImage = getEmptyStateImagePath("cycle", filter, currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("cycle", filter, isLightMode);
return (
<>

View File

@@ -1,5 +1,6 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// hooks
import { useUser } from "hooks/store";
// components
@@ -19,11 +20,15 @@ export interface ICyclesList {
export const CyclesList: FC<ICyclesList> = observer((props) => {
const { cycleIds, filter, workspaceSlug, projectId } = props;
// theme
const { resolvedTheme } = useTheme();
// store hooks
const { currentUser } = useUser();
const emptyStateDetail = CYCLE_EMPTY_STATE_DETAILS[filter as keyof typeof CYCLE_EMPTY_STATE_DETAILS];
const emptyStateImage = getEmptyStateImagePath("cycle", filter, currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("cycle", filter, isLightMode);
return (
<>

View File

@@ -50,11 +50,11 @@ export const DashboardWidgets = observer(() => {
// if the widget is full width, return it in a 2 column grid
if (widget.fullWidth)
return (
<div className="lg:col-span-2">
<div key={key} className="lg:col-span-2">
<WidgetComponent dashboardId={homeDashboardId} workspaceSlug={workspaceSlug} />
</div>
);
else return <WidgetComponent dashboardId={homeDashboardId} workspaceSlug={workspaceSlug} />;
else return <WidgetComponent key={key} dashboardId={homeDashboardId} workspaceSlug={workspaceSlug} />;
})}
</div>
);

View File

@@ -99,7 +99,7 @@ export const CreatedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
</div>
<Tab.Panels as="div" className="h-full">
{ISSUES_TABS_LIST.map((tab) => (
<Tab.Panel as="div" className="h-full flex flex-col">
<Tab.Panel key={tab.key} as="div" className="h-full flex flex-col">
<WidgetIssuesList
issues={widgetStats.issues}
tab={tab.key}

View File

@@ -57,7 +57,7 @@ const ProjectListItem: React.FC<ProjectListItemProps> = observer((props) => {
<div className="mt-2">
<AvatarGroup>
{projectDetails.members?.map((member) => (
<Avatar src={member.member__avatar} name={member.member__display_name} />
<Avatar key={member.member_id} src={member.member__avatar} name={member.member__display_name} />
))}
</AvatarGroup>
</div>

View File

@@ -30,6 +30,7 @@ type ButtonProps = {
hideIcon: boolean;
hideText?: boolean;
dropdownArrow: boolean;
isActive?: boolean;
dropdownArrowClassName: string;
placeholder: string;
tooltip: boolean;
@@ -51,6 +52,7 @@ const BorderButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
placeholder,
tooltip,
} = props;
@@ -60,6 +62,7 @@ const BorderButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
{ "bg-custom-background-80": isActive },
className
)}
>
@@ -111,6 +114,7 @@ const TransparentButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
placeholder,
tooltip,
} = props;
@@ -120,6 +124,7 @@ const TransparentButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
{ "bg-custom-background-80": isActive },
className
)}
>
@@ -268,6 +273,7 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "border-without-text" ? (
@@ -279,6 +285,7 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
hideIcon={hideIcon}
hideText
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "background-with-text" ? (
@@ -310,6 +317,7 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "transparent-without-text" ? (
@@ -321,6 +329,7 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
hideIcon={hideIcon}
hideText
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : null}

View File

@@ -2,7 +2,7 @@ import React, { useRef, useState } from "react";
import { Combobox } from "@headlessui/react";
import DatePicker from "react-datepicker";
import { usePopper } from "react-popper";
import { CalendarDays, X } from "lucide-react";
import { Calendar, CalendarDays, X } from "lucide-react";
// hooks
import { useDropdownKeyDown } from "hooks/use-dropdown-key-down";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
@@ -23,6 +23,7 @@ type Props = TDropdownProps & {
onChange: (val: Date | null) => void;
value: Date | string | null;
closeOnSelect?: boolean;
showPlaceholderIcon?: boolean;
};
type ButtonProps = {
@@ -33,9 +34,11 @@ type ButtonProps = {
isClearable: boolean;
hideIcon?: boolean;
hideText?: boolean;
isActive?: boolean;
onClear: () => void;
placeholder: string;
tooltip: boolean;
showPlaceholderIcon?: boolean;
};
const BorderButton = (props: ButtonProps) => {
@@ -47,6 +50,7 @@ const BorderButton = (props: ButtonProps) => {
isClearable,
hideIcon = false,
hideText = false,
isActive = false,
onClear,
placeholder,
tooltip,
@@ -61,6 +65,7 @@ const BorderButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
{ "bg-custom-background-80": isActive },
className
)}
>
@@ -131,9 +136,11 @@ const TransparentButton = (props: ButtonProps) => {
isClearable,
hideIcon = false,
hideText = false,
isActive = false,
onClear,
placeholder,
tooltip,
showPlaceholderIcon = false,
} = props;
return (
@@ -145,11 +152,16 @@ const TransparentButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
{ "bg-custom-background-80": isActive },
className
)}
>
{!hideIcon && icon}
{!hideText && <span className="flex-grow truncate">{date ? renderFormattedDate(date) : placeholder}</span>}
{showPlaceholderIcon && !date && (
<Calendar className="h-2.5 w-2.5 flex-shrink-0 hidden group-hover:inline text-custom-text-400" />
)}
{isClearable && (
<X
className={cn("h-2 w-2 flex-shrink-0", clearIconClassName)}
@@ -183,6 +195,7 @@ export const DateDropdown: React.FC<Props> = (props) => {
placement,
tabIndex,
tooltip = false,
showPlaceholderIcon = false,
value,
} = props;
const [isOpen, setIsOpen] = useState(false);
@@ -246,6 +259,7 @@ export const DateDropdown: React.FC<Props> = (props) => {
placeholder={placeholder}
isClearable={isClearable && isDateSelected}
onClear={() => onChange(null)}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "border-without-text" ? (
@@ -258,6 +272,7 @@ export const DateDropdown: React.FC<Props> = (props) => {
placeholder={placeholder}
isClearable={isClearable && isDateSelected}
onClear={() => onChange(null)}
isActive={isOpen}
tooltip={tooltip}
hideText
/>
@@ -296,7 +311,9 @@ export const DateDropdown: React.FC<Props> = (props) => {
placeholder={placeholder}
isClearable={isClearable && isDateSelected}
onClear={() => onChange(null)}
isActive={isOpen}
tooltip={tooltip}
showPlaceholderIcon={showPlaceholderIcon}
/>
) : buttonVariant === "transparent-without-text" ? (
<TransparentButton
@@ -308,8 +325,10 @@ export const DateDropdown: React.FC<Props> = (props) => {
placeholder={placeholder}
isClearable={isClearable && isDateSelected}
onClear={() => onChange(null)}
isActive={isOpen}
tooltip={tooltip}
hideText
showPlaceholderIcon={showPlaceholderIcon}
/>
) : null}
</button>

View File

@@ -31,6 +31,7 @@ type ButtonProps = {
dropdownArrowClassName: string;
hideIcon?: boolean;
hideText?: boolean;
isActive?: boolean;
placeholder: string;
tooltip: boolean;
};
@@ -51,6 +52,7 @@ const BorderButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
placeholder,
tooltip,
} = props;
@@ -64,6 +66,7 @@ const BorderButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
{ "bg-custom-background-80": isActive },
className
)}
>
@@ -123,6 +126,7 @@ const TransparentButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
placeholder,
tooltip,
} = props;
@@ -136,6 +140,7 @@ const TransparentButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
{ "bg-custom-background-80": isActive },
className
)}
>
@@ -276,6 +281,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "border-without-text" ? (
@@ -287,6 +293,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
hideIcon={hideIcon}
hideText
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "background-with-text" ? (
@@ -318,6 +325,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "transparent-without-text" ? (
@@ -329,6 +337,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
hideIcon={hideIcon}
hideText
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : null}

View File

@@ -14,6 +14,7 @@ type ButtonProps = {
placeholder: string;
hideIcon?: boolean;
hideText?: boolean;
isActive?: boolean;
tooltip: boolean;
userIds: string | string[] | null;
};
@@ -50,6 +51,7 @@ export const BorderButton = observer((props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
placeholder,
userIds,
tooltip,
@@ -57,7 +59,7 @@ export const BorderButton = observer((props: ButtonProps) => {
// store hooks
const { getUserDetails } = useMember();
const isMultiple = Array.isArray(userIds);
const isArray = Array.isArray(userIds);
return (
<Tooltip
@@ -68,13 +70,18 @@ export const BorderButton = observer((props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
{ "bg-custom-background-80": isActive },
className
)}
>
{!hideIcon && <ButtonAvatars tooltip={tooltip} userIds={userIds} />}
{!hideText && (
<span className="flex-grow truncate">
{userIds ? (isMultiple ? placeholder : getUserDetails(userIds)?.display_name) : placeholder}
<span className="flex-grow truncate text-sm leading-5">
{isArray && userIds.length > 0
? userIds.length === 1
? getUserDetails(userIds[0])?.display_name
: ""
: placeholder}
</span>
)}
{dropdownArrow && (
@@ -99,7 +106,7 @@ export const BackgroundButton = observer((props: ButtonProps) => {
// store hooks
const { getUserDetails } = useMember();
const isMultiple = Array.isArray(userIds);
const isArray = Array.isArray(userIds);
return (
<Tooltip
@@ -115,8 +122,12 @@ export const BackgroundButton = observer((props: ButtonProps) => {
>
{!hideIcon && <ButtonAvatars tooltip={tooltip} userIds={userIds} />}
{!hideText && (
<span className="flex-grow truncate">
{userIds ? (isMultiple ? placeholder : getUserDetails(userIds)?.display_name) : placeholder}
<span className="flex-grow truncate text-sm leading-5">
{isArray && userIds.length > 0
? userIds.length === 1
? getUserDetails(userIds[0])?.display_name
: ""
: placeholder}
</span>
)}
{dropdownArrow && (
@@ -134,6 +145,7 @@ export const TransparentButton = observer((props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
placeholder,
userIds,
tooltip,
@@ -141,7 +153,7 @@ export const TransparentButton = observer((props: ButtonProps) => {
// store hooks
const { getUserDetails } = useMember();
const isMultiple = Array.isArray(userIds);
const isArray = Array.isArray(userIds);
return (
<Tooltip
@@ -152,13 +164,18 @@ export const TransparentButton = observer((props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
{ "bg-custom-background-80": isActive },
className
)}
>
{!hideIcon && <ButtonAvatars tooltip={tooltip} userIds={userIds} />}
{!hideText && (
<span className="flex-grow truncate">
{userIds ? (isMultiple ? placeholder : getUserDetails(userIds)?.display_name) : placeholder}
<span className="flex-grow truncate text-sm leading-5">
{isArray && userIds.length > 0
? userIds.length === 1
? getUserDetails(userIds[0])?.display_name
: ""
: placeholder}
</span>
)}
{dropdownArrow && (

View File

@@ -147,6 +147,7 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "border-without-text" ? (
@@ -157,6 +158,7 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
hideText
/>
@@ -189,6 +191,7 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "transparent-without-text" ? (
@@ -199,6 +202,7 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
hideText
/>

View File

@@ -38,6 +38,7 @@ type ButtonProps = {
dropdownArrowClassName: string;
hideIcon?: boolean;
hideText?: boolean;
isActive?: boolean;
module: IModule | null;
placeholder: string;
tooltip: boolean;
@@ -50,6 +51,7 @@ const BorderButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
module,
placeholder,
tooltip,
@@ -60,6 +62,7 @@ const BorderButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
{ "bg-custom-background-80": isActive },
className
)}
>
@@ -110,6 +113,7 @@ const TransparentButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
module,
placeholder,
tooltip,
@@ -120,6 +124,7 @@ const TransparentButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
{ "bg-custom-background-80": isActive },
className
)}
>
@@ -267,6 +272,7 @@ export const ModuleDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "border-without-text" ? (
@@ -278,6 +284,7 @@ export const ModuleDropdown: React.FC<Props> = observer((props) => {
hideIcon={hideIcon}
hideText
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "background-with-text" ? (
@@ -309,6 +316,7 @@ export const ModuleDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "transparent-without-text" ? (
@@ -320,6 +328,7 @@ export const ModuleDropdown: React.FC<Props> = observer((props) => {
hideIcon={hideIcon}
hideText
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : null}

View File

@@ -31,6 +31,7 @@ type ButtonProps = {
dropdownArrowClassName: string;
hideIcon?: boolean;
hideText?: boolean;
isActive?: boolean;
highlightUrgent: boolean;
priority: TIssuePriorities;
tooltip: boolean;
@@ -181,6 +182,7 @@ const TransparentButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
highlightUrgent,
priority,
tooltip,
@@ -207,6 +209,7 @@ const TransparentButton = (props: ButtonProps) => {
"px-0.5": hideText,
// highlight the whole button if text is hidden and priority is urgent
"bg-red-500 border-red-500": priority === "urgent" && hideText && highlightUrgent,
"bg-custom-background-80": isActive,
},
className
)}
@@ -312,7 +315,13 @@ export const PriorityDropdown: React.FC<Props> = (props) => {
as="div"
ref={dropdownRef}
tabIndex={tabIndex}
className={cn("h-full", className)}
className={cn(
"h-full",
{
"bg-custom-background-80": isOpen,
},
className
)}
value={value}
onChange={onChange}
disabled={disabled}
@@ -402,6 +411,7 @@ export const PriorityDropdown: React.FC<Props> = (props) => {
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "transparent-without-text" ? (
@@ -414,6 +424,7 @@ export const PriorityDropdown: React.FC<Props> = (props) => {
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
isActive={isOpen}
tooltip={tooltip}
hideText
/>

View File

@@ -30,6 +30,7 @@ type ButtonProps = {
dropdownArrowClassName: string;
hideIcon?: boolean;
hideText?: boolean;
isActive?: boolean;
state: IState | undefined;
tooltip: boolean;
};
@@ -41,6 +42,7 @@ const BorderButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
state,
tooltip,
} = props;
@@ -50,6 +52,9 @@ const BorderButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
{
"bg-custom-background-80": isActive,
},
className
)}
>
@@ -111,6 +116,7 @@ const TransparentButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
state,
tooltip,
} = props;
@@ -120,6 +126,9 @@ const TransparentButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
{
"bg-custom-background-80": isActive,
},
className
)}
>
@@ -251,6 +260,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "border-without-text" ? (
@@ -260,6 +270,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
isActive={isOpen}
tooltip={tooltip}
hideText
/>
@@ -289,6 +300,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "transparent-without-text" ? (
@@ -298,6 +310,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
isActive={isOpen}
tooltip={tooltip}
hideText
/>

View File

@@ -64,6 +64,7 @@ export const IssueParentSelect: React.FC<TIssueParentSelect> = observer((props)
{
"cursor-not-allowed": disabled,
"hover:bg-custom-background-80": !disabled,
"bg-custom-background-80": isParentIssueModalOpen,
},
className
)}
@@ -72,15 +73,20 @@ export const IssueParentSelect: React.FC<TIssueParentSelect> = observer((props)
>
{issue.parent_id && parentIssue ? (
<div className="flex items-center gap-1 bg-green-500/20 text-green-700 rounded px-1.5 py-1">
<Link
href={`/${workspaceSlug}/projects/${projectId}/issues/${parentIssue?.id}`}
className="text-xs font-medium"
>
{parentIssueProjectDetails?.identifier}-{parentIssue.sequence_id}
</Link>
<Tooltip tooltipHeading="Title" tooltipContent={parentIssue.name}>
<Link
href={`/${workspaceSlug}/projects/${projectId}/issues/${parentIssue?.id}`}
target="_blank"
rel="noopener noreferrer"
className="text-xs font-medium mt-0.5"
onClick={(e) => e.stopPropagation()}
>
{parentIssueProjectDetails?.identifier}-{parentIssue.sequence_id}
</Link>
</Tooltip>
{!disabled && (
<Tooltip tooltipContent="Remove">
<Tooltip tooltipContent="Remove" position="bottom">
<span
onClick={(e) => {
e.preventDefault();
@@ -96,7 +102,15 @@ export const IssueParentSelect: React.FC<TIssueParentSelect> = observer((props)
) : (
<span className="text-sm text-custom-text-400">Add parent issue</span>
)}
{!disabled && <Pencil className="h-4 w-4 flex-shrink-0 hidden group-hover:inline" />}
{!disabled && (
<span
className={cn("p-1 flex-shrink-0 opacity-0 group-hover:opacity-100", {
"text-custom-text-400": !issue.parent_id && !parentIssue,
})}
>
<Pencil className="h-2.5 w-2.5 flex-shrink-0" />
</span>
)}
</button>
</>
);

View File

@@ -1,4 +1,5 @@
import React from "react";
import Link from "next/link";
import { observer } from "mobx-react-lite";
import { CircleDot, CopyPlus, Pencil, X, XCircle } from "lucide-react";
// hooks
@@ -101,59 +102,72 @@ export const IssueRelationSelect: React.FC<TIssueRelationSelect> = observer((pro
<button
type="button"
className={cn(
"group flex items-center justify-between gap-2 px-2 py-0.5 rounded outline-none",
"group flex items-center gap-2 px-2 py-0.5 rounded outline-none",
{
"cursor-not-allowed": disabled,
"hover:bg-custom-background-80": !disabled,
"bg-custom-background-80": isRelationModalOpen === relationKey,
},
className
)}
onClick={() => toggleRelationModal(relationKey)}
disabled={disabled}
>
{relationIssueIds.length > 0 ? (
<div className="flex items-center gap-2 flex-wrap">
{relationIssueIds.map((relationIssueId) => {
const currentIssue = issueMap[relationIssueId];
if (!currentIssue) return;
<div className="flex items-start justify-between w-full">
{relationIssueIds.length > 0 ? (
<div className="flex items-center gap-2 py-0.5 flex-wrap">
{relationIssueIds.map((relationIssueId) => {
const currentIssue = issueMap[relationIssueId];
if (!currentIssue) return;
const projectDetails = getProjectById(currentIssue.project_id);
const projectDetails = getProjectById(currentIssue.project_id);
return (
<div
key={relationIssueId}
className={`group flex items-center gap-1 rounded px-1.5 py-1 ${issueRelationObject[relationKey].className}`}
>
<a
href={`/${workspaceSlug}/projects/${projectDetails?.id}/issues/${currentIssue.id}`}
target="_blank"
rel="noopener noreferrer"
className="text-xs font-medium"
onClick={(e) => e.stopPropagation()}
return (
<div
key={relationIssueId}
className={`group flex items-center gap-1 rounded px-1.5 pt-1 pb-1 leading-3 hover:bg-custom-background-90 ${issueRelationObject[relationKey].className}`}
>
{`${projectDetails?.identifier}-${currentIssue?.sequence_id}`}
</a>
{!disabled && (
<Tooltip tooltipContent="Remove">
<span
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
removeRelation(workspaceSlug, projectId, issueId, relationKey, relationIssueId);
}}
<Tooltip tooltipHeading="Title" tooltipContent={currentIssue.name}>
<Link
href={`/${workspaceSlug}/projects/${projectDetails?.id}/issues/${currentIssue.id}`}
target="_blank"
rel="noopener noreferrer"
className="text-xs font-medium mt-0.5"
onClick={(e) => e.stopPropagation()}
>
<X className="h-2.5 w-2.5 text-custom-text-300 hover:text-red-500" />
</span>
{`${projectDetails?.identifier}-${currentIssue?.sequence_id}`}
</Link>
</Tooltip>
)}
</div>
);
})}
</div>
) : (
<span className="text-sm text-custom-text-400">{issueRelationObject[relationKey].placeholder}</span>
)}
{!disabled && <Pencil className="h-4 w-4 flex-shrink-0 hidden group-hover:inline" />}
{!disabled && (
<Tooltip tooltipContent="Remove" position="bottom">
<span
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
removeRelation(workspaceSlug, projectId, issueId, relationKey, relationIssueId);
}}
>
<X className="h-2.5 w-2.5 text-custom-text-300 hover:text-red-500" />
</span>
</Tooltip>
)}
</div>
);
})}
</div>
) : (
<span className="text-sm text-custom-text-400">{issueRelationObject[relationKey].placeholder}</span>
)}
{!disabled && (
<span
className={cn("p-1 flex-shrink-0 opacity-0 group-hover:opacity-100", {
"text-custom-text-400": relationIssueIds.length === 0,
})}
>
<Pencil className="h-2.5 w-2.5 flex-shrink-0" />
</span>
)}
</div>
</button>
</>
);

View File

@@ -184,7 +184,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
projectId={projectId?.toString() ?? ""}
placeholder="Add assignees"
multiple
buttonVariant={issue?.assignee_ids?.length > 0 ? "transparent-without-text" : "transparent-with-text"}
buttonVariant={issue?.assignee_ids?.length > 1 ? "transparent-without-text" : "transparent-with-text"}
className="w-3/5 flex-grow group"
buttonContainerClassName="w-full text-left"
buttonClassName={`text-sm justify-between ${
@@ -233,6 +233,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
buttonClassName={`text-sm ${issue?.start_date ? "" : "text-custom-text-400"}`}
hideIcon
clearIconClassName="h-3 w-3 hidden group-hover:inline"
showPlaceholderIcon
/>
</div>
@@ -257,6 +258,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
buttonClassName={`text-sm ${issue?.target_date ? "" : "text-custom-text-400"}`}
hideIcon
clearIconClassName="h-3 w-3 hidden group-hover:inline"
showPlaceholderIcon
/>
</div>
@@ -332,8 +334,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
/>
</div>
<div className="flex items-center gap-2 min-h-8">
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<div className="flex gap-2 min-h-8">
<div className="flex gap-1 pt-2 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<RelatedIcon className="h-4 w-4 flex-shrink-0" />
<span>Relates to</span>
</div>
@@ -347,8 +349,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
/>
</div>
<div className="flex items-center gap-2 min-h-8">
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<div className="flex gap-2 min-h-8">
<div className="flex gap-1 pt-2 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<XCircle className="h-4 w-4 flex-shrink-0" />
<span>Blocking</span>
</div>
@@ -362,8 +364,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
/>
</div>
<div className="flex items-center gap-2 min-h-8">
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<div className="flex gap-2 min-h-8">
<div className="flex gap-1 pt-2 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<CircleDot className="h-4 w-4 flex-shrink-0" />
<span>Blocked by</span>
</div>
@@ -377,8 +379,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
/>
</div>
<div className="flex items-center gap-2 min-h-8">
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<div className="flex gap-2 min-h-8">
<div className="flex gap-1 pt-2 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<CopyPlus className="h-4 w-4 flex-shrink-0" />
<span>Duplicate of</span>
</div>

View File

@@ -1,6 +1,7 @@
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
import size from "lodash/size";
import { useTheme } from "next-themes";
// hooks
import { useIssues, useUser } from "hooks/store";
// components
@@ -26,6 +27,9 @@ export const ProjectArchivedEmptyState: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
membership: { currentProjectRole },
currentUser,
@@ -35,12 +39,9 @@ export const ProjectArchivedEmptyState: React.FC = observer(() => {
const userFilters = issuesFilter?.issueFilters?.filters;
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath(
"empty-filters",
activeLayout ?? "list",
currentUser?.theme.theme === "light"
);
const EmptyStateImagePath = getEmptyStateImagePath("archived", "empty-issues", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode);
const EmptyStateImagePath = getEmptyStateImagePath("archived", "empty-issues", isLightMode);
const issueFilterCount = size(
Object.fromEntries(

View File

@@ -1,6 +1,7 @@
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
import size from "lodash/size";
import { useTheme } from "next-themes";
// hooks
import { useIssues, useUser } from "hooks/store";
// components
@@ -26,6 +27,9 @@ export const ProjectDraftEmptyState: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
membership: { currentProjectRole },
currentUser,
@@ -35,12 +39,9 @@ export const ProjectDraftEmptyState: React.FC = observer(() => {
const userFilters = issuesFilter?.issueFilters?.filters;
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath(
"empty-filters",
activeLayout ?? "list",
currentUser?.theme.theme === "light"
);
const EmptyStateImagePath = getEmptyStateImagePath("draft", "empty-issues", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode);
const EmptyStateImagePath = getEmptyStateImagePath("draft", "empty-issues", isLightMode);
const issueFilterCount = size(
Object.fromEntries(

View File

@@ -1,6 +1,7 @@
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
import size from "lodash/size";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useIssues, useUser } from "hooks/store";
// components
@@ -26,6 +27,8 @@ export const ProjectEmptyState: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
commandPalette: commandPaletteStore,
@@ -40,12 +43,9 @@ export const ProjectEmptyState: React.FC = observer(() => {
const userFilters = issuesFilter?.issueFilters?.filters;
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath(
"empty-filters",
activeLayout ?? "list",
currentUser?.theme.theme === "light"
);
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "issues", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode);
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "issues", isLightMode);
const issueFilterCount = size(
Object.fromEntries(

View File

@@ -28,7 +28,7 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug || !userId) return;
await issues.updateIssue(workspaceSlug, userId, issue.id, issue);
await issues.updateIssue(workspaceSlug, issue.project_id, issue.id, issue, userId);
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !userId) return;

View File

@@ -29,7 +29,7 @@ export const ProfileIssuesListLayout: FC = observer(() => {
[EIssueActions.UPDATE]: async (issue: TIssue) => {
if (!workspaceSlug || !userId) return;
await issues.updateIssue(workspaceSlug, userId, issue.id, issue);
await issues.updateIssue(workspaceSlug, issue.project_id, issue.id, issue, userId);
},
[EIssueActions.DELETE]: async (issue: TIssue) => {
if (!workspaceSlug || !userId) return;

View File

@@ -82,17 +82,17 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
if (storeLabels && storeLabels.length > 0) projectLabels = storeLabels;
const options = projectLabels.map((label) => ({
value: label.id,
query: label.name,
value: label?.id,
query: label?.name,
content: (
<div className="flex items-center justify-start gap-2 overflow-hidden">
<span
className="h-2.5 w-2.5 flex-shrink-0 rounded-full"
style={{
backgroundColor: label.color,
backgroundColor: label?.color,
}}
/>
<div className="line-clamp-1 inline-block truncate">{label.name}</div>
<div className="line-clamp-1 inline-block truncate">{label?.name}</div>
</div>
),
}));
@@ -106,11 +106,11 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
value.length <= maxRender ? (
<>
{projectLabels
?.filter((l) => value.includes(l.id))
?.filter((l) => value.includes(l?.id))
.map((label) => (
<Tooltip position="top" tooltipHeading="Labels" tooltipContent={label.name ?? ""}>
<Tooltip position="top" tooltipHeading="Labels" tooltipContent={label?.name ?? ""}>
<div
key={label.id}
key={label?.id}
className={`flex overflow-hidden hover:bg-custom-background-80 ${
!disabled && "cursor-pointer"
} h-full max-w-full flex-shrink-0 items-center rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 text-xs`}
@@ -122,7 +122,7 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
backgroundColor: label?.color ?? "#000000",
}}
/>
<div className="line-clamp-1 inline-block w-auto max-w-[100px] truncate">{label.name}</div>
<div className="line-clamp-1 inline-block w-auto max-w-[100px] truncate">{label?.name}</div>
</div>
</div>
</Tooltip>
@@ -138,8 +138,8 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
position="top"
tooltipHeading="Labels"
tooltipContent={projectLabels
?.filter((l) => value.includes(l.id))
.map((l) => l.name)
?.filter((l) => value.includes(l?.id))
.map((l) => l?.name)
.join(", ")}
>
<div className="flex h-full items-center gap-1.5 text-custom-text-200">

View File

@@ -3,6 +3,7 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
import isEmpty from "lodash/isEmpty";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useGlobalView, useIssues, useProject, useUser } from "hooks/store";
import { useWorkspaceIssueProperties } from "hooks/use-workspace-issue-properties";
@@ -25,6 +26,8 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, globalViewId } = router.query;
// theme
const { resolvedTheme } = useTheme();
//swr hook for fetching issue properties
useWorkspaceIssueProperties(workspaceSlug);
// store
@@ -46,7 +49,8 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
const currentView = isDefaultView ? groupedIssueIds.dataViewId : "custom-view";
const currentViewDetails = ALL_ISSUES_EMPTY_STATE_DETAILS[currentView as keyof typeof ALL_ISSUES_EMPTY_STATE_DETAILS];
const emptyStateImage = getEmptyStateImagePath("all-issues", currentView, currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("all-issues", currentView, isLightMode);
// filter init from the query params

View File

@@ -99,7 +99,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
projectId={projectId}
placeholder="Add assignees"
multiple
buttonVariant={issue?.assignee_ids?.length > 0 ? "transparent-without-text" : "transparent-with-text"}
buttonVariant={issue?.assignee_ids?.length > 1 ? "transparent-without-text" : "transparent-with-text"}
className="w-3/4 flex-grow group"
buttonContainerClassName="w-full text-left"
buttonClassName={`text-sm justify-between ${issue?.assignee_ids.length > 0 ? "" : "text-custom-text-400"}`}
@@ -148,6 +148,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
buttonClassName={`text-sm ${issue?.start_date ? "" : "text-custom-text-400"}`}
hideIcon
clearIconClassName="h-3 w-3 hidden group-hover:inline"
showPlaceholderIcon
/>
</div>
@@ -173,6 +174,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
buttonClassName={`text-sm ${issue?.target_date ? "" : "text-custom-text-400"}`}
hideIcon
clearIconClassName="h-3 w-3 hidden group-hover:inline"
showPlaceholderIcon
/>
</div>
@@ -251,8 +253,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
</div>
{/* relates to */}
<div className="flex items-center gap-3 min-h-8">
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<div className="flex gap-3 min-h-8">
<div className="flex pt-2 gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<RelatedIcon className="h-4 w-4 flex-shrink-0" />
<span>Relates to</span>
</div>
@@ -267,8 +269,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
</div>
{/* blocking */}
<div className="flex items-center gap-3 min-h-8">
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<div className="flex gap-3 min-h-8">
<div className="flex pt-2 gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<XCircle className="h-4 w-4 flex-shrink-0" />
<span>Blocking</span>
</div>
@@ -283,8 +285,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
</div>
{/* blocked by */}
<div className="flex items-center gap-3 min-h-8">
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<div className="flex gap-3 min-h-8">
<div className="flex pt-2 gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<CircleDot className="h-4 w-4 flex-shrink-0" />
<span>Blocked by</span>
</div>
@@ -299,8 +301,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
</div>
{/* duplicate of */}
<div className="flex items-center gap-3 min-h-8">
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<div className="flex gap-3 min-h-8">
<div className="flex pt-2 gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<CopyPlus className="h-4 w-4 flex-shrink-0" />
<span>Duplicate of</span>
</div>

View File

@@ -1,5 +1,6 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useModule, useUser } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage";
@@ -15,6 +16,8 @@ export const ModulesListView: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, peekModule } = router.query;
// theme
const { resolvedTheme } = useTheme();
// store hooks
const { commandPalette: commandPaletteStore } = useApplication();
const {
@@ -25,7 +28,8 @@ export const ModulesListView: React.FC = observer(() => {
const { storedValue: modulesView } = useLocalStorage("modules_view", "grid");
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "modules", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "modules", isLightMode);
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;

View File

@@ -95,6 +95,7 @@ export const NotificationHeader: React.FC<NotificationHeaderProps> = (props) =>
<MoreVertical className="h-3.5 w-3.5" />
</div>
}
closeOnSelect
>
<CustomMenu.MenuItem onClick={markAllNotificationsAsRead}>
<div className="flex items-center gap-2">

View File

@@ -1,4 +1,5 @@
import { useEffect } from "react";
import { useTheme } from "next-themes";
import { observer } from "mobx-react-lite";
// hooks
import { useApplication, useDashboard, useProject, useUser } from "hooks/store";
@@ -14,6 +15,8 @@ import { Spinner } from "@plane/ui";
import { EUserWorkspaceRoles } from "constants/workspace";
export const WorkspaceDashboardView = observer(() => {
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
commandPalette: { toggleCreateProjectModal },
@@ -28,7 +31,8 @@ export const WorkspaceDashboardView = observer(() => {
const { homeDashboardId, fetchHomeDashboardWidgets } = useDashboard();
const { joinedProjectIds } = useProject();
const emptyStateImage = getEmptyStateImagePath("onboarding", "dashboard", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("onboarding", "dashboard", isLightMode);
const handleTourCompleted = () => {
updateTourCompleted()

View File

@@ -1,5 +1,6 @@
import { FC } from "react";
import { useRouter } from "next/router";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useUser } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage";
@@ -18,7 +19,9 @@ type IPagesListView = {
export const PagesListView: FC<IPagesListView> = (props) => {
const { pageIds: projectPageIds } = props;
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
commandPalette: { toggleCreatePageModal },
} = useApplication();
@@ -36,11 +39,8 @@ export const PagesListView: FC<IPagesListView> = (props) => {
? PAGE_EMPTY_STATE_DETAILS[pageTab as keyof typeof PAGE_EMPTY_STATE_DETAILS]
: PAGE_EMPTY_STATE_DETAILS["All"];
const emptyStateImage = getEmptyStateImagePath(
"pages",
currentPageTabDetails.key,
currentUser?.theme.theme === "light"
);
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("pages", currentPageTabDetails.key, isLightMode);
const isButtonVisible = currentPageTabDetails.key !== "archived" && currentPageTabDetails.key !== "favorites";

View File

@@ -1,5 +1,6 @@
import React, { FC } from "react";
import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useUser } from "hooks/store";
import { useProjectPages } from "hooks/store/use-project-specific-pages";
@@ -14,6 +15,8 @@ import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
import { EUserProjectRoles } from "constants/project";
export const RecentPagesList: FC = observer(() => {
// theme
const { resolvedTheme } = useTheme();
// store hooks
const { commandPalette: commandPaletteStore } = useApplication();
const {
@@ -22,7 +25,8 @@ export const RecentPagesList: FC = observer(() => {
} = useUser();
const { recentProjectPages } = useProjectPages();
const EmptyStateImagePath = getEmptyStateImagePath("pages", "recent", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const EmptyStateImagePath = getEmptyStateImagePath("pages", "recent", isLightMode);
// FIXME: replace any with proper type
const isEmpty = recentProjectPages && Object.values(recentProjectPages).every((value: any) => value.length === 0);

View File

@@ -2,6 +2,7 @@ import React from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// components
import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root";
import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/roots/profile-issues-root";
@@ -27,6 +28,9 @@ export const ProfileIssuesPage = observer((props: IProfileIssuesPage) => {
workspaceSlug: string;
userId: string;
};
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
membership: { currentWorkspaceRole },
currentUser,
@@ -46,7 +50,8 @@ export const ProfileIssuesPage = observer((props: IProfileIssuesPage) => {
}
);
const emptyStateImage = getEmptyStateImagePath("profile", type, currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("profile", type, isLightMode);
const activeLayout = issueFilters?.displayFilters?.layout || undefined;

View File

@@ -1,16 +1,17 @@
import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useProject, useUser } from "hooks/store";
// components
import { ProjectCard } from "components/project";
import { Loader } from "@plane/ui";
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
// icons
import { Plus } from "lucide-react";
// constants
import { EUserWorkspaceRoles } from "constants/workspace";
export const ProjectCardList = observer(() => {
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
commandPalette: commandPaletteStore,
@@ -22,7 +23,8 @@ export const ProjectCardList = observer(() => {
} = useUser();
const { workspaceProjectIds, searchedProjects, getProjectById } = useProject();
const emptyStateImage = getEmptyStateImagePath("onboarding", "projects", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("onboarding", "projects", isLightMode);
const isEditingAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;

View File

@@ -206,7 +206,7 @@ export const ProjectSidebarList: FC = observer(() => {
type="button"
className="group flex w-full items-center gap-1 whitespace-nowrap rounded px-1.5 text-left text-sm font-semibold text-custom-sidebar-text-400 hover:bg-custom-sidebar-background-80"
>
Projects
Your projects
{open ? (
<ChevronDown className="h-3.5 w-3.5" />
) : (

View File

@@ -1,6 +1,7 @@
import { useState } from "react";
import { observer } from "mobx-react-lite";
import { Search } from "lucide-react";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useProjectView, useUser } from "hooks/store";
// components
@@ -14,6 +15,8 @@ import { EUserProjectRoles } from "constants/project";
export const ProjectViewsList = observer(() => {
// states
const [query, setQuery] = useState("");
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
commandPalette: { toggleCreateViewModal },
@@ -43,7 +46,8 @@ export const ProjectViewsList = observer(() => {
const viewsList = projectViewIds.map((viewId) => getViewById(viewId));
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "views", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "views", isLightMode);
const filteredViewsList = viewsList.filter((v) => v?.name.toLowerCase().includes(query.toLowerCase()));

View File

@@ -304,7 +304,17 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
},
my_issues: {
spreadsheet: {
filters: ["priority", "state_group", "labels", "assignees", "created_by", "project", "start_date", "target_date"],
filters: [
"priority",
"state_group",
"labels",
"assignees",
"created_by",
"subscriber",
"project",
"start_date",
"target_date",
],
display_properties: true,
display_filters: {
type: [null, "active", "backlog"],

View File

@@ -264,6 +264,13 @@ const useUserNotification = () => {
await userNotificationServices
.markAllNotificationsAsRead(workspaceSlug.toString(), markAsReadParams)
.then(() => {
setToastAlert({
type: "success",
title: "Success!",
message: "All Notifications marked as read.",
});
})
.catch(() => {
setToastAlert({
type: "error",

View File

@@ -1,6 +1,7 @@
import React, { Fragment, ReactElement } from "react";
import { observer } from "mobx-react-lite";
import { Tab } from "@headlessui/react";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useProject, useUser } from "hooks/store";
// layouts
@@ -16,6 +17,8 @@ import { EUserWorkspaceRoles } from "constants/workspace";
import { NextPageWithLayout } from "lib/types";
const AnalyticsPage: NextPageWithLayout = observer(() => {
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
commandPalette: { toggleCreateProjectModal },
@@ -27,7 +30,8 @@ const AnalyticsPage: NextPageWithLayout = observer(() => {
} = useUser();
const { workspaceProjectIds } = useProject();
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "analytics", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "analytics", isLightMode);
const isEditingAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
return (

View File

@@ -2,6 +2,7 @@ import { Fragment, useCallback, useState, ReactElement } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Tab } from "@headlessui/react";
import { useTheme } from "next-themes";
// hooks
import { useCycle, useUser } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage";
@@ -22,6 +23,8 @@ import { EUserWorkspaceRoles } from "constants/workspace";
const ProjectCyclesPage: NextPageWithLayout = observer(() => {
const [createModal, setCreateModal] = useState(false);
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
membership: { currentProjectRole },
@@ -49,7 +52,9 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
},
[handleCurrentLayout, setCycleTab]
);
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "cycles", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "cycles", isLightMode);
const totalCycles = currentProjectCycleIds?.length ?? 0;

View File

@@ -4,6 +4,7 @@ import dynamic from "next/dynamic";
import { Tab } from "@headlessui/react";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useUser } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage";
@@ -48,7 +49,9 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => {
const { workspaceSlug, projectId } = router.query;
// states
const [createUpdatePageModal, setCreateUpdatePageModal] = useState(false);
// store
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
currentUser,
currentUserLoader,
@@ -94,7 +97,8 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => {
}
};
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "pages", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "pages", isLightMode);
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;

View File

@@ -76,6 +76,8 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
labels: filters?.labels || undefined,
start_date: filters?.start_date || undefined,
target_date: filters?.target_date || undefined,
project: filters.project || undefined,
subscriber: filters.subscriber || undefined,
// display filters
type: displayFilters?.type || undefined,
sub_issue: displayFilters?.sub_issue ?? true,