From d5410799e49b95fcfcdffc470efe6a9350e43c42 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 21 Oct 2025 11:24:34 +0100 Subject: [PATCH] fix: prevent new workspace modal from being closed without an active workspace --- apps/web/src/components/Dashboard.tsx | 17 +++++++++++------ apps/web/src/components/modal.tsx | 18 +++++++++++++++--- apps/web/src/providers/modal.tsx | 19 +++++++++++++++++-- apps/web/src/views/boards/index.tsx | 11 ++--------- 4 files changed, 45 insertions(+), 20 deletions(-) diff --git a/apps/web/src/components/Dashboard.tsx b/apps/web/src/components/Dashboard.tsx index 461bc5cb..61fa6a30 100644 --- a/apps/web/src/components/Dashboard.tsx +++ b/apps/web/src/components/Dashboard.tsx @@ -1,4 +1,5 @@ -import { useRef, useState } from "react"; +import { useTheme } from "next-themes"; +import { useEffect, useRef, useState } from "react"; import { TbLayoutSidebarLeftCollapse, TbLayoutSidebarLeftExpand, @@ -10,10 +11,7 @@ import { authClient } from "@kan/auth/client"; import { useClickOutside } from "~/hooks/useClickOutside"; import { useModal } from "~/providers/modal"; -import { useTheme } from "next-themes"; -import { WorkspaceProvider } from "~/providers/workspace"; -import FeedbackModal from "./FeedbackModal"; -import Modal from "./modal"; +import { useWorkspace, WorkspaceProvider } from "~/providers/workspace"; import SideNavigation from "./SideNavigation"; interface DashboardProps { @@ -42,7 +40,8 @@ export default function Dashboard({ hasRightPanel = false, }: DashboardProps) { const { theme } = useTheme(); - const { modalContentType } = useModal(); + const { openModal } = useModal(); + const { availableWorkspaces, hasLoaded } = useWorkspace(); const { data: session, isPending: sessionLoading } = authClient.useSession(); @@ -90,6 +89,12 @@ export default function Dashboard({ } }); + useEffect(() => { + if (hasLoaded && availableWorkspaces.length === 0) { + openModal("NEW_WORKSPACE", undefined, undefined, false); + } + }, [hasLoaded, availableWorkspaces.length, openModal]); + const isDarkMode = theme === "dark"; return ( diff --git a/apps/web/src/components/modal.tsx b/apps/web/src/components/modal.tsx index 5d5f5778..c094328e 100644 --- a/apps/web/src/components/modal.tsx +++ b/apps/web/src/components/modal.tsx @@ -8,6 +8,7 @@ interface Props { modalSize?: "sm" | "md" | "lg"; positionFromTop?: "sm" | "md" | "lg"; isVisible?: boolean; + closeOnClickOutside?: boolean; } const Modal: React.FC = ({ @@ -15,10 +16,17 @@ const Modal: React.FC = ({ modalSize = "sm", positionFromTop = "md", isVisible, + closeOnClickOutside, }) => { - const { isOpen, closeModal } = useModal(); + const { + isOpen, + closeModal, + closeOnClickOutside: modalCloseOnClickOutside, + } = useModal(); - const shouldShow = isVisible !== undefined ? isVisible : isOpen; + const shouldShow = isVisible ?? isOpen; + const shouldCloseOnClickOutside = + closeOnClickOutside ?? modalCloseOnClickOutside; const modalSizeMap = { sm: "max-w-[400px]", @@ -34,7 +42,11 @@ const Modal: React.FC = ({ return ( - + null} + > void; closeModal: () => void; closeModals: (count: number) => void; @@ -23,6 +25,7 @@ interface ModalContextType { modalContentType: string; entityId: string; entityLabel: string; + closeOnClickOutside: boolean; modalStates: Record; setModalState: (modalType: string, state: any) => void; getModalState: (modalType: string) => any; @@ -41,10 +44,21 @@ export const ModalProvider: React.FC = ({ children }) => { const modalContentType = currentModal?.contentType || ""; const entityId = currentModal?.entityId || ""; const entityLabel = currentModal?.entityLabel || ""; + const closeOnClickOutside = currentModal?.closeOnClickOutside ?? true; const openModal = useCallback( - (contentType: string, entityId?: string, entityLabel?: string) => { - const newModal: ModalState = { contentType, entityId, entityLabel }; + ( + contentType: string, + entityId?: string, + entityLabel?: string, + closeOnClickOutside?: boolean, + ) => { + const newModal: ModalState = { + contentType, + entityId, + entityLabel, + closeOnClickOutside, + }; setModalStack((prev) => [...prev, newModal]); }, [], @@ -107,6 +121,7 @@ export const ModalProvider: React.FC = ({ children }) => { modalContentType, entityId, entityLabel, + closeOnClickOutside, modalStates, setModalState, getModalState, diff --git a/apps/web/src/views/boards/index.tsx b/apps/web/src/views/boards/index.tsx index f719a5a9..795f427a 100644 --- a/apps/web/src/views/boards/index.tsx +++ b/apps/web/src/views/boards/index.tsx @@ -1,5 +1,4 @@ import { t } from "@lingui/core/macro"; -import { useEffect } from "react"; import { HiArrowDownTray, HiOutlinePlusSmall } from "react-icons/hi2"; import Button from "~/components/Button"; @@ -15,18 +14,12 @@ import { NewBoardForm } from "./components/NewBoardForm"; export default function BoardsPage({ isTemplate }: { isTemplate?: boolean }) { const { openModal, modalContentType, isOpen } = useModal(); - const { availableWorkspaces, workspace, hasLoaded } = useWorkspace(); - - useEffect(() => { - if (hasLoaded && availableWorkspaces.length === 0) { - openModal("NEW_WORKSPACE"); - } - }, [hasLoaded, availableWorkspaces.length, openModal]); + const { workspace } = useWorkspace(); return ( <>