From 56ed2221c8475c4994a53f9e3818f63312e0408f Mon Sep 17 00:00:00 2001 From: C4rnivore Date: Mon, 20 Oct 2025 17:08:35 +0500 Subject: [PATCH] toasts; --- client/src/components/toasts/ToastLayout.tsx | 61 +++++++++++++++++++ .../src/components/toasts/ToastsContainer.tsx | 31 ++++++++++ client/src/main.tsx | 2 + client/src/pages/HomePage.tsx | 27 ++++++++ client/src/store/toastsStore.ts | 24 ++++++++ 5 files changed, 145 insertions(+) create mode 100644 client/src/components/toasts/ToastLayout.tsx create mode 100644 client/src/components/toasts/ToastsContainer.tsx create mode 100644 client/src/store/toastsStore.ts diff --git a/client/src/components/toasts/ToastLayout.tsx b/client/src/components/toasts/ToastLayout.tsx new file mode 100644 index 0000000..8f3567d --- /dev/null +++ b/client/src/components/toasts/ToastLayout.tsx @@ -0,0 +1,61 @@ +import clsx from "clsx"; +import React from "react"; +import Button from "../ui/Button"; +import useToastsStore from "../../store/toastsStore"; + +export interface ToastLayoutProps { + id: string; + type: "notification" | "warning"; + title: string; + message: string; + onDeny: () => void; + onAllow: () => void; + image?: string; + icon?: React.ReactNode; +} + +export default function ToastLayout({ + id, + type, + title, + message, + onDeny, + onAllow, +}: ToastLayoutProps) { + const { removeToast } = useToastsStore(); + + function handleDeny() { + onDeny(); + removeToast(id); + } + + function handleAllow() { + onAllow(); + removeToast(id); + } + + return ( +
+
+
+ {title} +
+
{message}
+ +
+ + +
+
+
+ ); +} diff --git a/client/src/components/toasts/ToastsContainer.tsx b/client/src/components/toasts/ToastsContainer.tsx new file mode 100644 index 0000000..fe7f8b6 --- /dev/null +++ b/client/src/components/toasts/ToastsContainer.tsx @@ -0,0 +1,31 @@ +import useToastsStore from "../../store/toastsStore"; +import ToastLayout from "./ToastLayout"; +import { AnimatePresence, motion } from "motion/react"; + +export default function ToastsContainer() { + const { toasts } = useToastsStore(); + + return ( +
+ + {toasts.map((toast) => ( + + + + ))} + +
+ ); +} diff --git a/client/src/main.tsx b/client/src/main.tsx index 16220df..5f0f989 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -11,6 +11,7 @@ import ProtectedRoute from "./components/ProtectedRoute"; import PublicRoute from "./components/PublicRoute"; import ModalContainer from "./components/ModalContainer"; import PopupContainer from "./components/PopupContainer"; +import ToastsContainer from "./components/toasts/ToastsContainer"; // import NewSessionPage from "./pages/NewSessionPage"; import TestPage from "./pages/TestPage"; import NewSessionPage from "./pages/NewSessionPage"; @@ -55,5 +56,6 @@ createRoot(document.getElementById("root")!).render( + ); diff --git a/client/src/pages/HomePage.tsx b/client/src/pages/HomePage.tsx index ae9a568..73412f8 100644 --- a/client/src/pages/HomePage.tsx +++ b/client/src/pages/HomePage.tsx @@ -9,6 +9,8 @@ import useModalStore from "../store/modalStore"; import CogFilledIcon from "../components/icons/CogFilledIcon"; import SessionUsersPanel from "../components/SessionUsersPanel"; import SharePopup from "../components/popups/SharePopup"; +import { useEffect } from "react"; +import useToastsStore from "../store/toastsStore"; function HomePage() { const { data: user } = useMe(); @@ -23,6 +25,31 @@ function HomePage() { const { setPopup } = usePopupStore(); const { setModal } = useModalStore(); + // -------------------------------- Toasts test -------------------------------- + const { addToast } = useToastsStore(); + + useEffect(() => { + addToast({ + id: crypto.randomUUID(), + type: "warning", + title: "Тестовое предупреждение", + message: "Это тестовое предупреждение", + onDeny: () => {}, + onAllow: () => {}, + }); + const timer = setTimeout(() => { + addToast({ + id: crypto.randomUUID(), + type: "notification", + title: "Тестовое уведомление", + message: "Это тестовое уведомление", + onDeny: () => {}, + onAllow: () => {}, + }); + }, 1000); + return () => clearTimeout(timer); + }, []); + return (
diff --git a/client/src/store/toastsStore.ts b/client/src/store/toastsStore.ts new file mode 100644 index 0000000..956ce27 --- /dev/null +++ b/client/src/store/toastsStore.ts @@ -0,0 +1,24 @@ +import { create } from "zustand"; +import type { ToastLayoutProps } from "../components/toasts/ToastLayout"; + +interface ToastState { + toasts: ToastLayoutProps[]; + addToast: (toast: ToastLayoutProps) => void; + removeToast: (id: ToastLayoutProps["id"]) => void; + clearToasts: () => void; +} + +const useToastsStore = create((set) => ({ + toasts: [], + addToast: (toast) => + set((state) => ({ + toasts: [...state.toasts, toast], + })), + removeToast: (id) => + set((state) => ({ + toasts: state.toasts.filter((toast) => toast.id !== id), + })), + clearToasts: () => set({ toasts: [] }), +})); + +export default useToastsStore; \ No newline at end of file