diff --git a/packages/tailwind-config-custom/tailwind.config.js b/packages/tailwind-config-custom/tailwind.config.js index 3465b819671..5d767e84fc8 100644 --- a/packages/tailwind-config-custom/tailwind.config.js +++ b/packages/tailwind-config-custom/tailwind.config.js @@ -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: { diff --git a/packages/ui/package.json b/packages/ui/package.json index 756a0f2f1be..91a010a1eac 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -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": { diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index b90b6993a77..218d375fa97 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -10,3 +10,4 @@ export * from "./spinners"; export * from "./tooltip"; export * from "./loader"; export * from "./control-link"; +export * from "./toast"; diff --git a/packages/ui/src/spinners/circular-bar-spinner.tsx b/packages/ui/src/spinners/circular-bar-spinner.tsx new file mode 100644 index 00000000000..3be8af43aad --- /dev/null +++ b/packages/ui/src/spinners/circular-bar-spinner.tsx @@ -0,0 +1,35 @@ +import * as React from "react"; + +interface ICircularBarSpinner extends React.SVGAttributes { + height?: string; + width?: string; + className?: string | undefined; +} + +export const CircularBarSpinner: React.FC = ({ + height = "16px", + width = "16px", + className = "", +}) => ( +
+ + + + + + + + + + + + +
+); diff --git a/packages/ui/src/spinners/index.ts b/packages/ui/src/spinners/index.ts index 76856817261..a871a9b77b8 100644 --- a/packages/ui/src/spinners/index.ts +++ b/packages/ui/src/spinners/index.ts @@ -1 +1,2 @@ export * from "./circular-spinner"; +export * from "./circular-bar-spinner"; diff --git a/packages/ui/src/toast/index.tsx b/packages/ui/src/toast/index.tsx new file mode 100644 index 00000000000..7553262756a --- /dev/null +++ b/packages/ui/src/toast/index.tsx @@ -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; + title: string; + message?: string; + }; + +type PromiseToastCallback = (data: ToastData) => string; + +type PromiseToastData = { + title: string; + message?: PromiseToastCallback; +}; + +type PromiseToastOptions = { + loading?: string; + success: PromiseToastData; + error: PromiseToastData; +}; + +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 ; +}; + +export const setToast = (props: SetToastProps) => { + const renderToastContent = ({ + toastId, + icon, + textColorClassName, + backgroundColorClassName, + borderColorClassName, + }: ToastContentProps) => + props.type === TOAST_TYPE.LOADING ? ( +
{ + e.stopPropagation(); + e.preventDefault(); + }} + className={cn("w-[350px] h-[67.3px] rounded-lg border shadow-sm p-2", backgroundColorClassName, borderColorClassName)} + > +
+ {icon &&
{icon}
} +
+
{props.title ?? "Loading..."}
+
+ toast.dismiss(toastId)} + /> +
+
+
+
+ ) : ( +
{ + e.stopPropagation(); + e.preventDefault(); + }} + className={cn( + "relative flex flex-col w-[350px] rounded-lg border shadow-sm p-2", + backgroundColorClassName, + borderColorClassName + )} + > + toast.dismiss(toastId)} + /> +
+ {icon &&
{icon}
} +
+
{props.title}
+ {props.message &&
{props.message}
} +
+
+
+ ); + + switch (props.type) { + case TOAST_TYPE.SUCCESS: + return toast.custom( + (toastId) => + renderToastContent({ + toastId, + icon: , + 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: , + 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: , + 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: , + textColorClassName: "text-toast-text-loading", + backgroundColorClassName: "bg-toast-background-loading", + borderColorClassName: "border-toast-border-loading", + }) + ); + } +}; + +export const setPromiseToast = ( + promise: Promise, + options: PromiseToastOptions +): 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), + }); + }); +}; diff --git a/web/components/account/deactivate-account-modal.tsx b/web/components/account/deactivate-account-modal.tsx index 701db6ad945..41d1fd7ca4e 100644 --- a/web/components/account/deactivate-account-modal.tsx +++ b/web/components/account/deactivate-account-modal.tsx @@ -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) => { const router = useRouter(); - const { setToastAlert } = useToast(); const { setTheme } = useTheme(); const handleClose = () => { @@ -39,8 +36,8 @@ export const DeactivateAccountModal: React.FC = (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) => { 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) => {
-
diff --git a/web/components/account/o-auth/o-auth-options.tsx b/web/components/account/o-auth/o-auth-options.tsx index 7c8468acb78..bbb73b855d0 100644 --- a/web/components/account/o-auth/o-auth-options.tsx +++ b/web/components/account/o-auth/o-auth-options.tsx @@ -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 = 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 = 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 = 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.", }); } diff --git a/web/components/account/sign-in-forms/email.tsx b/web/components/account/sign-in-forms/email.tsx index 67ef720fe63..881c75f8338 100644 --- a/web/components/account/sign-in-forms/email.tsx +++ b/web/components/account/sign-in-forms/email.tsx @@ -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 = 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 = 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.", }) diff --git a/web/components/account/sign-in-forms/optional-set-password.tsx b/web/components/account/sign-in-forms/optional-set-password.tsx index 1ea5ca79217..8fc7935cdec 100644 --- a/web/components/account/sign-in-forms/optional-set-password.tsx +++ b/web/components/account/sign-in-forms/optional-set-password.tsx @@ -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) => { 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) => { 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) => { state: "FAILED", first_time: false, }); - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: err?.error ?? "Something went wrong. Please try again.", }); diff --git a/web/components/account/sign-in-forms/password.tsx b/web/components/account/sign-in-forms/password.tsx index 98719df63a1..7d51b0cf557 100644 --- a/web/components/account/sign-in-forms/password.tsx +++ b/web/components/account/sign-in-forms/password.tsx @@ -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 = 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 = 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 = 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.", }) diff --git a/web/components/account/sign-in-forms/unique-code.tsx b/web/components/account/sign-in-forms/unique-code.tsx index 55dbe86e246..25ee4c462b2 100644 --- a/web/components/account/sign-in-forms/unique-code.tsx +++ b/web/components/account/sign-in-forms/unique-code.tsx @@ -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) => { 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) => { 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) => { .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) => { }); }) .catch((err) => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: err?.error ?? "Something went wrong. Please try again.", }) diff --git a/web/components/account/sign-up-forms/email.tsx b/web/components/account/sign-up-forms/email.tsx index 0d5861b4ee2..b65ca95bf59 100644 --- a/web/components/account/sign-up-forms/email.tsx +++ b/web/components/account/sign-up-forms/email.tsx @@ -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 = 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 = 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.", }) diff --git a/web/components/account/sign-up-forms/optional-set-password.tsx b/web/components/account/sign-up-forms/optional-set-password.tsx index b49adabbb5c..651f2815ffd 100644 --- a/web/components/account/sign-up-forms/optional-set-password.tsx +++ b/web/components/account/sign-up-forms/optional-set-password.tsx @@ -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) => { 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) => { 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) => { state: "FAILED", first_time: true, }); - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: err?.error ?? "Something went wrong. Please try again.", }); diff --git a/web/components/account/sign-up-forms/password.tsx b/web/components/account/sign-up-forms/password.tsx index 293e03ef874..5207a50243b 100644 --- a/web/components/account/sign-up-forms/password.tsx +++ b/web/components/account/sign-up-forms/password.tsx @@ -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 = 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 = 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.", }) diff --git a/web/components/account/sign-up-forms/unique-code.tsx b/web/components/account/sign-up-forms/unique-code.tsx index 1b54ef9ebc1..51705ea6782 100644 --- a/web/components/account/sign-up-forms/unique-code.tsx +++ b/web/components/account/sign-up-forms/unique-code.tsx @@ -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) => { 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) => { 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) => { .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) => { }); }) .catch((err) => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: err?.error ?? "Something went wrong. Please try again.", }) diff --git a/web/components/analytics/custom-analytics/sidebar/sidebar.tsx b/web/components/analytics/custom-analytics/sidebar/sidebar.tsx index 3ad2805f28f..aab7f874f28 100644 --- a/web/components/analytics/custom-analytics/sidebar/sidebar.tsx +++ b/web/components/analytics/custom-analytics/sidebar/sidebar.tsx @@ -1,4 +1,4 @@ -import { useEffect, } from "react"; +import { useEffect } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { mutate } from "swr"; @@ -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 = 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 = 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 = 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.", }) diff --git a/web/components/api-token/delete-token-modal.tsx b/web/components/api-token/delete-token-modal.tsx index 993289c10c7..472431df3e4 100644 --- a/web/components/api-token/delete-token-modal.tsx +++ b/web/components/api-token/delete-token-modal.tsx @@ -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) => { const { isOpen, onClose, tokenId } = props; // states const [deleteLoading, setDeleteLoading] = useState(false); - // hooks - const { setToastAlert } = useToast(); // router const router = useRouter(); const { workspaceSlug } = router.query; @@ -44,8 +40,8 @@ export const DeleteApiTokenModal: FC = (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) => { handleClose(); }) .catch((err) => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error", message: err?.message ?? "Something went wrong. Please try again.", }) diff --git a/web/components/api-token/modal/create-token-modal.tsx b/web/components/api-token/modal/create-token-modal.tsx index b3fc3df78ec..c90e743bc6c 100644 --- a/web/components/api-token/modal/create-token-modal.tsx +++ b/web/components/api-token/modal/create-token-modal.tsx @@ -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) => { // 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) => { ); }) .catch((err) => { - setToastAlert({ - message: err.message, - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error", + message: err.message, }); throw err; diff --git a/web/components/api-token/modal/form.tsx b/web/components/api-token/modal/form.tsx index 77753e64d20..9fc16081593 100644 --- a/web/components/api-token/modal/form.tsx +++ b/web/components/api-token/modal/form.tsx @@ -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) => { const { handleClose, neverExpires, toggleNeverExpires, onSubmit } = props; // states const [customDate, setCustomDate] = useState(null); - // toast alert - const { setToastAlert } = useToast(); // form const { control, @@ -80,8 +76,8 @@ export const CreateApiTokenForm: React.FC = (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.", }); diff --git a/web/components/api-token/modal/generated-token-details.tsx b/web/components/api-token/modal/generated-token-details.tsx index f28ea348126..fcae6b2496d 100644 --- a/web/components/api-token/modal/generated-token-details.tsx +++ b/web/components/api-token/modal/generated-token-details.tsx @@ -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) => { 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.", }) diff --git a/web/components/command-palette/actions/issue-actions/actions-list.tsx b/web/components/command-palette/actions/issue-actions/actions-list.tsx index 55f72c85d11..75bacb0c33d 100644 --- a/web/components/command-palette/actions/issue-actions/actions-list.tsx +++ b/web/components/command-palette/actions/issue-actions/actions-list.tsx @@ -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 = observer((props) => { } = useApplication(); const { currentUser } = useUser(); - const { setToastAlert } = useToast(); - const handleUpdateIssue = async (formData: Partial) => { if (!workspaceSlug || !projectId || !issueDetails) return; @@ -71,14 +67,14 @@ export const CommandPaletteIssueActions: React.FC = 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", }); }); diff --git a/web/components/command-palette/actions/theme-actions.tsx b/web/components/command-palette/actions/theme-actions.tsx index 976a63c871d..187bdfec899 100644 --- a/web/components/command-palette/actions/theme-actions.tsx +++ b/web/components/command-palette/actions/theme-actions.tsx @@ -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 = 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", }); }); }; diff --git a/web/components/command-palette/command-palette.tsx b/web/components/command-palette/command-palette.tsx index 396003589a4..e878489f40e 100644 --- a/web/components/command-palette/command-palette.tsx +++ b/web/components/command-palette/command-palette.tsx @@ -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) => { diff --git a/web/components/core/modals/bulk-delete-issues-modal.tsx b/web/components/core/modals/bulk-delete-issues-modal.tsx index 39be2872b59..7eeb791745f 100644 --- a/web/components/core/modals/bulk-delete-issues-modal.tsx +++ b/web/components/core/modals/bulk-delete-issues-modal.tsx @@ -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 = observer((props) => { : null ); - const { setToastAlert } = useToast(); - const { handleSubmit, watch, @@ -79,8 +75,8 @@ export const BulkDeleteIssuesModal: React.FC = 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 = 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.", }) diff --git a/web/components/core/modals/existing-issues-list-modal.tsx b/web/components/core/modals/existing-issues-list-modal.tsx index c4fa25c6d6c..4dc9745acc7 100644 --- a/web/components/core/modals/existing-issues-list-modal.tsx +++ b/web/components/core/modals/existing-issues-list-modal.tsx @@ -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) => { const debouncedSearchTerm: string = useDebounce(searchTerm, 500); - const { setToastAlert } = useToast(); - const handleClose = () => { onClose(); setSearchTerm(""); @@ -54,8 +50,8 @@ export const ExistingIssuesListModal: React.FC = (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) => { handleClose(); - setToastAlert({ + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success", - type: "success", message: `Issue${selectedIssues.length > 1 ? "s" : ""} added successfully`, }); }; diff --git a/web/components/core/modals/gpt-assistant-popover.tsx b/web/components/core/modals/gpt-assistant-popover.tsx index 590015e122c..49c2f4326ff 100644 --- a/web/components/core/modals/gpt-assistant-popover.tsx +++ b/web/components/core/modals/gpt-assistant-popover.tsx @@ -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) => { // 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) => { ? 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) => { }; const handleInvalidTask = () => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Please enter some task to get AI assistance.", }); diff --git a/web/components/core/modals/user-image-upload-modal.tsx b/web/components/core/modals/user-image-upload-modal.tsx index 6debc2c15e2..3d0fbb9ee11 100644 --- a/web/components/core/modals/user-image-upload-modal.tsx +++ b/web/components/core/modals/user-image-upload-modal.tsx @@ -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 = observer((props) => { // states const [image, setImage] = useState(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 = 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.", }) diff --git a/web/components/core/modals/workspace-image-upload-modal.tsx b/web/components/core/modals/workspace-image-upload-modal.tsx index e04ccf8209d..eec62b91909 100644 --- a/web/components/core/modals/workspace-image-upload-modal.tsx +++ b/web/components/core/modals/workspace-image-upload-modal.tsx @@ -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 = 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 = 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.", }) diff --git a/web/components/core/sidebar/links-list.tsx b/web/components/core/sidebar/links-list.tsx index 48a5e16b721..6b987e30833 100644 --- a/web/components/core/sidebar/links-list.tsx +++ b/web/components/core/sidebar/links-list.tsx @@ -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 = 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", }); }; diff --git a/web/components/cycles/active-cycle-details.tsx b/web/components/cycles/active-cycle-details.tsx index 1fae0412f5b..a3e5f0fbd13 100644 --- a/web/components/cycles/active-cycle-details.tsx +++ b/web/components/cycles/active-cycle-details.tsx @@ -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 = 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 = 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 = 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.", + }, }); }; diff --git a/web/components/cycles/cycles-board-card.tsx b/web/components/cycles/cycles-board-card.tsx index 7d6b1e00036..72af8409df3 100644 --- a/web/components/cycles/cycles-board-card.tsx +++ b/web/components/cycles/cycles-board-card.tsx @@ -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 = 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 = 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 = 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) => { 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) => { diff --git a/web/components/cycles/cycles-list-item.tsx b/web/components/cycles/cycles-list-item.tsx index 31958cd847f..9ab2e3de8ed 100644 --- a/web/components/cycles/cycles-list-item.tsx +++ b/web/components/cycles/cycles-list-item.tsx @@ -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 = observer((props) => { } = useUser(); const { getCycleById, addCycleToFavorites, removeCycleFromFavorites } = useCycle(); const { getUserDetails } = useMember(); - // toast alert - const { setToastAlert } = useToast(); const handleCopyText = (e: MouseEvent) => { e.preventDefault(); @@ -54,8 +61,8 @@ export const CyclesListItem: FC = 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 = 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) => { 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) => { diff --git a/web/components/cycles/delete-modal.tsx b/web/components/cycles/delete-modal.tsx index 5dc0306ab45..239fe6a663b 100644 --- a/web/components/cycles/delete-modal.tsx +++ b/web/components/cycles/delete-modal.tsx @@ -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 = 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 = 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 = observer((props) => { handleClose(); } catch (error) { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Warning!", message: "Something went wrong please try again later.", }); diff --git a/web/components/cycles/modal.tsx b/web/components/cycles/modal.tsx index b22afb2b44d..1d60f1dc4fd 100644 --- a/web/components/cycles/modal.tsx +++ b/web/components/cycles/modal.tsx @@ -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 = (props) => { const { captureCycleEvent } = useEventTracker(); const { workspaceProjectIds } = useProject(); const { createCycle, updateCycleDetails } = useCycle(); - // toast alert - const { setToastAlert } = useToast(); const { setValue: setCycleTab } = useLocalStorage("cycle_tab", "active"); @@ -43,8 +42,8 @@ export const CycleCreateUpdateModal: React.FC = (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 = (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 = (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 = (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 = (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.", }); diff --git a/web/components/cycles/sidebar.tsx b/web/components/cycles/sidebar.tsx index 646736bd2b6..f01c840f1fc 100644 --- a/web/components/cycles/sidebar.tsx +++ b/web/components/cycles/sidebar.tsx @@ -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 = 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 = 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 = 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.", diff --git a/web/components/cycles/transfer-issues-modal.tsx b/web/components/cycles/transfer-issues-modal.tsx index adff1954578..be3b26a7b72 100644 --- a/web/components/cycles/transfer-issues-modal.tsx +++ b/web/components/cycles/transfer-issues-modal.tsx @@ -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 = 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.", }); diff --git a/web/components/estimates/create-update-estimate-modal.tsx b/web/components/estimates/create-update-estimate-modal.tsx index 0a607e88db1..1ca39c84abf 100644 --- a/web/components/estimates/create-update-estimate-modal.tsx +++ b/web/components/estimates/create-update-estimate-modal.tsx @@ -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 = 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 = 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 = 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 = 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 = 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 = 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 = observer((props) => { formData.value6, ]) ) { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Estimate points cannot have duplicate values.", }); diff --git a/web/components/estimates/delete-estimate-modal.tsx b/web/components/estimates/delete-estimate-modal.tsx index 8055ddb90d0..ac51d231273 100644 --- a/web/components/estimates/delete-estimate-modal.tsx +++ b/web/components/estimates/delete-estimate-modal.tsx @@ -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 = 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 = 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", }); diff --git a/web/components/estimates/estimate-list-item.tsx b/web/components/estimates/estimate-list-item.tsx index b6effa71183..37932a0accf 100644 --- a/web/components/estimates/estimate-list-item.tsx +++ b/web/components/estimates/estimate-list-item.tsx @@ -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 = 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 = 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.", }); diff --git a/web/components/estimates/estimates-list.tsx b/web/components/estimates/estimates-list.tsx index 1dabc618148..711f713a670 100644 --- a/web/components/estimates/estimates-list.tsx +++ b/web/components/estimates/estimates-list.tsx @@ -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", }); diff --git a/web/components/exporter/export-modal.tsx b/web/components/exporter/export-modal.tsx index b1f5297754c..f38550b3a28 100644 --- a/web/components/exporter/export-modal.tsx +++ b/web/components/exporter/export-modal.tsx @@ -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 = 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 = 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 = observer((props) => { }) .catch(() => { setExportLoading(false); - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Export was unsuccessful. Please try again.", }); diff --git a/web/components/inbox/inbox-issue-actions.tsx b/web/components/inbox/inbox-issue-actions.tsx index 8a3bb42614d..bc90dfb2269 100644 --- a/web/components/inbox/inbox-issue-actions.tsx +++ b/web/components/inbox/inbox-issue-actions.tsx @@ -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 = observer((p currentUser, membership: { currentProjectRole }, } = useUser(); - const { setToastAlert } = useToast(); // states const [date, setDate] = useState(new Date()); @@ -74,8 +72,8 @@ export const InboxIssueActionsHeader: FC = 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 = 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 = observer((p inboxIssueId, updateInboxIssueStatus, removeInboxIssue, - setToastAlert, captureIssueEvent, router, ] diff --git a/web/components/inbox/modals/create-issue-modal.tsx b/web/components/inbox/modals/create-issue-modal.tsx index 84c4bef1ea8..2ef65c49762 100644 --- a/web/components/inbox/modals/create-issue-modal.tsx +++ b/web/components/inbox/modals/create-issue-modal.tsx @@ -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 = observer((props) => { const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false); // refs const editorRef = useRef(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 = 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 = 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 = 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.", }); diff --git a/web/components/inbox/modals/select-duplicate.tsx b/web/components/inbox/modals/select-duplicate.tsx index e4acca626e2..c9e0a1dadb2 100644 --- a/web/components/inbox/modals/select-duplicate.tsx +++ b/web/components/inbox/modals/select-duplicate.tsx @@ -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) => { const [query, setQuery] = useState(""); const [selectedItem, setSelectedItem] = useState(""); - const { setToastAlert } = useToast(); - const router = useRouter(); const { workspaceSlug, projectId, issueId } = router.query; @@ -62,9 +57,9 @@ export const SelectDuplicateInboxIssueModal: React.FC = (props) => { const handleSubmit = () => { if (!selectedItem || selectedItem.length === 0) - return setToastAlert({ + return setToast({ title: "Error", - type: "error", + type: TOAST_TYPE.ERROR, }); onSubmit(selectedItem); handleClose(); diff --git a/web/components/instance/ai-form.tsx b/web/components/instance/ai-form.tsx index 50ea90096bc..250feb511c6 100644 --- a/web/components/instance/ai-form.tsx +++ b/web/components/instance/ai-form.tsx @@ -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 = (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 = (props) => { await instanceStore .updateInstanceConfigurations(payload) .then(() => - setToastAlert({ + setToast({ title: "Success", - type: "success", + type: TOAST_TYPE.SUCCESS, message: "AI Settings updated successfully", }) ) diff --git a/web/components/instance/email-form.tsx b/web/components/instance/email-form.tsx index 9a97f32883d..9e9e4a86550 100644 --- a/web/components/instance/email-form.tsx +++ b/web/components/instance/email-form.tsx @@ -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 = (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 = (props) => { await instanceStore .updateInstanceConfigurations(payload) .then(() => - setToastAlert({ + setToast({ title: "Success", - type: "success", + type: TOAST_TYPE.SUCCESS, message: "Email Settings updated successfully", }) ) diff --git a/web/components/instance/general-form.tsx b/web/components/instance/general-form.tsx index 7fa06265f9d..6fedc88313e 100644 --- a/web/components/instance/general-form.tsx +++ b/web/components/instance/general-form.tsx @@ -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 = (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 = (props) => { await instanceStore .updateInstanceInfo(payload) .then(() => - setToastAlert({ + setToast({ title: "Success", - type: "success", + type: TOAST_TYPE.SUCCESS, message: "Settings updated successfully", }) ) diff --git a/web/components/instance/github-config-form.tsx b/web/components/instance/github-config-form.tsx index 75639f82bd3..20fb08aff58 100644 --- a/web/components/instance/github-config-form.tsx +++ b/web/components/instance/github-config-form.tsx @@ -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 = (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 = (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 = (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", }); }} diff --git a/web/components/instance/google-config-form.tsx b/web/components/instance/google-config-form.tsx index cd7c3ab7d60..27d4f43007b 100644 --- a/web/components/instance/google-config-form.tsx +++ b/web/components/instance/google-config-form.tsx @@ -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 = (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 = (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 = (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", }); }} diff --git a/web/components/instance/image-config-form.tsx b/web/components/instance/image-config-form.tsx index 93ce8871937..26694c4ecdb 100644 --- a/web/components/instance/image-config-form.tsx +++ b/web/components/instance/image-config-form.tsx @@ -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 = (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 = (props) => await instanceStore .updateInstanceConfigurations(payload) .then(() => - setToastAlert({ + setToast({ title: "Success", - type: "success", + type: TOAST_TYPE.SUCCESS, message: "Image Configuration Settings updated successfully", }) ) diff --git a/web/components/instance/setup-form/sign-in-form.tsx b/web/components/instance/setup-form/sign-in-form.tsx index c4a9de6a396..174c66e825f 100644 --- a/web/components/instance/setup-form/sign-in-form.tsx +++ b/web/components/instance/setup-form/sign-in-form.tsx @@ -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 = (props) => { password: "", }, }); - // hooks - const { setToastAlert } = useToast(); const handleFormSubmit = async (formValues: InstanceSetupEmailFormValues) => { const payload = { @@ -56,8 +52,8 @@ export const InstanceSetupSignInForm: FC = (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.", }); diff --git a/web/components/instance/sidebar-dropdown.tsx b/web/components/instance/sidebar-dropdown.tsx index 2bac426d62a..e763663d2eb 100644 --- a/web/components/instance/sidebar-dropdown.tsx +++ b/web/components/instance/sidebar-dropdown.tsx @@ -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.", }) diff --git a/web/components/integration/delete-import-modal.tsx b/web/components/integration/delete-import-modal.tsx index bc1351125e0..ee9fadaa0a6 100644 --- a/web/components/integration/delete-import-modal.tsx +++ b/web/components/integration/delete-import-modal.tsx @@ -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 = ({ 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 = ({ 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.", }) diff --git a/web/components/integration/github/root.tsx b/web/components/integration/github/root.tsx index bc577328aa6..f26f651fe71 100644 --- a/web/components/integration/github/root.tsx +++ b/web/components/integration/github/root.tsx @@ -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({ 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.", }) diff --git a/web/components/integration/single-integration-card.tsx b/web/components/integration/single-integration-card.tsx index 3026d698114..b36082b67c4 100644 --- a/web/components/integration/single-integration-card.tsx +++ b/web/components/integration/single-integration-card.tsx @@ -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 = observer(({ integration }) const { membership: { currentWorkspaceRole }, } = useUser(); - // toast alert - const { setToastAlert } = useToast(); const isUserAdmin = currentWorkspaceRole === 20; @@ -87,8 +84,8 @@ export const SingleIntegrationCard: React.FC = 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 = 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.`, }); diff --git a/web/components/issues/archive-issue-modal.tsx b/web/components/issues/archive-issue-modal.tsx index 94c4c801a43..03207795761 100644 --- a/web/components/issues/archive-issue-modal.tsx +++ b/web/components/issues/archive-issue-modal.tsx @@ -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) => { // 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) => { await onSubmit() .then(() => onClose()) .catch(() => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Issue could not be archived. Please try again.", }) diff --git a/web/components/issues/attachment/root.tsx b/web/components/issues/attachment/root.tsx index ffa17d3371e..fa3d0c22040 100644 --- a/web/components/issues/attachment/root.tsx +++ b/web/components/issues/attachment/root.tsx @@ -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 = (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 = (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 = (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 ( diff --git a/web/components/issues/delete-issue-modal.tsx b/web/components/issues/delete-issue-modal.tsx index 3a9c0653edf..b8e6b3f8592 100644 --- a/web/components/issues/delete-issue-modal.tsx +++ b/web/components/issues/delete-issue-modal.tsx @@ -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) => { const [isDeleting, setIsDeleting] = useState(false); - const { setToastAlert } = useToast(); // hooks const { getProjectById } = useProject(); @@ -50,9 +47,9 @@ export const DeleteIssueModal: React.FC = (props) => { onClose(); }) .catch(() => { - setToastAlert({ + setToast({ title: "Error", - type: "error", + type: TOAST_TYPE.ERROR, message: "Failed to delete issue", }); }) diff --git a/web/components/issues/description-form.tsx b/web/components/issues/description-form.tsx index c64c147ea90..1ee22a6cb1a 100644 --- a/web/components/issues/description-form.tsx +++ b/web/components/issues/description-form.tsx @@ -71,16 +71,10 @@ export const IssueDescriptionForm: FC = observer((props) => { async (formData: Partial) => { 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 ?? "

", - }, - false - ); + await issueOperations.update(workspaceSlug, projectId, issueId, { + name: formData.name ?? "", + description_html: formData.description_html ?? "

", + }); }, [workspaceSlug, projectId, issueId, issueOperations] ); diff --git a/web/components/issues/description-input.tsx b/web/components/issues/description-input.tsx index 79634fa84aa..65e82df5fc3 100644 --- a/web/components/issues/description-input.tsx +++ b/web/components/issues/description-input.tsx @@ -41,11 +41,9 @@ export const IssueDescriptionInput: FC = (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 diff --git a/web/components/issues/issue-detail/cycle-select.tsx b/web/components/issues/issue-detail/cycle-select.tsx index 84dccefac48..4da762a9dbc 100644 --- a/web/components/issues/issue-detail/cycle-select.tsx +++ b/web/components/issues/issue-detail/cycle-select.tsx @@ -56,7 +56,6 @@ export const IssueCycleSelect: React.FC = observer((props) => dropdownArrow dropdownArrowClassName="h-3.5 w-3.5 hidden group-hover:inline" /> - {isUpdating && }
); }); diff --git a/web/components/issues/issue-detail/inbox/root.tsx b/web/components/issues/issue-detail/inbox/root.tsx index d96b36efae3..9b0e961c0bb 100644 --- a/web/components/issues/issue-detail/inbox/root.tsx +++ b/web/components/issues/issue-detail/inbox/root.tsx @@ -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 = (props) => { fetchComments, } = useIssueDetail(); const { captureIssueEvent } = useEventTracker(); - const { setToastAlert } = useToast(); const { membership: { currentProjectRole }, } = useUser(); @@ -53,17 +53,9 @@ export const InboxIssueDetailRoot: FC = (props) => { projectId: string, issueId: string, data: Partial, - 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 = (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 = (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 = (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( diff --git a/web/components/issues/issue-detail/issue-activity/root.tsx b/web/components/issues/issue-detail/issue-activity/root.tsx index 695b248de20..673e6c2afc2 100644 --- a/web/components/issues/issue-detail/issue-activity/root.tsx +++ b/web/components/issues/issue-detail/issue-activity/root.tsx @@ -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 = observer((props) => { const { workspaceSlug, projectId, issueId } = props; // hooks const { createComment, updateComment, removeComment } = useIssueDetail(); - const { setToastAlert } = useToast(); const { getProjectById } = useProject(); // state const [activityTab, setActivityTab] = useState("all"); @@ -56,15 +56,15 @@ export const IssueActivity: FC = 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 = 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 = 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); diff --git a/web/components/issues/issue-detail/label/create-label.tsx b/web/components/issues/issue-detail/label/create-label.tsx index 72bc034f872..8a6eea17ed0 100644 --- a/web/components/issues/issue-detail/label/create-label.tsx +++ b/web/components/issues/issue-detail/label/create-label.tsx @@ -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 = { export const LabelCreate: FC = (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 = (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.", }); } diff --git a/web/components/issues/issue-detail/label/root.tsx b/web/components/issues/issue-detail/label/root.tsx index 94f9b451f28..59ce1f54ca5 100644 --- a/web/components/issues/issue-detail/label/root.tsx +++ b/web/components/issues/issue-detail/label/root.tsx @@ -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 = observer((props) => { // hooks const { updateIssue } = useIssueDetail(); const { createLabel } = useLabel(); - const { setToastAlert } = useToast(); const labelOperations: TLabelOperations = useMemo( () => ({ @@ -35,16 +35,10 @@ export const IssueLabel: FC = 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 = 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 ( diff --git a/web/components/issues/issue-detail/links/link-detail.tsx b/web/components/issues/issue-detail/links/link-detail.tsx index 6c37f86f993..f1b003b99b0 100644 --- a/web/components/issues/issue-detail/links/link-detail.tsx +++ b/web/components/issues/issue-detail/links/link-detail.tsx @@ -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 = (props) => { link: { getLinkById }, } = useIssueDetail(); const { getUserDetails } = useMember(); - const { setToastAlert } = useToast(); // state const [isIssueLinkModalOpen, setIsIssueLinkModalOpen] = useState(false); @@ -55,8 +53,8 @@ export const IssueLinkDetail: FC = (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", }); diff --git a/web/components/issues/issue-detail/links/root.tsx b/web/components/issues/issue-detail/links/root.tsx index 94124085a49..672a9e35ebf 100644 --- a/web/components/issues/issue-detail/links/root.tsx +++ b/web/components/issues/issue-detail/links/root.tsx @@ -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 = (props) => { [toggleIssueLinkModalStore] ); - const { setToastAlert } = useToast(); - const handleLinkOperations: TLinkOperations = useMemo( () => ({ create: async (data: Partial) => { 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 = (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 = (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 ( diff --git a/web/components/issues/issue-detail/module-select.tsx b/web/components/issues/issue-detail/module-select.tsx index 41f4a06d609..f0fe06a2e3f 100644 --- a/web/components/issues/issue-detail/module-select.tsx +++ b/web/components/issues/issue-detail/module-select.tsx @@ -75,7 +75,6 @@ export const IssueModuleSelect: React.FC = observer((props) showTooltip multiple /> - {isUpdating && }
); }); diff --git a/web/components/issues/issue-detail/reactions/issue-comment.tsx b/web/components/issues/issue-detail/reactions/issue-comment.tsx index 30a8621e4f4..2268540bfcc 100644 --- a/web/components/issues/issue-detail/reactions/issue-comment.tsx +++ b/web/components/issues/issue-detail/reactions/issue-comment.tsx @@ -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 = 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 = 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 = 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 = observer((props) currentUser, createCommentReaction, removeCommentReaction, - setToastAlert, userReactions, ] ); diff --git a/web/components/issues/issue-detail/reactions/issue.tsx b/web/components/issues/issue-detail/reactions/issue.tsx index d6b33e36bb8..a9bc264f395 100644 --- a/web/components/issues/issue-detail/reactions/issue.tsx +++ b/web/components/issues/issue-detail/reactions/issue.tsx @@ -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 = 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 = 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 = 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 = observer((props) => { else await issueReactionOperations.create(reaction); }, }), - [workspaceSlug, projectId, issueId, currentUser, createReaction, removeReaction, setToastAlert, userReactions] + [workspaceSlug, projectId, issueId, currentUser, createReaction, removeReaction, userReactions] ); return ( diff --git a/web/components/issues/issue-detail/relation-select.tsx b/web/components/issues/issue-detail/relation-select.tsx index 67bba869742..26037740640 100644 --- a/web/components/issues/issue-detail/relation-select.tsx +++ b/web/components/issues/issue-detail/relation-select.tsx @@ -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 = 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.", }); diff --git a/web/components/issues/issue-detail/root.tsx b/web/components/issues/issue-detail/root.tsx index 0e343d9a87c..be0fbb74def 100644 --- a/web/components/issues/issue-detail/root.tsx +++ b/web/components/issues/issue-detail/root.tsx @@ -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; - update: ( - workspaceSlug: string, - projectId: string, - issueId: string, - data: Partial, - showToast?: boolean - ) => Promise; + update: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; remove: (workspaceSlug: string, projectId: string, issueId: string) => Promise; archive?: (workspaceSlug: string, projectId: string, issueId: string) => Promise; restore?: (workspaceSlug: string, projectId: string, issueId: string) => Promise; @@ -76,7 +71,6 @@ export const IssueDetailRoot: FC = 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 = observer((props) => { console.error("Error fetching the parent issue"); } }, - update: async ( - workspaceSlug: string, - projectId: string, - issueId: string, - data: Partial, - showToast: boolean = true - ) => { + update: async (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => { 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = observer((props) => { addModulesToIssue, removeIssueFromModule, removeModulesFromIssue, - setToastAlert, ] ); diff --git a/web/components/issues/issue-detail/sidebar.tsx b/web/components/issues/issue-detail/sidebar.tsx index a65cb7f1647..33dc4cdf50c 100644 --- a/web/components/issues/issue-detail/sidebar.tsx +++ b/web/components/issues/issue-detail/sidebar.tsx @@ -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 = 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 = 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.", }); diff --git a/web/components/issues/issue-detail/subscription.tsx b/web/components/issues/issue-detail/subscription.tsx index 7321ef27f55..f4025a2f375 100644 --- a/web/components/issues/issue-detail/subscription.tsx +++ b/web/components/issues/issue-detail/subscription.tsx @@ -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 = observer((props) => { createSubscription, removeSubscription, } = useIssueDetail(); - const { setToastAlert } = useToast(); // state const [loading, setLoading] = useState(false); @@ -33,16 +31,16 @@ export const IssueSubscription: FC = 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.", }); diff --git a/web/components/issues/issue-layouts/calendar/base-calendar-root.tsx b/web/components/issues/issue-layouts/calendar/base-calendar-root.tsx index 43f62e5bee4..fb3373a0620 100644 --- a/web/components/issues/issue-layouts/calendar/base-calendar-root.tsx +++ b/web/components/issues/issue-layouts/calendar/base-calendar-root.tsx @@ -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", }); }); diff --git a/web/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx b/web/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx index 6db9323facc..c70b05c7093 100644 --- a/web/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx +++ b/web/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx @@ -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 = observer((props) => { const ref = useRef(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 = 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 = 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(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, + }); + }); } }; diff --git a/web/components/issues/issue-layouts/empty-states/cycle.tsx b/web/components/issues/issue-layouts/empty-states/cycle.tsx index 4b767617362..b23b1998e1e 100644 --- a/web/components/issues/issue-layouts/empty-states/cycle.tsx +++ b/web/components/issues/issue-layouts/empty-states/cycle.tsx @@ -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 = 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.", }); diff --git a/web/components/issues/issue-layouts/empty-states/module.tsx b/web/components/issues/issue-layouts/empty-states/module.tsx index ef7ec729c07..7a5c6f57f2f 100644 --- a/web/components/issues/issue-layouts/empty-states/module.tsx +++ b/web/components/issues/issue-layouts/empty-states/module.tsx @@ -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 = 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 = 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.", }) diff --git a/web/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx b/web/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx index 7ed6a87306f..10b73ab355a 100644 --- a/web/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx +++ b/web/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx @@ -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 = 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 = 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(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 ( diff --git a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx index 7bdaf282d22..3951e7032dd 100644 --- a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx +++ b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx @@ -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 = 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 = 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", }); }); diff --git a/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx b/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx index f49af292217..d2f1febd71f 100644 --- a/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx @@ -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 = 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 = 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.", }); diff --git a/web/components/issues/issue-layouts/kanban/quick-add-issue-form.tsx b/web/components/issues/issue-layouts/kanban/quick-add-issue-form.tsx index 51316343182..20f0cd8e0d7 100644 --- a/web/components/issues/issue-layouts/kanban/quick-add-issue-form.tsx +++ b/web/components/issues/issue-layouts/kanban/quick-add-issue-form.tsx @@ -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 = obser useKeypress("Escape", handleClose); useOutsideClickDetector(ref, handleClose); - const { setToastAlert } = useToast(); const { reset, @@ -97,39 +97,42 @@ export const KanBanQuickAddIssueForm: React.FC = 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(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, + }); + }); } }; diff --git a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx index 8d9164b3767..404107af4a0 100644 --- a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx +++ b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx @@ -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.", }); diff --git a/web/components/issues/issue-layouts/list/quick-add-issue-form.tsx b/web/components/issues/issue-layouts/list/quick-add-issue-form.tsx index 8d1ce6d9c54..3c71293b424 100644 --- a/web/components/issues/issue-layouts/list/quick-add-issue-form.tsx +++ b/web/components/issues/issue-layouts/list/quick-add-issue-form.tsx @@ -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 = observer((props useKeypress("Escape", handleClose); useOutsideClickDetector(ref, handleClose); - const { setToastAlert } = useToast(); const { reset, @@ -101,31 +101,35 @@ export const ListQuickAddIssueForm: FC = 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(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, + }); + }); } }; diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx index 1d0472454b8..01dc991d4b6 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx @@ -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 = 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 = 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", }) diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx index 9bad920c891..c07518f2eba 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx @@ -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 = (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", }) diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx index 3c0ed980007..ade72b133a9 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx @@ -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 = 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 = observer((pro const handleCopyIssueLink = () => copyUrlToClipboard(issueLink).then(() => - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Link copied", message: "Issue link copied to clipboard", }) diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx index 5532fa2a0dd..47f79e6f184 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx @@ -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 = 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 = observer((pr const handleCopyIssueLink = () => copyUrlToClipboard(issueLink).then(() => - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Link copied", message: "Issue link copied to clipboard", }) diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx index b3292900919..aaa37774753 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx @@ -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 = 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", }) diff --git a/web/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx b/web/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx index 3cba3c6cdb8..4b0d7aed413 100644 --- a/web/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx @@ -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 = observer((props) => // hooks useKeypress("Escape", handleClose); useOutsideClickDetector(ref, handleClose); - const { setToastAlert } = useToast(); useEffect(() => { setFocus("name"); @@ -100,13 +100,13 @@ export const SpreadsheetQuickAddIssueForm: React.FC = 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 = 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 = 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 = 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(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); + }); } }; diff --git a/web/components/issues/issue-modal/draft-issue-layout.tsx b/web/components/issues/issue-modal/draft-issue-layout.tsx index 1f9935c989e..b4dae211d7d 100644 --- a/web/components/issues/issue-modal/draft-issue-layout.tsx +++ b/web/components/issues/issue-modal/draft-issue-layout.tsx @@ -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 = 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 = 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 = observer((props) => { onClose(false); }) .catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Issue could not be created. Please try again.", }); diff --git a/web/components/issues/issue-modal/form.tsx b/web/components/issues/issue-modal/form.tsx index e2e4e784e8f..7fcb6cffa62 100644 --- a/web/components/issues/issue-modal/form.tsx +++ b/web/components/issues/issue-modal/form.tsx @@ -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 = 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 = 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 = 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.", }); diff --git a/web/components/issues/issue-modal/modal.tsx b/web/components/issues/issue-modal/modal.tsx index 1da02f0acd1..3b97f9c4e3e 100644 --- a/web/components/issues/issue-modal/modal.tsx +++ b/web/components/issues/issue-modal/modal.tsx @@ -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 = observer((prop }; // router const router = useRouter(); - // toast alert - const { setToastAlert } = useToast(); // local storage const { storedValue: localStorageDraftIssues, setValue: setLocalStorageDraftIssue } = useLocalStorage< Record> @@ -186,9 +185,8 @@ export const CreateUpdateIssueModal: React.FC = 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 = 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 = 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 = observer((prop }); handleClose(); } catch (error) { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Issue could not be created. Please try again.", }); diff --git a/web/components/issues/peek-overview/header.tsx b/web/components/issues/peek-overview/header.tsx index 8db7fd0ace2..8d8ec00df3b 100644 --- a/web/components/issues/peek-overview/header.tsx +++ b/web/components/issues/peek-overview/header.tsx @@ -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 = 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 = 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.", }); diff --git a/web/components/issues/peek-overview/root.tsx b/web/components/issues/peek-overview/root.tsx index 466bffee7a0..b28cc5de60f 100644 --- a/web/components/issues/peek-overview/root.tsx +++ b/web/components/issues/peek-overview/root.tsx @@ -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; - update: ( - workspaceSlug: string, - projectId: string, - issueId: string, - data: Partial, - showToast?: boolean - ) => Promise; + update: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; remove: (workspaceSlug: string, projectId: string, issueId: string) => Promise; archive: (workspaceSlug: string, projectId: string, issueId: string) => Promise; restore: (workspaceSlug: string, projectId: string, issueId: string) => Promise; @@ -49,8 +44,6 @@ export type TIssuePeekOperations = { export const IssuePeekOverview: FC = 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 = observer((props) => { console.error("Error fetching the parent issue"); } }, - update: async ( - workspaceSlug: string, - projectId: string, - issueId: string, - data: Partial, - 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) => { + 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = observer((props) => { addModulesToIssue, removeIssueFromModule, removeModulesFromIssue, - setToastAlert, captureIssueEvent, router.asPath, ] diff --git a/web/components/issues/peek-overview/view.tsx b/web/components/issues/peek-overview/view.tsx index cb2100ce03d..f94901c454f 100644 --- a/web/components/issues/peek-overview/view.tsx +++ b/web/components/issues/peek-overview/view.tsx @@ -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 = 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(); } }); diff --git a/web/components/issues/sub-issues/root.tsx b/web/components/issues/sub-issues/root.tsx index 5e406116c45..da49200dd07 100644 --- a/web/components/issues/sub-issues/root.tsx +++ b/web/components/issues/sub-issues/root.tsx @@ -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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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); diff --git a/web/components/issues/title-input.tsx b/web/components/issues/title-input.tsx index 4a4057a6ac6..2db4eb4b5ff 100644 --- a/web/components/issues/title-input.tsx +++ b/web/components/issues/title-input.tsx @@ -32,7 +32,7 @@ export const IssueTitleInput: FC = 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(); diff --git a/web/components/labels/create-label-modal.tsx b/web/components/labels/create-label-modal.tsx index e53e9114744..b6a3f63e87d 100644 --- a/web/components/labels/create-label-modal.tsx +++ b/web/components/labels/create-label-modal.tsx @@ -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 = observer((props) => { reset(defaultValues); }; - const { setToastAlert } = useToast(); - const onSubmit = async (formData: IIssueLabel) => { if (!workspaceSlug) return; @@ -75,9 +72,9 @@ export const CreateLabelModal: React.FC = 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); diff --git a/web/components/labels/create-update-label-inline.tsx b/web/components/labels/create-update-label-inline.tsx index 2d2be046dcd..d30d48a6ac9 100644 --- a/web/components/labels/create-update-label-inline.tsx +++ b/web/components/labels/create-update-label-inline.tsx @@ -6,9 +6,8 @@ import { Controller, SubmitHandler, useForm } from "react-hook-form"; import { Popover, Transition } from "@headlessui/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 { IIssueLabel } from "@plane/types"; // fetch-keys @@ -35,8 +34,6 @@ export const CreateUpdateLabelInline = observer( const { workspaceSlug, projectId } = router.query; // store hooks const { createLabel, updateLabel } = useLabel(); - // toast alert - const { setToastAlert } = useToast(); // form info const { handleSubmit, @@ -65,9 +62,9 @@ export const CreateUpdateLabelInline = observer( reset(defaultValues); }) .catch((error) => { - setToastAlert({ + setToast({ title: "Oops!", - type: "error", + type: TOAST_TYPE.ERROR, message: error?.error ?? "Error while adding the label", }); reset(formData); @@ -83,9 +80,9 @@ export const CreateUpdateLabelInline = observer( handleClose(); }) .catch((error) => { - setToastAlert({ + setToast({ title: "Oops!", - type: "error", + type: TOAST_TYPE.ERROR, message: error?.error ?? "Error while updating the label", }); reset(formData); diff --git a/web/components/labels/delete-label-modal.tsx b/web/components/labels/delete-label-modal.tsx index 64d15eb65cb..83b3e807d88 100644 --- a/web/components/labels/delete-label-modal.tsx +++ b/web/components/labels/delete-label-modal.tsx @@ -6,10 +6,8 @@ import { observer } from "mobx-react-lite"; import { useLabel } from "hooks/store"; // icons import { AlertTriangle } from "lucide-react"; -// hooks -import useToast from "hooks/use-toast"; // ui -import { Button } from "@plane/ui"; +import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // types import type { IIssueLabel } from "@plane/types"; @@ -28,8 +26,6 @@ export const DeleteLabelModal: React.FC = observer((props) => { const { deleteLabel } = useLabel(); // states const [isDeleteLoading, setIsDeleteLoading] = useState(false); - // hooks - const { setToastAlert } = useToast(); const handleClose = () => { onClose(); @@ -49,8 +45,8 @@ export const DeleteLabelModal: React.FC = observer((props) => { setIsDeleteLoading(false); const error = err?.error || "Label could not be deleted. Please try again."; - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: error, }); diff --git a/web/components/modules/delete-module-modal.tsx b/web/components/modules/delete-module-modal.tsx index bf2e529b74c..de9571179d8 100644 --- a/web/components/modules/delete-module-modal.tsx +++ b/web/components/modules/delete-module-modal.tsx @@ -4,9 +4,8 @@ import { Dialog, Transition } from "@headlessui/react"; import { observer } from "mobx-react-lite"; // hooks import { useEventTracker, useModule } from "hooks/store"; -import useToast from "hooks/use-toast"; // ui -import { Button } from "@plane/ui"; +import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // icons import { AlertTriangle } from "lucide-react"; // types @@ -30,8 +29,6 @@ export const DeleteModuleModal: React.FC = observer((props) => { // store hooks const { captureModuleEvent } = useEventTracker(); const { deleteModule } = useModule(); - // toast alert - const { setToastAlert } = useToast(); const handleClose = () => { onClose(); @@ -47,8 +44,8 @@ export const DeleteModuleModal: React.FC = observer((props) => { .then(() => { if (moduleId || peekModule) router.push(`/${workspaceSlug}/projects/${data.project_id}/modules`); handleClose(); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Module deleted successfully.", }); @@ -58,8 +55,8 @@ export const DeleteModuleModal: React.FC = observer((props) => { }); }) .catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Module could not be deleted. Please try again.", }); diff --git a/web/components/modules/modal.tsx b/web/components/modules/modal.tsx index 47f3313964b..00781affeea 100644 --- a/web/components/modules/modal.tsx +++ b/web/components/modules/modal.tsx @@ -4,7 +4,8 @@ import { useForm } from "react-hook-form"; import { Dialog, Transition } from "@headlessui/react"; // hooks import { useEventTracker, useModule, useProject } from "hooks/store"; -import useToast from "hooks/use-toast"; +// ui +import { TOAST_TYPE, setToast } from "@plane/ui"; // components import { ModuleForm } from "components/modules"; // types @@ -36,8 +37,6 @@ export const CreateUpdateModuleModal: React.FC = observer((props) => { const { captureModuleEvent } = useEventTracker(); const { workspaceProjectIds } = useProject(); const { createModule, updateModuleDetails } = useModule(); - // toast alert - const { setToastAlert } = useToast(); const handleClose = () => { reset(defaultValues); @@ -55,8 +54,8 @@ export const CreateUpdateModuleModal: React.FC = observer((props) => { await createModule(workspaceSlug.toString(), selectedProjectId, payload) .then((res) => { handleClose(); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Module created successfully.", }); @@ -66,8 +65,8 @@ export const CreateUpdateModuleModal: React.FC = observer((props) => { }); }) .catch((err) => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: err.detail ?? "Module could not be created. Please try again.", }); @@ -86,8 +85,8 @@ export const CreateUpdateModuleModal: React.FC = observer((props) => { .then((res) => { handleClose(); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Module updated successfully.", }); @@ -97,8 +96,8 @@ export const CreateUpdateModuleModal: React.FC = observer((props) => { }); }) .catch((err) => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: err.detail ?? "Module could not be updated. Please try again.", }); diff --git a/web/components/modules/module-card-item.tsx b/web/components/modules/module-card-item.tsx index 4dec3df6eed..52cc6097be3 100644 --- a/web/components/modules/module-card-item.tsx +++ b/web/components/modules/module-card-item.tsx @@ -5,11 +5,10 @@ import { observer } from "mobx-react-lite"; import { Info, LinkIcon, Pencil, Star, Trash2 } from "lucide-react"; // hooks import { useEventTracker, useMember, useModule, useUser } from "hooks/store"; -import useToast from "hooks/use-toast"; // components import { CreateUpdateModuleModal, DeleteModuleModal } from "components/modules"; // ui -import { Avatar, AvatarGroup, CustomMenu, LayersIcon, Tooltip } from "@plane/ui"; +import { Avatar, AvatarGroup, CustomMenu, LayersIcon, Tooltip, TOAST_TYPE, setToast, setPromiseToast } from "@plane/ui"; // helpers import { copyUrlToClipboard } from "helpers/string.helper"; import { renderFormattedDate } from "helpers/date-time.helper"; @@ -30,8 +29,6 @@ export const ModuleCardItem: React.FC = observer((props) => { // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; - // toast alert - const { setToastAlert } = useToast(); // store hooks const { membership: { currentProjectRole }, @@ -48,21 +45,27 @@ export const ModuleCardItem: React.FC = observer((props) => { e.preventDefault(); if (!workspaceSlug || !projectId) return; - addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), moduleId) - .then(() => { + const addToFavoritePromise = addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), moduleId).then( + () => { captureEvent(MODULE_FAVORITED, { module_id: moduleId, element: "Grid layout", state: "SUCCESS", }); - }) - .catch(() => { - setToastAlert({ - type: "error", - title: "Error!", - message: "Couldn't add the module to favorites. Please try again.", - }); - }); + } + ); + + setPromiseToast(addToFavoritePromise, { + loading: "Adding module to favorites...", + success: { + title: "Success!", + message: () => "Module added to favorites.", + }, + error: { + title: "Error!", + message: () => "Couldn't add the module to favorites. Please try again.", + }, + }); }; const handleRemoveFromFavorites = (e: React.MouseEvent) => { @@ -70,29 +73,37 @@ export const ModuleCardItem: React.FC = observer((props) => { e.preventDefault(); if (!workspaceSlug || !projectId) return; - removeModuleFromFavorites(workspaceSlug.toString(), projectId.toString(), moduleId) - .then(() => { - captureEvent(MODULE_UNFAVORITED, { - module_id: moduleId, - element: "Grid layout", - state: "SUCCESS", - }); - }) - .catch(() => { - setToastAlert({ - type: "error", - title: "Error!", - message: "Couldn't remove the module from favorites. Please try again.", - }); + const removeFromFavoritePromise = removeModuleFromFavorites( + workspaceSlug.toString(), + projectId.toString(), + moduleId + ).then(() => { + captureEvent(MODULE_UNFAVORITED, { + module_id: moduleId, + element: "Grid layout", + state: "SUCCESS", }); + }); + + setPromiseToast(removeFromFavoritePromise, { + loading: "Removing module from favorites...", + success: { + title: "Success!", + message: () => "Module removed from favorites.", + }, + error: { + title: "Error!", + message: () => "Couldn't remove the module from favorites. Please try again.", + }, + }); }; const handleCopyText = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/modules/${moduleId}`).then(() => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Link Copied!", message: "Module link copied to clipboard.", }); diff --git a/web/components/modules/module-list-item.tsx b/web/components/modules/module-list-item.tsx index 3d7468f24a0..72ed16adf1e 100644 --- a/web/components/modules/module-list-item.tsx +++ b/web/components/modules/module-list-item.tsx @@ -5,11 +5,19 @@ import { observer } from "mobx-react-lite"; import { Check, Info, LinkIcon, Pencil, Star, Trash2, User2 } from "lucide-react"; // hooks import { useModule, useUser, useEventTracker, useMember } from "hooks/store"; -import useToast from "hooks/use-toast"; // components import { CreateUpdateModuleModal, DeleteModuleModal } from "components/modules"; // ui -import { Avatar, AvatarGroup, CircularProgressIndicator, CustomMenu, Tooltip } from "@plane/ui"; +import { + Avatar, + AvatarGroup, + CircularProgressIndicator, + CustomMenu, + Tooltip, + TOAST_TYPE, + setToast, + setPromiseToast, +} from "@plane/ui"; // helpers import { copyUrlToClipboard } from "helpers/string.helper"; import { renderFormattedDate } from "helpers/date-time.helper"; @@ -30,8 +38,6 @@ export const ModuleListItem: React.FC = observer((props) => { // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; - // toast alert - const { setToastAlert } = useToast(); // store hooks const { membership: { currentProjectRole }, @@ -48,21 +54,27 @@ export const ModuleListItem: React.FC = observer((props) => { e.preventDefault(); if (!workspaceSlug || !projectId) return; - addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), moduleId) - .then(() => { + const addToFavoritePromise = addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), moduleId).then( + () => { captureEvent(MODULE_FAVORITED, { module_id: moduleId, element: "Grid layout", state: "SUCCESS", }); - }) - .catch(() => { - setToastAlert({ - type: "error", - title: "Error!", - message: "Couldn't add the module to favorites. Please try again.", - }); - }); + } + ); + + setPromiseToast(addToFavoritePromise, { + loading: "Adding module to favorites...", + success: { + title: "Success!", + message: () => "Module added to favorites.", + }, + error: { + title: "Error!", + message: () => "Couldn't add the module to favorites. Please try again.", + }, + }); }; const handleRemoveFromFavorites = (e: React.MouseEvent) => { @@ -70,29 +82,37 @@ export const ModuleListItem: React.FC = observer((props) => { e.preventDefault(); if (!workspaceSlug || !projectId) return; - removeModuleFromFavorites(workspaceSlug.toString(), projectId.toString(), moduleId) - .then(() => { - captureEvent(MODULE_UNFAVORITED, { - module_id: moduleId, - element: "Grid layout", - state: "SUCCESS", - }); - }) - .catch(() => { - setToastAlert({ - type: "error", - title: "Error!", - message: "Couldn't remove the module from favorites. Please try again.", - }); + const removeFromFavoritePromise = removeModuleFromFavorites( + workspaceSlug.toString(), + projectId.toString(), + moduleId + ).then(() => { + captureEvent(MODULE_UNFAVORITED, { + module_id: moduleId, + element: "Grid layout", + state: "SUCCESS", }); + }); + + setPromiseToast(removeFromFavoritePromise, { + loading: "Removing module from favorites...", + success: { + title: "Success!", + message: () => "Module removed from favorites.", + }, + error: { + title: "Error!", + message: () => "Couldn't remove the module from favorites. Please try again.", + }, + }); }; const handleCopyText = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/modules/${moduleId}`).then(() => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Link Copied!", message: "Module link copied to clipboard.", }); diff --git a/web/components/modules/sidebar.tsx b/web/components/modules/sidebar.tsx index c8c55321c8c..ad3da373cd5 100644 --- a/web/components/modules/sidebar.tsx +++ b/web/components/modules/sidebar.tsx @@ -16,14 +16,22 @@ import { } from "lucide-react"; // hooks import { useModule, useUser, useEventTracker } from "hooks/store"; -import useToast from "hooks/use-toast"; // components import { LinkModal, LinksList, SidebarProgressStats } from "components/core"; import { DeleteModuleModal } from "components/modules"; import ProgressChart from "components/core/sidebar/progress-chart"; import { DateRangeDropdown, MemberDropdown } from "components/dropdowns"; // ui -import { CustomMenu, Loader, LayersIcon, CustomSelect, ModuleStatusIcon, UserGroupIcon } from "@plane/ui"; +import { + CustomMenu, + Loader, + LayersIcon, + CustomSelect, + ModuleStatusIcon, + UserGroupIcon, + TOAST_TYPE, + setToast, +} from "@plane/ui"; // helpers import { renderFormattedPayloadDate } from "helpers/date-time.helper"; import { copyUrlToClipboard } from "helpers/string.helper"; @@ -65,8 +73,6 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => { const { setTrackElement, captureModuleEvent, captureEvent } = useEventTracker(); const moduleDetails = getModuleById(moduleId); - const { setToastAlert } = useToast(); - const { reset, control } = useForm({ defaultValues, }); @@ -99,15 +105,15 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => { module_id: moduleId, state: "SUCCESS", }); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Module link created", message: "Module link created successfully.", }); }) .catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Some error occurred", }); @@ -125,15 +131,15 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => { module_id: moduleId, state: "SUCCESS", }); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Module link updated", message: "Module link updated successfully.", }); }) .catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Some error occurred", }); @@ -149,15 +155,15 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => { module_id: moduleId, state: "SUCCESS", }); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Module link deleted", message: "Module link deleted successfully.", }); }) .catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Some error occurred", }); @@ -167,15 +173,15 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => { const handleCopyText = () => { copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/modules/${moduleId}`) .then(() => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Link copied", message: "Module link copied to clipboard", }); }) .catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Some error occurred", }); @@ -187,8 +193,8 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => { start_date: startDate ? renderFormattedPayloadDate(startDate) : null, target_date: targetDate ? renderFormattedPayloadDate(targetDate) : null, }); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Module updated successfully.", }); diff --git a/web/components/notifications/notification-card.tsx b/web/components/notifications/notification-card.tsx index 03d849a8219..bd26dcfa588 100644 --- a/web/components/notifications/notification-card.tsx +++ b/web/components/notifications/notification-card.tsx @@ -5,10 +5,9 @@ import Link from "next/link"; import { Menu } from "@headlessui/react"; import { ArchiveRestore, Clock, MessageSquare, MoreVertical, User2 } from "lucide-react"; // hooks -import useToast from "hooks/use-toast"; import { useEventTracker } from "hooks/store"; // icons -import { ArchiveIcon, CustomMenu, Tooltip } from "@plane/ui"; +import { ArchiveIcon, CustomMenu, Tooltip, TOAST_TYPE, setToast } from "@plane/ui"; // constants import { snoozeOptions } from "constants/notification"; // helper @@ -50,8 +49,6 @@ export const NotificationCard: React.FC = (props) => { const { workspaceSlug } = router.query; // states const [showSnoozeOptions, setShowSnoozeOptions] = React.useState(false); - // toast alert - const { setToastAlert } = useToast(); // refs const snoozeRef = useRef(null); @@ -62,9 +59,9 @@ export const NotificationCard: React.FC = (props) => { icon: , onClick: () => { markNotificationReadStatusToggle(notification.id).then(() => { - setToastAlert({ + setToast({ title: notification.read_at ? "Notification marked as read" : "Notification marked as unread", - type: "success", + type: TOAST_TYPE.SUCCESS, }); }); }, @@ -79,9 +76,9 @@ export const NotificationCard: React.FC = (props) => { ), onClick: () => { markNotificationArchivedStatus(notification.id).then(() => { - setToastAlert({ + setToast({ title: notification.archived_at ? "Notification un-archived" : "Notification archived", - type: "success", + type: TOAST_TYPE.SUCCESS, }); }); }, @@ -94,9 +91,9 @@ export const NotificationCard: React.FC = (props) => { return; } markSnoozeNotification(notification.id, date).then(() => { - setToastAlert({ + setToast({ title: `Notification snoozed till ${renderFormattedDate(date)}`, - type: "success", + type: TOAST_TYPE.SUCCESS, }); }); }; @@ -330,9 +327,9 @@ export const NotificationCard: React.FC = (props) => { tab: selectedTab, state: "SUCCESS", }); - setToastAlert({ + setToast({ title: notification.read_at ? "Notification marked as read" : "Notification marked as unread", - type: "success", + type: TOAST_TYPE.SUCCESS, }); }); }, @@ -352,9 +349,9 @@ export const NotificationCard: React.FC = (props) => { tab: selectedTab, state: "SUCCESS", }); - setToastAlert({ + setToast({ title: notification.archived_at ? "Notification un-archived" : "Notification archived", - type: "success", + type: TOAST_TYPE.SUCCESS, }); }); }, @@ -403,9 +400,9 @@ export const NotificationCard: React.FC = (props) => { tab: selectedTab, state: "SUCCESS", }); - setToastAlert({ + setToast({ title: `Notification snoozed till ${renderFormattedDate(item.value)}`, - type: "success", + type: TOAST_TYPE.SUCCESS, }); }); }} diff --git a/web/components/notifications/select-snooze-till-modal.tsx b/web/components/notifications/select-snooze-till-modal.tsx index f89b3a96345..c2875b8dd4c 100644 --- a/web/components/notifications/select-snooze-till-modal.tsx +++ b/web/components/notifications/select-snooze-till-modal.tsx @@ -6,10 +6,8 @@ import { Transition, Dialog } from "@headlessui/react"; import { X } from "lucide-react"; // constants import { allTimeIn30MinutesInterval12HoursFormat } from "constants/notification"; -// hooks -import useToast from "hooks/use-toast"; // ui -import { Button, CustomSelect } from "@plane/ui"; +import { Button, CustomSelect, TOAST_TYPE, setToast } from "@plane/ui"; // types import type { IUserNotification } from "@plane/types"; @@ -41,8 +39,6 @@ export const SnoozeNotificationModal: FC = (props) => { const router = useRouter(); const { workspaceSlug } = router.query; - const { setToastAlert } = useToast(); - const { formState: { isSubmitting }, reset, @@ -100,10 +96,10 @@ export const SnoozeNotificationModal: FC = (props) => { await handleSubmitSnooze(notification.id, dateTime).then(() => { handleClose(); onSuccess(); - setToastAlert({ + setToast({ title: "Notification snoozed", message: "Notification snoozed successfully", - type: "success", + type: TOAST_TYPE.SUCCESS, }); }); }; diff --git a/web/components/onboarding/invite-members.tsx b/web/components/onboarding/invite-members.tsx index 561a428d626..1f78fcf204c 100644 --- a/web/components/onboarding/invite-members.tsx +++ b/web/components/onboarding/invite-members.tsx @@ -17,10 +17,9 @@ import { Check, ChevronDown, Plus, XCircle } from "lucide-react"; // services import { WorkspaceService } from "services/workspace.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"; // components import { OnboardingStepIndicator } from "components/onboarding/step-indicator"; // hooks @@ -269,7 +268,6 @@ export const InviteMembers: React.FC = (props) => { const [isInvitationDisabled, setIsInvitationDisabled] = useState(true); - const { setToastAlert } = useToast(); const { resolvedTheme } = useTheme(); // store hooks const { captureEvent } = useEventTracker(); @@ -322,8 +320,8 @@ export const InviteMembers: React.FC = (props) => { state: "SUCCESS", element: "Onboarding", }); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Invitations sent successfully.", }); @@ -336,8 +334,8 @@ export const InviteMembers: React.FC = (props) => { state: "FAILED", element: "Onboarding", }); - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: err?.error, }); diff --git a/web/components/onboarding/switch-delete-account-modal.tsx b/web/components/onboarding/switch-delete-account-modal.tsx index 66b98fb2355..ff37e5802b4 100644 --- a/web/components/onboarding/switch-delete-account-modal.tsx +++ b/web/components/onboarding/switch-delete-account-modal.tsx @@ -6,7 +6,8 @@ import { Dialog, Transition } from "@headlessui/react"; import { Trash2 } from "lucide-react"; // hooks import { useUser } from "hooks/store"; -import useToast from "hooks/use-toast"; +// ui +import { TOAST_TYPE, setToast } from "@plane/ui"; type Props = { isOpen: boolean; @@ -25,8 +26,6 @@ export const SwitchOrDeleteAccountModal: React.FC = (props) => { const { resolvedTheme, setTheme } = useTheme(); - const { setToastAlert } = useToast(); - const handleClose = () => { setSwitchingAccount(false); setIsDeactivating(false); @@ -44,8 +43,8 @@ export const SwitchOrDeleteAccountModal: React.FC = (props) => { handleClose(); }) .catch(() => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Failed to sign out. Please try again.", }) @@ -58,8 +57,8 @@ export const SwitchOrDeleteAccountModal: React.FC = (props) => { await deactivateAccount() .then(() => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Account deleted successfully.", }); @@ -69,8 +68,8 @@ export const SwitchOrDeleteAccountModal: React.FC = (props) => { handleClose(); }) .catch((err) => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: err?.error, }) diff --git a/web/components/onboarding/workspace.tsx b/web/components/onboarding/workspace.tsx index ad9342e3aef..5ba5ca81c5e 100644 --- a/web/components/onboarding/workspace.tsx +++ b/web/components/onboarding/workspace.tsx @@ -1,12 +1,11 @@ import { useState } from "react"; import { Control, Controller, FieldErrors, UseFormHandleSubmit, UseFormSetValue } from "react-hook-form"; // ui -import { Button, Input } from "@plane/ui"; +import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui"; // types import { IUser, IWorkspace, TOnboardingSteps } from "@plane/types"; // hooks import { useEventTracker, useUser, useWorkspace } from "hooks/store"; -import useToast from "hooks/use-toast"; // services import { WorkspaceService } from "services/workspace.service"; // constants @@ -35,8 +34,6 @@ export const Workspace: React.FC = (props) => { const { updateCurrentUser } = useUser(); const { createWorkspace, fetchWorkspaces, workspaces } = useWorkspace(); const { captureWorkspaceEvent } = useEventTracker(); - // toast alert - const { setToastAlert } = useToast(); const handleCreateWorkspace = async (formData: IWorkspace) => { if (isSubmitting) return; @@ -49,8 +46,8 @@ export const Workspace: React.FC = (props) => { await createWorkspace(formData) .then(async (res) => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Workspace created successfully.", }); @@ -75,8 +72,8 @@ export const Workspace: React.FC = (props) => { element: "Onboarding", }, }); - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Workspace could not be created. Please try again.", }); @@ -84,8 +81,8 @@ export const Workspace: React.FC = (props) => { } else setSlugError(true); }) .catch(() => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Some error occurred while creating workspace. Please try again.", }) diff --git a/web/components/pages/delete-page-modal.tsx b/web/components/pages/delete-page-modal.tsx index bba19b31c95..67cd175f004 100644 --- a/web/components/pages/delete-page-modal.tsx +++ b/web/components/pages/delete-page-modal.tsx @@ -5,9 +5,8 @@ import { Dialog, Transition } from "@headlessui/react"; import { AlertTriangle } from "lucide-react"; // hooks import { useEventTracker, usePage } from "hooks/store"; -import useToast from "hooks/use-toast"; // ui -import { Button } from "@plane/ui"; +import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // types import { useProjectPages } from "hooks/store/use-project-page"; // constants @@ -32,9 +31,6 @@ export const DeletePageModal: React.FC = observer((pr const { capturePageEvent } = useEventTracker(); const pageStore = usePage(pageId); - // toast alert - const { setToastAlert } = useToast(); - if (!pageStore) return null; const { name } = pageStore; @@ -60,8 +56,8 @@ export const DeletePageModal: React.FC = observer((pr }, }); handleClose(); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Page deleted successfully.", }); @@ -74,8 +70,8 @@ export const DeletePageModal: React.FC = observer((pr state: "FAILED", }, }); - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Page could not be deleted. Please try again.", }); diff --git a/web/components/profile/preferences/email-notification-form.tsx b/web/components/profile/preferences/email-notification-form.tsx index e041b28d8f3..fd158e2d5a4 100644 --- a/web/components/profile/preferences/email-notification-form.tsx +++ b/web/components/profile/preferences/email-notification-form.tsx @@ -1,9 +1,7 @@ import React, { FC } from "react"; import { Controller, useForm } from "react-hook-form"; // ui -import { Button, Checkbox } from "@plane/ui"; -// hooks -import useToast from "hooks/use-toast"; +import { Button, Checkbox, TOAST_TYPE, setToast } from "@plane/ui"; // services import { UserService } from "services/user.service"; // types @@ -18,8 +16,6 @@ const userService = new UserService(); export const EmailNotificationForm: FC = (props) => { const { data } = props; - // toast - const { setToastAlert } = useToast(); // form data const { handleSubmit, @@ -45,9 +41,9 @@ export const EmailNotificationForm: FC = (props) => await userService .updateCurrentUserEmailNotificationSettings(payload) .then(() => - setToastAlert({ + setToast({ title: "Success", - type: "success", + type: TOAST_TYPE.SUCCESS, message: "Email Notification Settings updated successfully", }) ) diff --git a/web/components/project/card.tsx b/web/components/project/card.tsx index 3d6f62b7b8d..9f554cfea8f 100644 --- a/web/components/project/card.tsx +++ b/web/components/project/card.tsx @@ -5,11 +5,10 @@ import { LinkIcon, Lock, Pencil, Star } from "lucide-react"; import Link from "next/link"; // hooks import { useProject } from "hooks/store"; -import useToast from "hooks/use-toast"; // components import { DeleteProjectModal, JoinProjectModal } from "components/project"; // ui -import { Avatar, AvatarGroup, Button, Tooltip } from "@plane/ui"; +import { Avatar, AvatarGroup, Button, Tooltip, TOAST_TYPE, setToast, setPromiseToast } from "@plane/ui"; // helpers import { copyTextToClipboard } from "helpers/string.helper"; import { renderEmoji } from "helpers/emoji.helper"; @@ -27,8 +26,6 @@ export const ProjectCard: React.FC = observer((props) => { // router const router = useRouter(); const { workspaceSlug } = router.query; - // toast alert - const { setToastAlert } = useToast(); // states const [deleteProjectModalOpen, setDeleteProjectModal] = useState(false); const [joinProjectModalOpen, setJoinProjectModal] = useState(false); @@ -42,24 +39,34 @@ export const ProjectCard: React.FC = observer((props) => { const handleAddToFavorites = () => { if (!workspaceSlug) return; - addProjectToFavorites(workspaceSlug.toString(), project.id).catch(() => { - setToastAlert({ - type: "error", + const addToFavoritePromise = addProjectToFavorites(workspaceSlug.toString(), project.id); + setPromiseToast(addToFavoritePromise, { + loading: "Adding project to favorites...", + success: { + title: "Success!", + message: () => "Project added to favorites.", + }, + error: { title: "Error!", - message: "Couldn't remove the project from favorites. Please try again.", - }); + message: () => "Couldn't add the project to favorites. Please try again.", + }, }); }; const handleRemoveFromFavorites = () => { if (!workspaceSlug || !project) return; - removeProjectFromFavorites(workspaceSlug.toString(), project.id).catch(() => { - setToastAlert({ - type: "error", + const removeFromFavoritePromise = removeProjectFromFavorites(workspaceSlug.toString(), project.id); + setPromiseToast(removeFromFavoritePromise, { + loading: "Removing project from favorites...", + success: { + title: "Success!", + message: () => "Project removed from favorites.", + }, + error: { title: "Error!", - message: "Couldn't remove the project from favorites. Please try again.", - }); + message: () => "Couldn't remove the project from favorites. Please try again.", + }, }); }; @@ -67,8 +74,8 @@ export const ProjectCard: React.FC = observer((props) => { const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${project.id}/issues`).then(() => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Link Copied!", message: "Project link copied to clipboard.", }); diff --git a/web/components/project/create-project-modal.tsx b/web/components/project/create-project-modal.tsx index 49a42a0a3a3..f7bbd92cf24 100644 --- a/web/components/project/create-project-modal.tsx +++ b/web/components/project/create-project-modal.tsx @@ -5,9 +5,8 @@ import { observer } from "mobx-react-lite"; import { X } from "lucide-react"; // hooks import { useEventTracker, useProject, useUser } from "hooks/store"; -import useToast from "hooks/use-toast"; // ui -import { Button, CustomSelect, Input, TextArea } from "@plane/ui"; +import { Button, CustomSelect, Input, TextArea, TOAST_TYPE, setToast } from "@plane/ui"; // components import { ImagePickerPopover } from "components/core"; import EmojiIconPicker from "components/emoji-icon-picker"; @@ -32,16 +31,14 @@ interface IIsGuestCondition { } const IsGuestCondition: FC = ({ onClose }) => { - const { setToastAlert } = useToast(); - useEffect(() => { onClose(); - setToastAlert({ + setToast({ title: "Error", - type: "error", + type: TOAST_TYPE.ERROR, message: "You don't have permission to create project.", }); - }, [onClose, setToastAlert]); + }, [onClose]); return null; }; @@ -69,8 +66,6 @@ export const CreateProjectModal: FC = observer((props) => { const { addProjectToFavorites, createProject } = useProject(); // states const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true); - // toast - const { setToastAlert } = useToast(); // form info const cover_image = PROJECT_UNSPLASH_COVERS[Math.floor(Math.random() * PROJECT_UNSPLASH_COVERS.length)]; const { @@ -108,8 +103,8 @@ export const CreateProjectModal: FC = observer((props) => { if (!workspaceSlug) return; addProjectToFavorites(workspaceSlug.toString(), projectId).catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Couldn't remove the project from favorites. Please try again.", }); @@ -137,8 +132,8 @@ export const CreateProjectModal: FC = observer((props) => { eventName: PROJECT_CREATED, payload: newPayload, }); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Project created successfully.", }); @@ -149,8 +144,8 @@ export const CreateProjectModal: FC = observer((props) => { }) .catch((err) => { Object.keys(err.data).map((key) => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: err.data[key], }); diff --git a/web/components/project/delete-project-modal.tsx b/web/components/project/delete-project-modal.tsx index 791ac3672ca..844bd3aadc4 100644 --- a/web/components/project/delete-project-modal.tsx +++ b/web/components/project/delete-project-modal.tsx @@ -5,9 +5,8 @@ import { Dialog, Transition } from "@headlessui/react"; import { AlertTriangle } from "lucide-react"; // hooks import { useEventTracker, useProject, useWorkspace } 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 { IProject } from "@plane/types"; // constants @@ -33,8 +32,6 @@ export const DeleteProjectModal: React.FC = (props) => { // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; - // toast alert - const { setToastAlert } = useToast(); // form info const { control, @@ -67,8 +64,8 @@ export const DeleteProjectModal: React.FC = (props) => { eventName: PROJECT_DELETED, payload: { ...project, state: "SUCCESS", element: "Project general settings" }, }); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Project deleted successfully.", }); @@ -78,8 +75,8 @@ export const DeleteProjectModal: React.FC = (props) => { eventName: PROJECT_DELETED, payload: { ...project, state: "FAILED", element: "Project general settings" }, }); - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Something went wrong. Please try again later.", }); diff --git a/web/components/project/form.tsx b/web/components/project/form.tsx index 267103dc8ba..ef5a20024a0 100644 --- a/web/components/project/form.tsx +++ b/web/components/project/form.tsx @@ -2,11 +2,10 @@ import { FC, useEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; // hooks import { useEventTracker, useProject } from "hooks/store"; -import useToast from "hooks/use-toast"; // components import EmojiIconPicker from "components/emoji-icon-picker"; import { ImagePickerPopover } from "components/core"; -import { Button, CustomSelect, Input, TextArea } from "@plane/ui"; +import { Button, CustomSelect, Input, TextArea, TOAST_TYPE, setToast } from "@plane/ui"; // icons import { Lock } from "lucide-react"; // types @@ -33,8 +32,6 @@ export const ProjectDetailsForm: FC = (props) => { // store hooks const { captureProjectEvent } = useEventTracker(); const { updateProject } = useProject(); - // toast alert - const { setToastAlert } = useToast(); // form info const { handleSubmit, @@ -84,8 +81,8 @@ export const ProjectDetailsForm: FC = (props) => { element: "Project general settings", }, }); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Project updated successfully", }); @@ -95,8 +92,8 @@ export const ProjectDetailsForm: FC = (props) => { eventName: PROJECT_UPDATED, payload: { ...payload, state: "FAILED", element: "Project general settings" }, }); - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: error?.error ?? "Project could not be updated. Please try again.", }); diff --git a/web/components/project/integration-card.tsx b/web/components/project/integration-card.tsx index d2910b34a3d..cf256098f1e 100644 --- a/web/components/project/integration-card.tsx +++ b/web/components/project/integration-card.tsx @@ -8,12 +8,13 @@ import useSWR, { mutate } from "swr"; import { ProjectService } from "services/project"; // hooks import { useRouter } from "next/router"; -import useToast from "hooks/use-toast"; // components import { SelectRepository, SelectChannel } from "components/integration"; // icons import GithubLogo from "public/logos/github-square.png"; import SlackLogo from "public/services/slack.png"; +// ui +import { TOAST_TYPE, setToast } from "@plane/ui"; // types import { IWorkspaceIntegration } from "@plane/types"; // fetch-keys @@ -41,8 +42,6 @@ export const IntegrationCard: React.FC = ({ integration }) => { const router = useRouter(); const { workspaceSlug, projectId } = router.query; - const { setToastAlert } = useToast(); - const { data: syncedGithubRepository } = useSWR( projectId ? PROJECT_GITHUB_REPOSITORY(projectId as string) : null, () => @@ -71,16 +70,16 @@ export const IntegrationCard: React.FC = ({ integration }) => { .then(() => { mutate(PROJECT_GITHUB_REPOSITORY(projectId as string)); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: `${login}/${name} repository synced with the project successfully.`, }); }) .catch((err) => { console.error(err); - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Repository could not be synced with the project. Please try again.", }); diff --git a/web/components/project/leave-project-modal.tsx b/web/components/project/leave-project-modal.tsx index 0827568ce50..45618d4f246 100644 --- a/web/components/project/leave-project-modal.tsx +++ b/web/components/project/leave-project-modal.tsx @@ -6,9 +6,8 @@ import { AlertTriangleIcon } from "lucide-react"; import { observer } from "mobx-react-lite"; // hooks import { useEventTracker, useUser } 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 { IProject } from "@plane/types"; // constants @@ -40,8 +39,6 @@ export const LeaveProjectModal: FC = observer((props) => { const { membership: { leaveProject }, } = useUser(); - // toast - const { setToastAlert } = useToast(); const { control, @@ -71,8 +68,8 @@ export const LeaveProjectModal: FC = observer((props) => { }); }) .catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Something went wrong please try again later.", }); @@ -82,22 +79,22 @@ export const LeaveProjectModal: FC = observer((props) => { }); }); } else { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Please confirm leaving the project by typing the 'Leave Project'.", }); } } else { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Please enter the project name as shown in the description.", }); } } else { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Please fill all fields.", }); diff --git a/web/components/project/member-list-item.tsx b/web/components/project/member-list-item.tsx index 6a27eccd512..6bab775b8c0 100644 --- a/web/components/project/member-list-item.tsx +++ b/web/components/project/member-list-item.tsx @@ -4,11 +4,10 @@ import Link from "next/link"; import { observer } from "mobx-react-lite"; // hooks import { useEventTracker, useMember, useProject, useUser } from "hooks/store"; -import useToast from "hooks/use-toast"; // components import { ConfirmProjectMemberRemove } from "components/project"; // ui -import { CustomSelect, Tooltip } from "@plane/ui"; +import { CustomSelect, Tooltip, TOAST_TYPE, setToast } from "@plane/ui"; // icons import { ChevronDown, Dot, XCircle } from "lucide-react"; // constants @@ -37,8 +36,6 @@ export const ProjectMemberListItem: React.FC = observer((props) => { project: { removeMemberFromProject, getProjectMemberDetails, updateMember }, } = useMember(); const { captureEvent } = useEventTracker(); - // toast alert - const { setToastAlert } = useToast(); // derived values const isAdmin = currentProjectRole === EUserProjectRoles.ADMIN; @@ -58,8 +55,8 @@ export const ProjectMemberListItem: React.FC = observer((props) => { router.push(`/${workspaceSlug}/projects`); }) .catch((err) => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error", message: err?.error || "Something went wrong. Please try again.", }) @@ -67,8 +64,8 @@ export const ProjectMemberListItem: React.FC = observer((props) => { } else await removeMemberFromProject(workspaceSlug.toString(), projectId.toString(), userDetails.member.id).catch( (err) => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error", message: err?.error || "Something went wrong. Please try again.", }) @@ -151,8 +148,8 @@ export const ProjectMemberListItem: React.FC = 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 ?? "An error occurred while updating member role. Please try again.", }); diff --git a/web/components/project/project-settings-member-defaults.tsx b/web/components/project/project-settings-member-defaults.tsx index b4713f7395b..91c06cdfcca 100644 --- a/web/components/project/project-settings-member-defaults.tsx +++ b/web/components/project/project-settings-member-defaults.tsx @@ -4,12 +4,11 @@ import useSWR from "swr"; import { observer } from "mobx-react-lite"; // hooks import { useProject, useUser } from "hooks/store"; -import useToast from "hooks/use-toast"; import { Controller, useForm } from "react-hook-form"; import { MemberSelect } from "components/project"; // ui -import { Loader } from "@plane/ui"; +import { Loader, TOAST_TYPE, setToast } from "@plane/ui"; // types import { IProject, IUserLite, IWorkspace } from "@plane/types"; // fetch-keys @@ -33,8 +32,6 @@ export const ProjectSettingsMemberDefaults: React.FC = observer(() => { const { currentProjectDetails, fetchProjectDetails, updateProject } = useProject(); // derived values const isAdmin = currentProjectRole === EUserProjectRoles.ADMIN; - // hooks - const { setToastAlert } = useToast(); // form info const { reset, control } = useForm({ defaultValues }); // fetching user members @@ -72,9 +69,9 @@ export const ProjectSettingsMemberDefaults: React.FC = observer(() => { }) .then(() => { fetchProjectDetails(workspaceSlug.toString(), projectId.toString()); - setToastAlert({ + setToast({ title: "Success", - type: "success", + type: TOAST_TYPE.SUCCESS, message: "Project updated successfully", }); }) diff --git a/web/components/project/publish-project/modal.tsx b/web/components/project/publish-project/modal.tsx index 77be4c84b27..64cf87fb5c2 100644 --- a/web/components/project/publish-project/modal.tsx +++ b/web/components/project/publish-project/modal.tsx @@ -6,9 +6,8 @@ import { Dialog, Transition } from "@headlessui/react"; import { Check, CircleDot, Globe2 } from "lucide-react"; // hooks import { useProjectPublish } from "hooks/store"; -import useToast from "hooks/use-toast"; // ui -import { Button, Loader, ToggleSwitch } from "@plane/ui"; +import { Button, Loader, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui"; import { CustomPopover } from "./popover"; // types import { IProject } from "@plane/types"; @@ -71,8 +70,6 @@ export const PublishProjectModal: React.FC = observer((props) => { unPublishProject, fetchSettingsLoader, } = useProjectPublish(); - // toast alert - const { setToastAlert } = useToast(); // form info const { control, @@ -150,8 +147,8 @@ export const PublishProjectModal: React.FC = observer((props) => { await updateProjectSettingsAsync(workspaceSlug.toString(), project.id, payload.id ?? "", payload) .then((res) => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Publish settings updated successfully!", }); @@ -176,8 +173,8 @@ export const PublishProjectModal: React.FC = observer((props) => { return res; }) .catch(() => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Something went wrong while un-publishing the project.", }) @@ -208,8 +205,8 @@ export const PublishProjectModal: React.FC = observer((props) => { const handleFormSubmit = async (formData: FormData) => { if (!formData.views || formData.views.length === 0) { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Please select at least one view layout to publish the project.", }); diff --git a/web/components/project/send-project-invitation-modal.tsx b/web/components/project/send-project-invitation-modal.tsx index ef7913fb08c..da2f37e9f94 100644 --- a/web/components/project/send-project-invitation-modal.tsx +++ b/web/components/project/send-project-invitation-modal.tsx @@ -6,9 +6,8 @@ import { Dialog, Transition } from "@headlessui/react"; import { ChevronDown, Plus, X } from "lucide-react"; // hooks import { useEventTracker, useMember, useUser, useWorkspace } from "hooks/store"; -import useToast from "hooks/use-toast"; // ui -import { Avatar, Button, CustomSelect, CustomSearchSelect } from "@plane/ui"; +import { Avatar, Button, CustomSelect, CustomSearchSelect, TOAST_TYPE, setToast } from "@plane/ui"; // helpers import { getUserRole } from "helpers/user.helper"; // constants @@ -45,8 +44,6 @@ export const SendProjectInvitationModal: React.FC = observer((props) => { // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; - // toast alert - const { setToastAlert } = useToast(); // store hooks const { captureEvent } = useEventTracker(); const { @@ -84,9 +81,9 @@ export const SendProjectInvitationModal: React.FC = observer((props) => { .then(() => { if (onSuccess) onSuccess(); onClose(); - setToastAlert({ + setToast({ title: "Success", - type: "success", + type: TOAST_TYPE.SUCCESS, message: "Members added successfully.", }); captureEvent(PROJECT_MEMBER_ADDED, { diff --git a/web/components/project/settings/features-list.tsx b/web/components/project/settings/features-list.tsx index 22e69827efa..efbcc085765 100644 --- a/web/components/project/settings/features-list.tsx +++ b/web/components/project/settings/features-list.tsx @@ -2,10 +2,10 @@ import { FC } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { ContrastIcon, FileText, Inbox, Layers } from "lucide-react"; -import { DiceIcon, ToggleSwitch } from "@plane/ui"; // hooks import { useEventTracker, useProject, useUser, useWorkspace } from "hooks/store"; -import useToast from "hooks/use-toast"; +// ui +import { DiceIcon, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui"; // types import { IProject } from "@plane/types"; // constants @@ -58,13 +58,11 @@ export const ProjectFeaturesList: FC = observer(() => { } = useUser(); const { currentProjectDetails, updateProject } = useProject(); const isAdmin = currentProjectRole === EUserProjectRoles.ADMIN; - // toast alert - const { setToastAlert } = useToast(); const handleSubmit = async (formData: Partial) => { if (!workspaceSlug || !projectId || !currentProjectDetails) return; - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Project feature updated successfully.", }); diff --git a/web/components/project/sidebar-list-item.tsx b/web/components/project/sidebar-list-item.tsx index 695e0bce4f9..00dc858d0c5 100644 --- a/web/components/project/sidebar-list-item.tsx +++ b/web/components/project/sidebar-list-item.tsx @@ -21,13 +21,22 @@ import { // hooks import { useApplication, useEventTracker, useInbox, useProject } from "hooks/store"; import useOutsideClickDetector from "hooks/use-outside-click-detector"; -import useToast from "hooks/use-toast"; // helpers import { cn } from "helpers/common.helper"; import { getNumberCount } from "helpers/string.helper"; import { renderEmoji } from "helpers/emoji.helper"; +// ui +import { + CustomMenu, + Tooltip, + ArchiveIcon, + PhotoFilterIcon, + DiceIcon, + ContrastIcon, + LayersIcon, + setPromiseToast, +} from "@plane/ui"; // components -import { CustomMenu, Tooltip, ArchiveIcon, PhotoFilterIcon, DiceIcon, ContrastIcon, LayersIcon } from "@plane/ui"; import { LeaveProjectModal, PublishProjectModal } from "components/project"; import { EUserProjectRoles } from "constants/project"; @@ -93,8 +102,6 @@ export const ProjectSidebarListItem: React.FC = observer((props) => { // router const router = useRouter(); const { workspaceSlug, projectId: URLProjectId } = router.query; - // toast alert - const { setToastAlert } = useToast(); // derived values const project = getProjectById(projectId); @@ -112,24 +119,34 @@ export const ProjectSidebarListItem: React.FC = observer((props) => { const handleAddToFavorites = () => { if (!workspaceSlug || !project) return; - addProjectToFavorites(workspaceSlug.toString(), project.id).catch(() => { - setToastAlert({ - type: "error", + const addToFavoritePromise = addProjectToFavorites(workspaceSlug.toString(), project.id); + setPromiseToast(addToFavoritePromise, { + loading: "Adding project to favorites...", + success: { + title: "Success!", + message: () => "Project added to favorites.", + }, + error: { title: "Error!", - message: "Couldn't remove the project from favorites. Please try again.", - }); + message: () => "Couldn't add the project to favorites. Please try again.", + }, }); }; const handleRemoveFromFavorites = () => { if (!workspaceSlug || !project) return; - removeProjectFromFavorites(workspaceSlug.toString(), project.id).catch(() => { - setToastAlert({ - type: "error", + const removeFromFavoritePromise = removeProjectFromFavorites(workspaceSlug.toString(), project.id); + setPromiseToast(removeFromFavoritePromise, { + loading: "Removing project from favorites...", + success: { + title: "Success!", + message: () => "Project removed from favorites.", + }, + error: { title: "Error!", - message: "Couldn't remove the project from favorites. Please try again.", - }); + message: () => "Couldn't remove the project from favorites. Please try again.", + }, }); }; diff --git a/web/components/project/sidebar-list.tsx b/web/components/project/sidebar-list.tsx index 983e23932c7..05e09f565de 100644 --- a/web/components/project/sidebar-list.tsx +++ b/web/components/project/sidebar-list.tsx @@ -6,7 +6,8 @@ import { observer } from "mobx-react-lite"; import { ChevronDown, ChevronRight, Plus } from "lucide-react"; // hooks import { useApplication, useEventTracker, useProject, useUser } from "hooks/store"; -import useToast from "hooks/use-toast"; +// ui +import { TOAST_TYPE, setToast } from "@plane/ui"; // components import { CreateProjectModal, ProjectSidebarListItem } from "components/project"; // helpers @@ -42,15 +43,13 @@ export const ProjectSidebarList: FC = observer(() => { // router const router = useRouter(); const { workspaceSlug } = router.query; - // toast - const { setToastAlert } = useToast(); const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; const handleCopyText = (projectId: string) => { copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/issues`).then(() => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Link Copied!", message: "Project link copied to clipboard.", }); @@ -72,8 +71,8 @@ export const ProjectSidebarList: FC = observer(() => { const updatedSortOrder = orderJoinedProjects(source.index, destination.index, draggableId, joinedProjectsList); if (updatedSortOrder != undefined) updateProjectView(workspaceSlug.toString(), draggableId, { sort_order: updatedSortOrder }).catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Something went wrong. Please try again.", }); diff --git a/web/components/states/create-state-modal.tsx b/web/components/states/create-state-modal.tsx index db91bb6b058..f39e3f33585 100644 --- a/web/components/states/create-state-modal.tsx +++ b/web/components/states/create-state-modal.tsx @@ -6,9 +6,8 @@ import { Dialog, Popover, Transition } from "@headlessui/react"; import { observer } from "mobx-react-lite"; // hooks import { useProjectState } from "hooks/store"; -import useToast from "hooks/use-toast"; // ui -import { Button, CustomSelect, Input, TextArea } from "@plane/ui"; +import { Button, CustomSelect, Input, TextArea, TOAST_TYPE, setToast } from "@plane/ui"; // icons import { ChevronDown } from "lucide-react"; // types @@ -37,8 +36,6 @@ export const CreateStateModal: React.FC = observer((props) => { const { workspaceSlug } = router.query; // store hooks const { createState } = useProjectState(); - // toast alert - const { setToastAlert } = useToast(); // form info const { formState: { errors, isSubmitting }, @@ -71,15 +68,15 @@ export const CreateStateModal: React.FC = observer((props) => { if (typeof error === "object") { Object.keys(error).forEach((key) => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: Array.isArray(error[key]) ? error[key].join(", ") : error[key], }); }); } else { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: error ?? err.status === 400 diff --git a/web/components/states/create-update-state-inline.tsx b/web/components/states/create-update-state-inline.tsx index 037cd483d90..0a50208cd1f 100644 --- a/web/components/states/create-update-state-inline.tsx +++ b/web/components/states/create-update-state-inline.tsx @@ -6,9 +6,8 @@ import { Popover, Transition } from "@headlessui/react"; import { observer } from "mobx-react-lite"; // hooks import { useEventTracker, useProjectState } from "hooks/store"; -import useToast from "hooks/use-toast"; // ui -import { Button, CustomSelect, Input, Tooltip } from "@plane/ui"; +import { Button, CustomSelect, Input, Tooltip, TOAST_TYPE, setToast } from "@plane/ui"; // types import type { IState } from "@plane/types"; // constants @@ -39,8 +38,6 @@ export const CreateUpdateStateInline: React.FC = observer((props) => { // store hooks const { captureProjectStateEvent, setTrackElement } = useEventTracker(); const { createState, updateState } = useProjectState(); - // toast alert - const { setToastAlert } = useToast(); // form info const { handleSubmit, @@ -82,8 +79,8 @@ export const CreateUpdateStateInline: React.FC = observer((props) => { await createState(workspaceSlug.toString(), projectId.toString(), formData) .then((res) => { handleClose(); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "State created successfully.", }); @@ -98,14 +95,14 @@ export const CreateUpdateStateInline: React.FC = observer((props) => { }) .catch((error) => { if (error.status === 400) - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "State with that name already exists. Please try again with another name.", }); else - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "State could not be created. Please try again.", }); @@ -135,22 +132,22 @@ export const CreateUpdateStateInline: React.FC = observer((props) => { element: "Project settings states page", }, }); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "State updated successfully.", }); }) .catch((error) => { if (error.status === 400) - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Another state exists with the same name. Please try again with another name.", }); else - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "State could not be updated. Please try again.", }); diff --git a/web/components/states/delete-state-modal.tsx b/web/components/states/delete-state-modal.tsx index 12de3860872..df47c8b12ef 100644 --- a/web/components/states/delete-state-modal.tsx +++ b/web/components/states/delete-state-modal.tsx @@ -5,9 +5,8 @@ import { observer } from "mobx-react-lite"; import { AlertTriangle } from "lucide-react"; // hooks import { useEventTracker, useProjectState } from "hooks/store"; -import useToast from "hooks/use-toast"; // ui -import { Button } from "@plane/ui"; +import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // types import type { IState } from "@plane/types"; // constants @@ -29,8 +28,6 @@ export const DeleteStateModal: React.FC = observer((props) => { // store hooks const { captureProjectStateEvent } = useEventTracker(); const { deleteState } = useProjectState(); - // toast alert - const { setToastAlert } = useToast(); const handleClose = () => { onClose(); @@ -55,15 +52,15 @@ export const DeleteStateModal: React.FC = observer((props) => { }) .catch((err) => { if (err.status === 400) - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "This state contains some issues within it, please move them to some other state to delete this state.", }); else - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "State could not be deleted. Please try again.", }); diff --git a/web/components/toast-alert/index.tsx b/web/components/toast-alert/index.tsx deleted file mode 100644 index b4df6ea052b..00000000000 --- a/web/components/toast-alert/index.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React from "react"; -// hooks -import useToast from "hooks/use-toast"; -// icons -import { AlertTriangle, CheckCircle, Info, X, XCircle } from "lucide-react"; - -const ToastAlerts = () => { - const { alerts, removeAlert } = useToast(); - - if (!alerts) return null; - - return ( -
- {alerts.map((alert) => ( -
-
- -
-
-
-
- {alert.type === "success" ? ( -
-
-

{alert.title}

- {alert.message &&

{alert.message}

} -
-
-
-
- ))} -
- ); -}; - -export default ToastAlerts; diff --git a/web/components/views/delete-view-modal.tsx b/web/components/views/delete-view-modal.tsx index 5bd477352b0..f4fe8e12051 100644 --- a/web/components/views/delete-view-modal.tsx +++ b/web/components/views/delete-view-modal.tsx @@ -5,9 +5,8 @@ import { Dialog, Transition } from "@headlessui/react"; import { AlertTriangle } from "lucide-react"; // hooks import { useProjectView } from "hooks/store"; -import useToast from "hooks/use-toast"; // ui -import { Button } from "@plane/ui"; +import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // types import { IProjectView } from "@plane/types"; @@ -26,8 +25,6 @@ export const DeleteProjectViewModal: React.FC = observer((props) => { const { workspaceSlug, projectId } = router.query; // store hooks const { deleteView } = useProjectView(); - // toast alert - const { setToastAlert } = useToast(); const handleClose = () => { onClose(); @@ -43,15 +40,15 @@ export const DeleteProjectViewModal: React.FC = observer((props) => { .then(() => { handleClose(); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "View deleted successfully.", }); }) .catch(() => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "View could not be deleted. Please try again.", }) diff --git a/web/components/views/modal.tsx b/web/components/views/modal.tsx index 43cea7d5c94..a1abef1a4a0 100644 --- a/web/components/views/modal.tsx +++ b/web/components/views/modal.tsx @@ -3,7 +3,8 @@ import { observer } from "mobx-react-lite"; import { Dialog, Transition } from "@headlessui/react"; // hooks import { useProjectView } from "hooks/store"; -import useToast from "hooks/use-toast"; +// ui +import { TOAST_TYPE, setToast } from "@plane/ui"; // components import { ProjectViewForm } from "components/views"; // types @@ -22,8 +23,6 @@ export const CreateUpdateProjectViewModal: FC = observer((props) => { const { data, isOpen, onClose, preLoadedData, workspaceSlug, projectId } = props; // store hooks const { createView, updateView } = useProjectView(); - // toast alert - const { setToastAlert } = useToast(); const handleClose = () => { onClose(); @@ -33,15 +32,15 @@ export const CreateUpdateProjectViewModal: FC = observer((props) => { await createView(workspaceSlug, projectId, payload) .then(() => { handleClose(); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "View created successfully.", }); }) .catch(() => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Something went wrong. Please try again.", }) @@ -52,8 +51,8 @@ export const CreateUpdateProjectViewModal: FC = observer((props) => { await updateView(workspaceSlug, projectId, data?.id as string, payload) .then(() => handleClose()) .catch((err) => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: err.detail ?? "Something went wrong. Please try again.", }) diff --git a/web/components/views/view-list-item.tsx b/web/components/views/view-list-item.tsx index 48cc12ada36..7ff1ee92e48 100644 --- a/web/components/views/view-list-item.tsx +++ b/web/components/views/view-list-item.tsx @@ -5,11 +5,10 @@ import { observer } from "mobx-react-lite"; import { LinkIcon, PencilIcon, StarIcon, TrashIcon } from "lucide-react"; // hooks import { useProjectView, useUser } from "hooks/store"; -import useToast from "hooks/use-toast"; // components import { CreateUpdateProjectViewModal, DeleteProjectViewModal } from "components/views"; // ui -import { CustomMenu } from "@plane/ui"; +import { CustomMenu, TOAST_TYPE, setToast } from "@plane/ui"; // helpers import { calculateTotalFilters } from "helpers/filter.helper"; import { copyUrlToClipboard } from "helpers/string.helper"; @@ -30,8 +29,6 @@ export const ProjectViewListItem: React.FC = observer((props) => { // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; - // toast alert - const { setToastAlert } = useToast(); // store hooks const { membership: { currentProjectRole }, @@ -54,8 +51,8 @@ export const ProjectViewListItem: React.FC = observer((props) => { e.stopPropagation(); e.preventDefault(); copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/views/${view.id}`).then(() => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Link Copied!", message: "View link copied to clipboard.", }); diff --git a/web/components/web-hooks/create-webhook-modal.tsx b/web/components/web-hooks/create-webhook-modal.tsx index f8301bf53da..ecbd4ccd3ff 100644 --- a/web/components/web-hooks/create-webhook-modal.tsx +++ b/web/components/web-hooks/create-webhook-modal.tsx @@ -5,11 +5,12 @@ import { Dialog, Transition } from "@headlessui/react"; import { WebhookForm } from "./form"; import { GeneratedHookDetails } from "./generated-hook-details"; // hooks -import useToast from "hooks/use-toast"; // helpers import { csvDownload } from "helpers/download.helper"; // utils import { getCurrentHookAsCSV } from "./utils"; +// ui +import { TOAST_TYPE, setToast } from "@plane/ui"; // types import { IWebhook, IWorkspace, TWebhookEventTypes } from "@plane/types"; @@ -34,8 +35,6 @@ export const CreateWebhookModal: React.FC = (props) => { // router const router = useRouter(); const { workspaceSlug } = router.query; - // toast - const { setToastAlert } = useToast(); const handleCreateWebhook = async (formData: IWebhook, webhookEventType: TWebhookEventTypes) => { if (!workspaceSlug) return; @@ -65,8 +64,8 @@ export const CreateWebhookModal: React.FC = (props) => { await createWebhook(workspaceSlug.toString(), payload) .then(({ webHook, secretKey }) => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Webhook created successfully.", }); @@ -77,8 +76,8 @@ export const CreateWebhookModal: React.FC = (props) => { csvDownload(csvData, `webhook-secret-key-${Date.now()}`); }) .catch((error) => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: error?.error ?? "Something went wrong. Please try again.", }); diff --git a/web/components/web-hooks/delete-webhook-modal.tsx b/web/components/web-hooks/delete-webhook-modal.tsx index 6cc30bb5796..52c7a6595e8 100644 --- a/web/components/web-hooks/delete-webhook-modal.tsx +++ b/web/components/web-hooks/delete-webhook-modal.tsx @@ -4,9 +4,8 @@ import { Dialog, Transition } from "@headlessui/react"; import { AlertTriangle } from "lucide-react"; // hooks import { useWebhook } from "hooks/store"; -import useToast from "hooks/use-toast"; // ui -import { Button } from "@plane/ui"; +import { Button, TOAST_TYPE, setToast } from "@plane/ui"; interface IDeleteWebhook { isOpen: boolean; @@ -19,8 +18,6 @@ export const DeleteWebhookModal: FC = (props) => { const [isDeleting, setIsDeleting] = useState(false); // router const router = useRouter(); - // toast - const { setToastAlert } = useToast(); // store hooks const { removeWebhook } = useWebhook(); @@ -37,16 +34,16 @@ export const DeleteWebhookModal: FC = (props) => { removeWebhook(workspaceSlug.toString(), webhookId.toString()) .then(() => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Webhook deleted successfully.", }); router.replace(`/${workspaceSlug}/settings/webhooks/`); }) .catch((error) => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: error?.error ?? "Something went wrong. Please try again.", }) diff --git a/web/components/web-hooks/form/secret-key.tsx b/web/components/web-hooks/form/secret-key.tsx index 2d6a69fd6f5..7e9d9deda22 100644 --- a/web/components/web-hooks/form/secret-key.tsx +++ b/web/components/web-hooks/form/secret-key.tsx @@ -1,16 +1,16 @@ import { useState, FC } from "react"; import { useRouter } from "next/router"; -import { Button, Tooltip } from "@plane/ui"; import { Copy, Eye, EyeOff, RefreshCw } from "lucide-react"; import { observer } from "mobx-react-lite"; // hooks import { useWebhook, useWorkspace } from "hooks/store"; -import useToast from "hooks/use-toast"; // helpers import { copyTextToClipboard } from "helpers/string.helper"; import { csvDownload } from "helpers/download.helper"; // utils import { getCurrentHookAsCSV } from "../utils"; +// ui +import { Button, Tooltip, TOAST_TYPE, setToast } from "@plane/ui"; // types import { IWebhook } from "@plane/types"; @@ -29,23 +29,21 @@ export const WebhookSecretKey: FC = observer((props) => { // store hooks const { currentWorkspace } = useWorkspace(); const { currentWebhook, regenerateSecretKey, webhookSecretKey } = useWebhook(); - // hooks - const { setToastAlert } = useToast(); const handleCopySecretKey = () => { if (!webhookSecretKey) return; copyTextToClipboard(webhookSecretKey) .then(() => - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Secret key copied to clipboard.", }) ) .catch(() => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Error occurred while copying secret key.", }) @@ -59,8 +57,8 @@ export const WebhookSecretKey: FC = observer((props) => { regenerateSecretKey(workspaceSlug.toString(), data.id) .then(() => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "New key regenerated successfully.", }); @@ -71,8 +69,8 @@ export const WebhookSecretKey: FC = observer((props) => { } }) .catch((err) => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: err?.error ?? "Something went wrong. Please try again.", }) diff --git a/web/components/workspace/create-workspace-form.tsx b/web/components/workspace/create-workspace-form.tsx index b4f1644698e..e8e40cf8536 100644 --- a/web/components/workspace/create-workspace-form.tsx +++ b/web/components/workspace/create-workspace-form.tsx @@ -6,9 +6,8 @@ import { Controller, useForm } from "react-hook-form"; import { WorkspaceService } from "services/workspace.service"; // hooks import { useEventTracker, useWorkspace } from "hooks/store"; -import useToast from "hooks/use-toast"; // ui -import { Button, CustomSelect, Input } from "@plane/ui"; +import { Button, CustomSelect, Input, TOAST_TYPE, setToast } from "@plane/ui"; // types import { IWorkspace } from "@plane/types"; // constants @@ -51,8 +50,6 @@ export const CreateWorkspaceForm: FC = observer((props) => { // store hooks const { captureWorkspaceEvent } = useEventTracker(); const { createWorkspace } = useWorkspace(); - // toast alert - const { setToastAlert } = useToast(); // form info const { handleSubmit, @@ -79,8 +76,8 @@ export const CreateWorkspaceForm: FC = observer((props) => { element: "Create workspace page", }, }); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Workspace created successfully.", }); @@ -95,8 +92,8 @@ export const CreateWorkspaceForm: FC = observer((props) => { element: "Create workspace page", }, }); - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Workspace could not be created. Please try again.", }); @@ -104,8 +101,8 @@ export const CreateWorkspaceForm: FC = observer((props) => { } else setSlugError(true); }) .catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Some error occurred while creating workspace. Please try again.", }); diff --git a/web/components/workspace/delete-workspace-modal.tsx b/web/components/workspace/delete-workspace-modal.tsx index a90ac9cdf81..dbb2ef4f026 100644 --- a/web/components/workspace/delete-workspace-modal.tsx +++ b/web/components/workspace/delete-workspace-modal.tsx @@ -6,9 +6,8 @@ import { Dialog, Transition } from "@headlessui/react"; import { AlertTriangle } from "lucide-react"; // hooks import { useEventTracker, useWorkspace } 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 { IWorkspace } from "@plane/types"; // constants @@ -32,8 +31,6 @@ export const DeleteWorkspaceModal: React.FC = observer((props) => { // store hooks const { captureWorkspaceEvent } = useEventTracker(); const { deleteWorkspace } = useWorkspace(); - // toast alert - const { setToastAlert } = useToast(); // form info const { control, @@ -69,15 +66,15 @@ export const DeleteWorkspaceModal: React.FC = observer((props) => { element: "Workspace general settings page", }, }); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Workspace deleted successfully.", }); }) .catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Something went wrong. Please try again later.", }); diff --git a/web/components/workspace/settings/invitations-list-item.tsx b/web/components/workspace/settings/invitations-list-item.tsx index 9e37a2fb51a..9a9df5cb1f5 100644 --- a/web/components/workspace/settings/invitations-list-item.tsx +++ b/web/components/workspace/settings/invitations-list-item.tsx @@ -4,11 +4,10 @@ import { observer } from "mobx-react-lite"; import { ChevronDown, XCircle } from "lucide-react"; // hooks import { useMember, useUser } from "hooks/store"; -import useToast from "hooks/use-toast"; // components import { ConfirmWorkspaceMemberRemove } from "components/workspace"; // ui -import { CustomSelect, Tooltip } from "@plane/ui"; +import { CustomSelect, Tooltip, TOAST_TYPE, setToast } from "@plane/ui"; // constants import { EUserWorkspaceRoles, ROLE } from "constants/workspace"; @@ -30,8 +29,6 @@ export const WorkspaceInvitationsListItem: FC = observer((props) => { const { workspace: { updateMemberInvitation, deleteMemberInvitation, getWorkspaceInvitationDetails }, } = useMember(); - // toast alert - const { setToastAlert } = useToast(); // derived values const invitationDetails = getWorkspaceInvitationDetails(invitationId); @@ -40,15 +37,15 @@ export const WorkspaceInvitationsListItem: FC = observer((props) => { await deleteMemberInvitation(workspaceSlug.toString(), invitationDetails.id) .then(() => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success", message: "Invitation removed successfully.", }); }) .catch((err) => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error", message: err?.error || "Something went wrong. Please try again.", }) @@ -116,8 +113,8 @@ export const WorkspaceInvitationsListItem: FC = observer((props) => { updateMemberInvitation(workspaceSlug.toString(), invitationDetails.id, { role: value, }).catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "An error occurred while updating member role. Please try again.", }); diff --git a/web/components/workspace/settings/members-list-item.tsx b/web/components/workspace/settings/members-list-item.tsx index 76c9bbedf8b..c6c8d1d364e 100644 --- a/web/components/workspace/settings/members-list-item.tsx +++ b/web/components/workspace/settings/members-list-item.tsx @@ -5,11 +5,10 @@ import { observer } from "mobx-react-lite"; import { ChevronDown, Dot, XCircle } from "lucide-react"; // hooks import { useEventTracker, useMember, useUser } from "hooks/store"; -import useToast from "hooks/use-toast"; // components import { ConfirmWorkspaceMemberRemove } from "components/workspace"; // ui -import { CustomSelect, Tooltip } from "@plane/ui"; +import { CustomSelect, Tooltip, TOAST_TYPE, setToast } from "@plane/ui"; // constants import { EUserWorkspaceRoles, ROLE } from "constants/workspace"; import { WORKSPACE_MEMBER_lEAVE } from "constants/event-tracker"; @@ -35,8 +34,6 @@ export const WorkspaceMembersListItem: FC = observer((props) => { workspace: { updateMember, removeMemberFromWorkspace, getWorkspaceMemberDetails }, } = useMember(); const { captureEvent } = useEventTracker(); - // toast alert - const { setToastAlert } = useToast(); // derived values const memberDetails = getWorkspaceMemberDetails(memberId); @@ -52,8 +49,8 @@ export const WorkspaceMembersListItem: FC = observer((props) => { router.push("/profile"); }) .catch((err) => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error", message: err?.error || "Something went wrong. Please try again.", }) @@ -64,8 +61,8 @@ export const WorkspaceMembersListItem: FC = observer((props) => { if (!workspaceSlug || !memberDetails) return; await removeMemberFromWorkspace(workspaceSlug.toString(), memberDetails.member.id).catch((err) => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error", message: err?.error || "Something went wrong. Please try again.", }) @@ -165,8 +162,8 @@ export const WorkspaceMembersListItem: FC = observer((props) => { updateMember(workspaceSlug.toString(), memberDetails.member.id, { role: value, }).catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "An error occurred while updating member role. Please try again.", }); diff --git a/web/components/workspace/settings/workspace-details.tsx b/web/components/workspace/settings/workspace-details.tsx index 44da4291f66..d491ca08e45 100644 --- a/web/components/workspace/settings/workspace-details.tsx +++ b/web/components/workspace/settings/workspace-details.tsx @@ -7,12 +7,11 @@ import { ChevronDown, ChevronUp, Pencil } from "lucide-react"; import { FileService } from "services/file.service"; // hooks import { useEventTracker, useUser, useWorkspace } from "hooks/store"; -import useToast from "hooks/use-toast"; // components import { DeleteWorkspaceModal } from "components/workspace"; import { WorkspaceImageUploadModal } from "components/core"; // ui -import { Button, CustomSelect, Input, Spinner } from "@plane/ui"; +import { Button, CustomSelect, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui"; // helpers import { copyUrlToClipboard } from "helpers/string.helper"; // types @@ -43,8 +42,6 @@ export const WorkspaceDetails: FC = observer(() => { membership: { currentWorkspaceRole }, } = useUser(); const { currentWorkspace, updateWorkspace } = useWorkspace(); - // toast alert - const { setToastAlert } = useToast(); // form info const { handleSubmit, @@ -77,9 +74,9 @@ export const WorkspaceDetails: FC = observer(() => { element: "Workspace general settings page", }, }); - setToastAlert({ + setToast({ title: "Success", - type: "success", + type: TOAST_TYPE.SUCCESS, message: "Workspace updated successfully", }); }) @@ -110,16 +107,16 @@ export const WorkspaceDetails: FC = observer(() => { fileService.deleteFile(currentWorkspace.id, url).then(() => { updateWorkspace(currentWorkspace.slug, { logo: "" }) .then(() => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Workspace picture removed successfully.", }); setIsImageUploadModalOpen(false); }) .catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "There was some error in deleting your profile picture. Please try again.", }); @@ -132,8 +129,8 @@ export const WorkspaceDetails: FC = observer(() => { if (!currentWorkspace) return; copyUrlToClipboard(`${currentWorkspace.slug}`).then(() => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Workspace URL copied to the clipboard.", }); }); diff --git a/web/components/workspace/sidebar-dropdown.tsx b/web/components/workspace/sidebar-dropdown.tsx index 984bc1caf35..98a133ee356 100644 --- a/web/components/workspace/sidebar-dropdown.tsx +++ b/web/components/workspace/sidebar-dropdown.tsx @@ -9,10 +9,8 @@ import { Check, ChevronDown, CircleUserRound, LogOut, Mails, PlusSquare, Setting import { usePopper } from "react-popper"; // hooks import { useApplication, useUser, useWorkspace } from "hooks/store"; -// hooks -import useToast from "hooks/use-toast"; // ui -import { Avatar, Loader } from "@plane/ui"; +import { Avatar, Loader, TOAST_TYPE, setToast } from "@plane/ui"; // types import { IWorkspace } from "@plane/types"; // Static Data @@ -58,8 +56,6 @@ export const WorkspaceSidebarDropdown = observer(() => { } = useApplication(); const { currentUser, updateCurrentUser, isUserInstanceAdmin, signOut } = useUser(); const { currentWorkspace: activeWorkspace, workspaces } = useWorkspace(); - // hooks - const { setToastAlert } = useToast(); const { setTheme } = useTheme(); // popper-js refs const [referenceElement, setReferenceElement] = useState(null); @@ -88,8 +84,8 @@ export const WorkspaceSidebarDropdown = observer(() => { router.push("/"); }) .catch(() => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Failed to sign out. Please try again.", }) diff --git a/web/components/workspace/views/delete-view-modal.tsx b/web/components/workspace/views/delete-view-modal.tsx index b6028d5417f..85d56cc6395 100644 --- a/web/components/workspace/views/delete-view-modal.tsx +++ b/web/components/workspace/views/delete-view-modal.tsx @@ -5,9 +5,8 @@ import { observer } from "mobx-react-lite"; import { AlertTriangle } from "lucide-react"; // store hooks import { useGlobalView, useEventTracker } from "hooks/store"; -import useToast from "hooks/use-toast"; // ui -import { Button } from "@plane/ui"; +import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // types import { IWorkspaceView } from "@plane/types"; // constants @@ -29,8 +28,6 @@ export const DeleteGlobalViewModal: React.FC = observer((props) => { // store hooks const { deleteGlobalView } = useGlobalView(); const { captureEvent } = useEventTracker(); - // toast alert - const { setToastAlert } = useToast(); const handleClose = () => { onClose(); @@ -53,8 +50,8 @@ export const DeleteGlobalViewModal: React.FC = observer((props) => { view_id: data.id, state: "FAILED", }); - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Something went wrong while deleting the view. Please try again.", }); diff --git a/web/components/workspace/views/modal.tsx b/web/components/workspace/views/modal.tsx index b66d555fa64..6543a83212b 100644 --- a/web/components/workspace/views/modal.tsx +++ b/web/components/workspace/views/modal.tsx @@ -4,7 +4,8 @@ import { observer } from "mobx-react-lite"; import { Dialog, Transition } from "@headlessui/react"; // store hooks import { useEventTracker, useGlobalView } from "hooks/store"; -import useToast from "hooks/use-toast"; +// ui +import { TOAST_TYPE, setToast } from "@plane/ui"; // components import { WorkspaceViewForm } from "components/workspace"; // types @@ -27,8 +28,6 @@ export const CreateUpdateWorkspaceViewModal: React.FC = observer((props) // store hooks const { createGlobalView, updateGlobalView } = useGlobalView(); const { captureEvent } = useEventTracker(); - // toast alert - const { setToastAlert } = useToast(); const handleClose = () => { onClose(); @@ -51,8 +50,8 @@ export const CreateUpdateWorkspaceViewModal: React.FC = observer((props) applied_filters: res.filters, state: "SUCCESS", }); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "View created successfully.", }); @@ -65,8 +64,8 @@ export const CreateUpdateWorkspaceViewModal: React.FC = observer((props) applied_filters: payload?.filters, state: "FAILED", }); - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "View could not be created. Please try again.", }); @@ -90,8 +89,8 @@ export const CreateUpdateWorkspaceViewModal: React.FC = observer((props) applied_filters: res.filters, state: "SUCCESS", }); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "View updated successfully.", }); @@ -103,8 +102,8 @@ export const CreateUpdateWorkspaceViewModal: React.FC = observer((props) applied_filters: data.filters, state: "FAILED", }); - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "View could not be updated. Please try again.", }); diff --git a/web/contexts/toast.context.tsx b/web/contexts/toast.context.tsx deleted file mode 100644 index 30e100b209b..00000000000 --- a/web/contexts/toast.context.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import React, { createContext, useCallback, useReducer } from "react"; -// uuid -import { v4 as uuid } from "uuid"; -// components -import ToastAlert from "components/toast-alert"; - -export const toastContext = createContext({} as ContextType); - -// types -type ToastAlert = { - id: string; - title: string; - message?: string; - type: "success" | "error" | "warning" | "info"; -}; - -type ReducerActionType = { - type: "SET_TOAST_ALERT" | "REMOVE_TOAST_ALERT"; - payload: ToastAlert; -}; - -type ContextType = { - alerts?: ToastAlert[]; - removeAlert: (id: string) => void; - setToastAlert: (data: { - title: string; - type?: "success" | "error" | "warning" | "info" | undefined; - message?: string | undefined; - }) => void; -}; - -type StateType = { - toastAlerts?: ToastAlert[]; -}; - -type ReducerFunctionType = (state: StateType, action: ReducerActionType) => StateType; - -export const initialState: StateType = { - toastAlerts: [], -}; - -export const reducer: ReducerFunctionType = (state, action) => { - const { type, payload } = action; - - switch (type) { - case "SET_TOAST_ALERT": - return { - ...state, - toastAlerts: [...(state.toastAlerts ?? []), payload], - }; - - case "REMOVE_TOAST_ALERT": - return { - ...state, - toastAlerts: state.toastAlerts?.filter((toastAlert) => toastAlert.id !== payload.id), - }; - - default: { - return state; - } - } -}; - -export const ToastContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const [state, dispatch] = useReducer(reducer, initialState); - - const removeAlert = useCallback((id: string) => { - dispatch({ - type: "REMOVE_TOAST_ALERT", - payload: { id, title: "", message: "", type: "success" }, - }); - }, []); - - const setToastAlert = useCallback( - (data: { title: string; type?: "success" | "error" | "warning" | "info"; message?: string }) => { - const id = uuid(); - const { title, type, message } = data; - dispatch({ - type: "SET_TOAST_ALERT", - payload: { id, title, message, type: type ?? "success" }, - }); - - const timer = setTimeout(() => { - removeAlert(id); - clearTimeout(timer); - }, 3000); - }, - [removeAlert] - ); - - return ( - - - {children} - - ); -}; diff --git a/web/helpers/theme.helper.ts b/web/helpers/theme.helper.ts index 16cd8cd7992..a9aa5b91302 100644 --- a/web/helpers/theme.helper.ts +++ b/web/helpers/theme.helper.ts @@ -118,3 +118,6 @@ export const unsetCustomCssVariables = () => { dom?.style.removeProperty("--color-scheme"); } }; + +export const resolveGeneralTheme = (resolvedTheme: string | undefined) => + resolvedTheme?.includes("light") ? "light" : resolvedTheme?.includes("dark") ? "dark" : "system"; diff --git a/web/hooks/use-toast.tsx b/web/hooks/use-toast.tsx deleted file mode 100644 index 6de3c104c67..00000000000 --- a/web/hooks/use-toast.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { useContext } from "react"; -import { toastContext } from "contexts/toast.context"; - -const useToast = () => { - const toastContextData = useContext(toastContext); - return toastContextData; -}; - -export default useToast; diff --git a/web/hooks/use-user-notifications.tsx b/web/hooks/use-user-notifications.tsx index 17a2c63dcc9..3c2ec6332a1 100644 --- a/web/hooks/use-user-notifications.tsx +++ b/web/hooks/use-user-notifications.tsx @@ -5,12 +5,12 @@ import useSWR from "swr"; import useSWRInfinite from "swr/infinite"; // services import { NotificationService } from "services/notification.service"; -// hooks -import useToast from "./use-toast"; // fetch-keys import { UNREAD_NOTIFICATIONS_COUNT, getPaginatedNotificationKey } from "constants/fetch-keys"; // type import type { NotificationType, NotificationCount, IMarkAllAsReadPayload } from "@plane/types"; +// ui +import { TOAST_TYPE, setToast } from "@plane/ui"; const PER_PAGE = 30; @@ -20,8 +20,6 @@ const useUserNotification = () => { const router = useRouter(); const { workspaceSlug } = router.query; - const { setToastAlert } = useToast(); - const [snoozed, setSnoozed] = useState(false); const [archived, setArchived] = useState(false); const [readNotification, setReadNotification] = useState(false); @@ -265,15 +263,15 @@ const useUserNotification = () => { await userNotificationServices .markAllNotificationsAsRead(workspaceSlug.toString(), markAsReadParams) .then(() => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "All Notifications marked as read.", }); }) .catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Something went wrong. Please try again.", }); diff --git a/web/layouts/settings-layout/profile/sidebar.tsx b/web/layouts/settings-layout/profile/sidebar.tsx index 85f82961fee..326fe72879e 100644 --- a/web/layouts/settings-layout/profile/sidebar.tsx +++ b/web/layouts/settings-layout/profile/sidebar.tsx @@ -7,9 +7,8 @@ import { useTheme } from "next-themes"; import { ChevronLeft, LogOut, MoveLeft, Plus, UserPlus } from "lucide-react"; // hooks import { useApplication, useUser, useWorkspace } from "hooks/store"; -import useToast from "hooks/use-toast"; // ui -import { Tooltip } from "@plane/ui"; +import { Tooltip, TOAST_TYPE, setToast } from "@plane/ui"; // constants import { PROFILE_ACTION_LINKS } from "constants/profile"; import useOutsideClickDetector from "hooks/use-outside-click-detector"; @@ -36,8 +35,6 @@ export const ProfileLayoutSidebar = observer(() => { const router = useRouter(); // next themes const { setTheme } = useTheme(); - // toast - const { setToastAlert } = useToast(); // store hooks const { theme: { sidebarCollapsed, toggleSidebar }, @@ -92,8 +89,8 @@ export const ProfileLayoutSidebar = observer(() => { router.push("/"); }) .catch(() => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Failed to sign out. Please try again.", }) diff --git a/web/lib/app-provider.tsx b/web/lib/app-provider.tsx index 9c06947af89..a917936138d 100644 --- a/web/lib/app-provider.tsx +++ b/web/lib/app-provider.tsx @@ -3,18 +3,19 @@ import dynamic from "next/dynamic"; import Router from "next/router"; import NProgress from "nprogress"; import { observer } from "mobx-react-lite"; -import { ThemeProvider } from "next-themes"; +import { useTheme } from "next-themes"; // hooks import { useApplication, useUser, useWorkspace } from "hooks/store"; +// ui +import { Toast } from "@plane/ui"; // constants -import { THEMES } from "constants/themes"; +import { SWR_CONFIG } from "constants/swr-config"; // layouts import InstanceLayout from "layouts/instance-layout"; // contexts -import { ToastContextProvider } from "contexts/toast.context"; import { SWRConfig } from "swr"; -// constants -import { SWR_CONFIG } from "constants/swr-config"; +//helpers +import { resolveGeneralTheme } from "helpers/theme.helper"; // dynamic imports const StoreWrapper = dynamic(() => import("lib/wrappers/store-wrapper"), { ssr: false }); const PostHogProvider = dynamic(() => import("lib/posthog-provider"), { ssr: false }); @@ -41,27 +42,29 @@ export const AppProvider: FC = observer((props) => { const { config: { envConfig }, } = useApplication(); + // themes + const { resolvedTheme } = useTheme(); return ( - - - - - - - {children} - - - - - - + <> + {/* TODO: Need to handle custom themes for toast */} + + + + + + {children} + + + + + ); }); diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx index ee1be4ebb08..6e74de061d1 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx @@ -3,7 +3,6 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react"; import useSWR from "swr"; // hooks -import useToast from "hooks/use-toast"; import { useIssueDetail, useIssues, useProject, useUser } from "hooks/store"; // layouts import { AppLayout } from "layouts/app-layout"; @@ -12,7 +11,7 @@ import { IssueDetailRoot } from "components/issues"; import { ProjectArchivedIssueDetailsHeader } from "components/headers"; import { PageHead } from "components/core"; // ui -import { ArchiveIcon, Button, Loader } from "@plane/ui"; +import { ArchiveIcon, Button, Loader, TOAST_TYPE, setToast } from "@plane/ui"; // icons import { RotateCcw } from "lucide-react"; // types @@ -35,7 +34,6 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = observer(() => { const { issues: { restoreIssue }, } = useIssues(EIssuesStoreType.ARCHIVED); - const { setToastAlert } = useToast(); const { getProjectById } = useProject(); const { membership: { currentProjectRole }, @@ -66,8 +64,8 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = observer(() => { await restoreIssue(workspaceSlug.toString(), projectId.toString(), archivedIssueId.toString()) .then(() => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success", message: issue && @@ -78,8 +76,8 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = observer(() => { router.push(`/${workspaceSlug}/projects/${projectId}/issues/${archivedIssueId}`); }) .catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Something went wrong. Please try again.", }); diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index bee4fc9c724..c44f6186ef7 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -8,7 +8,6 @@ import { Controller, useForm } from "react-hook-form"; import { useApplication, usePage, useUser, useWorkspace } from "hooks/store"; import useReloadConfirmations from "hooks/use-reload-confirmation"; -import useToast from "hooks/use-toast"; // services import { FileService } from "services/file.service"; // layouts @@ -18,7 +17,7 @@ import { GptAssistantPopover, PageHead } from "components/core"; import { PageDetailsHeader } from "components/headers/page-details"; // ui import { DocumentEditorWithRef, DocumentReadOnlyEditorWithRef } from "@plane/document-editor"; -import { Spinner } from "@plane/ui"; +import { Spinner, TOAST_TYPE, setToast } from "@plane/ui"; // assets // helpers // types @@ -53,8 +52,6 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { currentUser, membership: { currentProjectRole }, } = useUser(); - // toast alert - const { setToastAlert } = useToast(); const { handleSubmit, setValue, watch, getValues, control, reset } = useForm({ defaultValues: { name: "", description_html: "" }, @@ -148,10 +145,10 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { message: string; type: "success" | "error" | "warning" | "info"; }) => { - setToastAlert({ + setToast({ title, message, - type, + type: type as TOAST_TYPE, }); }; diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx index 8c4780cba0c..1cefb941831 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx @@ -6,8 +6,8 @@ import { useProject, useUser } from "hooks/store"; // layouts import { AppLayout } from "layouts/app-layout"; import { ProjectSettingLayout } from "layouts/settings-layout"; -// hooks -import useToast from "hooks/use-toast"; +// ui +import { TOAST_TYPE, setToast } from "@plane/ui"; // components import { AutoArchiveAutomation, AutoCloseAutomation } from "components/automation"; import { PageHead } from "components/core"; @@ -22,8 +22,6 @@ const AutomationSettingsPage: NextPageWithLayout = observer(() => { // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; - // toast alert - const { setToastAlert } = useToast(); // store hooks const { membership: { currentProjectRole }, @@ -34,8 +32,8 @@ const AutomationSettingsPage: NextPageWithLayout = observer(() => { if (!workspaceSlug || !projectId || !projectDetails) return; await updateProject(workspaceSlug.toString(), projectId.toString(), formData).catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Something went wrong. Please try again.", }); diff --git a/web/pages/[workspaceSlug]/settings/members.tsx b/web/pages/[workspaceSlug]/settings/members.tsx index b8739ae7756..f635588c21d 100644 --- a/web/pages/[workspaceSlug]/settings/members.tsx +++ b/web/pages/[workspaceSlug]/settings/members.tsx @@ -4,7 +4,6 @@ import { observer } from "mobx-react-lite"; import { Search } from "lucide-react"; // hooks import { useEventTracker, useMember, useUser, useWorkspace } from "hooks/store"; -import useToast from "hooks/use-toast"; // layouts import { AppLayout } from "layouts/app-layout"; import { WorkspaceSettingLayout } from "layouts/settings-layout"; @@ -13,7 +12,7 @@ import { WorkspaceSettingHeader } from "components/headers"; import { SendWorkspaceInvitationModal, WorkspaceMembersList } from "components/workspace"; import { PageHead } from "components/core"; // ui -import { Button } from "@plane/ui"; +import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // types import { NextPageWithLayout } from "lib/types"; import { IWorkspaceBulkInviteFormData } from "@plane/types"; @@ -39,8 +38,6 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => { workspace: { inviteMembersToWorkspace }, } = useMember(); const { currentWorkspace } = useWorkspace(); - // toast alert - const { setToastAlert } = useToast(); const handleWorkspaceInvite = (data: IWorkspaceBulkInviteFormData) => { if (!workspaceSlug) return; @@ -59,8 +56,8 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => { state: "SUCCESS", element: "Workspace settings member page", }); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Invitations sent successfully.", }); @@ -77,8 +74,8 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => { state: "FAILED", element: "Workspace settings member page", }); - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: `${err.error ?? "Something went wrong. Please try again."}`, }); diff --git a/web/pages/[workspaceSlug]/settings/webhooks/[webhookId].tsx b/web/pages/[workspaceSlug]/settings/webhooks/[webhookId].tsx index 60e65e90544..bafaa3aaad7 100644 --- a/web/pages/[workspaceSlug]/settings/webhooks/[webhookId].tsx +++ b/web/pages/[workspaceSlug]/settings/webhooks/[webhookId].tsx @@ -7,14 +7,12 @@ import { useUser, useWebhook, useWorkspace } from "hooks/store"; // layouts import { AppLayout } from "layouts/app-layout"; import { WorkspaceSettingLayout } from "layouts/settings-layout"; -// hooks -import useToast from "hooks/use-toast"; // components import { WorkspaceSettingHeader } from "components/headers"; import { DeleteWebhookModal, WebhookDeleteSection, WebhookForm } from "components/web-hooks"; import { PageHead } from "components/core"; // ui -import { Spinner } from "@plane/ui"; +import { Spinner, TOAST_TYPE, setToast } from "@plane/ui"; // types import { NextPageWithLayout } from "lib/types"; import { IWebhook } from "@plane/types"; @@ -31,8 +29,6 @@ const WebhookDetailsPage: NextPageWithLayout = observer(() => { } = useUser(); const { currentWebhook, fetchWebhookById, updateWebhook } = useWebhook(); const { currentWorkspace } = useWorkspace(); - // toast - const { setToastAlert } = useToast(); // TODO: fix this error // useEffect(() => { @@ -62,15 +58,15 @@ const WebhookDetailsPage: NextPageWithLayout = observer(() => { }; await updateWebhook(workspaceSlug.toString(), formData.id, payload) .then(() => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Webhook updated successfully.", }); }) .catch((error) => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: error?.error ?? "Something went wrong. Please try again.", }); diff --git a/web/pages/_app.tsx b/web/pages/_app.tsx index 75023d36e9b..bc82302569b 100644 --- a/web/pages/_app.tsx +++ b/web/pages/_app.tsx @@ -1,12 +1,14 @@ import { ReactElement } from "react"; import Head from "next/head"; import { AppProps } from "next/app"; +import { ThemeProvider } from "next-themes"; // styles import "styles/globals.css"; import "styles/command-pallette.css"; import "styles/nprogress.css"; import "styles/react-day-picker.css"; // constants +import { THEMES } from "constants/themes"; import { SITE_TITLE } from "constants/seo-variables"; // mobx store provider import { StoreProvider } from "contexts/store-context"; @@ -29,7 +31,9 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { {SITE_TITLE} - {getLayout()} + + {getLayout()} + ); diff --git a/web/pages/_error.tsx b/web/pages/_error.tsx index 11a7ee852e2..0a530cf9f37 100644 --- a/web/pages/_error.tsx +++ b/web/pages/_error.tsx @@ -4,12 +4,10 @@ import { useRouter } from "next/router"; // services import { AuthService } from "services/auth.service"; -// hooks -import useToast from "hooks/use-toast"; // layouts import DefaultLayout from "layouts/default-layout"; // ui -import { Button } from "@plane/ui"; +import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // services const authService = new AuthService(); @@ -17,14 +15,12 @@ const authService = new AuthService(); const CustomErrorComponent = () => { const router = useRouter(); - const { setToastAlert } = useToast(); - const handleSignOut = async () => { await authService .signOut() .catch(() => - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Failed to sign out. Please try again.", }) diff --git a/web/pages/accounts/forgot-password.tsx b/web/pages/accounts/forgot-password.tsx index 0eef16009c3..e167fc0372c 100644 --- a/web/pages/accounts/forgot-password.tsx +++ b/web/pages/accounts/forgot-password.tsx @@ -5,7 +5,6 @@ import { Controller, useForm } from "react-hook-form"; // services import { AuthService } from "services/auth.service"; // hooks -import useToast from "hooks/use-toast"; import useTimer from "hooks/use-timer"; import { useEventTracker } from "hooks/store"; // layouts @@ -14,7 +13,7 @@ import DefaultLayout from "layouts/default-layout"; import { LatestFeatureBlock } from "components/common"; import { PageHead } from "components/core"; // ui -import { Button, Input } from "@plane/ui"; +import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui"; // images import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; // helpers @@ -40,8 +39,6 @@ const ForgotPasswordPage: NextPageWithLayout = () => { const { email } = router.query; // store hooks const { captureEvent } = useEventTracker(); - // toast - const { setToastAlert } = useToast(); // timer const { timer: resendTimerCode, setTimer: setResendCodeTimer } = useTimer(0); // form info @@ -65,8 +62,8 @@ const ForgotPasswordPage: NextPageWithLayout = () => { captureEvent(FORGOT_PASS_LINK, { state: "SUCCESS", }); - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Email sent", message: "Check your inbox for a link to reset your password. If it doesn't appear within a few minutes, check your spam folder.", @@ -77,8 +74,8 @@ const ForgotPasswordPage: NextPageWithLayout = () => { captureEvent(FORGOT_PASS_LINK, { state: "FAILED", }); - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: err?.error ?? "Something went wrong. Please try again.", }); diff --git a/web/pages/accounts/reset-password.tsx b/web/pages/accounts/reset-password.tsx index c848245ac1c..f7a49a19dbf 100644 --- a/web/pages/accounts/reset-password.tsx +++ b/web/pages/accounts/reset-password.tsx @@ -5,7 +5,6 @@ import { Controller, useForm } from "react-hook-form"; // services import { AuthService } from "services/auth.service"; // hooks -import useToast from "hooks/use-toast"; import useSignInRedirection from "hooks/use-sign-in-redirection"; import { useEventTracker } from "hooks/store"; // layouts @@ -14,7 +13,7 @@ import DefaultLayout from "layouts/default-layout"; import { LatestFeatureBlock } from "components/common"; import { PageHead } from "components/core"; // ui -import { Button, Input } from "@plane/ui"; +import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui"; // images import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; // helpers @@ -47,8 +46,6 @@ const ResetPasswordPage: NextPageWithLayout = () => { const [showPassword, setShowPassword] = useState(false); // store hooks const { captureEvent } = useEventTracker(); - // toast - const { setToastAlert } = useToast(); // sign in redirection hook const { handleRedirection } = useSignInRedirection(); // form info @@ -82,8 +79,8 @@ const ResetPasswordPage: NextPageWithLayout = () => { captureEvent(NEW_PASS_CREATED, { state: "FAILED", }); - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: err?.error ?? "Something went wrong. Please try again.", }); diff --git a/web/pages/god-mode/authorization.tsx b/web/pages/god-mode/authorization.tsx index e36a1a455c3..6274fca2047 100644 --- a/web/pages/god-mode/authorization.tsx +++ b/web/pages/god-mode/authorization.tsx @@ -8,10 +8,8 @@ import { InstanceAdminLayout } from "layouts/admin-layout"; import { NextPageWithLayout } from "lib/types"; // hooks import { useApplication } from "hooks/store"; -// hooks -import useToast from "hooks/use-toast"; // ui -import { Loader, ToggleSwitch } from "@plane/ui"; +import { Loader, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui"; // components import { InstanceGithubConfigForm, InstanceGoogleConfigForm } from "components/instance"; import { PageHead } from "components/core"; @@ -24,9 +22,6 @@ const InstanceAdminAuthorizationPage: NextPageWithLayout = observer(() => { useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations()); - // toast - const { setToastAlert } = useToast(); - // state const [isSubmitting, setIsSubmitting] = useState(false); @@ -46,18 +41,18 @@ const InstanceAdminAuthorizationPage: NextPageWithLayout = observer(() => { await updateInstanceConfigurations(payload) .then(() => { - setToastAlert({ + setToast({ title: "Success", - type: "success", + type: TOAST_TYPE.SUCCESS, message: "SSO and OAuth Settings updated successfully", }); setIsSubmitting(false); }) .catch((err) => { console.error(err); - setToastAlert({ + setToast({ title: "Error", - type: "error", + type: TOAST_TYPE.ERROR, message: "Failed to update SSO and OAuth Settings", }); setIsSubmitting(false); diff --git a/web/pages/invitations/index.tsx b/web/pages/invitations/index.tsx index b5acec19689..18441f0a0dc 100644 --- a/web/pages/invitations/index.tsx +++ b/web/pages/invitations/index.tsx @@ -11,12 +11,11 @@ import { WorkspaceService } from "services/workspace.service"; import { UserService } from "services/user.service"; // hooks import { useEventTracker, useUser } from "hooks/store"; -import useToast from "hooks/use-toast"; // layouts import DefaultLayout from "layouts/default-layout"; import { UserAuthWrapper } from "layouts/auth-layout"; // ui -import { Button } from "@plane/ui"; +import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // images import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-logo.svg"; import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.svg"; @@ -48,8 +47,6 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => { const router = useRouter(); // next-themes const { theme } = useTheme(); - // toast alert - const { setToastAlert } = useToast(); const { data: invitations } = useSWR("USER_WORKSPACE_INVITATIONS", () => workspaceService.userWorkspaceInvitations()); @@ -68,8 +65,8 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => { const submitInvitations = () => { if (invitationsRespond.length === 0) { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Please select at least one invitation.", }); @@ -101,8 +98,8 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => { router.push(`/${redirectWorkspace?.slug}`); }) .catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Something went wrong, Please try again.", }); @@ -116,8 +113,8 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => { state: "FAILED", element: "Workspace invitations page", }); - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "Something went wrong, Please try again.", }); diff --git a/web/pages/profile/change-password.tsx b/web/pages/profile/change-password.tsx index 80e2965d67e..f37a2b6a65a 100644 --- a/web/pages/profile/change-password.tsx +++ b/web/pages/profile/change-password.tsx @@ -8,12 +8,10 @@ import { useApplication, useUser } from "hooks/store"; import { UserService } from "services/user.service"; // components import { PageHead } from "components/core"; -// hooks -import useToast from "hooks/use-toast"; // layout import { ProfileSettingsLayout } from "layouts/settings-layout"; // ui -import { Button, Input, Spinner } from "@plane/ui"; +import { Button, Input, Spinner, TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui"; // types import { NextPageWithLayout } from "lib/types"; import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle"; @@ -46,33 +44,28 @@ const ChangePasswordPage: NextPageWithLayout = observer(() => { handleSubmit, formState: { errors, isSubmitting }, } = useForm({ defaultValues }); - const { setToastAlert } = useToast(); const handleChangePassword = async (formData: FormValues) => { if (formData.new_password !== formData.confirm_password) { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "The new password and the confirm password don't match.", }); return; } - await userService - .changePassword(formData) - .then(() => { - setToastAlert({ - type: "success", - title: "Success!", - message: "Password changed successfully.", - }); - }) - .catch((error) => { - setToastAlert({ - type: "error", - title: "Error!", - message: error?.error ?? "Something went wrong. Please try again.", - }); - }); + const changePasswordPromise = userService.changePassword(formData); + setPromiseToast(changePasswordPromise, { + loading: "Changing password...", + success: { + title: "Success!", + message: () => "Password changed successfully.", + }, + error: { + title: "Error!", + message: () => "Something went wrong. Please try again.", + }, + }); }; useEffect(() => { diff --git a/web/pages/profile/index.tsx b/web/pages/profile/index.tsx index bdde41d08f6..aecbfebf7c6 100644 --- a/web/pages/profile/index.tsx +++ b/web/pages/profile/index.tsx @@ -7,14 +7,22 @@ import { FileService } from "services/file.service"; // hooks import { useApplication, useUser } from "hooks/store"; import useUserAuth from "hooks/use-user-auth"; -import useToast from "hooks/use-toast"; // layouts import { ProfileSettingsLayout } from "layouts/settings-layout"; // components import { ImagePickerPopover, UserImageUploadModal, PageHead } from "components/core"; import { DeactivateAccountModal } from "components/account"; // ui -import { Button, CustomSelect, CustomSearchSelect, Input, Spinner } from "@plane/ui"; +import { + Button, + CustomSelect, + CustomSearchSelect, + Input, + Spinner, + TOAST_TYPE, + setPromiseToast, + setToast, +} from "@plane/ui"; // icons import { ChevronDown, User2 } from "lucide-react"; // types @@ -52,8 +60,6 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => { control, formState: { errors }, } = useForm({ defaultValues }); - // toast alert - const { setToastAlert } = useToast(); // store hooks const { currentUser: myProfile, updateCurrentUser, currentUserLoader } = useUser(); // custom hooks @@ -76,24 +82,22 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => { user_timezone: formData.user_timezone, }; - await updateCurrentUser(payload) - .then(() => { - setToastAlert({ - type: "success", - title: "Success!", - message: "Profile updated successfully.", - }); - }) - .catch(() => - setToastAlert({ - type: "error", - title: "Error!", - message: "There was some error in updating your profile. Please try again.", - }) - ); - setTimeout(() => { - setIsLoading(false); - }, 300); + const updateCurrentUserDetail = updateCurrentUser(payload).finally(() => setIsLoading(false)); + setPromiseToast(updateCurrentUserDetail, { + loading: "Updating...", + success: { + title: "Success!", + message: () => `Profile updated successfully.`, + }, + error: { + title: "Error!", + message: () => `There was some error in updating your profile. Please try again.`, + }, + }); + + // setTimeout(() => { + // setIsLoading(false); + // }, 300); }; const handleDelete = (url: string | null | undefined, updateUser: boolean = false) => { @@ -105,16 +109,16 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => { if (updateUser) updateCurrentUser({ avatar: "" }) .then(() => { - setToastAlert({ - type: "success", + setToast({ + type: TOAST_TYPE.SUCCESS, title: "Success!", - message: "Profile picture removed successfully.", + message: "Profile picture deleted successfully.", }); setIsRemoving(false); }) .catch(() => { - setToastAlert({ - type: "error", + setToast({ + type: TOAST_TYPE.ERROR, title: "Error!", message: "There was some error in deleting your profile picture. Please try again.", }); diff --git a/web/pages/profile/preferences/theme.tsx b/web/pages/profile/preferences/theme.tsx index 134ace79e99..94540aeda9c 100644 --- a/web/pages/profile/preferences/theme.tsx +++ b/web/pages/profile/preferences/theme.tsx @@ -3,13 +3,12 @@ import { observer } from "mobx-react-lite"; import { useTheme } from "next-themes"; // hooks import { useUser } from "hooks/store"; -import useToast from "hooks/use-toast"; // layouts import { ProfilePreferenceSettingsLayout } from "layouts/settings-layout/profile/preferences"; // components import { CustomThemeSelector, ThemeSwitch, PageHead } from "components/core"; // ui -import { Spinner } from "@plane/ui"; +import { Spinner, setPromiseToast } from "@plane/ui"; // constants import { I_THEME_OPTION, THEME_OPTIONS } from "constants/themes"; // type @@ -24,7 +23,6 @@ const ProfilePreferencesThemePage: NextPageWithLayout = observer(() => { const userTheme = currentUser?.theme; // hooks const { setTheme } = useTheme(); - const { setToastAlert } = useToast(); useEffect(() => { if (userTheme) { @@ -37,11 +35,18 @@ const ProfilePreferencesThemePage: NextPageWithLayout = observer(() => { const handleThemeChange = (themeOption: I_THEME_OPTION) => { setTheme(themeOption.value); - updateCurrentUserTheme(themeOption.value).catch(() => { - setToastAlert({ - title: "Failed to Update the theme", - type: "error", - }); + const updateCurrentUserThemePromise = updateCurrentUserTheme(themeOption.value); + + setPromiseToast(updateCurrentUserThemePromise, { + loading: "Updating theme...", + success: { + title: "Success!", + message: () => "Theme updated successfully!", + }, + error: { + title: "Error!", + message: () => "Failed to Update the theme", + }, }); }; diff --git a/web/styles/globals.css b/web/styles/globals.css index e4de1a3da81..6c51e75c47a 100644 --- a/web/styles/globals.css +++ b/web/styles/globals.css @@ -149,6 +149,27 @@ --color-onboarding-border-300: 229, 229, 229, 0.5; --color-onboarding-shadow-sm: 0px 4px 20px 0px rgba(126, 139, 171, 0.1); + + /* toast theme */ + --color-toast-success-text: 62, 155, 79; + --color-toast-error-text: 220, 62, 66; + --color-toast-warning-text: 255, 186, 24; + --color-toast-info-text: 51, 88, 212; + --color-toast-loading-text: 28, 32, 36; + --color-toast-secondary-text: 128, 131, 141; + --color-toast-tertiary-text: 96, 100, 108; + + --color-toast-success-background: 253, 253, 254; + --color-toast-error-background: 255, 252, 252; + --color-toast-warning-background: 254, 253, 251; + --color-toast-info-background: 253, 253, 254; + --color-toast-loading-background: 253, 253, 254; + + --color-toast-success-border: 218, 241, 219; + --color-toast-error-border: 255, 219, 220; + --color-toast-warning-border: 255, 247, 194; + --color-toast-info-border: 210, 222, 255; + --color-toast-loading-border: 224, 225, 230; } [data-theme="light-contrast"] { @@ -217,6 +238,27 @@ --color-onboarding-border-300: 34, 35, 38, 0.5; --color-onboarding-shadow-sm: 0px 4px 20px 0px rgba(39, 44, 56, 0.1); + + /* toast theme */ + --color-toast-success-text: 178, 221, 181; + --color-toast-error-text: 206, 44, 49; + --color-toast-warning-text: 255, 186, 24; + --color-toast-info-text: 141, 164, 239; + --color-toast-loading-text: 255, 255, 255; + --color-toast-secondary-text: 185, 187, 198; + --color-toast-tertiary-text: 139, 141, 152; + + --color-toast-success-background: 46, 46, 46; + --color-toast-error-background: 46, 46, 46; + --color-toast-warning-background: 46, 46, 46; + --color-toast-info-background: 46, 46, 46; + --color-toast-loading-background: 46, 46, 46; + + --color-toast-success-border: 42, 126, 59; + --color-toast-error-border: 100, 23, 35; + --color-toast-warning-border: 79, 52, 34; + --color-toast-info-border: 58, 91, 199; + --color-toast-loading-border: 96, 100, 108; } [data-theme="dark-contrast"] { diff --git a/yarn.lock b/yarn.lock index f413d1a44f1..81e6224e84d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8107,6 +8107,11 @@ snake-case@^3.0.4: dot-case "^3.0.4" tslib "^2.0.3" +sonner@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/sonner/-/sonner-1.4.2.tgz#92740c293e9d911de726080995bd8a0cc677ccd1" + integrity sha512-x3Kfzfhb56V/ErvUnH5dZcsu6QkZpyIlRAogO4vAbN+AkBsA/8CFqOV+5djqbE5pQCpejtO4JBWL1zRj2sO/Vg== + source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"