Refactor alertDropDown components
Adjust singleAlertComponent's formatting for better readability and add a custom hook to handle click outside functionality in alertDropdown component.
This commit is contained in:
parent
cdfaed1999
commit
917cc51148
6 changed files with 509 additions and 451 deletions
|
|
@ -1,10 +1,18 @@
|
||||||
import { XCircleIcon, XMarkIcon, InformationCircleIcon, CheckCircleIcon } from "@heroicons/react/24/outline";
|
import {
|
||||||
|
XCircleIcon,
|
||||||
|
XMarkIcon,
|
||||||
|
InformationCircleIcon,
|
||||||
|
CheckCircleIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { SingleAlertComponentType } from "../../../../types/alerts";
|
import { SingleAlertComponentType } from "../../../../types/alerts";
|
||||||
|
|
||||||
export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponentType) {
|
export default function SingleAlert({
|
||||||
|
dropItem,
|
||||||
|
removeAlert,
|
||||||
|
}: SingleAlertComponentType) {
|
||||||
const [show, setShow] = useState(true);
|
const [show, setShow] = useState(true);
|
||||||
const type = dropItem.type;
|
const type = dropItem.type;
|
||||||
|
|
||||||
|
|
@ -20,13 +28,13 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
|
||||||
leaveFrom={"transform translate-x-0"}
|
leaveFrom={"transform translate-x-0"}
|
||||||
leaveTo={"transform translate-x-[-100%]"}
|
leaveTo={"transform translate-x-[-100%]"}
|
||||||
>
|
>
|
||||||
{type === "error"?
|
{type === "error" ? (
|
||||||
<div className="flex bg-red-50 rounded-md p-3 mb-2 mx-2" key={dropItem.id}>
|
<div
|
||||||
|
className="flex bg-red-50 rounded-md p-3 mb-2 mx-2"
|
||||||
|
key={dropItem.id}
|
||||||
|
>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<XCircleIcon
|
<XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" />
|
||||||
className="h-5 w-5 text-red-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3">
|
<div className="ml-3">
|
||||||
<h3 className="text-sm font-medium text-red-800">
|
<h3 className="text-sm font-medium text-red-800">
|
||||||
|
|
@ -49,7 +57,10 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500);
|
setShow(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
removeAlert(dropItem.id);
|
||||||
|
}, 500);
|
||||||
}}
|
}}
|
||||||
className="inline-flex rounded-md bg-red-50 p-1.5 text-red-500"
|
className="inline-flex rounded-md bg-red-50 p-1.5 text-red-500"
|
||||||
>
|
>
|
||||||
|
|
@ -59,8 +70,11 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
:(type === "notice" ?
|
) : type === "notice" ? (
|
||||||
<div className="flex rounded-md bg-blue-50 p-3 mb-2 mx-2" key={dropItem.id}>
|
<div
|
||||||
|
className="flex rounded-md bg-blue-50 p-3 mb-2 mx-2"
|
||||||
|
key={dropItem.id}
|
||||||
|
>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<InformationCircleIcon
|
<InformationCircleIcon
|
||||||
className="h-5 w-5 text-blue-400"
|
className="h-5 w-5 text-blue-400"
|
||||||
|
|
@ -87,7 +101,10 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500);
|
setShow(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
removeAlert(dropItem.id);
|
||||||
|
}, 500);
|
||||||
}}
|
}}
|
||||||
className="inline-flex rounded-md bg-blue-50 p-1.5 text-blue-500"
|
className="inline-flex rounded-md bg-blue-50 p-1.5 text-blue-500"
|
||||||
>
|
>
|
||||||
|
|
@ -97,8 +114,11 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
:
|
) : (
|
||||||
<div className="flex bg-green-50 p-3 mb-2 mx-2 rounded-md" key={dropItem.id}>
|
<div
|
||||||
|
className="flex bg-green-50 p-3 mb-2 mx-2 rounded-md"
|
||||||
|
key={dropItem.id}
|
||||||
|
>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<CheckCircleIcon
|
<CheckCircleIcon
|
||||||
className="h-5 w-5 text-green-400"
|
className="h-5 w-5 text-green-400"
|
||||||
|
|
@ -115,7 +135,10 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500);
|
setShow(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
removeAlert(dropItem.id);
|
||||||
|
}, 500);
|
||||||
}}
|
}}
|
||||||
className="inline-flex rounded-md bg-green-50 p-1.5 text-green-500"
|
className="inline-flex rounded-md bg-green-50 p-1.5 text-green-500"
|
||||||
>
|
>
|
||||||
|
|
@ -125,10 +148,7 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
|
|
||||||
</Transition>
|
</Transition>
|
||||||
)
|
);
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -5,29 +5,15 @@ import { TrashIcon } from "@heroicons/react/24/outline";
|
||||||
import SingleAlert from "./components/singleAlertComponent";
|
import SingleAlert from "./components/singleAlertComponent";
|
||||||
import { AlertDropdownType } from "../../types/alerts";
|
import { AlertDropdownType } from "../../types/alerts";
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
|
import { useOnClickOutside } from "../hooks/useOnClickOutside";
|
||||||
export default function AlertDropdown({}: AlertDropdownType) {
|
export default function AlertDropdown({}: AlertDropdownType) {
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const componentRef = useRef<HTMLDivElement>(null);
|
const componentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
// Use the custom hook
|
||||||
function handleClickOutside(event: MouseEvent) {
|
useOnClickOutside(componentRef, () => {
|
||||||
if (
|
|
||||||
componentRef.current &&
|
|
||||||
!componentRef.current.contains(event.target as Node)
|
|
||||||
) {
|
|
||||||
closePopUp();
|
closePopUp();
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Bind the event listener
|
|
||||||
document.addEventListener("mousedown", handleClickOutside);
|
|
||||||
|
|
||||||
// Cleanup the event listener when the component is unmounted
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("mousedown", handleClickOutside);
|
|
||||||
};
|
|
||||||
}, [componentRef]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
notificationList,
|
notificationList,
|
||||||
|
|
@ -38,13 +24,13 @@ export default function AlertDropdown({}: AlertDropdownType) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={componentRef}
|
ref={componentRef}
|
||||||
className="z-10 py-3 pb-4 rounded-md bg-white ring-1 ring-black ring-opacity-5 shadow-lg focus:outline-none overflow-hidden w-[16rem] h-[28rem] flex flex-col"
|
className="z-10 py-3 pb-4 rounded-md bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 shadow-lg focus:outline-none overflow-hidden w-[16rem] h-[28rem] flex flex-col"
|
||||||
>
|
>
|
||||||
<div className="flex pl-3 flex-row justify-between text-md font-medium text-gray-800">
|
<div className="flex pl-3 flex-row justify-between text-md font-medium text-gray-800 dark:text-gray-200">
|
||||||
Notifications
|
Notifications
|
||||||
<div className="flex gap-2 pr-3 ">
|
<div className="flex gap-2 pr-3 ">
|
||||||
<button
|
<button
|
||||||
className="hover:text-red-500"
|
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
closePopUp();
|
closePopUp();
|
||||||
setTimeout(clearNotificationList, 100);
|
setTimeout(clearNotificationList, 100);
|
||||||
|
|
@ -52,12 +38,15 @@ export default function AlertDropdown({}: AlertDropdownType) {
|
||||||
>
|
>
|
||||||
<TrashIcon className="w-[1.1rem] h-[1.1rem]" />
|
<TrashIcon className="w-[1.1rem] h-[1.1rem]" />
|
||||||
</button>
|
</button>
|
||||||
<button className="hover:text-red-500" onClick={closePopUp}>
|
<button
|
||||||
|
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
|
||||||
|
onClick={closePopUp}
|
||||||
|
>
|
||||||
<XMarkIcon className="h-5 w-5" />
|
<XMarkIcon className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 flex flex-col overflow-y-scroll w-full h-full scrollbar-hide">
|
<div className="mt-2 flex flex-col overflow-y-scroll w-full h-full scrollbar-hide text-gray-900 dark:text-gray-300">
|
||||||
{notificationList.length !== 0 ? (
|
{notificationList.length !== 0 ? (
|
||||||
notificationList.map((alertItem, index) => (
|
notificationList.map((alertItem, index) => (
|
||||||
<SingleAlert
|
<SingleAlert
|
||||||
|
|
@ -67,7 +56,7 @@ export default function AlertDropdown({}: AlertDropdownType) {
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="h-full w-full pb-16 text-gray-500 flex justify-center items-center">
|
<div className="h-full w-full pb-16 text-gray-500 dark:text-gray-500 flex justify-center items-center">
|
||||||
No new notifications
|
No new notifications
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
33
src/frontend/src/alerts/hooks/useOnClickOutside/index.ts
Normal file
33
src/frontend/src/alerts/hooks/useOnClickOutside/index.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
export function useOnClickOutside(ref, handler) {
|
||||||
|
useEffect(() => {
|
||||||
|
const listener = (event) => {
|
||||||
|
// Do nothing if clicking ref's element or its children
|
||||||
|
if (!ref.current || ref.current.contains(event.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handler(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attach the listener to the document
|
||||||
|
document.addEventListener("mousedown", listener, { passive: true });
|
||||||
|
|
||||||
|
// Attach the listener to the react-flow instance
|
||||||
|
const reactFlowContainer = document.querySelector(".react-flow");
|
||||||
|
if (reactFlowContainer) {
|
||||||
|
reactFlowContainer.addEventListener("mousedown", listener, {
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the listener when the component is unmounted
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousedown", listener);
|
||||||
|
if (reactFlowContainer) {
|
||||||
|
reactFlowContainer.removeEventListener("mousedown", listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [ref, handler]); // Rerun only if ref or handler changes
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,7 @@ export default function ExportModal() {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
const {setErrorData}= useContext(alertContext)
|
const { setErrorData } = useContext(alertContext);
|
||||||
const { flows, tabIndex, updateFlow, downloadFlow } = useContext(TabsContext);
|
const { flows, tabIndex, updateFlow, downloadFlow } = useContext(TabsContext);
|
||||||
function setModalOpen(x: boolean) {
|
function setModalOpen(x: boolean) {
|
||||||
setOpen(x);
|
setOpen(x);
|
||||||
|
|
@ -25,8 +25,8 @@ export default function ExportModal() {
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const [checked,setChecked] = useState(true)
|
const [checked, setChecked] = useState(true);
|
||||||
const [name,setName] = useState(flows[tabIndex].name)
|
const [name, setName] = useState(flows[tabIndex].name);
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||||
<Dialog
|
<Dialog
|
||||||
|
|
@ -98,14 +98,13 @@ export default function ExportModal() {
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if(event.target.value!=""){
|
if (event.target.value != "") {
|
||||||
let newFlow = flows[tabIndex];
|
let newFlow = flows[tabIndex];
|
||||||
newFlow.name = event.target.value;
|
newFlow.name = event.target.value;
|
||||||
setName(event.target.value)
|
setName(event.target.value);
|
||||||
updateFlow(newFlow);
|
updateFlow(newFlow);
|
||||||
}
|
} else {
|
||||||
else{
|
setName(event.target.value);
|
||||||
setName(event.target.value)
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -113,7 +112,7 @@ export default function ExportModal() {
|
||||||
value={name ?? null}
|
value={name ?? null}
|
||||||
placeholder="File name"
|
placeholder="File name"
|
||||||
id="name"
|
id="name"
|
||||||
className="focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500"
|
className="focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500 text-gray-900 dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
|
|
@ -121,7 +120,11 @@ export default function ExportModal() {
|
||||||
htmlFor="description"
|
htmlFor="description"
|
||||||
className="block mb-2 font-medium text-gray-700 dark:text-white"
|
className="block mb-2 font-medium text-gray-700 dark:text-white"
|
||||||
>
|
>
|
||||||
Description <span className="text-gray-400 text-sm"> (optional)</span>
|
Description{" "}
|
||||||
|
<span className="text-gray-400 text-sm">
|
||||||
|
{" "}
|
||||||
|
(optional)
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
name="description"
|
name="description"
|
||||||
|
|
@ -134,14 +137,15 @@ export default function ExportModal() {
|
||||||
value={flows[tabIndex].description ?? null}
|
value={flows[tabIndex].description ?? null}
|
||||||
placeholder="Flow description"
|
placeholder="Flow description"
|
||||||
rows={3}
|
rows={3}
|
||||||
className=" focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500"
|
className=" focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 text-gray-900 dark:text-gray-100 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500"
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="checkbox" className="flex items-center">
|
<label htmlFor="checkbox" className="flex items-center">
|
||||||
<input
|
<input
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setChecked(event.target.checked)
|
setChecked(event.target.checked);
|
||||||
}}
|
}}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
id="checkbox"
|
id="checkbox"
|
||||||
|
|
@ -156,8 +160,8 @@ export default function ExportModal() {
|
||||||
<div className="w-full flex justify-end">
|
<div className="w-full flex justify-end">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if(checked) downloadFlow(flows[tabIndex]);
|
if (checked) downloadFlow(flows[tabIndex]);
|
||||||
else downloadFlow(removeApiKeys(flows[tabIndex]))
|
else downloadFlow(removeApiKeys(flows[tabIndex]));
|
||||||
}}
|
}}
|
||||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -54,18 +54,13 @@ export default function TabsManagerComponent() {
|
||||||
/>
|
/>
|
||||||
<div className="ml-auto mr-2 flex gap-3">
|
<div className="ml-auto mr-2 flex gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() => openPopUp(<ImportModal />)}
|
||||||
openPopUp(
|
|
||||||
<ImportModal
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className="flex items-center gap-1 pr-2 border-gray-400 border-r text-sm text-gray-600 hover:text-gray-500"
|
className="flex items-center gap-1 pr-2 border-gray-400 border-r text-sm text-gray-600 hover:text-gray-500"
|
||||||
>
|
>
|
||||||
Import <ArrowUpTrayIcon className="w-5 h-5" />
|
Import <ArrowUpTrayIcon className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() =>openPopUp(<ExportModal/>)}
|
onClick={() => openPopUp(<ExportModal />)}
|
||||||
className="flex items-center gap-1 mr-2 text-sm text-gray-600 hover:text-gray-500"
|
className="flex items-center gap-1 mr-2 text-sm text-gray-600 hover:text-gray-500"
|
||||||
>
|
>
|
||||||
Export <ArrowDownTrayIcon className="h-5 w-5" />
|
Export <ArrowDownTrayIcon className="h-5 w-5" />
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,24 @@
|
||||||
export type ErrorAlertType = {title:string,list:Array<string>,id:string,removeAlert:(id:string)=>void}
|
export type ErrorAlertType = {
|
||||||
export type NoticeAlertType = {title:string,link:string,id:string,removeAlert:(id:string)=>void}
|
title: string;
|
||||||
export type SuccessAlertType = {title:string,id:string, removeAlert:(id:string)=>void}
|
list: Array<string>;
|
||||||
export type SingleAlertComponentType = {dropItem:AlertItemType,removeAlert:(index:string)=>void}
|
id: string;
|
||||||
|
removeAlert: (id: string) => void;
|
||||||
|
};
|
||||||
|
export type NoticeAlertType = {
|
||||||
|
title: string;
|
||||||
|
link: string;
|
||||||
|
id: string;
|
||||||
|
removeAlert: (id: string) => void;
|
||||||
|
};
|
||||||
|
export type SuccessAlertType = {
|
||||||
|
title: string;
|
||||||
|
id: string;
|
||||||
|
removeAlert: (id: string) => void;
|
||||||
|
};
|
||||||
|
export type SingleAlertComponentType = {
|
||||||
|
dropItem: AlertItemType;
|
||||||
|
removeAlert: (index: string) => void;
|
||||||
|
};
|
||||||
export type AlertDropdownType = {};
|
export type AlertDropdownType = {};
|
||||||
export type AlertItemType = {
|
export type AlertItemType = {
|
||||||
type: "notice" | "error" | "success";
|
type: "notice" | "error" | "success";
|
||||||
|
|
@ -9,4 +26,4 @@ export type AlertItemType = {
|
||||||
link?: string;
|
link?: string;
|
||||||
list?: Array<string>;
|
list?: Array<string>;
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue