[WEB-570] chore: toast refactor (#3836)

* new toast setup

* chore: new toast implementation.

* chore: move toast component to ui package.

* chore: replace `setToast` with `setPromiseToast` in required places for better UX.
* chore: code cleanup.

* chore: update theme.

* fix: theme switching issue.

* chore: remove toast from issue update operations.

* chore: add promise toast for add/ remove issue to cycle/ module and remove local spinners.

---------

Co-authored-by: rahulramesha <rahulramesham@gmail.com>
This commit is contained in:
Prateek Shourya 2024-03-06 14:18:41 +05:30 committed by GitHub
parent c06ef4d1d7
commit 53367a6bc4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
167 changed files with 1827 additions and 1896 deletions

View File

@ -198,6 +198,31 @@ module.exports = {
300: convertToRGB("--color-onboarding-border-300"),
},
},
toast: {
text: {
success: convertToRGB("--color-toast-success-text"),
error: convertToRGB("--color-toast-error-text"),
warning: convertToRGB("--color-toast-warning-text"),
info: convertToRGB("--color-toast-info-text"),
loading: convertToRGB("--color-toast-loading-text"),
secondary: convertToRGB("--color-toast-secondary-text"),
tertiary: convertToRGB("--color-toast-tertiary-text"),
},
background: {
success: convertToRGB("--color-toast-success-background"),
error: convertToRGB("--color-toast-error-background"),
warning: convertToRGB("--color-toast-warning-background"),
info: convertToRGB("--color-toast-info-background"),
loading: convertToRGB("--color-toast-loading-background"),
},
border: {
success: convertToRGB("--color-toast-success-border"),
error: convertToRGB("--color-toast-error-border"),
warning: convertToRGB("--color-toast-warning-border"),
info: convertToRGB("--color-toast-info-border"),
loading: convertToRGB("--color-toast-loading-border"),
},
},
},
keyframes: {
leftToaster: {

View File

@ -26,6 +26,7 @@
"react-color": "^2.19.3",
"react-dom": "^18.2.0",
"react-popper": "^2.3.0",
"sonner": "^1.4.2",
"tailwind-merge": "^2.0.0"
},
"devDependencies": {

View File

@ -10,3 +10,4 @@ export * from "./spinners";
export * from "./tooltip";
export * from "./loader";
export * from "./control-link";
export * from "./toast";

View File

@ -0,0 +1,35 @@
import * as React from "react";
interface ICircularBarSpinner extends React.SVGAttributes<SVGElement> {
height?: string;
width?: string;
className?: string | undefined;
}
export const CircularBarSpinner: React.FC<ICircularBarSpinner> = ({
height = "16px",
width = "16px",
className = "",
}) => (
<div role="status">
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 24 24" className={className}>
<g>
<rect width={2} height={5} x={11} y={1} fill="currentColor" opacity={0.14} />
<rect width={2} height={5} x={11} y={1} fill="currentColor" opacity={0.29} transform="rotate(30 12 12)" />
<rect width={2} height={5} x={11} y={1} fill="currentColor" opacity={0.43} transform="rotate(60 12 12)" />
<rect width={2} height={5} x={11} y={1} fill="currentColor" opacity={0.57} transform="rotate(90 12 12)" />
<rect width={2} height={5} x={11} y={1} fill="currentColor" opacity={0.71} transform="rotate(120 12 12)" />
<rect width={2} height={5} x={11} y={1} fill="currentColor" opacity={0.86} transform="rotate(150 12 12)" />
<rect width={2} height={5} x={11} y={1} fill="currentColor" transform="rotate(180 12 12)" />
<animateTransform
attributeName="transform"
calcMode="discrete"
dur="0.75s"
repeatCount="indefinite"
type="rotate"
values="0 12 12;30 12 12;60 12 12;90 12 12;120 12 12;150 12 12;180 12 12;210 12 12;240 12 12;270 12 12;300 12 12;330 12 12;360 12 12"
/>
</g>
</svg>
</div>
);

View File

@ -1 +1,2 @@
export * from "./circular-spinner";
export * from "./circular-bar-spinner";

View File

@ -0,0 +1,206 @@
import * as React from "react";
import { Toaster, toast } from "sonner";
// icons
import { AlertTriangle, CheckCircle2, X, XCircle } from "lucide-react";
// spinner
import { CircularBarSpinner } from "../spinners";
// helper
import { cn } from "../../helpers";
export enum TOAST_TYPE {
SUCCESS = "success",
ERROR = "error",
INFO = "info",
WARNING = "warning",
LOADING = "loading",
}
type SetToastProps =
| {
type: TOAST_TYPE.LOADING;
title?: string;
}
| {
id?: string | number;
type: Exclude<TOAST_TYPE, TOAST_TYPE.LOADING>;
title: string;
message?: string;
};
type PromiseToastCallback<ToastData> = (data: ToastData) => string;
type PromiseToastData<ToastData> = {
title: string;
message?: PromiseToastCallback<ToastData>;
};
type PromiseToastOptions<ToastData> = {
loading?: string;
success: PromiseToastData<ToastData>;
error: PromiseToastData<ToastData>;
};
type ToastContentProps = {
toastId: string | number;
icon?: React.ReactNode;
textColorClassName: string;
backgroundColorClassName: string;
borderColorClassName: string;
};
type ToastProps = {
theme: "light" | "dark" | "system";
};
export const Toast = (props: ToastProps) => {
const { theme } = props;
return <Toaster visibleToasts={5} gap={20} theme={theme} />;
};
export const setToast = (props: SetToastProps) => {
const renderToastContent = ({
toastId,
icon,
textColorClassName,
backgroundColorClassName,
borderColorClassName,
}: ToastContentProps) =>
props.type === TOAST_TYPE.LOADING ? (
<div
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
}}
className={cn("w-[350px] h-[67.3px] rounded-lg border shadow-sm p-2", backgroundColorClassName, borderColorClassName)}
>
<div className="w-full h-full flex items-center justify-center px-4 py-2">
{icon && <div className="flex items-center justify-center">{icon}</div>}
<div className={cn("w-full flex items-center gap-0.5 pr-1", icon ? "pl-4" : "pl-1")}>
<div className={cn("grow text-sm font-semibold", textColorClassName)}>{props.title ?? "Loading..."}</div>
<div className="flex-shrink-0">
<X
className="text-toast-text-secondary hover:text-toast-text-tertiary cursor-pointer"
strokeWidth={1.5}
width={14}
height={14}
onClick={() => toast.dismiss(toastId)}
/>
</div>
</div>
</div>
</div>
) : (
<div
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
}}
className={cn(
"relative flex flex-col w-[350px] rounded-lg border shadow-sm p-2",
backgroundColorClassName,
borderColorClassName
)}
>
<X
className="fixed top-2 right-2.5 text-toast-text-secondary hover:text-toast-text-tertiary cursor-pointer"
strokeWidth={1.5}
width={14}
height={14}
onClick={() => toast.dismiss(toastId)}
/>
<div className="w-full flex items-center px-4 py-2">
{icon && <div className="flex items-center justify-center">{icon}</div>}
<div className={cn("flex flex-col gap-0.5 pr-1", icon ? "pl-6" : "pl-1")}>
<div className={cn("text-sm font-semibold", textColorClassName)}>{props.title}</div>
{props.message && <div className="text-toast-text-secondary text-xs font-medium">{props.message}</div>}
</div>
</div>
</div>
);
switch (props.type) {
case TOAST_TYPE.SUCCESS:
return toast.custom(
(toastId) =>
renderToastContent({
toastId,
icon: <CheckCircle2 width={28} height={28} strokeWidth={1.5} className="text-toast-text-success" />,
textColorClassName: "text-toast-text-success",
backgroundColorClassName: "bg-toast-background-success",
borderColorClassName: "border-toast-border-success",
}),
props.id ? { id: props.id } : {}
);
case TOAST_TYPE.ERROR:
return toast.custom(
(toastId) =>
renderToastContent({
toastId,
icon: <XCircle width={28} height={28} strokeWidth={1.5} className="text-toast-text-error" />,
textColorClassName: "text-toast-text-error",
backgroundColorClassName: "bg-toast-background-error",
borderColorClassName: "border-toast-border-error",
}),
props.id ? { id: props.id } : {}
);
case TOAST_TYPE.WARNING:
return toast.custom(
(toastId) =>
renderToastContent({
toastId,
icon: <AlertTriangle width={28} height={28} strokeWidth={1.5} className="text-toast-text-warning" />,
textColorClassName: "text-toast-text-warning",
backgroundColorClassName: "bg-toast-background-warning",
borderColorClassName: "border-toast-border-warning",
}),
props.id ? { id: props.id } : {}
);
case TOAST_TYPE.INFO:
return toast.custom(
(toastId) =>
renderToastContent({
toastId,
textColorClassName: "text-toast-text-info",
backgroundColorClassName: "bg-toast-background-info",
borderColorClassName: "border-toast-border-info",
}),
props.id ? { id: props.id } : {}
);
case TOAST_TYPE.LOADING:
return toast.custom((toastId) =>
renderToastContent({
toastId,
icon: <CircularBarSpinner className="text-toast-text-tertiary" />,
textColorClassName: "text-toast-text-loading",
backgroundColorClassName: "bg-toast-background-loading",
borderColorClassName: "border-toast-border-loading",
})
);
}
};
export const setPromiseToast = <ToastData,>(
promise: Promise<ToastData>,
options: PromiseToastOptions<ToastData>
): void => {
const tId = setToast({ type: TOAST_TYPE.LOADING, title: options.loading });
promise
.then((data: ToastData) => {
setToast({
type: TOAST_TYPE.SUCCESS,
id: tId,
title: options.success.title,
message: options.success.message?.(data),
});
})
.catch((data: ToastData) => {
setToast({
type: TOAST_TYPE.ERROR,
id: tId,
title: options.error.title,
message: options.error.message?.(data),
});
});
};

View File

