diff --git a/apps/desktop/src/components/devtool/trial-begin-modal.tsx b/apps/desktop/src/components/devtool/trial-begin-modal.tsx new file mode 100644 index 0000000000..bda8c1883e --- /dev/null +++ b/apps/desktop/src/components/devtool/trial-begin-modal.tsx @@ -0,0 +1,118 @@ +import { Brain, Cloud, ExternalLink, Puzzle, Sparkle, X } from "lucide-react"; +import { useEffect } from "react"; +import { createPortal } from "react-dom"; +import { create } from "zustand"; + +import { cn } from "@hypr/utils"; + +type TrialBeginModalStore = { + isOpen: boolean; + open: () => void; + close: () => void; +}; + +export const useTrialBeginModal = create((set) => ({ + isOpen: false, + open: () => set({ isOpen: true }), + close: () => set({ isOpen: false }), +})); + +export function TrialBeginModal() { + const { isOpen, close } = useTrialBeginModal(); + + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === "Escape" && isOpen) { + close(); + } + }; + + if (isOpen) { + document.addEventListener("keydown", handleEscape); + } + + return () => { + document.removeEventListener("keydown", handleEscape); + }; + }, [isOpen, close]); + + if (!isOpen) { + return null; + } + + return createPortal( + <> +
+
e.stopPropagation()} + /> +
+ +
+
e.stopPropagation()} + > + + +
+
+

+ Welcome to Pro! +

+

+ You just gained access to these features +

+
+ +
+ {[ + { label: "Pro AI models", icon: Sparkle }, + { label: "Cloud sync", icon: Cloud }, + { label: "Memory", icon: Brain }, + { label: "Integrations", icon: Puzzle }, + { label: "Shareable links", icon: ExternalLink }, + { label: "and more", icon: null }, + ].map(({ label, icon: Icon }) => ( +
+ {Icon && } + {label} +
+ ))} +
+ + +
+
+
+ , + document.body, + ); +} diff --git a/apps/desktop/src/components/devtool/trial-expired-modal.tsx b/apps/desktop/src/components/devtool/trial-expired-modal.tsx index 0846a03aed..e74d332ed7 100644 --- a/apps/desktop/src/components/devtool/trial-expired-modal.tsx +++ b/apps/desktop/src/components/devtool/trial-expired-modal.tsx @@ -1,7 +1,8 @@ import { Brain, Cloud, ExternalLink, Puzzle, Sparkle, X } from "lucide-react"; +import { useEffect } from "react"; +import { createPortal } from "react-dom"; import { create } from "zustand"; -import { Modal } from "@hypr/ui/components/ui/modal"; import { cn } from "@hypr/utils"; import { useBillingAccess } from "../../billing"; @@ -27,58 +28,96 @@ export function TrialExpiredModal() { close(); }; - return ( - -
- + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === "Escape" && isOpen) { + close(); + } + }; -
-
-

- Your free trial is over -

-

- You can keep using Hyprnote for free, -
- but here's what you'll be losing -

-
+ if (isOpen) { + document.addEventListener("keydown", handleEscape); + } -
- {[ - { label: "Pro AI models", icon: Sparkle }, - { label: "Cloud sync", icon: Cloud }, - { label: "Memory", icon: Brain }, - { label: "Integrations", icon: Puzzle }, - { label: "Shareable links", icon: ExternalLink }, - ].map(({ label, icon: Icon }) => ( -
- - {label} -
- ))} -
+ return () => { + document.removeEventListener("keydown", handleEscape); + }; + }, [isOpen, close]); + + if (!isOpen) { + return null; + } + + return createPortal( + <> +
+
e.stopPropagation()} + /> +
+
+
e.stopPropagation()} + > + +
+
+

+ Your free trial is over +

+

+ Here's what you just lost access to +

+
+ +
+ {[ + { label: "Pro AI models", icon: Sparkle }, + { label: "Cloud sync", icon: Cloud }, + { label: "Memory", icon: Brain }, + { label: "Integrations", icon: Puzzle }, + { label: "Shareable links", icon: ExternalLink }, + { label: "and more", icon: null }, + ].map(({ label, icon: Icon }) => ( +
+ {Icon && } + {label} +
+ ))} +
+ + +
- + , + document.body, ); } diff --git a/apps/desktop/src/components/main-app-layout.tsx b/apps/desktop/src/components/main-app-layout.tsx index 601301e50b..b8492cbb15 100644 --- a/apps/desktop/src/components/main-app-layout.tsx +++ b/apps/desktop/src/components/main-app-layout.tsx @@ -8,6 +8,7 @@ import { AuthProvider } from "../auth"; import { BillingProvider } from "../billing"; import { NetworkProvider } from "../contexts/network"; import { useTabs } from "../store/zustand/tabs"; +import { TrialBeginModal } from "./devtool/trial-begin-modal"; import { TrialExpiredModal } from "./devtool/trial-expired-modal"; import { useNewNote } from "./main/shared"; @@ -19,6 +20,7 @@ export default function MainAppLayout() { + diff --git a/apps/desktop/src/components/main/sidebar/devtool.tsx b/apps/desktop/src/components/main/sidebar/devtool.tsx index 6777194677..59f0e90d5b 100644 --- a/apps/desktop/src/components/main/sidebar/devtool.tsx +++ b/apps/desktop/src/components/main/sidebar/devtool.tsx @@ -11,6 +11,7 @@ import { } from "../../../store/tinybase/store/main"; import { useTabs } from "../../../store/zustand/tabs"; import { type SeedDefinition, seeds } from "../../devtool/seed/index"; +import { useTrialBeginModal } from "../../devtool/trial-begin-modal"; import { useTrialExpiredModal } from "../../devtool/trial-expired-modal"; import { getLatestVersion } from "../body/changelog"; @@ -313,11 +314,25 @@ function NavigationCard() { } function ModalsCard() { + const { open: openTrialBeginModal } = useTrialBeginModal(); const { open: openTrialExpiredModal } = useTrialExpiredModal(); return (
+