mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 02:51:51 +00:00
feat(ui): changed to a better toast for ui and a global error handler for all server error messages
This commit is contained in:
@@ -1,66 +0,0 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
type NotificationType = "success" | "error" | "info";
|
||||
|
||||
export type TNotification = {
|
||||
text: string;
|
||||
type?: NotificationType;
|
||||
timeoutMs?: number;
|
||||
};
|
||||
|
||||
interface NotificationProps {
|
||||
notification: Required<TNotification>;
|
||||
clearNotification: (text: string) => void;
|
||||
}
|
||||
|
||||
const Notification = ({ notification, clearNotification }: NotificationProps) => {
|
||||
const timeout = useRef<number>();
|
||||
|
||||
const handleClearNotification = () => clearNotification(notification.text);
|
||||
|
||||
const setNotifTimeout = () => {
|
||||
timeout.current = window.setTimeout(handleClearNotification, notification.timeoutMs);
|
||||
};
|
||||
|
||||
const cancelNotifTimeout = () => {
|
||||
clearTimeout(timeout.current);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setNotifTimeout();
|
||||
|
||||
return cancelNotifTimeout;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="pointer-events-auto relative right-3 mb-3 flex w-full items-center justify-between rounded-md border border-bunker-500 bg-mineshaft-700 px-6 py-4"
|
||||
role="alert"
|
||||
>
|
||||
{notification.type === "error" && (
|
||||
<div className="absolute top-0 left-0 h-1 w-full rounded-t-md bg-red" />
|
||||
)}
|
||||
{notification.type === "success" && (
|
||||
<div className="absolute top-0 left-0 h-1 w-full rounded-t-md bg-green" />
|
||||
)}
|
||||
{notification.type === "info" && (
|
||||
<div className="absolute top-0 left-0 h-1 w-full rounded-t-md bg-yellow" />
|
||||
)}
|
||||
<p className="text-md font-base mt-0.5 text-bunker-200">{notification.text}</p>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-lg"
|
||||
onClick={() => clearNotification(notification.text)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className="absolute right-2 top-3 h-4 w-4 pl-2 text-bunker-300 hover:text-white"
|
||||
icon={faXmark}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Notification;
|
||||
@@ -1,52 +0,0 @@
|
||||
import { createContext, ReactNode, useCallback, useContext, useMemo, useState } from "react";
|
||||
|
||||
import { TNotification } from "./Notification";
|
||||
import Notifications from "./Notifications";
|
||||
|
||||
type NotificationContextState = {
|
||||
createNotification: (newNotification: TNotification) => void;
|
||||
};
|
||||
|
||||
const NotificationContext = createContext<NotificationContextState>({
|
||||
createNotification: () => console.log("createNotification not set!")
|
||||
});
|
||||
|
||||
export const useNotificationContext = () => useContext(NotificationContext);
|
||||
|
||||
interface NotificationProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
// TODO: Migration to radix toast
|
||||
const NotificationProvider = ({ children }: NotificationProviderProps) => {
|
||||
const [notifications, setNotifications] = useState<Required<TNotification>[]>([]);
|
||||
|
||||
const clearNotification = (text: string) =>
|
||||
setNotifications((state) => state.filter((notif) => notif.text !== text));
|
||||
|
||||
const createNotification = useCallback(
|
||||
({ text, type = "success", timeoutMs = 4000 }: TNotification) => {
|
||||
const doesNotifExist = notifications.some((notif) => notif.text === text);
|
||||
|
||||
if (doesNotifExist) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newNotification: Required<TNotification> = { text, type, timeoutMs };
|
||||
|
||||
setNotifications((state) => [...state, newNotification]);
|
||||
},
|
||||
[notifications]
|
||||
);
|
||||
|
||||
const value = useMemo(() => ({ createNotification }), [createNotification]);
|
||||
|
||||
return (
|
||||
<NotificationContext.Provider value={value}>
|
||||
<Notifications notifications={notifications} clearNotification={clearNotification} />
|
||||
{children}
|
||||
</NotificationContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotificationProvider;
|
||||
@@ -1,22 +0,0 @@
|
||||
import Notification, { TNotification } from "./Notification";
|
||||
|
||||
interface NoticationsProps {
|
||||
notifications: Required<TNotification>[];
|
||||
clearNotification: (text: string) => void;
|
||||
}
|
||||
|
||||
const Notifications = ({ notifications, clearNotification }: NoticationsProps) => {
|
||||
if (!notifications.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="pointer-events-none fixed right-2 bottom-2 z-[100] hidden h-full w-96 gap-y-2 md:flex md:flex-col-reverse">
|
||||
{notifications.map((notif) => (
|
||||
<Notification key={notif.text} notification={notif} clearNotification={clearNotification} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Notifications;
|
||||
@@ -8,7 +8,7 @@ import { parseDocument, Scalar, YAMLMap } from "yaml";
|
||||
|
||||
import Button from "../basic/buttons/Button";
|
||||
import Error from "../basic/Error";
|
||||
import { useNotificationContext } from "../context/Notifications/NotificationProvider";
|
||||
import { createNotification } from "../notifications";
|
||||
import { parseDotEnv } from "../utilities/parseDotEnv";
|
||||
import guidGenerator from "../utilities/randomId";
|
||||
|
||||
@@ -33,7 +33,7 @@ const DropZone = ({
|
||||
numCurrentRows
|
||||
}: DropZoneProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { createNotification } = useNotificationContext();
|
||||
|
||||
|
||||
const handleDragEnter = (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -133,7 +133,6 @@ const DropZone = ({
|
||||
createNotification({
|
||||
text: "You can't inject files from VS Code. Click 'Reveal in finder', and drag your file directly from the directory where it's located.",
|
||||
type: "error",
|
||||
timeoutMs: 10000
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ReactNode } from "react";
|
||||
import { Id, toast, ToastContainer, ToastOptions, TypeOptions } from "react-toastify";
|
||||
|
||||
export type TNotification = {
|
||||
title?: string;
|
||||
text: ReactNode;
|
||||
};
|
||||
|
||||
export const NotificationContent = ({ title, text }: TNotification) => {
|
||||
return (
|
||||
<div className="msg-container">
|
||||
{title && <div className="text-md mb-1 font-medium">{title}</div>}
|
||||
<div className={title ? "text-sm" : "text-md"}>{text}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const createNotification = (
|
||||
myProps: TNotification & { type?: TypeOptions },
|
||||
toastProps: ToastOptions = {}
|
||||
): Id =>
|
||||
toast(<NotificationContent {...myProps} />, {
|
||||
position: "bottom-right",
|
||||
...toastProps,
|
||||
theme: "dark",
|
||||
type: myProps?.type || "info"
|
||||
});
|
||||
|
||||
export const NotificationContainer = () => <ToastContainer hideProgressBar />;
|
||||
@@ -0,0 +1 @@
|
||||
export { createNotification, NotificationContainer } from "./Notifications";
|
||||
@@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { generateUserBackupKey } from "@app/lib/crypto";
|
||||
|
||||
import { useNotificationContext } from "../context/Notifications/NotificationProvider";
|
||||
import { createNotification } from "../notifications";
|
||||
import { generateBackupPDFAsync } from "../utilities/generateBackupPDF";
|
||||
import { Button } from "../v2";
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function DonwloadBackupPDFStep({
|
||||
name
|
||||
}: DownloadBackupPDFStepProps): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const { createNotification } = useNotificationContext();
|
||||
|
||||
const [isLoading, setIsLoading] = useToggle();
|
||||
|
||||
const handleBackupKeyGenerate = async () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import Link from "next/link";
|
||||
import axios from "axios";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { useSendVerificationEmail } from "@app/hooks/api";
|
||||
|
||||
import { Button, Input } from "../v2";
|
||||
@@ -27,7 +27,7 @@ export default function EnterEmailStep({
|
||||
setEmail,
|
||||
incrementStep
|
||||
}: DownloadBackupPDFStepProps): JSX.Element {
|
||||
const { createNotification } = useNotificationContext();
|
||||
|
||||
const { mutateAsync, isLoading } = useSendVerificationEmail();
|
||||
const [emailError, setEmailError] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
@@ -110,7 +110,7 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => {
|
||||
} = useForm<FormData>({
|
||||
resolver: zodResolver(createTagSchema)
|
||||
});
|
||||
const { createNotification } = useNotificationContext();
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { useProjectPermission } from "@app/context";
|
||||
import { useGetUpgradeProjectStatus, useUpgradeProject } from "@app/hooks/api";
|
||||
import { Workspace } from "@app/hooks/api/types";
|
||||
@@ -25,7 +25,7 @@ export const UpgradeProjectAlert = ({
|
||||
project,
|
||||
transparent
|
||||
}: UpgradeProjectAlertProps): JSX.Element | null => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
|
||||
const router = useRouter();
|
||||
const { membership } = useProjectPermission();
|
||||
const upgradeProject = useUpgradeProject();
|
||||
|
||||
Reference in New Issue
Block a user