@ -7,9 +7,7 @@ import { mutate } from "swr";
// hooks
import { useUser } from "hooks/store";
// ui
import { Button } from "@plane/ui";
// hooks
import useToast from "hooks/use-toast";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
type Props = {
isOpen: boolean;
@ -26,7 +24,6 @@ export const DeactivateAccountModal: React.FC<Props> = (props) => {
const router = useRouter();
const { setToastAlert } = useToast();
const { setTheme } = useTheme();
const handleClose = () => {
@ -39,8 +36,8 @@ export const DeactivateAccountModal: React.FC<Props> = (props) => {
await deactivateAccount()
.then(() => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Account deactivated successfully.",
});
@ -50,8 +47,8 @@ export const DeactivateAccountModal: React.FC<Props> = (props) => {
handleClose();
})
.catch((err) =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error,
})
@ -90,7 +87,10 @@ export const DeactivateAccountModal: React.FC<Props> = (props) => {
<div className="">
<div className="flex items-start gap-x-4">
<div className="grid place-items-center rounded-full bg-red-500/20 p-2 sm:p-2 md:p-4 lg:p-4 mt-3 sm:mt-3 md:mt-0 lg:mt-0 ">
<Trash2 className="h-4 w-4 sm:h-4 sm:w-4 md:h-6 md:w-6 lg:h-6 lg:w-6 text-red-600" aria-hidden="true" />
<Trash2
className="h-4 w-4 sm:h-4 sm:w-4 md:h-6 md:w-6 lg:h-6 lg:w-6 text-red-600"
aria-hidden="true"
/>
</div>
<div>
<Dialog.Title as="h3" className="my-4 text-2xl font-medium leading-6 text-custom-text-100">

View File

@ -3,7 +3,8 @@ import { observer } from "mobx-react-lite";
import { AuthService } from "services/auth.service";
// hooks
import { useApplication } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { GitHubSignInButton, GoogleSignInButton } from "components/account";
@ -17,8 +18,6 @@ const authService = new AuthService();
export const OAuthOptions: React.FC<Props> = observer((props) => {
const { handleSignInRedirection, type } = props;
// toast alert
const { setToastAlert } = useToast();
// mobx store
const {
config: { envConfig },
@ -39,9 +38,9 @@ export const OAuthOptions: React.FC<Props> = observer((props) => {
if (response) handleSignInRedirection();
} else throw Error("Cant find credentials");
} catch (err: any) {
setToastAlert({
setToast({
type: TOAST_TYPE.ERROR,
title: "Error signing in!",
type: "error",
message: err?.error || "Something went wrong. Please try again later or contact the support team.",
});
}
@ -60,9 +59,9 @@ export const OAuthOptions: React.FC<Props> = observer((props) => {
if (response) handleSignInRedirection();
} else throw Error("Cant find credentials");
} catch (err: any) {
setToastAlert({
setToast({
type: TOAST_TYPE.ERROR,
title: "Error signing in!",
type: "error",
message: err?.error || "Something went wrong. Please try again later or contact the support team.",
});
}

View File

@ -4,10 +4,8 @@ import { XCircle } from "lucide-react";
import { observer } from "mobx-react-lite";
// services
import { AuthService } from "services/auth.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { Button, Input } from "@plane/ui";
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// helpers
import { checkEmailValidity } from "helpers/string.helper";
// types
@ -27,7 +25,6 @@ const authService = new AuthService();
export const SignInEmailForm: React.FC<Props> = observer((props) => {
const { onSubmit, updateEmail } = props;
// hooks
const { setToastAlert } = useToast();
const {
control,
formState: { errors, isSubmitting, isValid },
@ -52,8 +49,8 @@ export const SignInEmailForm: React.FC<Props> = observer((props) => {
.emailCheck(payload)
.then((res) => onSubmit(res.is_password_autoset))
.catch((err) =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error ?? "Something went wrong. Please try again.",
})

View File

@ -3,10 +3,9 @@ import { Controller, useForm } from "react-hook-form";
// services
import { AuthService } from "services/auth.service";
// hooks
import useToast from "hooks/use-toast";
import { useEventTracker } from "hooks/store";
// ui
import { Button, Input } from "@plane/ui";
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// helpers
import { checkEmailValidity } from "helpers/string.helper";
// icons
@ -38,8 +37,6 @@ export const SignInOptionalSetPasswordForm: React.FC<Props> = (props) => {
const [showPassword, setShowPassword] = useState(false);
// store hooks
const { captureEvent } = useEventTracker();
// toast alert
const { setToastAlert } = useToast();
// form info
const {
control,
@ -62,8 +59,8 @@ export const SignInOptionalSetPasswordForm: React.FC<Props> = (props) => {
await authService
.setPassword(payload)
.then(async () => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Password created successfully.",
});
@ -78,8 +75,8 @@ export const SignInOptionalSetPasswordForm: React.FC<Props> = (props) => {
state: "FAILED",
first_time: false,
});
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error ?? "Something went wrong. Please try again.",
});

View File

@ -6,12 +6,11 @@ import { Eye, EyeOff, XCircle } from "lucide-react";
// services
import { AuthService } from "services/auth.service";
// hooks
import useToast from "hooks/use-toast";
import { useApplication, useEventTracker } from "hooks/store";
// components
import { ESignInSteps, ForgotPasswordPopover } from "components/account";
// ui
import { Button, Input } from "@plane/ui";
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// helpers
import { checkEmailValidity } from "helpers/string.helper";
// types
@ -43,8 +42,6 @@ export const SignInPasswordForm: React.FC<Props> = observer((props) => {
// states
const [isSendingUniqueCode, setIsSendingUniqueCode] = useState(false);
const [showPassword, setShowPassword] = useState(false);
// toast alert
const { setToastAlert } = useToast();
const {
config: { envConfig },
} = useApplication();
@ -83,8 +80,8 @@ export const SignInPasswordForm: React.FC<Props> = observer((props) => {
await onSubmit();
})
.catch((err) =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error ?? "Something went wrong. Please try again.",
})
@ -107,8 +104,8 @@ export const SignInPasswordForm: React.FC<Props> = observer((props) => {
.generateUniqueCode({ email: emailFormValue })
.then(() => handleStepChange(ESignInSteps.USE_UNIQUE_CODE_FROM_PASSWORD))
.catch((err) =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error ?? "Something went wrong. Please try again.",
})

View File

@ -5,11 +5,10 @@ import { XCircle } from "lucide-react";
import { AuthService } from "services/auth.service";
import { UserService } from "services/user.service";
// hooks
import useToast from "hooks/use-toast";
import useTimer from "hooks/use-timer";
import { useEventTracker } from "hooks/store";
// ui
import { Button, Input } from "@plane/ui";
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// helpers
import { checkEmailValidity } from "helpers/string.helper";
// types
@ -42,8 +41,6 @@ export const SignInUniqueCodeForm: React.FC<Props> = (props) => {
const { email, onSubmit, handleEmailClear, submitButtonText } = props;
// states
const [isRequestingNewCode, setIsRequestingNewCode] = useState(false);
// toast alert
const { setToastAlert } = useToast();
// store hooks
const { captureEvent } = useEventTracker();
// timer
@ -84,8 +81,8 @@ export const SignInUniqueCodeForm: React.FC<Props> = (props) => {
captureEvent(CODE_VERIFIED, {
state: "FAILED",
});
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error ?? "Something went wrong. Please try again.",
});
@ -101,8 +98,8 @@ export const SignInUniqueCodeForm: React.FC<Props> = (props) => {
.generateUniqueCode(payload)
.then(() => {
setResendCodeTimer(30);
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "A new unique code has been sent to your email.",
});
@ -113,8 +110,8 @@ export const SignInUniqueCodeForm: React.FC<Props> = (props) => {
});
})
.catch((err) =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error ?? "Something went wrong. Please try again.",
})

View File

@ -4,10 +4,8 @@ import { XCircle } from "lucide-react";
import { observer } from "mobx-react-lite";
// services
import { AuthService } from "services/auth.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { Button, Input } from "@plane/ui";
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// helpers
import { checkEmailValidity } from "helpers/string.helper";
// types
@ -27,7 +25,6 @@ const authService = new AuthService();
export const SignUpEmailForm: React.FC<Props> = observer((props) => {
const { onSubmit, updateEmail } = props;
// hooks
const { setToastAlert } = useToast();
const {
control,
formState: { errors, isSubmitting, isValid },
@ -52,8 +49,8 @@ export const SignUpEmailForm: React.FC<Props> = observer((props) => {
.emailCheck(payload)
.then(() => onSubmit())
.catch((err) =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Error!",
message: err?.error ?? "Something went wrong. Please try again.",
})

View File

@ -3,14 +3,14 @@ import { Controller, useForm } from "react-hook-form";
// services
import { AuthService } from "services/auth.service";
// hooks
import useToast from "hooks/use-toast";
import { useEventTracker } from "hooks/store";
// ui
import { Button, Input } from "@plane/ui";
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// helpers
import { checkEmailValidity } from "helpers/string.helper";
// constants
// components
import { ESignUpSteps } from "components/account";
// constants
import { PASSWORD_CREATE_SELECTED, PASSWORD_CREATE_SKIPPED, SETUP_PASSWORD } from "constants/event-tracker";
// icons
import { Eye, EyeOff } from "lucide-react";
@ -41,8 +41,6 @@ export const SignUpOptionalSetPasswordForm: React.FC<Props> = (props) => {
const [showPassword, setShowPassword] = useState(false);
// store hooks
const { captureEvent } = useEventTracker();
// toast alert
const { setToastAlert } = useToast();
// form info
const {
control,
@ -65,8 +63,8 @@ export const SignUpOptionalSetPasswordForm: React.FC<Props> = (props) => {
await authService
.setPassword(payload)
.then(async () => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Password created successfully.",
});
@ -81,8 +79,8 @@ export const SignUpOptionalSetPasswordForm: React.FC<Props> = (props) => {
state: "FAILED",
first_time: true,
});
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error ?? "Something went wrong. Please try again.",
});

View File

@ -5,10 +5,8 @@ import { Controller, useForm } from "react-hook-form";
import { Eye, EyeOff, XCircle } from "lucide-react";
// services
import { AuthService } from "services/auth.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { Button, Input } from "@plane/ui";
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// helpers
import { checkEmailValidity } from "helpers/string.helper";
// types
@ -34,8 +32,6 @@ export const SignUpPasswordForm: React.FC<Props> = observer((props) => {
const { onSubmit } = props;
// states
const [showPassword, setShowPassword] = useState(false);
// toast alert
const { setToastAlert } = useToast();
// form info
const {
control,
@ -59,8 +55,8 @@ export const SignUpPasswordForm: React.FC<Props> = observer((props) => {
.passwordSignIn(payload)
.then(async () => await onSubmit())
.catch((err) =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error ?? "Something went wrong. Please try again.",
})

View File

@ -6,11 +6,10 @@ import { XCircle } from "lucide-react";
import { AuthService } from "services/auth.service";
import { UserService } from "services/user.service";
// hooks
import useToast from "hooks/use-toast";
import useTimer from "hooks/use-timer";
import { useEventTracker } from "hooks/store";
// ui
import { Button, Input } from "@plane/ui";
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// helpers
import { checkEmailValidity } from "helpers/string.helper";
// types
@ -44,8 +43,6 @@ export const SignUpUniqueCodeForm: React.FC<Props> = (props) => {
const [isRequestingNewCode, setIsRequestingNewCode] = useState(false);
// store hooks
const { captureEvent } = useEventTracker();
// toast alert
const { setToastAlert } = useToast();
// timer
const { timer: resendTimerCode, setTimer: setResendCodeTimer } = useTimer(30);
// form info
@ -84,8 +81,8 @@ export const SignUpUniqueCodeForm: React.FC<Props> = (props) => {
captureEvent(CODE_VERIFIED, {
state: "FAILED",
});
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error ?? "Something went wrong. Please try again.",
});
@ -101,8 +98,8 @@ export const SignUpUniqueCodeForm: React.FC<Props> = (props) => {
.generateUniqueCode(payload)
.then(() => {
setResendCodeTimer(30);
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "A new unique code has been sent to your email.",
});
@ -112,8 +109,8 @@ export const SignUpUniqueCodeForm: React.FC<Props> = (props) => {
});
})
.catch((err) =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error ?? "Something went wrong. Please try again.",
})

View File

@ -6,11 +6,10 @@ import { mutate } from "swr";
import { AnalyticsService } from "services/analytics.service";
// hooks
import { useCycle, useModule, useProject, useUser, useWorkspace } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { CustomAnalyticsSidebarHeader, CustomAnalyticsSidebarProjectsList } from "components/analytics";
// ui
import { Button, LayersIcon } from "@plane/ui";
import { Button, LayersIcon, TOAST_TYPE, setToast } from "@plane/ui";
// icons
import { CalendarDays, Download, RefreshCw } from "lucide-react";
// helpers
@ -34,8 +33,6 @@ export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
// router
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
// toast alert
const { setToastAlert } = useToast();
// store hooks
const { currentUser } = useUser();
const { workspaceProjectIds, getProjectById } = useProject();
@ -107,8 +104,8 @@ export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
analyticsService
.exportAnalytics(workspaceSlug.toString(), data)
.then((res) => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: res.message,
});
@ -116,8 +113,8 @@ export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
trackExportAnalytics();
})
.catch(() =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "There was some error in exporting the analytics. Please try again.",
})

View File

@ -4,10 +4,8 @@ import { mutate } from "swr";
import { Dialog, Transition } from "@headlessui/react";
// services
import { APITokenService } from "services/api_token.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { Button } from "@plane/ui";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
// types
import { IApiToken } from "@plane/types";
// fetch-keys
@ -25,8 +23,6 @@ export const DeleteApiTokenModal: FC<Props> = (props) => {
const { isOpen, onClose, tokenId } = props;
// states
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
// hooks
const { setToastAlert } = useToast();
// router
const router = useRouter();
const { workspaceSlug } = router.query;
@ -44,8 +40,8 @@ export const DeleteApiTokenModal: FC<Props> = (props) => {
apiTokenService
.deleteApiToken(workspaceSlug.toString(), tokenId)
.then(() => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Token deleted successfully.",
});
@ -59,8 +55,8 @@ export const DeleteApiTokenModal: FC<Props> = (props) => {
handleClose();
})
.catch((err) =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error",
message: err?.message ?? "Something went wrong. Please try again.",
})

View File

@ -4,8 +4,8 @@ import { mutate } from "swr";
import { Dialog, Transition } from "@headlessui/react";
// services
import { APITokenService } from "services/api_token.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { CreateApiTokenForm, GeneratedTokenDetails } from "components/api-token";
// helpers
@ -32,8 +32,6 @@ export const CreateApiTokenModal: React.FC<Props> = (props) => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// toast alert
const { setToastAlert } = useToast();
const handleClose = () => {
onClose();
@ -76,10 +74,10 @@ export const CreateApiTokenModal: React.FC<Props> = (props) => {
);
})
.catch((err) => {
setToastAlert({
message: err.message,
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error",
message: err.message,
});
throw err;

View File

@ -3,10 +3,8 @@ import { add } from "date-fns";
import { Controller, useForm } from "react-hook-form";
import { DateDropdown } from "components/dropdowns";
import { Calendar } from "lucide-react";
// hooks
import useToast from "hooks/use-toast";
// ui
import { Button, CustomSelect, Input, TextArea, ToggleSwitch } from "@plane/ui";
import { Button, CustomSelect, Input, TextArea, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui";
// helpers
import { renderFormattedDate, renderFormattedPayloadDate } from "helpers/date-time.helper";
// types
@ -66,8 +64,6 @@ export const CreateApiTokenForm: React.FC<Props> = (props) => {
const { handleClose, neverExpires, toggleNeverExpires, onSubmit } = props;
// states
const [customDate, setCustomDate] = useState<Date | null>(null);
// toast alert
const { setToastAlert } = useToast();
// form
const {
control,
@ -80,8 +76,8 @@ export const CreateApiTokenForm: React.FC<Props> = (props) => {
const handleFormSubmit = async (data: IApiToken) => {
// if never expires is toggled off, and the user has not selected a custom date or a predefined date, show an error
if (!neverExpires && (!data.expired_at || (data.expired_at === "custom" && !customDate)))
return setToastAlert({
type: "error",
return setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Please select an expiration date.",
});

View File

@ -1,8 +1,6 @@
import { Copy } from "lucide-react";
// hooks
import useToast from "hooks/use-toast";
// ui
import { Button, Tooltip } from "@plane/ui";
import { Button, Tooltip, TOAST_TYPE, setToast } from "@plane/ui";
// helpers
import { renderFormattedDate } from "helpers/date-time.helper";
import { copyTextToClipboard } from "helpers/string.helper";
@ -17,12 +15,10 @@ type Props = {
export const GeneratedTokenDetails: React.FC<Props> = (props) => {
const { handleClose, tokenDetails } = props;
const { setToastAlert } = useToast();
const copyApiToken = (token: string) => {
copyTextToClipboard(token).then(() =>
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Token copied to clipboard.",
})

View File

@ -4,10 +4,8 @@ import { Command } from "cmdk";
import { LinkIcon, Signal, Trash2, UserMinus2, UserPlus2 } from "lucide-react";
// hooks
import { useApplication, useUser, useIssues } from "hooks/store";
// hooks
import useToast from "hooks/use-toast";
// ui
import { DoubleCircleIcon, UserGroupIcon } from "@plane/ui";
import { DoubleCircleIcon, UserGroupIcon, TOAST_TYPE, setToast } from "@plane/ui";
// helpers
import { copyTextToClipboard } from "helpers/string.helper";
// types
@ -37,8 +35,6 @@ export const CommandPaletteIssueActions: React.FC<Props> = observer((props) => {
} = useApplication();
const { currentUser } = useUser();
const { setToastAlert } = useToast();
const handleUpdateIssue = async (formData: Partial<TIssue>) => {
if (!workspaceSlug || !projectId || !issueDetails) return;
@ -71,14 +67,14 @@ export const CommandPaletteIssueActions: React.FC<Props> = observer((props) => {
const url = new URL(window.location.href);
copyTextToClipboard(url.href)
.then(() => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Copied to clipboard",
});
})
.catch(() => {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Some error occurred",
});
});

View File

@ -5,7 +5,8 @@ import { Settings } from "lucide-react";
import { observer } from "mobx-react-lite";
// hooks
import { useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// constants
import { THEME_OPTIONS } from "constants/themes";
@ -21,15 +22,14 @@ export const CommandPaletteThemeActions: FC<Props> = observer((props) => {
const { updateCurrentUserTheme } = useUser();
// hooks
const { setTheme } = useTheme();
const { setToastAlert } = useToast();
const updateUserTheme = async (newTheme: string) => {
setTheme(newTheme);
return updateCurrentUserTheme(newTheme).catch(() => {
setToastAlert({
setToast({
type: TOAST_TYPE.ERROR,
title: "Failed to save user theme settings!",
type: "error",
});
});
};

View File

@ -4,7 +4,8 @@ import useSWR from "swr";
import { observer } from "mobx-react-lite";
// hooks
import { useApplication, useEventTracker, useIssues, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { CommandModal, ShortcutsModal } from "components/command-palette";
import { BulkDeleteIssuesModal } from "components/core";
@ -63,8 +64,6 @@ export const CommandPalette: FC = observer(() => {
createIssueStoreType,
} = commandPalette;
const { setToastAlert } = useToast();
const { data: issueDetails } = useSWR(
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null,
workspaceSlug && projectId && issueId
@ -78,18 +77,18 @@ export const CommandPalette: FC = observer(() => {
const url = new URL(window.location.href);
copyTextToClipboard(url.href)
.then(() => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Copied to clipboard",
});
})
.catch(() => {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Some error occurred",
});
});
}, [setToastAlert, issueId]);
}, [issueId]);
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {

View File

@ -6,10 +6,8 @@ import { SubmitHandler, useForm } from "react-hook-form";
import { Combobox, Dialog, Transition } from "@headlessui/react";
// services
import { IssueService } from "services/issue";
// hooks
import useToast from "hooks/use-toast";
// ui
import { Button, LayersIcon } from "@plane/ui";
import { Button, LayersIcon, TOAST_TYPE, setToast } from "@plane/ui";
// icons
import { Search } from "lucide-react";
// types
@ -55,8 +53,6 @@ export const BulkDeleteIssuesModal: React.FC<Props> = observer((props) => {
: null
);
const { setToastAlert } = useToast();
const {
handleSubmit,
watch,
@ -79,8 +75,8 @@ export const BulkDeleteIssuesModal: React.FC<Props> = observer((props) => {
if (!workspaceSlug || !projectId) return;
if (!data.delete_issue_ids || data.delete_issue_ids.length === 0) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Please select at least one issue.",
});
@ -91,16 +87,16 @@ export const BulkDeleteIssuesModal: React.FC<Props> = observer((props) => {
await removeBulkIssues(workspaceSlug as string, projectId as string, data.delete_issue_ids)
.then(() => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Issues deleted successfully!",
});
handleClose();
})
.catch(() =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Something went wrong. Please try again.",
})

View File

@ -3,11 +3,9 @@ import { Combobox, Dialog, Transition } from "@headlessui/react";
import { Rocket, Search, X } from "lucide-react";
// services
import { ProjectService } from "services/project";
// hooks
import useToast from "hooks/use-toast";
import useDebounce from "hooks/use-debounce";
// ui
import { Button, LayersIcon, Loader, ToggleSwitch, Tooltip } from "@plane/ui";
import { Button, LayersIcon, Loader, ToggleSwitch, Tooltip, TOAST_TYPE, setToast } from "@plane/ui";
// types
import { ISearchIssueResponse, TProjectIssuesSearchParams } from "@plane/types";
@ -43,8 +41,6 @@ export const ExistingIssuesListModal: React.FC<Props> = (props) => {
const debouncedSearchTerm: string = useDebounce(searchTerm, 500);
const { setToastAlert } = useToast();
const handleClose = () => {
onClose();
setSearchTerm("");
@ -54,8 +50,8 @@ export const ExistingIssuesListModal: React.FC<Props> = (props) => {
const onSubmit = async () => {
if (selectedIssues.length === 0) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Please select at least one issue.",
});
@ -69,9 +65,9 @@ export const ExistingIssuesListModal: React.FC<Props> = (props) => {
handleClose();
setToastAlert({
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success",
type: "success",
message: `Issue${selectedIssues.length > 1 ? "s" : ""} added successfully`,
});
};

View File

@ -3,10 +3,9 @@ import { useRouter } from "next/router";
import { Controller, useForm } from "react-hook-form"; // services
import { AIService } from "services/ai.service";
// hooks
import useToast from "hooks/use-toast";
import { usePopper } from "react-popper";
// ui
import { Button, Input } from "@plane/ui";
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { RichReadOnlyEditorWithRef } from "@plane/rich-text-editor";
import { Popover, Transition } from "@headlessui/react";
@ -44,8 +43,6 @@ export const GptAssistantPopover: React.FC<Props> = (props) => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// toast alert
const { setToastAlert } = useToast();
// popper
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: placement ?? "auto",
@ -78,8 +75,8 @@ export const GptAssistantPopover: React.FC<Props> = (props) => {
? error || "You have reached the maximum number of requests of 50 requests per month per user."
: error || "Some error occurred. Please try again.";
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: errorMessage,
});
@ -104,8 +101,8 @@ export const GptAssistantPopover: React.FC<Props> = (props) => {
};
const handleInvalidTask = () => {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Please enter some task to get AI assistance.",
});

View File

@ -6,10 +6,8 @@ import { Transition, Dialog } from "@headlessui/react";
import { useApplication } from "hooks/store";
// services
import { FileService } from "services/file.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { Button } from "@plane/ui";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
// icons
import { UserCircle2 } from "lucide-react";
// constants
@ -32,8 +30,6 @@ export const UserImageUploadModal: React.FC<Props> = observer((props) => {
// states
const [image, setImage] = useState<File | null>(null);
const [isImageUploading, setIsImageUploading] = useState(false);
// toast alert
const { setToastAlert } = useToast();
// store hooks
const {
config: { envConfig },
@ -76,8 +72,8 @@ export const UserImageUploadModal: React.FC<Props> = observer((props) => {
if (value) fileService.deleteUserFile(value);
})
.catch((err) =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error ?? "Something went wrong. Please try again.",
})

View File

@ -7,10 +7,8 @@ import { Transition, Dialog } from "@headlessui/react";
import { useApplication, useWorkspace } from "hooks/store";
// services
import { FileService } from "services/file.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { Button } from "@plane/ui";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
// icons
import { UserCircle2 } from "lucide-react";
// constants
@ -37,8 +35,6 @@ export const WorkspaceImageUploadModal: React.FC<Props> = observer((props) => {
const router = useRouter();
const { workspaceSlug } = router.query;
const { setToastAlert } = useToast();
const {
config: { envConfig },
} = useApplication();
@ -83,8 +79,8 @@ export const WorkspaceImageUploadModal: React.FC<Props> = observer((props) => {
if (value && currentWorkspace) fileService.deleteFile(currentWorkspace.id, value);
})
.catch((err) =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error ?? "Something went wrong. Please try again.",
})

View File

@ -1,5 +1,5 @@
// ui
import { ExternalLinkIcon, Tooltip } from "@plane/ui";
import { ExternalLinkIcon, Tooltip, TOAST_TYPE, setToast } from "@plane/ui";
// icons
import { Pencil, Trash2, LinkIcon } from "lucide-react";
// helpers
@ -7,7 +7,6 @@ import { calculateTimeAgo } from "helpers/date-time.helper";
// types
import { ILinkDetails, UserAuth } from "@plane/types";
// hooks
import useToast from "hooks/use-toast";
import { observer } from "mobx-react";
import { useMeasure } from "@nivo/core";
import { useMember } from "hooks/store";
@ -20,18 +19,16 @@ type Props = {
};
export const LinksList: React.FC<Props> = observer(({ links, handleDeleteLink, handleEditLink, userAuth }) => {
// toast
const { setToastAlert } = useToast();
const { getUserDetails } = useMember();
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
setToastAlert({
message: "The URL has been successfully copied to your clipboard",
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Copied to clipboard",
message: "The URL has been successfully copied to your clipboard",
});
};

View File

@ -5,7 +5,6 @@ import useSWR from "swr";
import { useTheme } from "next-themes";
// hooks
import { useCycle, useIssues, useMember, useProject, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { SingleProgressStats } from "components/core";
import {
@ -18,6 +17,7 @@ import {
PriorityIcon,
Avatar,
CycleGroupIcon,
setPromiseToast,
} from "@plane/ui";
// components
import ProgressChart from "components/core/sidebar/progress-chart";
@ -60,8 +60,6 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
} = useCycle();
const { currentProjectDetails } = useProject();
const { getUserDetails } = useMember();
// toast alert
const { setToastAlert } = useToast();
const { isLoading } = useSWR(
workspaceSlug && projectId ? `PROJECT_ACTIVE_CYCLE_${projectId}` : null,
@ -119,12 +117,18 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
e.preventDefault();
if (!workspaceSlug || !projectId) return;
addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), activeCycle.id).catch(() => {
setToastAlert({
type: "error",
const addToFavoritePromise = addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), activeCycle.id);
setPromiseToast(addToFavoritePromise, {
loading: "Adding cycle to favorites...",
success: {
title: "Success!",
message: () => "Cycle added to favorites.",
},
error: {
title: "Error!",
message: "Couldn't add the cycle to favorites. Please try again.",
});
message: () => "Couldn't add the cycle to favorites. Please try again.",
},
});
};
@ -132,12 +136,22 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
e.preventDefault();
if (!workspaceSlug || !projectId) return;
removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), activeCycle.id).catch(() => {
setToastAlert({
type: "error",
const removeFromFavoritePromise = removeCycleFromFavorites(
workspaceSlug?.toString(),
projectId.toString(),
activeCycle.id
);
setPromiseToast(removeFromFavoritePromise, {
loading: "Removing cycle from favorites...",
success: {
title: "Success!",
message: () => "Cycle removed from favorites.",
},
error: {
title: "Error!",
message: "Couldn't add the cycle to favorites. Please try again.",
});
message: () => "Couldn't remove the cycle from favorites. Please try again.",
},
});
};

View File

@ -4,11 +4,20 @@ import Link from "next/link";
import { observer } from "mobx-react";
// hooks
import { useEventTracker, useCycle, useUser, useMember } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { CycleCreateUpdateModal, CycleDeleteModal } from "components/cycles";
// ui
import { Avatar, AvatarGroup, CustomMenu, Tooltip, LayersIcon, CycleGroupIcon } from "@plane/ui";
import {
Avatar,
AvatarGroup,
CustomMenu,
Tooltip,
LayersIcon,
CycleGroupIcon,
TOAST_TYPE,
setToast,
setPromiseToast,
} from "@plane/ui";
// icons
import { Info, LinkIcon, Pencil, Star, Trash2 } from "lucide-react";
// helpers
@ -41,8 +50,6 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = observer((props) => {
} = useUser();
const { addCycleToFavorites, removeCycleFromFavorites, getCycleById } = useCycle();
const { getUserDetails } = useMember();
// toast alert
const { setToastAlert } = useToast();
// computed
const cycleDetails = getCycleById(cycleId);
@ -81,8 +88,8 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = observer((props) => {
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}`).then(() => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Link Copied!",
message: "Cycle link copied to clipboard.",
});
@ -93,42 +100,56 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = observer((props) => {
e.preventDefault();
if (!workspaceSlug || !projectId) return;
addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId)
.then(() => {
const addToFavoritePromise = addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId).then(
() => {
captureEvent(CYCLE_FAVORITED, {
cycle_id: cycleId,
element: "Grid layout",
state: "SUCCESS",
});
})
.catch(() => {
setToastAlert({
type: "error",
title: "Error!",
message: "Couldn't add the cycle to favorites. Please try again.",
});
});
}
);
setPromiseToast(addToFavoritePromise, {
loading: "Adding cycle to favorites...",
success: {
title: "Success!",
message: () => "Cycle added to favorites.",
},
error: {
title: "Error!",
message: () => "Couldn't add the cycle to favorites. Please try again.",
},
});
};
const handleRemoveFromFavorites = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
if (!workspaceSlug || !projectId) return;
removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId)
.then(() => {
captureEvent(CYCLE_UNFAVORITED, {
cycle_id: cycleId,
element: "Grid layout",
state: "SUCCESS",
});
})
.catch(() => {
setToastAlert({
type: "error",
title: "Error!",
message: "Couldn't add the cycle to favorites. Please try again.",
});
const removeFromFavoritePromise = removeCycleFromFavorites(
workspaceSlug?.toString(),
projectId.toString(),
cycleId
).then(() => {
captureEvent(CYCLE_UNFAVORITED, {
cycle_id: cycleId,
element: "Grid layout",
state: "SUCCESS",
});
});
setPromiseToast(removeFromFavoritePromise, {
loading: "Removing cycle from favorites...",
success: {
title: "Success!",
message: () => "Cycle removed from favorites.",
},
error: {
title: "Error!",
message: () => "Couldn't remove the cycle from favorites. Please try again.",
},
});
};
const handleEditCycle = (e: MouseEvent<HTMLButtonElement>) => {

View File

@ -4,11 +4,20 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react";
// hooks
import { useEventTracker, useCycle, useUser, useMember } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { CycleCreateUpdateModal, CycleDeleteModal } from "components/cycles";
// ui
import { CustomMenu, Tooltip, CircularProgressIndicator, CycleGroupIcon, AvatarGroup, Avatar } from "@plane/ui";
import {
CustomMenu,
Tooltip,
CircularProgressIndicator,
CycleGroupIcon,
AvatarGroup,
Avatar,
TOAST_TYPE,
setToast,
setPromiseToast,
} from "@plane/ui";
// icons
import { Check, Info, LinkIcon, Pencil, Star, Trash2, User2 } from "lucide-react";
// helpers
@ -45,8 +54,6 @@ export const CyclesListItem: FC<TCyclesListItem> = observer((props) => {
} = useUser();
const { getCycleById, addCycleToFavorites, removeCycleFromFavorites } = useCycle();
const { getUserDetails } = useMember();
// toast alert
const { setToastAlert } = useToast();
const handleCopyText = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
@ -54,8 +61,8 @@ export const CyclesListItem: FC<TCyclesListItem> = observer((props) => {
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}`).then(() => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Link Copied!",
message: "Cycle link copied to clipboard.",
});
@ -66,42 +73,56 @@ export const CyclesListItem: FC<TCyclesListItem> = observer((props) => {
e.preventDefault();
if (!workspaceSlug || !projectId) return;
addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId)
.then(() => {
const addToFavoritePromise = addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId).then(
() => {
captureEvent(CYCLE_FAVORITED, {
cycle_id: cycleId,
element: "List layout",
state: "SUCCESS",
});
})
.catch(() => {
setToastAlert({
type: "error",
title: "Error!",
message: "Couldn't add the cycle to favorites. Please try again.",
});
});
}
);
setPromiseToast(addToFavoritePromise, {
loading: "Adding cycle to favorites...",
success: {
title: "Success!",
message: () => "Cycle added to favorites.",
},
error: {
title: "Error!",
message: () => "Couldn't add the cycle to favorites. Please try again.",
},
});
};
const handleRemoveFromFavorites = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
if (!workspaceSlug || !projectId) return;
removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId)
.then(() => {
captureEvent(CYCLE_UNFAVORITED, {
cycle_id: cycleId,
element: "List layout",
state: "SUCCESS",
});
})
.catch(() => {
setToastAlert({
type: "error",
title: "Error!",
message: "Couldn't add the cycle to favorites. Please try again.",
});
const removeFromFavoritePromise = removeCycleFromFavorites(
workspaceSlug?.toString(),
projectId.toString(),
cycleId
).then(() => {
captureEvent(CYCLE_UNFAVORITED, {
cycle_id: cycleId,
element: "List layout",
state: "SUCCESS",
});
});
setPromiseToast(removeFromFavoritePromise, {
loading: "Removing cycle from favorites...",
success: {
title: "Success!",
message: () => "Cycle removed from favorites.",
},
error: {
title: "Error!",
message: () => "Couldn't remove the cycle from favorites. Please try again.",
},
});
};
const handleEditCycle = (e: MouseEvent<HTMLButtonElement>) => {

View File

@ -5,9 +5,8 @@ import { observer } from "mobx-react-lite";
import { AlertTriangle } from "lucide-react";
// hooks
import { useEventTracker, useCycle } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { Button } from "@plane/ui";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
// types
import { ICycle } from "@plane/types";
// constants
@ -31,8 +30,6 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
// store hooks
const { captureCycleEvent } = useEventTracker();
const { deleteCycle } = useCycle();
// toast alert
const { setToastAlert } = useToast();
const formSubmit = async () => {
if (!cycle) return;
@ -41,8 +38,8 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
try {
await deleteCycle(workspaceSlug, projectId, cycle.id)
.then(() => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Cycle deleted successfully.",
});
@ -62,8 +59,8 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
handleClose();
} catch (error) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Warning!",
message: "Something went wrong please try again later.",
});

View File

@ -4,10 +4,11 @@ import { Dialog, Transition } from "@headlessui/react";
import { CycleService } from "services/cycle.service";
// hooks
import { useEventTracker, useCycle, useProject } from "hooks/store";
import useToast from "hooks/use-toast";
import useLocalStorage from "hooks/use-local-storage";
// components
import { CycleForm } from "components/cycles";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// types
import type { CycleDateCheckData, ICycle, TCycleView } from "@plane/types";
// constants
@ -32,8 +33,6 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
const { captureCycleEvent } = useEventTracker();
const { workspaceProjectIds } = useProject();
const { createCycle, updateCycleDetails } = useCycle();
// toast alert
const { setToastAlert } = useToast();
const { setValue: setCycleTab } = useLocalStorage<TCycleView>("cycle_tab", "active");
@ -43,8 +42,8 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
const selectedProjectId = payload.project_id ?? projectId.toString();
await createCycle(workspaceSlug, selectedProjectId, payload)
.then((res) => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Cycle created successfully.",
});
@ -54,8 +53,8 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
});
})
.catch((err) => {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err.detail ?? "Error in creating cycle. Please try again.",
});
@ -77,8 +76,8 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
eventName: CYCLE_UPDATED,
payload: { ...res, changed_properties: changed_properties, state: "SUCCESS" },
});
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Cycle updated successfully.",
});
@ -88,8 +87,8 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
eventName: CYCLE_UPDATED,
payload: { ...payload, state: "FAILED" },
});
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err.detail ?? "Error in updating cycle. Please try again.",
});
@ -138,8 +137,8 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
}
handleClose();
} else
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "You already have a cycle on the given dates, if you want to create a draft cycle, remove the dates.",
});

View File

@ -8,13 +8,12 @@ import isEmpty from "lodash/isEmpty";
import { CycleService } from "services/cycle.service";
// hooks
import { useEventTracker, useCycle, useUser, useMember } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { SidebarProgressStats } from "components/core";
import ProgressChart from "components/core/sidebar/progress-chart";
import { CycleDeleteModal } from "components/cycles/delete-modal";
// ui
import { Avatar, CustomMenu, Loader, LayersIcon } from "@plane/ui";
import { Avatar, CustomMenu, Loader, LayersIcon, TOAST_TYPE, setToast } from "@plane/ui";
// icons
import { ChevronDown, LinkIcon, Trash2, UserCircle2, AlertCircle, ChevronRight, CalendarClock } from "lucide-react";
// helpers
@ -60,8 +59,6 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
// derived values
const cycleDetails = getCycleById(cycleId);
const cycleOwnerDetails = cycleDetails ? getUserDetails(cycleDetails.owned_by_id) : undefined;
// toast alert
const { setToastAlert } = useToast();
// form info
const { control, reset } = useForm({
defaultValues,
@ -98,15 +95,15 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
const handleCopyText = () => {
copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/cycles/${cycleId}`)
.then(() => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Link Copied!",
message: "Cycle link copied to clipboard.",
});
})
.catch(() => {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Some error occurred",
});
});
@ -147,14 +144,14 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
if (isDateValid) {
submitChanges(payload, "date_range");
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Cycle updated successfully.",
});
} else {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message:
"You already have a cycle on the given dates, if you want to create a draft cycle, you can do that by removing both the dates.",

View File

@ -3,10 +3,10 @@ import { useRouter } from "next/router";
import { Dialog, Transition } from "@headlessui/react";
import { observer } from "mobx-react-lite";
// hooks
import useToast from "hooks/use-toast";
import { useCycle, useIssues } from "hooks/store";
// ui
import { ContrastIcon, TransferIcon, TOAST_TYPE, setToast } from "@plane/ui";
//icons
import { ContrastIcon, TransferIcon } from "@plane/ui";
import { AlertCircle, Search, X } from "lucide-react";
// constants
import { EIssuesStoreType } from "constants/issue";
@ -30,23 +30,21 @@ export const TransferIssuesModal: React.FC<Props> = observer((props) => {
const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query;
const { setToastAlert } = useToast();
const transferIssue = async (payload: any) => {
if (!workspaceSlug || !projectId || !cycleId) return;
// TODO: import transferIssuesFromCycle from store
await transferIssuesFromCycle(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), payload)
.then(() => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Issues transferred successfully",
message: "Issues have been transferred successfully",
});
})
.catch(() => {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Issues cannot be transfer. Please try again.",
});

View File

@ -5,9 +5,8 @@ import { Dialog, Transition } from "@headlessui/react";
import { observer } from "mobx-react-lite";
// store hooks
import { useEstimate } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { Button, Input, TextArea } from "@plane/ui";
import { Button, Input, TextArea, TOAST_TYPE, setToast } from "@plane/ui";
// helpers
import { checkDuplicates } from "helpers/array.helper";
// types
@ -40,8 +39,6 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
// store hooks
const { createEstimate, updateEstimate } = useEstimate();
// form info
// toast alert
const { setToastAlert } = useToast();
const {
formState: { errors, isSubmitting },
handleSubmit,
@ -67,8 +64,8 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
const error = err?.error;
const errorString = Array.isArray(error) ? error[0] : error;
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message:
errorString ?? err.status === 400
@ -89,8 +86,8 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
const error = err?.error;
const errorString = Array.isArray(error) ? error[0] : error;
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: errorString ?? "Estimate could not be updated. Please try again.",
});
@ -99,8 +96,8 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
const onSubmit = async (formData: FormValues) => {
if (!formData.name || formData.name === "") {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Estimate title cannot be empty.",
});
@ -115,8 +112,8 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
formData.value5 === "" ||
formData.value6 === ""
) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Estimate point cannot be empty.",
});
@ -131,8 +128,8 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
formData.value5.length > 20 ||
formData.value6.length > 20
) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Estimate point cannot have more than 20 characters.",
});
@ -149,8 +146,8 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
formData.value6,
])
) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Estimate points cannot have duplicate values.",
});

View File

@ -5,11 +5,10 @@ import { observer } from "mobx-react-lite";
import { AlertTriangle } from "lucide-react";
// store hooks
import { useEstimate } from "hooks/store";
import useToast from "hooks/use-toast";
// types
import { IEstimate } from "@plane/types";
// ui
import { Button } from "@plane/ui";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
type Props = {
isOpen: boolean;
@ -26,8 +25,6 @@ export const DeleteEstimateModal: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId } = router.query;
// store hooks
const { deleteEstimate } = useEstimate();
// toast alert
const { setToastAlert } = useToast();
const handleEstimateDelete = () => {
if (!workspaceSlug || !projectId) return;
@ -43,8 +40,8 @@ export const DeleteEstimateModal: React.FC<Props> = observer((props) => {
const error = err?.error;
const errorString = Array.isArray(error) ? error[0] : error;
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: errorString ?? "Estimate could not be deleted. Please try again",
});

View File

@ -3,9 +3,8 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// hooks
import { useProject } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { Button, CustomMenu } from "@plane/ui";
import { Button, CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
//icons
import { Pencil, Trash2 } from "lucide-react";
// helpers
@ -26,8 +25,6 @@ export const EstimateListItem: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId } = router.query;
// store hooks
const { currentProjectDetails, updateProject } = useProject();
// hooks
const { setToastAlert } = useToast();
const handleUseEstimate = async () => {
if (!workspaceSlug || !projectId) return;
@ -38,8 +35,8 @@ export const EstimateListItem: React.FC<Props> = observer((props) => {
const error = err?.error;
const errorString = Array.isArray(error) ? error[0] : error;
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: errorString ?? "Estimate points could not be used. Please try again.",
});

View File

@ -4,12 +4,11 @@ import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// store hooks
import { useEstimate, useProject, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { CreateUpdateEstimateModal, DeleteEstimateModal, EstimateListItem } from "components/estimates";
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
// ui
import { Button, Loader } from "@plane/ui";
import { Button, Loader, TOAST_TYPE, setToast } from "@plane/ui";
// types
import { IEstimate } from "@plane/types";
// helpers
@ -31,8 +30,6 @@ export const EstimatesList: React.FC = observer(() => {
const { updateProject, currentProjectDetails } = useProject();
const { projectEstimates, getProjectEstimateById } = useEstimate();
const { currentUser } = useUser();
// toast alert
const { setToastAlert } = useToast();
const editEstimate = (estimate: IEstimate) => {
setEstimateFormOpen(true);
@ -50,8 +47,8 @@ export const EstimatesList: React.FC = observer(() => {
const error = err?.error;
const errorString = Array.isArray(error) ? error[0] : error;
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: errorString ?? "Estimate could not be disabled. Please try again",
});

View File

@ -6,10 +6,8 @@ import { Dialog, Transition } from "@headlessui/react";
import { useProject } from "hooks/store";
// services
import { ProjectExportService } from "services/project";
// hooks
import useToast from "hooks/use-toast";
// ui
import { Button, CustomSearchSelect } from "@plane/ui";
import { Button, CustomSearchSelect, TOAST_TYPE, setToast } from "@plane/ui";
// types
import { IUser, IImporterService } from "@plane/types";
@ -34,8 +32,6 @@ export const Exporter: React.FC<Props> = observer((props) => {
const { workspaceSlug } = router.query;
// store hooks
const { workspaceProjectIds, getProjectById } = useProject();
// toast alert
const { setToastAlert } = useToast();
const options = workspaceProjectIds?.map((projectId) => {
const projectDetails = getProjectById(projectId);
@ -71,8 +67,8 @@ export const Exporter: React.FC<Props> = observer((props) => {
mutateServices();
router.push(`/${workspaceSlug}/settings/exports`);
setExportLoading(false);
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Export Successful",
message: `You will be able to download the exported ${
provider === "csv" ? "CSV" : provider === "xlsx" ? "Excel" : provider === "json" ? "JSON" : ""
@ -81,8 +77,8 @@ export const Exporter: React.FC<Props> = observer((props) => {
})
.catch(() => {
setExportLoading(false);
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Export was unsuccessful. Please try again.",
});

View File

@ -5,7 +5,6 @@ import { DayPicker } from "react-day-picker";
import { Popover } from "@headlessui/react";
// hooks
import { useUser, useInboxIssues, useIssueDetail, useWorkspace, useEventTracker } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import {
AcceptIssueModal,
@ -14,7 +13,7 @@ import {
SelectDuplicateInboxIssueModal,
} from "components/inbox";
// ui
import { Button } from "@plane/ui";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
// icons
import { CheckCircle2, ChevronDown, ChevronUp, Clock, FileStack, Trash2, XCircle } from "lucide-react";
// types
@ -51,7 +50,6 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
currentUser,
membership: { currentProjectRole },
} = useUser();
const { setToastAlert } = useToast();
// states
const [date, setDate] = useState(new Date());
@ -74,8 +72,8 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
if (!workspaceSlug || !projectId || !inboxId || !inboxIssueId) throw new Error("Missing required parameters");
await updateInboxIssueStatus(workspaceSlug, projectId, inboxId, inboxIssueId, data);
} catch (error) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Something went wrong while updating inbox status. Please try again.",
});
@ -98,8 +96,8 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
pathname: `/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}`,
});
} catch (error) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Something went wrong while deleting inbox issue. Please try again.",
});
@ -122,7 +120,6 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
inboxIssueId,
updateInboxIssueStatus,
removeInboxIssue,
setToastAlert,
captureIssueEvent,
router,
]

