Compare commits

...

1 Commits

Author SHA1 Message Date
Henry
d5410799e4 fix: prevent new workspace modal from being closed without an active workspace 2025-10-21 11:24:34 +01:00
4 changed files with 45 additions and 20 deletions

View File

@@ -1,4 +1,5 @@
import { useRef, useState } from "react"; import { useTheme } from "next-themes";
import { useEffect, useRef, useState } from "react";
import { import {
TbLayoutSidebarLeftCollapse, TbLayoutSidebarLeftCollapse,
TbLayoutSidebarLeftExpand, TbLayoutSidebarLeftExpand,
@@ -10,10 +11,7 @@ import { authClient } from "@kan/auth/client";
import { useClickOutside } from "~/hooks/useClickOutside"; import { useClickOutside } from "~/hooks/useClickOutside";
import { useModal } from "~/providers/modal"; import { useModal } from "~/providers/modal";
import { useTheme } from "next-themes"; import { useWorkspace, WorkspaceProvider } from "~/providers/workspace";
import { WorkspaceProvider } from "~/providers/workspace";
import FeedbackModal from "./FeedbackModal";
import Modal from "./modal";
import SideNavigation from "./SideNavigation"; import SideNavigation from "./SideNavigation";
interface DashboardProps { interface DashboardProps {
@@ -42,7 +40,8 @@ export default function Dashboard({
hasRightPanel = false, hasRightPanel = false,
}: DashboardProps) { }: DashboardProps) {
const { theme } = useTheme(); const { theme } = useTheme();
const { modalContentType } = useModal(); const { openModal } = useModal();
const { availableWorkspaces, hasLoaded } = useWorkspace();
const { data: session, isPending: sessionLoading } = authClient.useSession(); 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"; const isDarkMode = theme === "dark";
return ( return (

View File

@@ -8,6 +8,7 @@ interface Props {
modalSize?: "sm" | "md" | "lg"; modalSize?: "sm" | "md" | "lg";
positionFromTop?: "sm" | "md" | "lg"; positionFromTop?: "sm" | "md" | "lg";
isVisible?: boolean; isVisible?: boolean;
closeOnClickOutside?: boolean;
} }
const Modal: React.FC<Props> = ({ const Modal: React.FC<Props> = ({
@@ -15,10 +16,17 @@ const Modal: React.FC<Props> = ({
modalSize = "sm", modalSize = "sm",
positionFromTop = "md", positionFromTop = "md",
isVisible, 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 = { const modalSizeMap = {
sm: "max-w-[400px]", sm: "max-w-[400px]",
@@ -34,7 +42,11 @@ const Modal: React.FC<Props> = ({
return ( return (
<Transition.Root show={shouldShow} as={Fragment}> <Transition.Root show={shouldShow} as={Fragment}>
<Dialog as="div" className="relative z-50" onClose={closeModal}> <Dialog
as="div"
className="relative z-50"
onClose={shouldCloseOnClickOutside ? closeModal : () => null}
>
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"

View File

@@ -4,6 +4,7 @@ interface ModalState {
contentType: string; contentType: string;
entityId?: string; entityId?: string;
entityLabel?: string; entityLabel?: string;
closeOnClickOutside?: boolean;
} }
interface Props { interface Props {
@@ -16,6 +17,7 @@ interface ModalContextType {
contentType: string, contentType: string,
entityId?: string, entityId?: string,
entityLabel?: string, entityLabel?: string,
closeOnClickOutside?: boolean,
) => void; ) => void;
closeModal: () => void; closeModal: () => void;
closeModals: (count: number) => void; closeModals: (count: number) => void;
@@ -23,6 +25,7 @@ interface ModalContextType {
modalContentType: string; modalContentType: string;
entityId: string; entityId: string;
entityLabel: string; entityLabel: string;
closeOnClickOutside: boolean;
modalStates: Record<string, any>; modalStates: Record<string, any>;
setModalState: (modalType: string, state: any) => void; setModalState: (modalType: string, state: any) => void;
getModalState: (modalType: string) => any; getModalState: (modalType: string) => any;
@@ -41,10 +44,21 @@ export const ModalProvider: React.FC<Props> = ({ children }) => {
const modalContentType = currentModal?.contentType || ""; const modalContentType = currentModal?.contentType || "";
const entityId = currentModal?.entityId || ""; const entityId = currentModal?.entityId || "";
const entityLabel = currentModal?.entityLabel || ""; const entityLabel = currentModal?.entityLabel || "";
const closeOnClickOutside = currentModal?.closeOnClickOutside ?? true;
const openModal = useCallback( 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]); setModalStack((prev) => [...prev, newModal]);
}, },
[], [],
@@ -107,6 +121,7 @@ export const ModalProvider: React.FC<Props> = ({ children }) => {
modalContentType, modalContentType,
entityId, entityId,
entityLabel, entityLabel,
closeOnClickOutside,
modalStates, modalStates,
setModalState, setModalState,
getModalState, getModalState,

View File

@@ -1,5 +1,4 @@
import { t } from "@lingui/core/macro"; import { t } from "@lingui/core/macro";
import { useEffect } from "react";
import { HiArrowDownTray, HiOutlinePlusSmall } from "react-icons/hi2"; import { HiArrowDownTray, HiOutlinePlusSmall } from "react-icons/hi2";
import Button from "~/components/Button"; import Button from "~/components/Button";
@@ -15,18 +14,12 @@ import { NewBoardForm } from "./components/NewBoardForm";
export default function BoardsPage({ isTemplate }: { isTemplate?: boolean }) { export default function BoardsPage({ isTemplate }: { isTemplate?: boolean }) {
const { openModal, modalContentType, isOpen } = useModal(); const { openModal, modalContentType, isOpen } = useModal();
const { availableWorkspaces, workspace, hasLoaded } = useWorkspace(); const { workspace } = useWorkspace();
useEffect(() => {
if (hasLoaded && availableWorkspaces.length === 0) {
openModal("NEW_WORKSPACE");
}
}, [hasLoaded, availableWorkspaces.length, openModal]);
return ( return (
<> <>
<PageHead <PageHead
title={t`${isTemplate ? "Templates" : "Boards"} | ${workspace.name ?? "Workspace"}`} title={t`${isTemplate ? "Templates" : "Boards"} | ${workspace.name || "Workspace"}`}
/> />
<div className="m-auto h-full max-w-[1100px] p-6 px-5 md:px-28 md:py-12"> <div className="m-auto h-full max-w-[1100px] p-6 px-5 md:px-28 md:py-12">
<div className="relative z-10 mb-8 flex w-full items-center justify-between"> <div className="relative z-10 mb-8 flex w-full items-center justify-between">