From 91381bc53b5b9fbafb271150103ec5e3805ed2fd Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sun, 30 Nov 2025 07:47:28 -0700 Subject: [PATCH] feat: add deprecation notice modal and fix stage plot blank page bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add DeprecationNoticeModal component with eventools branding - Gentle/positive messaging about platform transition - Links to eventools.io for beta signup - Mentions upcoming data export and self-hosting docs - Create useDeprecationNotice hook for SSR-safe localStorage access - Centralizes modal logic for Landing and Dashboard pages - Uses useEffect to avoid SSR hydration errors - Integrate modal into Landing and Dashboard pages - Shared localStorage key so dismissing on one page dismisses both - Fix StagePlotEditor TDZ (Temporal Dead Zone) bug - Reorder code so collaborationEnabled and broadcast are defined before being referenced in useEffect dependency array - Fixes blank page error in production builds 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/web/public/eventools-logo.png | Bin 0 -> 7616 bytes .../src/components/DeprecationNoticeModal.tsx | 84 ++++++++++++++++++ apps/web/src/hooks/useDeprecationNotice.ts | 21 +++++ apps/web/src/pages/Dashboard.tsx | 5 ++ apps/web/src/pages/Landing.tsx | 6 ++ apps/web/src/pages/StagePlotEditor.tsx | 74 +++++++-------- 6 files changed, 153 insertions(+), 37 deletions(-) create mode 100644 apps/web/public/eventools-logo.png create mode 100644 apps/web/src/components/DeprecationNoticeModal.tsx create mode 100644 apps/web/src/hooks/useDeprecationNotice.ts diff --git a/apps/web/public/eventools-logo.png b/apps/web/public/eventools-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7cc536a733f9c08c854aae95afa8806c8c7b8b14 GIT binary patch literal 7616 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 !2&G28vKAJ&Nbel7F$>+KXKD0TrS(} zyCX$;YT2RiDZe_7d>xE(VVR3p!%m+x_$DWNk1GkQE9iZXmxPA%PFc)$qZ<8`6DVBr z%ocK9)kIJYgO8L>@(EX~+NklE>groRCF3X~)FE{pV$QVkhu5_2@_ZL~KweTgr7z!~ zRY9LPxDt(N{2t=tEN7p-w@_66D={G<76<_8B`pQ|)}~}32cqxlN5&#mx@{sanLAYL z4-MnT+H34f6Y>EM>)gKn<#1UrRx&xU=EifIL@IzuWOp+8Zvm-8UH=PzxSbMdLXTLY z>wjq*;bj(SI4N|ZD0IQ@sYr$AF!6Qcj4n%~)iTF2fCYSvugDGO&@YY$_1pbd;!^Jw zgDA~#bF;P}KE|MQF)3nyIUxeb!^Nudj70P>^`+)r5h$|oCP!;sX{mG7`GNq%H?nZf z=^p65lK&U7aEl&^C{Y2oj!V1U2xgf?oVRiu2pQ7w69pmUGhKQx0_(5Yb*>sE-R%p6 zFFvb)su;2qenzHbP3E#Een9ZnW>|?dbQfaEZihwtbdyTpg+<%>fR-$@i8^hxTb{L_ zU;)@{Ca8)tGJS&lya(Ii_w0CmthEc&=_NuU;77bLcf9VkwRwitduwN}rN|-Om*8ML c50w1 void; +} + +const DeprecationNoticeModal: React.FC = ({ isOpen, onClose }) => { + if (!isOpen) return null; + + return ( +
+
e.stopPropagation()} + > +
+
+ +
+ +
+ eventools + +

We're Building Something New!

+ +
+

+ We're excited to announce that we're working on{" "} + eventools — the next + generation of event production tools. +

+ +

SoundDocs will be transitioning to eventools in the near future.

+ +
+

Don't worry — we'll provide:

+
    +
  • + + Instructions for downloading your data +
  • +
  • + + Self-hosting documentation for SoundDocs +
  • +
+
+
+ +
+ + Sign Up for eventools Beta + + + +
+
+
+
+
+ ); +}; + +export default DeprecationNoticeModal; diff --git a/apps/web/src/hooks/useDeprecationNotice.ts b/apps/web/src/hooks/useDeprecationNotice.ts new file mode 100644 index 0000000..9f3bd2a --- /dev/null +++ b/apps/web/src/hooks/useDeprecationNotice.ts @@ -0,0 +1,21 @@ +import { useState, useEffect } from "react"; + +const DEPRECATION_NOTICE_KEY = "sounddocs-deprecation-notice-dismissed"; + +export const useDeprecationNotice = () => { + const [isOpen, setIsOpen] = useState(false); + + useEffect(() => { + const dismissed = localStorage.getItem(DEPRECATION_NOTICE_KEY) === "true"; + if (!dismissed) { + setIsOpen(true); + } + }, []); + + const onClose = () => { + localStorage.setItem(DEPRECATION_NOTICE_KEY, "true"); + setIsOpen(false); + }; + + return { isOpen, onClose }; +}; diff --git a/apps/web/src/pages/Dashboard.tsx b/apps/web/src/pages/Dashboard.tsx index da45092..b2e675a 100644 --- a/apps/web/src/pages/Dashboard.tsx +++ b/apps/web/src/pages/Dashboard.tsx @@ -5,6 +5,8 @@ import { useAuth } from "../lib/AuthContext"; import Header from "../components/Header"; import Footer from "../components/Footer"; import AcoustIqBanner from "../components/AcoustIqBanner"; +import DeprecationNoticeModal from "@/components/DeprecationNoticeModal"; +import { useDeprecationNotice } from "@/hooks/useDeprecationNotice"; import { Info, Loader, @@ -21,6 +23,8 @@ import { const Dashboard = () => { const navigate = useNavigate(); const { user: authUser, loading: authLoading } = useAuth(); + const { isOpen: showDeprecationModal, onClose: handleDismissDeprecation } = + useDeprecationNotice(); const [loading, setLoading] = useState(true); const [userName, setUserName] = useState(""); @@ -264,6 +268,7 @@ const Dashboard = () => {