View File

@ -7,7 +7,6 @@ import { RichTextEditorWithRef } from "@plane/rich-text-editor";
import { Sparkle } from "lucide-react";
// hooks
import { useApplication, useEventTracker, useWorkspace, useInboxIssues, useMention } from "hooks/store";
import useToast from "hooks/use-toast";
// services
import { FileService } from "services/file.service";
import { AIService } from "services/ai.service";
@ -15,7 +14,7 @@ import { AIService } from "services/ai.service";
import { PriorityDropdown } from "components/dropdowns";
import { GptAssistantPopover } from "components/core";
// ui
import { Button, Input, ToggleSwitch } from "@plane/ui";
import { Button, Input, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui";
// types
import { TIssue } from "@plane/types";
// constants
@ -46,9 +45,6 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false);
// refs
const editorRef = useRef<any>(null);
// toast alert
const { setToastAlert } = useToast();
const { mentionHighlights, mentionSuggestions } = useMention();
// router
const router = useRouter();
const { workspaceSlug, projectId, inboxId } = router.query as {
@ -56,6 +52,8 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
projectId: string;
inboxId: string;
};
// hooks
const { mentionHighlights, mentionSuggestions } = useMention();
const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string;
@ -138,8 +136,8 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
})
.then((res) => {
if (res.response === "")
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message:
"Issue title isn't informative enough to generate the description. Please try with a different title.",
@ -150,14 +148,14 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
const error = err?.data?.error;
if (err.status === 429)
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: error || "You have reached the maximum number of requests of 50 requests per month per user.",
});
else
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: error || "Some error occurred. Please try again.",
});

