diff --git a/src/hooks.server.ts b/src/hooks.server.ts deleted file mode 100644 index 49af22b0..00000000 --- a/src/hooks.server.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { Handle } from '@sveltejs/kit'; - -export const handle: Handle = async ({ event, resolve }) => { - const response = await resolve(event, {}); - return response; -}; diff --git a/src/lib/components/McWrapper.svelte b/src/lib/components/McWrapper.svelte index 50911d37..38f8848b 100644 --- a/src/lib/components/McWrapper.svelte +++ b/src/lib/components/McWrapper.svelte @@ -1,5 +1,6 @@ + showPopup={!isOnMermaidAI()}> {@render children()} diff --git a/src/lib/components/Navbar.svelte b/src/lib/components/Navbar.svelte index 32be0b79..7607fcde 100644 --- a/src/lib/components/Navbar.svelte +++ b/src/lib/components/Navbar.svelte @@ -21,9 +21,10 @@ interface Props { mobileToggle?: Snippet; children: Snippet; + hidePromotion?: boolean; } - let { children, mobileToggle }: Props = $props(); + let { children, mobileToggle, hidePromotion = false }: Props = $props(); type Links = ComponentProps['links']; @@ -39,7 +40,7 @@ } ]; - let activePromotion = $state(getActivePromotion()); + let activePromotion = $state(hidePromotion ? undefined : getActivePromotion()); const trackBannerClick = () => { if (!plausible || !activePromotion) { @@ -54,7 +55,7 @@ {#if activePromotion}
+ import { Button } from '$/components/ui/button'; + import * as Dialog from '$/components/ui/dialog'; + import { dismissEditorChooser, isOnMermaidAI } from '$/util/migration/domainMigration'; + import { logEvent } from '$/util/stats'; + import CodeIcon from '~icons/custom/code'; + import OpenSourceIcon from '~icons/material-symbols/book-2-outline-rounded'; + import ChatIcon from '~icons/material-symbols/chat-outline-rounded'; + import EditIcon from '~icons/material-symbols/edit-outline-rounded'; + import HistoryIcon from '~icons/material-symbols/history'; + import HomeIcon from '~icons/material-symbols/home-storage-outline-rounded'; + import SparklesIcon from '~icons/material-symbols/kid-star-outline'; + import LanguageIcon from '~icons/material-symbols/language'; + import WidthIcon from '~icons/material-symbols/width-rounded'; + + interface Props { + open: boolean; + } + + let { open = $bindable() }: Props = $props(); + + const close = () => { + dismissEditorChooser(); + open = false; + }; + + const handleStartTrial = () => { + logEvent('editorChooserPlus'); + close(); + const utmSource = isOnMermaidAI() ? 'mermaid_ai_live' : 'mermaid_live_editor'; + window.open( + `https://mermaid.ai/app/user/billing/checkout?${new URLSearchParams({ + coupon: 'dmIuNbqx', + tier: 'plus', + utm_campaign: 'start_plus', + utm_medium: '2_editor_selection', + utm_source: utmSource + }).toString()}`, + '_blank' + ); + }; + + const handleStartFree = () => { + logEvent('editorChooserOpenSource'); + close(); + }; + + + { + if (!v) handleStartFree(); + }}> + + + Choose your editor + + You'll never see this again + + + +
+ +
+
+ Recommended +
+ +
+

Mermaid Plus

+

Unlock AI, storage and collaboration

+ +
+ + Limited time: 10% off + +
+ + + +
    +
  • + + Visual editor +
  • +
  • + + 300 AI credits +
  • +
  • + + Unlimited diagram storage +
  • +
  • + + Limitless diagram size +
  • +
  • + + View & comment collaboration +
  • +
+
+
+ + +
+

Open Source

+

Code only, no login, always free

+ + + +
    +
  • + + Diagram stored in URL +
  • +
  • + + Code editor +
  • +
  • + + Open source +
  • +
  • + + Version history +
  • +
+
+
+
+
diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 9a83ddba..f9b48755 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -11,5 +11,6 @@ export const TID = { export const C = { aiLiveEditor: 'ai_live_editor', + editorChooserDismissedKey: 'mermaid-editor-chooser-dismissed', utmSource: 'mermaid_live_editor' } as const; diff --git a/src/lib/util/migration/domainMigration.ts b/src/lib/util/migration/domainMigration.ts new file mode 100644 index 00000000..17fd8cd3 --- /dev/null +++ b/src/lib/util/migration/domainMigration.ts @@ -0,0 +1,62 @@ +import { C } from '$/constants'; +import { env } from '$/util/env'; + +const mermaidAiDomain = 'mermaid.ai'; + +/** + * Check if we're on mermaid.ai + */ +export const isOnMermaidAI = (): boolean => { + const domain = window.location.hostname; + return domain === mermaidAiDomain || domain.endsWith(`.${mermaidAiDomain}`); +}; + +// localStorage keys that indicate a returning user. +// Note: codeStore is excluded because it's always populated with the default state on first load. +const userDataStorageKeys = [ + 'manualHistoryStore', // Manual history entries + 'autoHistoryStore' // Auto history entries +]; + +/** + * Check if user has any stored data in localStorage. + * This includes saved diagrams, history entries, etc. + */ +const hasStoredUserData = (): boolean => { + for (const key of userDataStorageKeys) { + const value = window.localStorage.getItem(key); + if (value && value !== '[]' && value !== 'null' && value !== '{}') { + return true; + } + } + return false; +}; + +/** + * Check if the current URL has pako data (diagram content from a shared link). + */ +const hasPakoData = (): boolean => { + const hash = window.location.hash; + return ( + hash.includes('pako:') || hash.includes('base64:') || (hash.length > 10 && hash.startsWith('#')) + ); +}; + +/** + * Check if the editor chooser modal should be shown. + * Shows for new users who haven't dismissed it and aren't viewing a shared link. + */ +export const shouldShowEditorChooser = (): boolean => { + if (!env.isEnabledMermaidChartLinks) return false; + if (window.localStorage.getItem(C.editorChooserDismissedKey) === 'true') return false; + if (hasStoredUserData()) return false; + if (hasPakoData()) return false; + return true; +}; + +/** + * Dismiss the editor chooser modal permanently + */ +export const dismissEditorChooser = (): void => { + window.localStorage.setItem(C.editorChooserDismissedKey, 'true'); +}; diff --git a/src/lib/util/promos/NewYear2026.svelte b/src/lib/util/promos/NewYear2026.svelte index d9d5112e..e213b330 100644 --- a/src/lib/util/promos/NewYear2026.svelte +++ b/src/lib/util/promos/NewYear2026.svelte @@ -1,6 +1,6 @@