View File

@ -2,13 +2,10 @@ import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { Combobox, Dialog, Transition } from "@headlessui/react";
// hooks
import useToast from "hooks/use-toast";
// services
import { IssueService } from "services/issue";
// ui
import { Button, LayersIcon } from "@plane/ui";
import { Button, LayersIcon, TOAST_TYPE, setToast } from "@plane/ui";
// icons
import { Search } from "lucide-react";
// fetch-keys
@ -30,8 +27,6 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
const [query, setQuery] = useState("");
const [selectedItem, setSelectedItem] = useState<string>("");
const { setToastAlert } = useToast();
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
@ -62,9 +57,9 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
const handleSubmit = () => {
if (!selectedItem || selectedItem.length === 0)
return setToastAlert({
return setToast({
title: "Error",
type: "error",
type: TOAST_TYPE.ERROR,
});
onSubmit(selectedItem);
handleClose();

View File

@ -2,12 +2,11 @@ import { FC, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { Eye, EyeOff } from "lucide-react";
// ui
import { Button, Input } from "@plane/ui";
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// types
import { IFormattedInstanceConfiguration } from "@plane/types";
// hooks
import { useApplication } from "hooks/store";
import useToast from "hooks/use-toast";
export interface IInstanceAIForm {
config: IFormattedInstanceConfiguration;
@ -24,8 +23,6 @@ export const InstanceAIForm: FC<IInstanceAIForm> = (props) => {
const [showPassword, setShowPassword] = useState(false);
// store
const { instance: instanceStore } = useApplication();
// toast
const { setToastAlert } = useToast();
// form data
const {
handleSubmit,
@ -44,9 +41,9 @@ export const InstanceAIForm: FC<IInstanceAIForm> = (props) => {
await instanceStore
.updateInstanceConfigurations(payload)
.then(() =>
setToastAlert({
setToast({
title: "Success",
type: "success",
type: TOAST_TYPE.SUCCESS,
message: "AI Settings updated successfully",
})
)

View File

@ -1,13 +1,12 @@
import { FC, useState } from "react";
import { Controller, useForm } from "react-hook-form";
// ui
import { Button, Input, ToggleSwitch } from "@plane/ui";
import { Button, Input, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui";
import { Eye, EyeOff } from "lucide-react";
// types
import { IFormattedInstanceConfiguration } from "@plane/types";
// hooks
import { useApplication } from "hooks/store";
import useToast from "hooks/use-toast";
export interface IInstanceEmailForm {
config: IFormattedInstanceConfiguration;
@ -29,8 +28,6 @@ export const InstanceEmailForm: FC<IInstanceEmailForm> = (props) => {
const [showPassword, setShowPassword] = useState(false);
// store hooks
const { instance: instanceStore } = useApplication();
// toast
const { setToastAlert } = useToast();
// form data
const {
handleSubmit,
@ -55,9 +52,9 @@ export const InstanceEmailForm: FC<IInstanceEmailForm> = (props) => {
await instanceStore
.updateInstanceConfigurations(payload)
.then(() =>
setToastAlert({
setToast({
title: "Success",
type: "success",
type: TOAST_TYPE.SUCCESS,
message: "Email Settings updated successfully",
})
)

View File

@ -1,12 +1,11 @@
import { FC } from "react";
import { Controller, useForm } from "react-hook-form";
// ui
import { Button, Input } from "@plane/ui";
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// types
import { IInstance, IInstanceAdmin } from "@plane/types";
// hooks
import { useApplication } from "hooks/store";
import useToast from "hooks/use-toast";
export interface IInstanceGeneralForm {
instance: IInstance;
@ -22,8 +21,6 @@ export const InstanceGeneralForm: FC<IInstanceGeneralForm> = (props) => {
const { instance, instanceAdmins } = props;
// store hooks
const { instance: instanceStore } = useApplication();
// toast
const { setToastAlert } = useToast();
// form data
const {
handleSubmit,
@ -42,9 +39,9 @@ export const InstanceGeneralForm: FC<IInstanceGeneralForm> = (props) => {
await instanceStore
.updateInstanceInfo(payload)
.then(() =>
setToastAlert({
setToast({
title: "Success",
type: "success",
type: TOAST_TYPE.SUCCESS,
message: "Settings updated successfully",
})
)

View File

@ -2,12 +2,11 @@ import { FC, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { Copy, Eye, EyeOff } from "lucide-react";
// ui
import { Button, Input } from "@plane/ui";
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// types
import { IFormattedInstanceConfiguration } from "@plane/types";
// hooks
import { useApplication } from "hooks/store";
import useToast from "hooks/use-toast";
export interface IInstanceGithubConfigForm {
config: IFormattedInstanceConfiguration;
@ -24,8 +23,6 @@ export const InstanceGithubConfigForm: FC<IInstanceGithubConfigForm> = (props) =
const [showPassword, setShowPassword] = useState(false);
// store hooks
const { instance: instanceStore } = useApplication();
// toast
const { setToastAlert } = useToast();
// form data
const {
handleSubmit,
@ -44,9 +41,9 @@ export const InstanceGithubConfigForm: FC<IInstanceGithubConfigForm> = (props) =
await instanceStore
.updateInstanceConfigurations(payload)
.then(() =>
setToastAlert({
setToast({
title: "Success",
type: "success",
type: TOAST_TYPE.SUCCESS,
message: "Github Configuration Settings updated successfully",
})
)
@ -145,9 +142,9 @@ export const InstanceGithubConfigForm: FC<IInstanceGithubConfigForm> = (props) =
className="flex items-center justify-between py-2"
onClick={() => {
navigator.clipboard.writeText(originURL);
setToastAlert({
setToast({
message: "The Origin URL has been successfully copied to your clipboard",
type: "success",
type: TOAST_TYPE.SUCCESS,
title: "Copied to clipboard",
});
}}

View File

@ -2,12 +2,11 @@ import { FC } from "react";
import { Controller, useForm } from "react-hook-form";
import { Copy } from "lucide-react";
// ui
import { Button, Input } from "@plane/ui";
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// types
import { IFormattedInstanceConfiguration } from "@plane/types";
// hooks
import { useApplication } from "hooks/store";
import useToast from "hooks/use-toast";
export interface IInstanceGoogleConfigForm {
config: IFormattedInstanceConfiguration;
@ -22,8 +21,6 @@ export const InstanceGoogleConfigForm: FC<IInstanceGoogleConfigForm> = (props) =
const { config } = props;
// store hooks
const { instance: instanceStore } = useApplication();
// toast
const { setToastAlert } = useToast();
// form data
const {
handleSubmit,
@ -42,9 +39,9 @@ export const InstanceGoogleConfigForm: FC<IInstanceGoogleConfigForm> = (props) =
await instanceStore
.updateInstanceConfigurations(payload)
.then(() =>
setToastAlert({
setToast({
title: "Success",
type: "success",
type: TOAST_TYPE.SUCCESS,
message: "Google Configuration Settings updated successfully",
})
)
@ -94,9 +91,9 @@ export const InstanceGoogleConfigForm: FC<IInstanceGoogleConfigForm> = (props) =
className="flex items-center justify-between py-2"
onClick={() => {
navigator.clipboard.writeText(originURL);
setToastAlert({
setToast({
message: "The Origin URL has been successfully copied to your clipboard",
type: "success",
type: TOAST_TYPE.SUCCESS,
title: "Copied to clipboard",
});
}}

View File

@ -2,12 +2,11 @@ import { FC, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { Eye, EyeOff } from "lucide-react";
// ui
import { Button, Input } from "@plane/ui";
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// types
import { IFormattedInstanceConfiguration } from "@plane/types";
// hooks
import { useApplication } from "hooks/store";
import useToast from "hooks/use-toast";
export interface IInstanceImageConfigForm {
config: IFormattedInstanceConfiguration;
@ -23,8 +22,6 @@ export const InstanceImageConfigForm: FC<IInstanceImageConfigForm> = (props) =>
const [showPassword, setShowPassword] = useState(false);
// store hooks
const { instance: instanceStore } = useApplication();
// toast
const { setToastAlert } = useToast();
// form data
const {
handleSubmit,
@ -42,9 +39,9 @@ export const InstanceImageConfigForm: FC<IInstanceImageConfigForm> = (props) =>
await instanceStore
.updateInstanceConfigurations(payload)
.then(() =>
setToastAlert({
setToast({
title: "Success",
type: "success",
type: TOAST_TYPE.SUCCESS,
message: "Image Configuration Settings updated successfully",
})
)

View File

@ -4,12 +4,10 @@ import { Eye, EyeOff, XCircle } from "lucide-react";
// hooks
import { useUser } from "hooks/store";
// ui
import { Input, Button } from "@plane/ui";
import { Input, Button, TOAST_TYPE, setToast } from "@plane/ui";
// services
import { AuthService } from "services/auth.service";
const authService = new AuthService();
// hooks
import useToast from "hooks/use-toast";
// helpers
import { checkEmailValidity } from "helpers/string.helper";
@ -40,8 +38,6 @@ export const InstanceSetupSignInForm: FC<IInstanceSetupEmailForm> = (props) => {
password: "",
},
});
// hooks
const { setToastAlert } = useToast();
const handleFormSubmit = async (formValues: InstanceSetupEmailFormValues) => {
const payload = {
@ -56,8 +52,8 @@ export const InstanceSetupSignInForm: FC<IInstanceSetupEmailForm> = (props) => {
handleNextStep(formValues.email);
})
.catch((err) => {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error ?? "Something went wrong. Please try again.",
});

View File

@ -10,10 +10,8 @@ import { Menu, Transition } from "@headlessui/react";
import { LogIn, LogOut, Settings, UserCog2 } from "lucide-react";
// hooks
import { useApplication, useUser } from "hooks/store";
// hooks
import useToast from "hooks/use-toast";
// ui
import { Avatar, Tooltip } from "@plane/ui";
import { Avatar, Tooltip, TOAST_TYPE, setToast } from "@plane/ui";
// Static Data
const PROFILE_LINKS = [
@ -35,7 +33,6 @@ export const InstanceSidebarDropdown = observer(() => {
} = useApplication();
const { signOut, currentUser, currentUserSettings } = useUser();
// hooks
const { setToastAlert } = useToast();
const { setTheme } = useTheme();
// redirect url for normal mode
@ -53,8 +50,8 @@ export const InstanceSidebarDropdown = observer(() => {
router.push("/");
})
.catch(() =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Failed to sign out. Please try again.",
})

View File

@ -8,10 +8,8 @@ import { mutate } from "swr";
import { Dialog, Transition } from "@headlessui/react";
// services
import { IntegrationService } from "services/integrations/integration.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { Button, Input } from "@plane/ui";
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// icons
import { AlertTriangle } from "lucide-react";
// types
@ -36,8 +34,6 @@ export const DeleteImportModal: React.FC<Props> = ({ isOpen, handleClose, data }
const router = useRouter();
const { workspaceSlug } = router.query;
const { setToastAlert } = useToast();
const handleDeletion = () => {
if (!workspaceSlug || !data) return;
@ -52,8 +48,8 @@ export const DeleteImportModal: React.FC<Props> = ({ isOpen, handleClose, data }
integrationService
.deleteImporterService(workspaceSlug as string, data.service, data.id)
.catch(() =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Something went wrong. Please try again.",
})

View File

@ -10,8 +10,6 @@ import useSWR, { mutate } from "swr";
import { useForm } from "react-hook-form";
// services
import { IntegrationService, GithubIntegrationService } from "services/integrations";
// hooks
import useToast from "hooks/use-toast";
// components
import {
GithubImportConfigure,
@ -21,7 +19,7 @@ import {
GithubImportConfirm,
} from "components/integration";
// icons
import { UserGroupIcon } from "@plane/ui";
import { UserGroupIcon, TOAST_TYPE, setToast } from "@plane/ui";
import { ArrowLeft, Check, List, Settings, UploadCloud } from "lucide-react";
// images
import GithubLogo from "public/services/github.png";
@ -92,8 +90,6 @@ export const GithubImporterRoot: React.FC = () => {
const router = useRouter();
const { workspaceSlug, provider } = router.query;
const { setToastAlert } = useToast();
const { handleSubmit, control, setValue, watch } = useForm<TFormValues>({
defaultValues: defaultFormValues,
});
@ -149,8 +145,8 @@ export const GithubImporterRoot: React.FC = () => {
mutate(IMPORTER_SERVICES_LIST(workspaceSlug as string));
})
.catch(() =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Import was unsuccessful. Please try again.",
})

View File

@ -8,10 +8,9 @@ import useSWR, { mutate } from "swr";
import { IntegrationService } from "services/integrations";
// hooks
import { useApplication, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
import useIntegrationPopup from "hooks/use-integration-popup";
// ui
import { Button, Loader, Tooltip } from "@plane/ui";
import { Button, Loader, Tooltip, TOAST_TYPE, setToast } from "@plane/ui";
// icons
import GithubLogo from "public/services/github.png";
import SlackLogo from "public/services/slack.png";
@ -54,8 +53,6 @@ export const SingleIntegrationCard: React.FC<Props> = observer(({ integration })
const {
membership: { currentWorkspaceRole },
} = useUser();
// toast alert
const { setToastAlert } = useToast();
const isUserAdmin = currentWorkspaceRole === 20;
@ -87,8 +84,8 @@ export const SingleIntegrationCard: React.FC<Props> = observer(({ integration })
);
setDeletingIntegration(false);
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Deleted successfully!",
message: `${integration.title} integration deleted successfully.`,
});
@ -96,8 +93,8 @@ export const SingleIntegrationCard: React.FC<Props> = observer(({ integration })
.catch(() => {
setDeletingIntegration(false);
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: `${integration.title} integration could not be deleted. Please try again.`,
});

View File

@ -3,9 +3,8 @@ import { Dialog, Transition } from "@headlessui/react";
// hooks
import { useProject } from "hooks/store";
import { useIssues } from "hooks/store/use-issues";
import useToast from "hooks/use-toast";
// ui
import { Button } from "@plane/ui";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
// types
import { TIssue } from "@plane/types";
@ -24,8 +23,6 @@ export const ArchiveIssueModal: React.FC<Props> = (props) => {
// store hooks
const { getProjectById } = useProject();
const { issueMap } = useIssues();
// toast alert
const { setToastAlert } = useToast();
if (!dataId && !data) return null;
@ -44,8 +41,8 @@ export const ArchiveIssueModal: React.FC<Props> = (props) => {
await onSubmit()
.then(() => onClose())
.catch(() =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Issue could not be archived. Please try again.",
})

View File

@ -1,7 +1,8 @@
import { FC, useMemo } from "react";
// hooks
import { useEventTracker, useIssueDetail } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
// components
import { IssueAttachmentUpload } from "./attachment-upload";
import { IssueAttachmentsList } from "./attachments-list";
@ -24,19 +25,27 @@ export const IssueAttachmentRoot: FC<TIssueAttachmentRoot> = (props) => {
// hooks
const { createAttachment, removeAttachment } = useIssueDetail();
const { captureIssueEvent } = useEventTracker();
const { setToastAlert } = useToast();
const handleAttachmentOperations: TAttachmentOperations = useMemo(
() => ({
create: async (data: FormData) => {
try {
if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields");
const res = await createAttachment(workspaceSlug, projectId, issueId, data);
setToastAlert({
message: "The attachment has been successfully uploaded",
type: "success",
title: "Attachment uploaded",
const attachmentUploadPromise = createAttachment(workspaceSlug, projectId, issueId, data);
setPromiseToast(attachmentUploadPromise, {
loading: "Uploading attachment...",
success: {
title: "Attachment uploaded",
message: () => "The attachment has been successfully uploaded",
},
error: {
title: "Attachment not uploaded",
message: () => "The attachment could not be uploaded",
},
});
const res = await attachmentUploadPromise;
captureIssueEvent({
eventName: "Issue attachment added",
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
@ -50,20 +59,15 @@ export const IssueAttachmentRoot: FC<TIssueAttachmentRoot> = (props) => {
eventName: "Issue attachment added",
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
});
setToastAlert({
message: "The attachment could not be uploaded",
type: "error",
title: "Attachment not uploaded",
});
}
},
remove: async (attachmentId: string) => {
try {
if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields");
await removeAttachment(workspaceSlug, projectId, issueId, attachmentId);
setToastAlert({
setToast({
message: "The attachment has been successfully removed",
type: "success",
type: TOAST_TYPE.SUCCESS,
title: "Attachment removed",
});
captureIssueEvent({
@ -83,15 +87,15 @@ export const IssueAttachmentRoot: FC<TIssueAttachmentRoot> = (props) => {
change_details: "",
},
});
setToastAlert({
setToast({
message: "The Attachment could not be removed",
type: "error",
type: TOAST_TYPE.ERROR,
title: "Attachment not removed",
});
}
},
}),
[workspaceSlug, projectId, issueId, createAttachment, removeAttachment, setToastAlert]
[workspaceSlug, projectId, issueId, createAttachment, removeAttachment]
);
return (

View File

@ -2,9 +2,7 @@ import { useEffect, useState, Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react";
import { AlertTriangle } from "lucide-react";
// ui
import { Button } from "@plane/ui";
// hooks
import useToast from "hooks/use-toast";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
// types
import { useIssues } from "hooks/store/use-issues";
import { TIssue } from "@plane/types";
@ -25,7 +23,6 @@ export const DeleteIssueModal: React.FC<Props> = (props) => {
const [isDeleting, setIsDeleting] = useState(false);
const { setToastAlert } = useToast();
// hooks
const { getProjectById } = useProject();
@ -50,9 +47,9 @@ export const DeleteIssueModal: React.FC<Props> = (props) => {
onClose();
})
.catch(() => {
setToastAlert({
setToast({
title: "Error",
type: "error",
type: TOAST_TYPE.ERROR,
message: "Failed to delete issue",
});
})

View File

@ -71,16 +71,10 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = observer((props) => {
async (formData: Partial<TIssue>) => {
if (!formData?.name || formData?.name.length === 0 || formData?.name.length > 255) return;
await issueOperations.update(
workspaceSlug,
projectId,
issueId,
{
name: formData.name ?? "",
description_html: formData.description_html ?? "<p></p>",
},
false
);
await issueOperations.update(workspaceSlug, projectId, issueId, {
name: formData.name ?? "",
description_html: formData.description_html ?? "<p></p>",
});
},
[workspaceSlug, projectId, issueId, issueOperations]
);

View File

@ -41,11 +41,9 @@ export const IssueDescriptionInput: FC<IssueDescriptionInputProps> = (props) =>
useEffect(() => {
if (debouncedValue && debouncedValue !== value) {
issueOperations
.update(workspaceSlug, projectId, issueId, { description_html: debouncedValue }, false)
.finally(() => {
setIsSubmitting("submitted");
});
issueOperations.update(workspaceSlug, projectId, issueId, { description_html: debouncedValue }).finally(() => {
setIsSubmitting("submitted");
});
}
// DO NOT Add more dependencies here. It will cause multiple requests to be sent.
// eslint-disable-next-line react-hooks/exhaustive-deps

View File

@ -56,7 +56,6 @@ export const IssueCycleSelect: React.FC<TIssueCycleSelect> = observer((props) =>
dropdownArrow
dropdownArrowClassName="h-3.5 w-3.5 hidden group-hover:inline"
/>
{isUpdating && <Spinner className="h-4 w-4" />}
</div>
);
});

View File

@ -6,7 +6,8 @@ import { InboxIssueMainContent } from "./main-content";
import { InboxIssueDetailsSidebar } from "./sidebar";
// hooks
import { useEventTracker, useInboxIssues, useIssueDetail, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// types
import { TIssue } from "@plane/types";
import { TIssueOperations } from "../root";
@ -34,7 +35,6 @@ export const InboxIssueDetailRoot: FC<TInboxIssueDetailRoot> = (props) => {
fetchComments,
} = useIssueDetail();
const { captureIssueEvent } = useEventTracker();
const { setToastAlert } = useToast();
const {
membership: { currentProjectRole },
} = useUser();
@ -53,17 +53,9 @@ export const InboxIssueDetailRoot: FC<TInboxIssueDetailRoot> = (props) => {
projectId: string,
issueId: string,
data: Partial<TIssue>,
showToast: boolean = true
) => {
try {
await updateInboxIssue(workspaceSlug, projectId, inboxId, issueId, data);
if (showToast) {
setToastAlert({
title: "Issue updated successfully",
type: "success",
message: "Issue updated successfully",
});
}
captureIssueEvent({
eventName: "Inbox issue updated",
payload: { ...data, state: "SUCCESS", element: "Inbox" },
@ -74,9 +66,9 @@ export const InboxIssueDetailRoot: FC<TInboxIssueDetailRoot> = (props) => {
path: router.asPath,
});
} catch (error) {
setToastAlert({
setToast({
title: "Issue update failed",
type: "error",
type: TOAST_TYPE.ERROR,
message: "Issue update failed",
});
captureIssueEvent({
@ -93,9 +85,9 @@ export const InboxIssueDetailRoot: FC<TInboxIssueDetailRoot> = (props) => {
remove: async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
await removeInboxIssue(workspaceSlug, projectId, inboxId, issueId);
setToastAlert({
setToast({
title: "Issue deleted successfully",
type: "success",
type: TOAST_TYPE.SUCCESS,
message: "Issue deleted successfully",
});
captureIssueEvent({
@ -109,15 +101,15 @@ export const InboxIssueDetailRoot: FC<TInboxIssueDetailRoot> = (props) => {
payload: { id: issueId, state: "FAILED", element: "Inbox" },
path: router.asPath,
});
setToastAlert({
setToast({
title: "Issue delete failed",
type: "error",
type: TOAST_TYPE.ERROR,
message: "Issue delete failed",
});
}
},
}),
[inboxId, fetchInboxIssueById, updateInboxIssue, removeInboxIssue, setToastAlert]
[inboxId, fetchInboxIssueById, updateInboxIssue, removeInboxIssue]
);
useSWR(

View File

@ -3,7 +3,8 @@ import { observer } from "mobx-react-lite";
import { History, LucideIcon, MessageCircle, ListRestart } from "lucide-react";
// hooks
import { useIssueDetail, useProject } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { IssueActivityCommentRoot, IssueActivityRoot, IssueCommentRoot, IssueCommentCreate } from "./";
// types
@ -45,7 +46,6 @@ export const IssueActivity: FC<TIssueActivity> = observer((props) => {
const { workspaceSlug, projectId, issueId } = props;
// hooks
const { createComment, updateComment, removeComment } = useIssueDetail();
const { setToastAlert } = useToast();
const { getProjectById } = useProject();
// state
const [activityTab, setActivityTab] = useState<TActivityTabs>("all");
@ -56,15 +56,15 @@ export const IssueActivity: FC<TIssueActivity> = observer((props) => {
try {
if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing fields");
await createComment(workspaceSlug, projectId, issueId, data);
setToastAlert({
setToast({
title: "Comment created successfully.",
type: "success",
type: TOAST_TYPE.SUCCESS,
message: "Comment created successfully.",
});
} catch (error) {
setToastAlert({
setToast({
title: "Comment creation failed.",
type: "error",
type: TOAST_TYPE.ERROR,
message: "Comment creation failed. Please try again later.",
});
}
@ -73,15 +73,15 @@ export const IssueActivity: FC<TIssueActivity> = observer((props) => {
try {
if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing fields");
await updateComment(workspaceSlug, projectId, issueId, commentId, data);
setToastAlert({
setToast({
title: "Comment updated successfully.",
type: "success",
type: TOAST_TYPE.SUCCESS,
message: "Comment updated successfully.",
});
} catch (error) {
setToastAlert({
setToast({
title: "Comment update failed.",
type: "error",
type: TOAST_TYPE.ERROR,
message: "Comment update failed. Please try again later.",
});
}
@ -90,21 +90,21 @@ export const IssueActivity: FC<TIssueActivity> = observer((props) => {
try {
if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing fields");
await removeComment(workspaceSlug, projectId, issueId, commentId);
setToastAlert({
setToast({
title: "Comment removed successfully.",
type: "success",
type: TOAST_TYPE.SUCCESS,
message: "Comment removed successfully.",
});
} catch (error) {
setToastAlert({
setToast({
title: "Comment remove failed.",
type: "error",
type: TOAST_TYPE.ERROR,
message: "Comment remove failed. Please try again later.",
});
}
},
}),
[workspaceSlug, projectId, issueId, createComment, updateComment, removeComment, setToastAlert]
[workspaceSlug, projectId, issueId, createComment, updateComment, removeComment]
);
const project = getProjectById(projectId);

View File

@ -5,9 +5,8 @@ import { TwitterPicker } from "react-color";
import { Popover, Transition } from "@headlessui/react";
// hooks
import { useIssueDetail } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { Input } from "@plane/ui";
import { Input, TOAST_TYPE, setToast } from "@plane/ui";
// types
import { TLabelOperations } from "./root";
import { IIssueLabel } from "@plane/types";
@ -28,7 +27,6 @@ const defaultValues: Partial<IIssueLabel> = {
export const LabelCreate: FC<ILabelCreate> = (props) => {
const { workspaceSlug, projectId, issueId, labelOperations, disabled = false } = props;
// hooks
const { setToastAlert } = useToast();
const {
issue: { getIssueById },
} = useIssueDetail();
@ -63,9 +61,9 @@ export const LabelCreate: FC<ILabelCreate> = (props) => {
await labelOperations.updateIssue(workspaceSlug, projectId, issueId, { label_ids: currentLabels });
reset(defaultValues);
} catch (error) {
setToastAlert({
setToast({
title: "Label creation failed",
type: "error",
type: TOAST_TYPE.ERROR,
message: "Label creation failed. Please try again sometime later.",
});
}

View File

@ -4,9 +4,10 @@ import { observer } from "mobx-react-lite";
import { LabelList, LabelCreate, IssueLabelSelectRoot } from "./";
// hooks
import { useIssueDetail, useLabel } from "hooks/store";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// types
import { IIssueLabel, TIssue } from "@plane/types";
import useToast from "hooks/use-toast";
export type TIssueLabel = {
workspaceSlug: string;
@ -27,7 +28,6 @@ export const IssueLabel: FC<TIssueLabel> = observer((props) => {
// hooks
const { updateIssue } = useIssueDetail();
const { createLabel } = useLabel();
const { setToastAlert } = useToast();
const labelOperations: TLabelOperations = useMemo(
() => ({
@ -35,16 +35,10 @@ export const IssueLabel: FC<TIssueLabel> = observer((props) => {
try {
if (onLabelUpdate) onLabelUpdate(data.label_ids || []);
else await updateIssue(workspaceSlug, projectId, issueId, data);
if (!isInboxIssue)
setToastAlert({
title: "Issue updated successfully",
type: "success",
message: "Issue updated successfully",
});
} catch (error) {
setToastAlert({
setToast({
title: "Issue update failed",
type: "error",
type: TOAST_TYPE.ERROR,
message: "Issue update failed",
});
}
@ -53,23 +47,23 @@ export const IssueLabel: FC<TIssueLabel> = observer((props) => {
try {
const labelResponse = await createLabel(workspaceSlug, projectId, data);
if (!isInboxIssue)
setToastAlert({
setToast({
title: "Label created successfully",
type: "success",
type: TOAST_TYPE.SUCCESS,
message: "Label created successfully",
});
return labelResponse;
} catch (error) {
setToastAlert({
setToast({
title: "Label creation failed",
type: "error",
type: TOAST_TYPE.ERROR,
message: "Label creation failed",
});
return error;
}
},
}),
[updateIssue, createLabel, setToastAlert, onLabelUpdate]
[updateIssue, createLabel, onLabelUpdate]
);
return (

View File

@ -1,9 +1,8 @@
import { FC, useState } from "react";
// hooks
import useToast from "hooks/use-toast";
import { useIssueDetail, useMember } from "hooks/store";
// ui
import { ExternalLinkIcon, Tooltip } from "@plane/ui";
import { ExternalLinkIcon, Tooltip, TOAST_TYPE, setToast } from "@plane/ui";
// icons
import { Pencil, Trash2, LinkIcon } from "lucide-react";
// types
@ -27,7 +26,6 @@ export const IssueLinkDetail: FC<TIssueLinkDetail> = (props) => {
link: { getLinkById },
} = useIssueDetail();
const { getUserDetails } = useMember();
const { setToastAlert } = useToast();
// state
const [isIssueLinkModalOpen, setIsIssueLinkModalOpen] = useState(false);
@ -55,8 +53,8 @@ export const IssueLinkDetail: FC<TIssueLinkDetail> = (props) => {
className="flex w-full items-start justify-between gap-2 cursor-pointer"
onClick={() => {
copyTextToClipboard(linkDetail.url);
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Link copied!",
message: "Link copied to clipboard",
});

View File

@ -2,7 +2,8 @@ import { FC, useCallback, useMemo, useState } from "react";
import { Plus } from "lucide-react";
// hooks
import { useIssueDetail } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { IssueLinkCreateUpdateModal } from "./create-update-link-modal";
import { IssueLinkList } from "./links";
@ -37,24 +38,22 @@ export const IssueLinkRoot: FC<TIssueLinkRoot> = (props) => {
[toggleIssueLinkModalStore]
);
const { setToastAlert } = useToast();
const handleLinkOperations: TLinkOperations = useMemo(
() => ({
create: async (data: Partial<TIssueLink>) => {
try {
if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields");
await createLink(workspaceSlug, projectId, issueId, data);
setToastAlert({
setToast({
message: "The link has been successfully created",
type: "success",
type: TOAST_TYPE.SUCCESS,
title: "Link created",
});
toggleIssueLinkModal(false);
} catch (error) {
setToastAlert({
setToast({
message: "The link could not be created",
type: "error",
type: TOAST_TYPE.ERROR,
title: "Link not created",
});
}
@ -63,16 +62,16 @@ export const IssueLinkRoot: FC<TIssueLinkRoot> = (props) => {
try {
if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields");
await updateLink(workspaceSlug, projectId, issueId, linkId, data);
setToastAlert({
setToast({
message: "The link has been successfully updated",
type: "success",
type: TOAST_TYPE.SUCCESS,
title: "Link updated",
});
toggleIssueLinkModal(false);
} catch (error) {
setToastAlert({
setToast({
message: "The link could not be updated",
type: "error",
type: TOAST_TYPE.ERROR,
title: "Link not updated",
});
}
@ -81,22 +80,22 @@ export const IssueLinkRoot: FC<TIssueLinkRoot> = (props) => {
try {
if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields");
await removeLink(workspaceSlug, projectId, issueId, linkId);
setToastAlert({
setToast({
message: "The link has been successfully removed",
type: "success",
type: TOAST_TYPE.SUCCESS,
title: "Link removed",
});
toggleIssueLinkModal(false);
} catch (error) {
setToastAlert({
setToast({
message: "The link could not be removed",
type: "error",
type: TOAST_TYPE.ERROR,
title: "Link not removed",
});
}
},
}),
[workspaceSlug, projectId, issueId, createLink, updateLink, removeLink, setToastAlert, toggleIssueLinkModal]
[workspaceSlug, projectId, issueId, createLink, updateLink, removeLink, toggleIssueLinkModal]
);
return (

View File

@ -75,7 +75,6 @@ export const IssueModuleSelect: React.FC<TIssueModuleSelect> = observer((props)
showTooltip
multiple
/>
{isUpdating && <Spinner className="h-4 w-4" />}
</div>
);
});

View File

@ -4,7 +4,8 @@ import { observer } from "mobx-react-lite";
import { ReactionSelector } from "./reaction-selector";
// hooks
import { useIssueDetail } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// types
import { IUser } from "@plane/types";
import { renderEmoji } from "helpers/emoji.helper";
@ -25,7 +26,6 @@ export const IssueCommentReaction: FC<TIssueCommentReaction> = observer((props)
createCommentReaction,
removeCommentReaction,
} = useIssueDetail();
const { setToastAlert } = useToast();
const reactionIds = getCommentReactionsByCommentId(commentId);
const userReactions = commentReactionsByUser(commentId, currentUser.id).map((r) => r.reaction);
@ -36,15 +36,15 @@ export const IssueCommentReaction: FC<TIssueCommentReaction> = observer((props)
try {
if (!workspaceSlug || !projectId || !commentId) throw new Error("Missing fields");
await createCommentReaction(workspaceSlug, projectId, commentId, reaction);
setToastAlert({
setToast({
title: "Reaction created successfully",
type: "success",
type: TOAST_TYPE.SUCCESS,
message: "Reaction created successfully",
});
} catch (error) {
setToastAlert({
setToast({
title: "Reaction creation failed",
type: "error",
type: TOAST_TYPE.ERROR,
message: "Reaction creation failed",
});
}
@ -53,15 +53,15 @@ export const IssueCommentReaction: FC<TIssueCommentReaction> = observer((props)
try {
if (!workspaceSlug || !projectId || !commentId || !currentUser?.id) throw new Error("Missing fields");
removeCommentReaction(workspaceSlug, projectId, commentId, reaction, currentUser.id);
setToastAlert({
setToast({
title: "Reaction removed successfully",
type: "success",
type: TOAST_TYPE.SUCCESS,
message: "Reaction removed successfully",
});
} catch (error) {
setToastAlert({
setToast({
title: "Reaction remove failed",
type: "error",
type: TOAST_TYPE.ERROR,
message: "Reaction remove failed",
});
}
@ -78,7 +78,6 @@ export const IssueCommentReaction: FC<TIssueCommentReaction> = observer((props)
currentUser,
createCommentReaction,
removeCommentReaction,
setToastAlert,
userReactions,
]
);

View File

@ -4,7 +4,8 @@ import { observer } from "mobx-react-lite";
import { ReactionSelector } from "./reaction-selector";
// hooks
import { useIssueDetail } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// types
import { IUser } from "@plane/types";
import { renderEmoji } from "helpers/emoji.helper";
@ -24,7 +25,6 @@ export const IssueReaction: FC<TIssueReaction> = observer((props) => {
createReaction,
removeReaction,
} = useIssueDetail();
const { setToastAlert } = useToast();
const reactionIds = getReactionsByIssueId(issueId);
const userReactions = reactionsByUser(issueId, currentUser.id).map((r) => r.reaction);
@ -35,15 +35,15 @@ export const IssueReaction: FC<TIssueReaction> = observer((props) => {
try {
if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing fields");
await createReaction(workspaceSlug, projectId, issueId, reaction);
setToastAlert({
setToast({
title: "Reaction created successfully",
type: "success",
type: TOAST_TYPE.SUCCESS,
message: "Reaction created successfully",
});
} catch (error) {
setToastAlert({
setToast({
title: "Reaction creation failed",
type: "error",
type: TOAST_TYPE.ERROR,
message: "Reaction creation failed",
});
}
@ -52,15 +52,15 @@ export const IssueReaction: FC<TIssueReaction> = observer((props) => {
try {
if (!workspaceSlug || !projectId || !issueId || !currentUser?.id) throw new Error("Missing fields");
await removeReaction(workspaceSlug, projectId, issueId, reaction, currentUser.id);
setToastAlert({
setToast({
title: "Reaction removed successfully",
type: "success",
type: TOAST_TYPE.SUCCESS,
message: "Reaction removed successfully",
});
} catch (error) {
setToastAlert({
setToast({
title: "Reaction remove failed",
type: "error",
type: TOAST_TYPE.ERROR,
message: "Reaction remove failed",
});
}
@ -70,7 +70,7 @@ export const IssueReaction: FC<TIssueReaction> = observer((props) => {
else await issueReactionOperations.create(reaction);
},
}),
[workspaceSlug, projectId, issueId, currentUser, createReaction, removeReaction, setToastAlert, userReactions]
[workspaceSlug, projectId, issueId, currentUser, createReaction, removeReaction, userReactions]
);
return (

View File

@ -4,11 +4,10 @@ import { observer } from "mobx-react-lite";
import { CircleDot, CopyPlus, Pencil, X, XCircle } from "lucide-react";
// hooks
import { useIssueDetail, useIssues, useProject } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { ExistingIssuesListModal } from "components/core";
// ui
import { RelatedIcon, Tooltip } from "@plane/ui";
import { RelatedIcon, Tooltip, TOAST_TYPE, setToast } from "@plane/ui";
// helpers
import { cn } from "helpers/common.helper";
// types
@ -60,15 +59,13 @@ export const IssueRelationSelect: React.FC<TIssueRelationSelect> = observer((pro
toggleRelationModal,
} = useIssueDetail();
const { issueMap } = useIssues();
// toast alert
const { setToastAlert } = useToast();
const relationIssueIds = getRelationByIssueIdRelationType(issueId, relationKey);
const onSubmit = async (data: ISearchIssueResponse[]) => {
if (data.length === 0) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Please select at least one issue.",
});

View File

@ -10,9 +10,10 @@ import { EmptyState } from "components/common";
import emptyIssue from "public/empty-state/issue.svg";
// hooks
import { useApplication, useEventTracker, useIssueDetail, useIssues, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// types
import { TIssue } from "@plane/types";
// ui
import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
// constants
import { EUserProjectRoles } from "constants/project";
import { EIssuesStoreType } from "constants/issue";
@ -21,13 +22,7 @@ import { observer } from "mobx-react";
export type TIssueOperations = {
fetch: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
update: (
workspaceSlug: string,
projectId: string,
issueId: string,
data: Partial<TIssue>,
showToast?: boolean
) => Promise<void>;
update: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
remove: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
archive?: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
restore?: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
@ -76,7 +71,6 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
issues: { removeIssue: removeArchivedIssue },
} = useIssues(EIssuesStoreType.ARCHIVED);
const { captureIssueEvent } = useEventTracker();
const { setToastAlert } = useToast();
const {
membership: { currentProjectRole },
} = useUser();
@ -91,22 +85,9 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
console.error("Error fetching the parent issue");
}
},
update: async (
workspaceSlug: string,
projectId: string,
issueId: string,
data: Partial<TIssue>,
showToast: boolean = true
) => {
update: async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => {
try {
await updateIssue(workspaceSlug, projectId, issueId, data);
if (showToast) {
setToastAlert({
title: "Issue updated successfully",
type: "success",
message: "Issue updated successfully",
});
}
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...data, issueId, state: "SUCCESS", element: "Issue detail page" },
@ -126,9 +107,9 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
},
path: router.asPath,
});
setToastAlert({
setToast({
title: "Issue update failed",
type: "error",
type: TOAST_TYPE.ERROR,
message: "Issue update failed",
});
}
@ -138,9 +119,9 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
let response;
if (is_archived) response = await removeArchivedIssue(workspaceSlug, projectId, issueId);
else response = await removeIssue(workspaceSlug, projectId, issueId);
setToastAlert({
setToast({
title: "Issue deleted successfully",
type: "success",
type: TOAST_TYPE.SUCCESS,
message: "Issue deleted successfully",
});
captureIssueEvent({
@ -149,9 +130,9 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
path: router.asPath,
});
} catch (error) {
setToastAlert({
setToast({
title: "Issue delete failed",
type: "error",
type: TOAST_TYPE.ERROR,
message: "Issue delete failed",
});
captureIssueEvent({
@ -164,8 +145,8 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
archive: async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
await archiveIssue(workspaceSlug, projectId, issueId);
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Issue archived successfully.",
});
@ -175,8 +156,8 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
path: router.asPath,
});
} catch (error) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Issue could not be archived. Please try again.",
});
@ -189,12 +170,19 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
},
addIssueToCycle: async (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => {
try {
await addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds);
setToastAlert({
title: "Cycle added to issue successfully",
type: "success",
message: "Issue added to issue successfully",
const addToCyclePromise = addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds);
setPromiseToast(addToCyclePromise, {
loading: "Adding cycle to issue...",
success: {
title: "Success!",
message: () => "Cycle added to issue successfully",
},
error: {
title: "Error!",
message: () => "Cycle add to issue failed",
},
});
await addToCyclePromise;
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...issueIds, state: "SUCCESS", element: "Issue detail page" },
@ -214,21 +202,23 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
},
path: router.asPath,
});
setToastAlert({
title: "Cycle add to issue failed",
type: "error",
message: "Cycle add to issue failed",
});
}
},
removeIssueFromCycle: async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => {
try {
const response = await removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId);
setToastAlert({
title: "Cycle removed from issue successfully",
type: "success",
message: "Cycle removed from issue successfully",
const removeFromCyclePromise = removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId);
setPromiseToast(removeFromCyclePromise, {
loading: "Removing cycle from issue...",
success: {
title: "Success!",
message: () => "Cycle removed from issue successfully",
},
error: {
title: "Error!",
message: () => "Cycle remove from issue failed",
},
});
const response = await removeFromCyclePromise;
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...response, state: "SUCCESS", element: "Issue detail page" },
@ -248,21 +238,23 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
},
path: router.asPath,
});
setToastAlert({
title: "Cycle remove from issue failed",
type: "error",
message: "Cycle remove from issue failed",
});
}
},
addModulesToIssue: async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => {
try {
const response = await addModulesToIssue(workspaceSlug, projectId, issueId, moduleIds);
setToastAlert({
title: "Module added to issue successfully",
type: "success",
message: "Module added to issue successfully",
const addToModulePromise = addModulesToIssue(workspaceSlug, projectId, issueId, moduleIds);
setPromiseToast(addToModulePromise, {
loading: "Adding module to issue...",
success: {
title: "Success!",
message: () => "Module added to issue successfully",
},
error: {
title: "Error!",
message: () => "Module add to issue failed",
},
});
const response = await addToModulePromise;
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...response, state: "SUCCESS", element: "Issue detail page" },
@ -282,21 +274,23 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
},
path: router.asPath,
});
setToastAlert({
title: "Module add to issue failed",
type: "error",
message: "Module add to issue failed",
});
}
},
removeIssueFromModule: async (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => {
try {
await removeIssueFromModule(workspaceSlug, projectId, moduleId, issueId);
setToastAlert({
title: "Module removed from issue successfully",
type: "success",
message: "Module removed from issue successfully",
const removeFromModulePromise = removeIssueFromModule(workspaceSlug, projectId, moduleId, issueId);
setPromiseToast(removeFromModulePromise, {
loading: "Removing module from issue...",
success: {
title: "Success!",
message: () => "Module removed from issue successfully",
},
error: {
title: "Error!",
message: () => "Module remove from issue failed",
},
});
await removeFromModulePromise;
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
@ -316,11 +310,6 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
},
path: router.asPath,
});
setToastAlert({
title: "Module remove from issue failed",
type: "error",
message: "Module remove from issue failed",
});
}
},
removeModulesFromIssue: async (
@ -329,20 +318,19 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
issueId: string,
moduleIds: string[]
) => {
try {
await removeModulesFromIssue(workspaceSlug, projectId, issueId, moduleIds);
setToastAlert({
type: "success",
title: "Successful!",
message: "Issue removed from module successfully.",
});
} catch (error) {
setToastAlert({
type: "error",
const removeModulesFromIssuePromise = removeModulesFromIssue(workspaceSlug, projectId, issueId, moduleIds);
setPromiseToast(removeModulesFromIssuePromise, {
loading: "Removing module from issue...",
success: {
title: "Success!",
message: () => "Module removed from issue successfully",
},
error: {
title: "Error!",
message: "Issue could not be removed from module. Please try again.",
});
}
message: () => "Module remove from issue failed",
},
});
await removeModulesFromIssuePromise;
},
}),
[
@ -357,7 +345,6 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
addModulesToIssue,
removeIssueFromModule,
removeModulesFromIssue,
setToastAlert,
]
);

View File

@ -16,7 +16,6 @@ import {
} from "lucide-react";
// hooks
import { useEstimate, useIssueDetail, useProject, useProjectState, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import {
DeleteIssueModal,
@ -30,8 +29,18 @@ import {
} from "components/issues";
import { IssueSubscription } from "./subscription";
import { DateDropdown, EstimateDropdown, PriorityDropdown, MemberDropdown, StateDropdown } from "components/dropdowns";
// icons
import { ArchiveIcon, ContrastIcon, DiceIcon, DoubleCircleIcon, RelatedIcon, Tooltip, UserGroupIcon } from "@plane/ui";
// ui
import {
ArchiveIcon,
ContrastIcon,
DiceIcon,
DoubleCircleIcon,
RelatedIcon,
Tooltip,
UserGroupIcon,
TOAST_TYPE,
setToast,
} from "@plane/ui";
// helpers
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
import { copyTextToClipboard } from "helpers/string.helper";
@ -61,7 +70,6 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
const { getProjectById } = useProject();
const { currentUser } = useUser();
const { areEstimatesEnabledForCurrentProject } = useEstimate();
const { setToastAlert } = useToast();
const {
issue: { getIssueById },
} = useIssueDetail();
@ -73,8 +81,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
const handleCopyText = () => {
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`).then(() => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Link Copied!",
message: "Issue link copied to clipboard.",
});

View File

@ -2,10 +2,9 @@ import { Bell, BellOff } from "lucide-react";
import { observer } from "mobx-react-lite";
import { FC, useState } from "react";
// UI
import { Button, Loader } from "@plane/ui";
import { Button, Loader, TOAST_TYPE, setToast } from "@plane/ui";
// hooks
import { useIssueDetail } from "hooks/store";
import useToast from "hooks/use-toast";
import isNil from "lodash/isNil";
export type TIssueSubscription = {
@ -22,7 +21,6 @@ export const IssueSubscription: FC<TIssueSubscription> = observer((props) => {
createSubscription,
removeSubscription,
} = useIssueDetail();
const { setToastAlert } = useToast();
// state
const [loading, setLoading] = useState(false);
@ -33,16 +31,16 @@ export const IssueSubscription: FC<TIssueSubscription> = observer((props) => {
try {
if (isSubscribed) await removeSubscription(workspaceSlug, projectId, issueId);
else await createSubscription(workspaceSlug, projectId, issueId);
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: `Issue ${isSubscribed ? `unsubscribed` : `subscribed`} successfully.!`,
message: `Issue ${isSubscribed ? `unsubscribed` : `subscribed`} successfully.!`,
});
setLoading(false);
} catch (error) {
setLoading(false);
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error",
message: "Something went wrong. Please try again later.",
});

View File

@ -4,8 +4,8 @@ import { observer } from "mobx-react-lite";
import { DragDropContext, DropResult } from "@hello-pangea/dnd";
// components
import { CalendarChart } from "components/issues";
// hooks
import useToast from "hooks/use-toast";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// types
import { TGroupedIssues, TIssue } from "@plane/types";
import { IQuickActionProps } from "../list/list-view-types";
@ -41,7 +41,6 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
const { workspaceSlug, projectId } = router.query;
// hooks
const { setToastAlert } = useToast();
const { issueMap } = useIssues();
const {
membership: { currentProjectRole },
@ -73,9 +72,9 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
groupedIssueIds,
viewId
).catch((err) => {
setToastAlert({
setToast({
title: "Error",
type: "error",
type: TOAST_TYPE.ERROR,
message: err.detail ?? "Failed to perform this action",
});
});

View File

@ -4,13 +4,14 @@ import { useForm } from "react-hook-form";
import { observer } from "mobx-react-lite";
// hooks
import { useEventTracker, useProject } from "hooks/store";
import useToast from "hooks/use-toast";
import useKeypress from "hooks/use-keypress";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
// helpers
import { createIssuePayload } from "helpers/issue.helper";
// icons
import { PlusIcon } from "lucide-react";
// ui
import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
// types
import { TIssue } from "@plane/types";
// constants
@ -71,8 +72,6 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
const ref = useRef<HTMLDivElement>(null);
// states
const [isOpen, setIsOpen] = useState(false);
// toast alert
const { setToastAlert } = useToast();
// derived values
const projectDetail = projectId ? getProjectById(projectId.toString()) : null;
@ -102,13 +101,13 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
Object.keys(errors).forEach((key) => {
const error = errors[key as keyof TIssue];
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: error?.message?.toString() || "Some error occurred. Please try again.",
});
});
}, [errors, setToastAlert]);
}, [errors]);
const onSubmitHandler = async (formData: TIssue) => {
if (isSubmitting || !workspaceSlug || !projectId) return;
@ -120,39 +119,42 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
...formData,
});
try {
quickAddCallback &&
(await quickAddCallback(
workspaceSlug.toString(),
projectId.toString(),
{
...payload,
},
viewId
).then((res) => {
if (quickAddCallback) {
const quickAddPromise = quickAddCallback(
workspaceSlug.toString(),
projectId.toString(),
{
...payload,
},
viewId
);
setPromiseToast<any>(quickAddPromise, {
loading: "Adding issue...",
success: {
title: "Success!",
message: () => "Issue created successfully.",
},
error: {
title: "Error!",
message: (err) => err?.message || "Some error occurred. Please try again.",
},
});
await quickAddPromise
.then((res) => {
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: { ...res, state: "SUCCESS", element: "Calendar quick add" },
path: router.asPath,
});
}));
setToastAlert({
type: "success",
title: "Success!",
message: "Issue created successfully.",
});
} catch (err: any) {
console.error(err);
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: { ...payload, state: "FAILED", element: "Calendar quick add" },
path: router.asPath,
});
setToastAlert({
type: "error",
title: "Error!",
message: err?.message || "Some error occurred. Please try again.",
});
})
.catch(() => {
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: { ...payload, state: "FAILED", element: "Calendar quick add" },
path: router.asPath,
});
});
}
};

View File

@ -4,7 +4,8 @@ import { PlusIcon } from "lucide-react";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useEventTracker, useIssueDetail, useIssues, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { ExistingIssuesListModal } from "components/core";
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
@ -53,16 +54,14 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
currentUser,
} = useUser();
const { setToastAlert } = useToast();
const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => {
if (!workspaceSlug || !projectId || !cycleId) return;
const issueIds = data.map((i) => i.id);
await issues.addIssueToCycle(workspaceSlug.toString(), projectId, cycleId.toString(), issueIds).catch(() => {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Selected issues could not be added to the cycle. Please try again.",
});

View File

@ -4,7 +4,8 @@ import { PlusIcon } from "lucide-react";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useEventTracker, useIssues, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { ExistingIssuesListModal } from "components/core";
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
@ -51,8 +52,6 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
membership: { currentProjectRole: userRole },
currentUser,
} = useUser();
// toast alert
const { setToastAlert } = useToast();
const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => {
if (!workspaceSlug || !projectId || !moduleId) return;
@ -61,8 +60,8 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
await issues
.addIssuesToModule(workspaceSlug.toString(), projectId?.toString(), moduleId.toString(), issueIds)
.catch(() =>
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Selected issues could not be added to the module. Please try again.",
})

View File

@ -5,13 +5,14 @@ import { observer } from "mobx-react-lite";
import { PlusIcon } from "lucide-react";
// hooks
import { useEventTracker, useProject } from "hooks/store";
import useToast from "hooks/use-toast";
import useKeypress from "hooks/use-keypress";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
// helpers
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
import { createIssuePayload } from "helpers/issue.helper";
import { cn } from "helpers/common.helper";
// ui
import { setPromiseToast } from "@plane/ui";
// types
import { IProject, TIssue } from "@plane/types";
// constants
@ -70,7 +71,6 @@ export const GanttQuickAddIssueForm: React.FC<IGanttQuickAddIssueForm> = observe
// hooks
const { getProjectById } = useProject();
const { captureIssueEvent } = useEventTracker();
const { setToastAlert } = useToast();
const projectDetail = (projectId && getProjectById(projectId.toString())) || undefined;
@ -110,31 +110,35 @@ export const GanttQuickAddIssueForm: React.FC<IGanttQuickAddIssueForm> = observe
target_date: renderFormattedPayloadDate(targetDate),
});
try {
quickAddCallback &&
(await quickAddCallback(workspaceSlug.toString(), projectId.toString(), { ...payload }, viewId).then((res) => {
if (quickAddCallback) {
const quickAddPromise = quickAddCallback(workspaceSlug.toString(), projectId.toString(), { ...payload }, viewId);
setPromiseToast<any>(quickAddPromise, {
loading: "Adding issue...",
success: {
title: "Success!",
message: () => "Issue created successfully.",
},
error: {
title: "Error!",
message: (err) => err?.message || "Some error occurred. Please try again.",
},
});
await quickAddPromise
.then((res) => {
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: { ...res, state: "SUCCESS", element: "Gantt quick add" },
path: router.asPath,
});
}));
setToastAlert({
type: "success",
title: "Success!",
message: "Issue created successfully.",
});
} catch (err: any) {
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: { ...payload, state: "FAILED", element: "Gantt quick add" },
path: router.asPath,
});
setToastAlert({
type: "error",
title: "Error!",
message: err?.message || "Some error occurred. Please try again.",
});
})
.catch(() => {
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: { ...payload, state: "FAILED", element: "Gantt quick add" },
path: router.asPath,
});
});
}
};
return (

View File

@ -4,9 +4,8 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// hooks
import { useEventTracker, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { Spinner } from "@plane/ui";
import { Spinner, TOAST_TYPE, setToast } from "@plane/ui";
// types
import { TIssue } from "@plane/types";
import { EIssueActions } from "../types";
@ -80,8 +79,6 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
} = useUser();
const { captureIssueEvent } = useEventTracker();
const { issueMap } = useIssues();
// toast alert
const { setToastAlert } = useToast();
const issueIds = issues?.groupedIssueIds || [];
@ -159,9 +156,9 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
issueIds,
viewId
).catch((err) => {
setToastAlert({
setToast({
title: "Error",
type: "error",
type: TOAST_TYPE.ERROR,
message: err.detail ?? "Failed to perform this action",
});
});

View File

@ -1,13 +1,13 @@
import React, { FC } from "react";
import { useRouter } from "next/router";
// ui
import { CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { CustomMenu } from "@plane/ui";
import { ExistingIssuesListModal } from "components/core";
import { CreateUpdateIssueModal } from "components/issues";
// lucide icons
import { Minimize2, Maximize2, Circle, Plus } from "lucide-react";
// hooks
import useToast from "hooks/use-toast";
import { useEventTracker } from "hooks/store";
// mobx
import { observer } from "mobx-react-lite";
@ -56,8 +56,6 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
const isDraftIssue = router.pathname.includes("draft-issue");
const { setToastAlert } = useToast();
const renderExistingIssueModal = moduleId || cycleId;
const ExistingIssuesListModalPayload = moduleId ? { module: moduleId.toString() } : { cycle: true };
@ -69,8 +67,8 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
try {
addIssuesToView && addIssuesToView(issues);
} catch (error) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Selected issues could not be added to the cycle. Please try again.",
});

View File

@ -5,11 +5,12 @@ import { observer } from "mobx-react-lite";
import { PlusIcon } from "lucide-react";
// hooks
import { useEventTracker, useProject } from "hooks/store";
import useToast from "hooks/use-toast";
import useKeypress from "hooks/use-keypress";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
// helpers
import { createIssuePayload } from "helpers/issue.helper";
// ui
import { setPromiseToast } from "@plane/ui";
// types
import { TIssue } from "@plane/types";
// constants
@ -73,7 +74,6 @@ export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = obser
useKeypress("Escape", handleClose);
useOutsideClickDetector(ref, handleClose);
const { setToastAlert } = useToast();
const {
reset,
@ -97,39 +97,42 @@ export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = obser
...formData,
});
try {
quickAddCallback &&
(await quickAddCallback(
workspaceSlug.toString(),
projectId.toString(),
{
...payload,
},
viewId
).then((res) => {
if (quickAddCallback) {
const quickAddPromise = quickAddCallback(
workspaceSlug.toString(),
projectId.toString(),
{
...payload,
},
viewId
);
setPromiseToast<any>(quickAddPromise, {
loading: "Adding issue...",
success: {
title: "Success!",
message: () => "Issue created successfully.",
},
error: {
title: "Error!",
message: (err) => err?.message || "Some error occurred. Please try again.",
},
});
await quickAddPromise
.then((res) => {
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: { ...res, state: "SUCCESS", element: "Kanban quick add" },
path: router.asPath,
});
}));
setToastAlert({
type: "success",
title: "Success!",
message: "Issue created successfully.",
});
} catch (err: any) {
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: { ...payload, state: "FAILED", element: "Kanban quick add" },
path: router.asPath,
});
console.error(err);
setToastAlert({
type: "error",
title: "Error!",
message: err?.message || "Some error occurred. Please try again.",
});
})
.catch(() => {
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: { ...payload, state: "FAILED", element: "Kanban quick add" },
path: router.asPath,
});
});
}
};

View File

@ -4,14 +4,14 @@ import { CircleDashed, Plus } from "lucide-react";
// components
import { CreateUpdateIssueModal } from "components/issues";
import { ExistingIssuesListModal } from "components/core";
import { CustomMenu } from "@plane/ui";
// ui
import { CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
// mobx
import { observer } from "mobx-react-lite";
// hooks
import { useEventTracker } from "hooks/store";
// types
import { TIssue, ISearchIssueResponse } from "@plane/types";
import useToast from "hooks/use-toast";
import { useState } from "react";
import { TCreateModalStoreTypes } from "constants/issue";
@ -38,8 +38,6 @@ export const HeaderGroupByCard = observer(
const isDraftIssue = router.pathname.includes("draft-issue");
const { setToastAlert } = useToast();
const renderExistingIssueModal = moduleId || cycleId;
const ExistingIssuesListModalPayload = moduleId ? { module: moduleId.toString() } : { cycle: true };
@ -51,8 +49,8 @@ export const HeaderGroupByCard = observer(
try {
addIssuesToView && addIssuesToView(issues);
} catch (error) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Selected issues could not be added to the cycle. Please try again.",
});

View File

@ -5,12 +5,13 @@ import { PlusIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
// hooks
import { useEventTracker, useProject } from "hooks/store";
import useToast from "hooks/use-toast";
import useKeypress from "hooks/use-keypress";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
// constants
import { TIssue, IProject } from "@plane/types";
// ui
import { setPromiseToast } from "@plane/ui";
// types
import { TIssue, IProject } from "@plane/types";
// helper
import { createIssuePayload } from "helpers/issue.helper";
// constants
import { ISSUE_CREATED } from "constants/event-tracker";
@ -77,7 +78,6 @@ export const ListQuickAddIssueForm: FC<IListQuickAddIssueForm> = observer((props
useKeypress("Escape", handleClose);
useOutsideClickDetector(ref, handleClose);
const { setToastAlert } = useToast();
const {
reset,
@ -101,31 +101,35 @@ export const ListQuickAddIssueForm: FC<IListQuickAddIssueForm> = observer((props
...formData,
});
try {
quickAddCallback &&
(await quickAddCallback(workspaceSlug.toString(), projectId.toString(), { ...payload }, viewId).then((res) => {
if (quickAddCallback) {
const quickAddPromise = quickAddCallback(workspaceSlug.toString(), projectId.toString(), { ...payload }, viewId);
setPromiseToast<any>(quickAddPromise, {
loading: "Adding issue...",
success: {
title: "Success!",
message: () => "Issue created successfully.",
},
error: {
title: "Error!",
message: (err) => err?.message || "Some error occurred. Please try again.",
},
});
await quickAddPromise
.then((res) => {
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: { ...res, state: "SUCCESS", element: "List quick add" },
path: router.asPath,
});
}));
setToastAlert({
type: "success",
title: "Success!",
message: "Issue created successfully.",
});
} catch (err: any) {
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: { ...payload, state: "FAILED", element: "List quick add" },
path: router.asPath,
});
setToastAlert({
type: "error",
title: "Error!",
message: err?.message || "Some error occurred. Please try again.",
});
})
.catch(() => {
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: { ...payload, state: "FAILED", element: "List quick add" },
path: router.asPath,
});
});
}
};

View File

@ -1,12 +1,12 @@
import { useState } from "react";
import { useRouter } from "next/router";
import { ArchiveIcon, CustomMenu } from "@plane/ui";
import { observer } from "mobx-react";
import { Copy, ExternalLink, Link, Pencil, Trash2 } from "lucide-react";
import omit from "lodash/omit";
import { observer } from "mobx-react";
// hooks
import useToast from "hooks/use-toast";
import { useEventTracker, useProjectState } from "hooks/store";
// ui
import { ArchiveIcon, CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
// helpers
@ -39,8 +39,6 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
// store hooks
const { setTrackElement } = useEventTracker();
const { getStateById } = useProjectState();
// toast alert
const { setToastAlert } = useToast();
// derived values
const stateDetails = getStateById(issue.state_id);
const isEditingAllowed = !readOnly;
@ -54,8 +52,8 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
const handleOpenInNewTab = () => window.open(`/${issueLink}`, "_blank");
const handleCopyIssueLink = () =>
copyUrlToClipboard(issueLink).then(() =>
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Link copied",
message: "Issue link copied to clipboard",
})

View File

@ -1,10 +1,10 @@
import { useState } from "react";
import { useRouter } from "next/router";
import { CustomMenu } from "@plane/ui";
import { ExternalLink, Link, RotateCcw, Trash2 } from "lucide-react";
// hooks
import useToast from "hooks/use-toast";
import { useEventTracker, useIssues, useUser } from "hooks/store";
// ui
import { CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { DeleteIssueModal } from "components/issues";
// helpers
@ -32,16 +32,14 @@ export const ArchivedIssueQuickActions: React.FC<IQuickActionProps> = (props) =>
// auth
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER && !readOnly;
const isRestoringAllowed = handleRestore && isEditingAllowed;
// toast alert
const { setToastAlert } = useToast();
const issueLink = `${workspaceSlug}/projects/${issue.project_id}/archived-issues/${issue.id}`;
const handleOpenInNewTab = () => window.open(`/${issueLink}`, "_blank");
const handleCopyIssueLink = () =>
copyUrlToClipboard(issueLink).then(() =>
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Link copied",
message: "Issue link copied to clipboard",
})

View File

@ -1,12 +1,13 @@
import { useState } from "react";
import { useRouter } from "next/router";
import { ArchiveIcon, CustomMenu } from "@plane/ui";
import { observer } from "mobx-react";
import { Copy, ExternalLink, Link, Pencil, Trash2, XCircle } from "lucide-react";
import omit from "lodash/omit";
import { observer } from "mobx-react";
// hooks
import useToast from "hooks/use-toast";
import { useEventTracker, useIssues, useProjectState, useUser } from "hooks/store";
// ui
import { ArchiveIcon, CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
// icons
import { Copy, ExternalLink, Link, Pencil, Trash2, XCircle } from "lucide-react";
// components
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
// helpers
@ -45,8 +46,6 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
membership: { currentProjectRole },
} = useUser();
const { getStateById } = useProjectState();
// toast alert
const { setToastAlert } = useToast();
// derived values
const stateDetails = getStateById(issue.state_id);
// auth
@ -64,8 +63,8 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
const handleCopyIssueLink = () =>
copyUrlToClipboard(issueLink).then(() =>
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Link copied",
message: "Issue link copied to clipboard",
})

View File

@ -1,12 +1,12 @@
import { useState } from "react";
import { useRouter } from "next/router";
import { ArchiveIcon, CustomMenu } from "@plane/ui";
import { observer } from "mobx-react";
import { Copy, ExternalLink, Link, Pencil, Trash2, XCircle } from "lucide-react";
import omit from "lodash/omit";
import { observer } from "mobx-react";
// hooks
import useToast from "hooks/use-toast";
import { useIssues, useEventTracker, useUser, useProjectState } from "hooks/store";
// ui
import { ArchiveIcon, CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
import { Copy, ExternalLink, Link, Pencil, Trash2, XCircle } from "lucide-react";
// components
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
// helpers
@ -45,8 +45,6 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
membership: { currentProjectRole },
} = useUser();
const { getStateById } = useProjectState();
// toast alert
const { setToastAlert } = useToast();
// derived values
const stateDetails = getStateById(issue.state_id);
// auth
@ -64,8 +62,8 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
const handleCopyIssueLink = () =>
copyUrlToClipboard(issueLink).then(() =>
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Link copied",
message: "Issue link copied to clipboard",
})

View File

@ -1,12 +1,12 @@
import { useState } from "react";
import { useRouter } from "next/router";
import { ArchiveIcon, CustomMenu } from "@plane/ui";
import { observer } from "mobx-react";
import { Copy, ExternalLink, Link, Pencil, Trash2 } from "lucide-react";
import omit from "lodash/omit";
import { observer } from "mobx-react";
// hooks
import { useEventTracker, useIssues, useProjectState, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { ArchiveIcon, CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
import { Copy, ExternalLink, Link, Pencil, Trash2 } from "lucide-react";
// components
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
// helpers
@ -54,16 +54,14 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
!!stateDetails && [STATE_GROUPS.completed.key, STATE_GROUPS.cancelled.key].includes(stateDetails?.group);
const isDeletingAllowed = isEditingAllowed;
const { setToastAlert } = useToast();
const issueLink = `${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`;
const handleOpenInNewTab = () => window.open(`/${issueLink}`, "_blank");
const handleCopyIssueLink = () =>
copyUrlToClipboard(issueLink).then(() =>
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Link copied",
message: "Issue link copied to clipboard",
})

View File

@ -5,11 +5,12 @@ import { observer } from "mobx-react-lite";
import { PlusIcon } from "lucide-react";
// hooks
import { useEventTracker, useProject, useWorkspace } from "hooks/store";
import useToast from "hooks/use-toast";
import useKeypress from "hooks/use-keypress";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
// helpers
import { createIssuePayload } from "helpers/issue.helper";
// ui
import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
// types
import { TIssue } from "@plane/types";
// constants
@ -84,7 +85,6 @@ export const SpreadsheetQuickAddIssueForm: React.FC<Props> = observer((props) =>
// hooks
useKeypress("Escape", handleClose);
useOutsideClickDetector(ref, handleClose);
const { setToastAlert } = useToast();
useEffect(() => {
setFocus("name");
@ -100,13 +100,13 @@ export const SpreadsheetQuickAddIssueForm: React.FC<Props> = observer((props) =>
Object.keys(errors).forEach((key) => {
const error = errors[key as keyof TIssue];
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: error?.message?.toString() || "Some error occurred. Please try again.",
});
});
}, [errors, setToastAlert]);
}, [errors]);
// const onSubmitHandler = async (formData: TIssue) => {
// if (isSubmitting || !workspaceSlug || !projectId) return;
@ -130,8 +130,8 @@ export const SpreadsheetQuickAddIssueForm: React.FC<Props> = observer((props) =>
// payload
// );
// setToastAlert({
// type: "success",
// setToast({
// type: TOAST_TYPE.SUCCESS,
// title: "Success!",
// message: "Issue created successfully.",
// });
@ -140,8 +140,8 @@ export const SpreadsheetQuickAddIssueForm: React.FC<Props> = observer((props) =>
// const error = err?.[key];
// const errorTitle = error ? (Array.isArray(error) ? error.join(", ") : error) : null;
// setToastAlert({
// type: "error",
// setToast({
// type: TOAST_TYPE.ERROR,
// title: "Error!",
// message: errorTitle || "Some error occurred. Please try again.",
// });
@ -159,34 +159,41 @@ export const SpreadsheetQuickAddIssueForm: React.FC<Props> = observer((props) =>
...formData,
});
try {
quickAddCallback &&
(await quickAddCallback(currentWorkspace.slug, currentProjectDetails.id, { ...payload } as TIssue, viewId).then(
(res) => {
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: { ...res, state: "SUCCESS", element: "Spreadsheet quick add" },
path: router.asPath,
});
}
));
setToastAlert({
type: "success",
title: "Success!",
message: "Issue created successfully.",
});
} catch (err: any) {
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: { ...payload, state: "FAILED", element: "Spreadsheet quick add" },
path: router.asPath,
});
console.error(err);
setToastAlert({
type: "error",
title: "Error!",
message: err?.message || "Some error occurred. Please try again.",
if (quickAddCallback) {
const quickAddPromise = quickAddCallback(
currentWorkspace.slug,
currentProjectDetails.id,
{ ...payload } as TIssue,
viewId
);
setPromiseToast<any>(quickAddPromise, {
loading: "Adding issue...",
success: {
title: "Success!",
message: () => "Issue created successfully.",
},
error: {
title: "Error!",
message: (err) => err?.message || "Some error occurred. Please try again.",
},
});
await quickAddPromise
.then((res) => {
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: { ...res, state: "SUCCESS", element: "Spreadsheet quick add" },
path: router.asPath,
});
})
.catch((err) => {
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: { ...payload, state: "FAILED", element: "Spreadsheet quick add" },
path: router.asPath,
});
console.error(err);
});
}
};

View File

@ -2,10 +2,11 @@ import React, { useState } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// hooks
import useToast from "hooks/use-toast";
import { useEventTracker } from "hooks/store";
// services
import { IssueDraftService } from "services/issue";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { IssueFormRoot } from "components/issues/issue-modal/form";
import { ConfirmIssueDiscard } from "components/issues";
@ -43,8 +44,6 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// toast alert
const { setToastAlert } = useToast();
// store hooks
const { captureIssueEvent } = useEventTracker();
@ -61,8 +60,8 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
await issueDraftService
.createDraftIssue(workspaceSlug.toString(), projectId.toString(), payload)
.then((res) => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Draft Issue created successfully.",
});
@ -76,8 +75,8 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
onClose(false);
})
.catch(() => {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Issue could not be created. Please try again.",
});

View File

@ -7,7 +7,6 @@ import { LayoutPanelTop, Sparkle, X } from "lucide-react";
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
// hooks
import { useApplication, useEstimate, useIssueDetail, useMention, useProject, useWorkspace } from "hooks/store";
import useToast from "hooks/use-toast";
// services
import { AIService } from "services/ai.service";
import { FileService } from "services/file.service";
@ -27,7 +26,7 @@ import {
StateDropdown,
} from "components/dropdowns";
// ui
import { Button, CustomMenu, Input, Loader, ToggleSwitch } from "@plane/ui";
import { Button, CustomMenu, Input, Loader, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui";
// helpers
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
// types
@ -125,8 +124,6 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
const {
issue: { getIssueById },
} = useIssueDetail();
// toast alert
const { setToastAlert } = useToast();
// form info
const {
formState: { errors, isDirty, isSubmitting },
@ -199,8 +196,8 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
})
.then((res) => {
if (res.response === "")
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message:
"Issue title isn't informative enough to generate the description. Please try with a different title.",
@ -211,14 +208,14 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
const error = err?.data?.error;
if (err.status === 429)
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: error || "You have reached the maximum number of requests of 50 requests per month per user.",
});
else
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: error || "Some error occurred. Please try again.",
});

View File

@ -13,11 +13,12 @@ import {
useWorkspace,
useIssueDetail,
} from "hooks/store";
import useToast from "hooks/use-toast";
import useLocalStorage from "hooks/use-local-storage";
// components
import { DraftIssueLayout } from "./draft-issue-layout";
import { IssueFormRoot } from "./form";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// types
import type { TIssue } from "@plane/types";
// constants
@ -89,8 +90,6 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
};
// router
const router = useRouter();
// toast alert
const { setToastAlert } = useToast();
// local storage
const { storedValue: localStorageDraftIssues, setValue: setLocalStorageDraftIssue } = useLocalStorage<
Record<string, Partial<TIssue>>
@ -186,9 +185,8 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
await addIssueToCycle(response, payload.cycle_id);
if (payload.module_ids && payload.module_ids.length > 0 && storeType !== EIssuesStoreType.MODULE)
await addIssueToModule(response, payload.module_ids);
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Issue created successfully.",
});
@ -200,8 +198,8 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
!createMore && handleClose();
return response;
} catch (error) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Issue could not be created. Please try again.",
});
@ -221,8 +219,8 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
? await draftIssues.updateIssue(workspaceSlug, payload.project_id, data.id, payload)
: await currentIssueStore.updateIssue(workspaceSlug, payload.project_id, data.id, payload, viewId);
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Issue updated successfully.",
});
@ -233,8 +231,8 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
});
handleClose();
} catch (error) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Issue could not be created. Please try again.",
});

View File

@ -3,11 +3,18 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react";
import { MoveRight, MoveDiagonal, Link2, Trash2, RotateCcw } from "lucide-react";
// ui
import { ArchiveIcon, CenterPanelIcon, CustomSelect, FullScreenPanelIcon, SidePanelIcon, Tooltip } from "@plane/ui";
import {
ArchiveIcon,
CenterPanelIcon,
CustomSelect,
FullScreenPanelIcon,
SidePanelIcon,
Tooltip,
TOAST_TYPE,
setToast,
} from "@plane/ui";
// helpers
import { copyUrlToClipboard } from "helpers/string.helper";
// hooks
import useToast from "hooks/use-toast";
// store hooks
import { useIssueDetail, useProjectState, useUser } from "hooks/store";
// helpers
@ -74,8 +81,6 @@ export const IssuePeekOverviewHeader: FC<PeekOverviewHeaderProps> = observer((pr
issue: { getIssueById },
} = useIssueDetail();
const { getStateById } = useProjectState();
// hooks
const { setToastAlert } = useToast();
// derived values
const issueDetails = getIssueById(issueId);
const stateDetails = issueDetails ? getStateById(issueDetails?.state_id) : undefined;
@ -87,8 +92,8 @@ export const IssuePeekOverviewHeader: FC<PeekOverviewHeaderProps> = observer((pr
e.stopPropagation();
e.preventDefault();
copyUrlToClipboard(issueLink).then(() => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Link Copied!",
message: "Issue link copied to clipboard.",
});

View File

@ -2,8 +2,9 @@ import { FC, useEffect, useState, useMemo } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// hooks
import useToast from "hooks/use-toast";
import { useEventTracker, useIssueDetail, useIssues, useUser } from "hooks/store";
// ui
import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
// components
import { IssueView } from "components/issues";
// types
@ -20,13 +21,7 @@ interface IIssuePeekOverview {
export type TIssuePeekOperations = {
fetch: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
update: (
workspaceSlug: string,
projectId: string,
issueId: string,
data: Partial<TIssue>,
showToast?: boolean
) => Promise<void>;
update: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
remove: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
archive: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
restore: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
@ -49,8 +44,6 @@ export type TIssuePeekOperations = {
export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
const { is_archived = false, is_draft = false } = props;
// hooks
const { setToastAlert } = useToast();
// router
const router = useRouter();
const {
@ -86,49 +79,38 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
console.error("Error fetching the parent issue");
}
},
update: async (
workspaceSlug: string,
projectId: string,
issueId: string,
data: Partial<TIssue>,
showToast: boolean = true
) => {
try {
await updateIssue(workspaceSlug, projectId, issueId, data);
if (showToast)
setToastAlert({
title: "Issue updated successfully",
type: "success",
message: "Issue updated successfully",
update: async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => {
await updateIssue(workspaceSlug, projectId, issueId, data)
.then(() => {
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...data, issueId, state: "SUCCESS", element: "Issue peek-overview" },
updates: {
changed_property: Object.keys(data).join(","),
change_details: Object.values(data).join(","),
},
path: router.asPath,
});
})
.catch(() => {
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { state: "FAILED", element: "Issue peek-overview" },
path: router.asPath,
});
setToast({
title: "Issue update failed",
type: TOAST_TYPE.ERROR,
message: "Issue update failed",
});
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...data, issueId, state: "SUCCESS", element: "Issue peek-overview" },
updates: {
changed_property: Object.keys(data).join(","),
change_details: Object.values(data).join(","),
},
path: router.asPath,
});
} catch (error) {
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { state: "FAILED", element: "Issue peek-overview" },
path: router.asPath,
});
setToastAlert({
title: "Issue update failed",
type: "error",
message: "Issue update failed",
});
}
},
remove: async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
removeIssue(workspaceSlug, projectId, issueId);
setToastAlert({
setToast({
title: "Issue deleted successfully",
type: "success",
type: TOAST_TYPE.SUCCESS,
message: "Issue deleted successfully",
});
captureIssueEvent({
@ -137,9 +119,9 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
path: router.asPath,
});
} catch (error) {
setToastAlert({
setToast({
title: "Issue delete failed",
type: "error",
type: TOAST_TYPE.ERROR,
message: "Issue delete failed",
});
captureIssueEvent({
@ -152,8 +134,8 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
archive: async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
await archiveIssue(workspaceSlug, projectId, issueId);
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Issue archived successfully.",
});
@ -163,8 +145,8 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
path: router.asPath,
});
} catch (error) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Issue could not be archived. Please try again.",
});
@ -178,8 +160,8 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
restore: async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
await restoreIssue(workspaceSlug, projectId, issueId);
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Issue restored successfully.",
});
@ -189,8 +171,8 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
path: router.asPath,
});
} catch (error) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Issue could not be restored. Please try again.",
});
@ -203,12 +185,19 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
},
addIssueToCycle: async (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => {
try {
await addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds);
setToastAlert({
title: "Cycle added to issue successfully",
type: "success",
message: "Issue added to issue successfully",
const addToCyclePromise = addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds);
setPromiseToast(addToCyclePromise, {
loading: "Adding cycle to issue...",
success: {
title: "Success!",
message: () => "Cycle added to issue successfully",
},
error: {
title: "Error!",
message: () => "Cycle add to issue failed",
},
});
await addToCyclePromise;
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...issueIds, state: "SUCCESS", element: "Issue peek-overview" },
@ -228,21 +217,23 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
},
path: router.asPath,
});
setToastAlert({
title: "Cycle add to issue failed",
type: "error",
message: "Cycle add to issue failed",
});
}
},
removeIssueFromCycle: async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => {
try {
const response = await removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId);
setToastAlert({
title: "Cycle removed from issue successfully",
type: "success",
message: "Cycle removed from issue successfully",
const removeFromCyclePromise = removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId);
setPromiseToast(removeFromCyclePromise, {
loading: "Removing cycle from issue...",
success: {
title: "Success!",
message: () => "Cycle removed from issue successfully",
},
error: {
title: "Error!",
message: () => "Cycle remove from issue failed",
},
});
const response = await removeFromCyclePromise;
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...response, state: "SUCCESS", element: "Issue peek-overview" },
@ -253,11 +244,6 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
path: router.asPath,
});
} catch (error) {
setToastAlert({
title: "Cycle remove from issue failed",
type: "error",
message: "Cycle remove from issue failed",
});
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { state: "FAILED", element: "Issue peek-overview" },
@ -271,12 +257,19 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
},
addModulesToIssue: async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => {
try {
const response = await addModulesToIssue(workspaceSlug, projectId, issueId, moduleIds);
setToastAlert({
title: "Module added to issue successfully",
type: "success",
message: "Module added to issue successfully",
const addToModulePromise = addModulesToIssue(workspaceSlug, projectId, issueId, moduleIds);
setPromiseToast(addToModulePromise, {
loading: "Adding module to issue...",
success: {
title: "Success!",
message: () => "Module added to issue successfully",
},
error: {
title: "Error!",
message: () => "Module add to issue failed",
},
});
const response = await addToModulePromise;
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...response, state: "SUCCESS", element: "Issue peek-overview" },
@ -296,21 +289,23 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
},
path: router.asPath,
});
setToastAlert({
title: "Module add to issue failed",
type: "error",
message: "Module add to issue failed",
});
}
},
removeIssueFromModule: async (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => {
try {
await removeIssueFromModule(workspaceSlug, projectId, moduleId, issueId);
setToastAlert({
title: "Module removed from issue successfully",
type: "success",
message: "Module removed from issue successfully",
const removeFromModulePromise = removeIssueFromModule(workspaceSlug, projectId, moduleId, issueId);
setPromiseToast(removeFromModulePromise, {
loading: "Removing module from issue...",
success: {
title: "Success!",
message: () => "Module removed from issue successfully",
},
error: {
title: "Error!",
message: () => "Module remove from issue failed",
},
});
await removeFromModulePromise;
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" },
@ -330,11 +325,6 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
},
path: router.asPath,
});
setToastAlert({
title: "Module remove from issue failed",
type: "error",
message: "Module remove from issue failed",
});
}
},
removeModulesFromIssue: async (
@ -343,20 +333,19 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
issueId: string,
moduleIds: string[]
) => {
try {
await removeModulesFromIssue(workspaceSlug, projectId, issueId, moduleIds);
setToastAlert({
title: "Module removed from issue successfully",
type: "success",
message: "Module removed from issue successfully",
});
} catch (error) {
setToastAlert({
title: "Module remove from issue failed",
type: "error",
message: "Module remove from issue failed",
});
}
const removeModulesFromIssuePromise = removeModulesFromIssue(workspaceSlug, projectId, issueId, moduleIds);
setPromiseToast(removeModulesFromIssuePromise, {
loading: "Removing module from issue...",
success: {
title: "Success!",
message: () => "Module removed from issue successfully",
},
error: {
title: "Error!",
message: () => "Module remove from issue failed",
},
});
await removeModulesFromIssuePromise;
},
}),
[
@ -372,7 +361,6 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
addModulesToIssue,
removeIssueFromModule,
removeModulesFromIssue,
setToastAlert,
captureIssueEvent,
router.asPath,
]

View File

@ -5,7 +5,6 @@ import { observer } from "mobx-react-lite";
// hooks
import useOutsideClickDetector from "hooks/use-outside-click-detector";
import useKeypress from "hooks/use-keypress";
import useToast from "hooks/use-toast";
// store hooks
import { useIssueDetail } from "hooks/store";
// components
@ -50,15 +49,13 @@ export const IssueView: FC<IIssueView> = observer((props) => {
issue: { getIssueById },
} = useIssueDetail();
const issue = getIssueById(issueId);
// hooks
const { alerts } = useToast();
// remove peek id
const removeRoutePeekId = () => {
setPeekIssue(undefined);
};
useOutsideClickDetector(issuePeekOverviewRef, () => {
if (!isAnyModalOpen && (!alerts || alerts.length === 0)) {
if (!isAnyModalOpen) {
removeRoutePeekId();
}
});

View File

@ -4,14 +4,13 @@ import { observer } from "mobx-react-lite";
import { Plus, ChevronRight, ChevronDown, Loader } from "lucide-react";
// hooks
import { useEventTracker, useIssueDetail } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { ExistingIssuesListModal } from "components/core";
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
import { IssueList } from "./issues-list";
import { ProgressBar } from "./progressbar";
// ui
import { CustomMenu } from "@plane/ui";
import { CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
// helpers
import { copyTextToClipboard } from "helpers/string.helper";
// types
@ -46,8 +45,6 @@ export const SubIssuesRoot: FC<ISubIssuesRoot> = observer((props) => {
const { workspaceSlug, projectId, parentIssueId, disabled = false } = props;
// router
const router = useRouter();
// store hooks
const { setToastAlert } = useToast();
const {
issue: { getIssueById },
subIssues: { subIssuesByIssueId, stateDistributionByIssueId, subIssueHelpersByIssueId, setSubIssueHelpers },
@ -128,8 +125,8 @@ export const SubIssuesRoot: FC<ISubIssuesRoot> = observer((props) => {
copyText: (text: string) => {
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
copyTextToClipboard(`${originURL}/${text}`).then(() => {
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Link Copied!",
message: "Issue link copied to clipboard.",
});
@ -139,8 +136,8 @@ export const SubIssuesRoot: FC<ISubIssuesRoot> = observer((props) => {
try {
await fetchSubIssues(workspaceSlug, projectId, parentIssueId);
} catch (error) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error fetching sub-issues",
message: "Error fetching sub-issues",
});
@ -149,14 +146,14 @@ export const SubIssuesRoot: FC<ISubIssuesRoot> = observer((props) => {
addSubIssue: async (workspaceSlug: string, projectId: string, parentIssueId: string, issueIds: string[]) => {
try {
await createSubIssues(workspaceSlug, projectId, parentIssueId, issueIds);
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Sub-issues added successfully",
message: "Sub-issues added successfully",
});
} catch (error) {
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error adding sub-issue",
message: "Error adding sub-issue",
});
@ -183,8 +180,8 @@ export const SubIssuesRoot: FC<ISubIssuesRoot> = observer((props) => {
},
path: router.asPath,
});
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Sub-issue updated successfully",
message: "Sub-issue updated successfully",
});
@ -199,8 +196,8 @@ export const SubIssuesRoot: FC<ISubIssuesRoot> = observer((props) => {
},
path: router.asPath,
});
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error updating sub-issue",
message: "Error updating sub-issue",
});
@ -210,8 +207,8 @@ export const SubIssuesRoot: FC<ISubIssuesRoot> = observer((props) => {
try {
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
await removeSubIssue(workspaceSlug, projectId, parentIssueId, issueId);
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Sub-issue removed successfully",
message: "Sub-issue removed successfully",
});
@ -235,8 +232,8 @@ export const SubIssuesRoot: FC<ISubIssuesRoot> = observer((props) => {
},
path: router.asPath,
});
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error removing sub-issue",
message: "Error removing sub-issue",
});
@ -246,8 +243,8 @@ export const SubIssuesRoot: FC<ISubIssuesRoot> = observer((props) => {
try {
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
await deleteSubIssue(workspaceSlug, projectId, parentIssueId, issueId);
setToastAlert({
type: "success",
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Issue deleted successfully",
message: "Issue deleted successfully",
});
@ -263,15 +260,15 @@ export const SubIssuesRoot: FC<ISubIssuesRoot> = observer((props) => {
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
path: router.asPath,
});
setToastAlert({
type: "error",
setToast({
type: TOAST_TYPE.ERROR,
title: "Error deleting issue",
message: "Error deleting issue",
});
}
},
}),
[fetchSubIssues, createSubIssues, updateSubIssue, removeSubIssue, deleteSubIssue, setToastAlert, setSubIssueHelpers]
[fetchSubIssues, createSubIssues, updateSubIssue, removeSubIssue, deleteSubIssue, setSubIssueHelpers]
);
const issue = getIssueById(parentIssueId);

View File

@ -32,7 +32,7 @@ export const IssueTitleInput: FC<IssueTitleInputProps> = observer((props) => {
useEffect(() => {
const textarea = document.querySelector("#title-input");
if (debouncedValue && debouncedValue !== value) {
issueOperations.update(workspaceSlug, projectId, issueId, { name: debouncedValue }, false).finally(() => {
issueOperations.update(workspaceSlug, projectId, issueId, { name: debouncedValue }).finally(() => {
setIsSubmitting("saved");
if (textarea && !textarea.matches(":focus")) {
const trimmedTitle = debouncedValue.trim();

View File

@ -7,9 +7,8 @@ import { Dialog, Popover, Transition } from "@headlessui/react";
import { ChevronDown } from "lucide-react";
// hooks
import { useLabel } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { Button, Input } from "@plane/ui";
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// types
import type { IIssueLabel, IState } from "@plane/types";
// constants
@ -64,8 +63,6 @@ export const CreateLabelModal: React.FC<Props> = observer((props) => {
reset(defaultValues);
};
const { setToastAlert } = useToast();
const onSubmit = async (formData: IIssueLabel) => {
if (!workspaceSlug) return;
@ -75,9 +72,9 @@ export const CreateLabelModal: React.FC<Props> = observer((props) => {
if (onSuccess) onSuccess(res);
})
.catch((error) => {
setToastAlert({
setToast({
title: "Oops!",
type: "error",
type: TOAST_TYPE.ERROR,
message: error?.error ?? "Error while adding the label",
});
reset(formData);

Some files were not shown because too many files have changed in this diff Show More