diff --git a/app/src/components/home/HomeBanners.tsx b/app/src/components/home/HomeBanners.tsx index 466d3cbd47..3b4ebb73e3 100644 --- a/app/src/components/home/HomeBanners.tsx +++ b/app/src/components/home/HomeBanners.tsx @@ -1,5 +1,5 @@ import { useT } from '../../lib/i18n/I18nContext'; -import { BILLING_DASHBOARD_URL, DISCORD_INVITE_URL } from '../../utils/links'; +import { DISCORD_INVITE_URL, PRICING_URL } from '../../utils/links'; import { openUrl } from '../../utils/openUrl'; function formatUsd(amount: number): string { @@ -53,7 +53,7 @@ export function UsageLimitBanner({ diff --git a/app/src/components/settings/panels/BillingPanel.test.tsx b/app/src/components/settings/panels/BillingPanel.test.tsx index b4a74e70fa..bd7dc1c6c1 100644 --- a/app/src/components/settings/panels/BillingPanel.test.tsx +++ b/app/src/components/settings/panels/BillingPanel.test.tsx @@ -49,7 +49,7 @@ describe('', () => { fireEvent.click(screen.getByRole('button', { name: 'Open billing dashboard' })); await waitFor(() => expect(openUrlMock).toHaveBeenCalledTimes(1)); - expect(openUrlMock).toHaveBeenLastCalledWith('https://tinyhumans.ai/pricing'); + expect(openUrlMock).toHaveBeenLastCalledWith('https://tinyhumans.ai/dashboard'); }); it('invokes the navigation back handler from both the header and the inline button', async () => { diff --git a/app/src/components/upsell/GlobalUpsellBanner.tsx b/app/src/components/upsell/GlobalUpsellBanner.tsx index 7f778e2835..f620d5c140 100644 --- a/app/src/components/upsell/GlobalUpsellBanner.tsx +++ b/app/src/components/upsell/GlobalUpsellBanner.tsx @@ -1,6 +1,6 @@ import { useUsageState } from '../../hooks/useUsageState'; import { useT } from '../../lib/i18n/I18nContext'; -import { BILLING_DASHBOARD_URL } from '../../utils/links'; +import { PRICING_URL } from '../../utils/links'; import { openUrl } from '../../utils/openUrl'; import UpsellBanner from './UpsellBanner'; @@ -20,7 +20,7 @@ export default function GlobalUpsellBanner() { ctaLabel={t('chat.upgrade')} rounded={false} onCtaClick={() => { - void openUrl(BILLING_DASHBOARD_URL); + void openUrl(PRICING_URL); }} /> @@ -38,7 +38,7 @@ export default function GlobalUpsellBanner() { ctaLabel={t('chat.upgrade')} rounded={false} onCtaClick={() => { - void openUrl(BILLING_DASHBOARD_URL); + void openUrl(PRICING_URL); }} /> diff --git a/app/src/config/__tests__/navConfig.test.ts b/app/src/config/__tests__/navConfig.test.ts index 4550c37fe8..aded4ceef9 100644 --- a/app/src/config/__tests__/navConfig.test.ts +++ b/app/src/config/__tests__/navConfig.test.ts @@ -103,9 +103,9 @@ describe('AVATAR_MENU_ITEMS', () => { expect(openUrlItems).toEqual(['billing']); }); - it('opens billing on the public pricing page', () => { + it('opens billing on the authenticated dashboard', () => { expect(AVATAR_MENU_ITEMS.find(i => i.id === 'billing')?.target).toBe( - 'https://tinyhumans.ai/pricing' + 'https://tinyhumans.ai/dashboard' ); }); }); diff --git a/app/src/features/conversations/Conversations.tsx b/app/src/features/conversations/Conversations.tsx index a25ff26c85..d4ee7e303c 100644 --- a/app/src/features/conversations/Conversations.tsx +++ b/app/src/features/conversations/Conversations.tsx @@ -133,7 +133,7 @@ import type { ThreadMessage } from '../../types/thread'; import { splitAgentMessageIntoBubbles } from '../../utils/agentMessageBubbles'; import { chatThreadPath } from '../../utils/chatRoutes'; import { CHAT_ATTACHMENTS_ENABLED } from '../../utils/config'; -import { BILLING_DASHBOARD_URL } from '../../utils/links'; +import { PRICING_URL } from '../../utils/links'; import { openUrl } from '../../utils/openUrl'; import { isTauri, @@ -2785,7 +2785,7 @@ const Conversations = ({ )} ctaLabel={t('chat.upgrade')} onCtaClick={() => { - void openUrl(BILLING_DASHBOARD_URL); + void openUrl(PRICING_URL); }} dismissible onDismiss={() => dismissBanner('conversations-warning')} @@ -2830,7 +2830,7 @@ const Conversations = ({ type="button" data-analytics-id="chat-budget-top-up" onClick={() => { - void openUrl(BILLING_DASHBOARD_URL); + void openUrl(PRICING_URL); }} className="px-3 py-1.5 rounded-lg bg-coral-500 hover:bg-coral-400 text-content-inverted text-xs font-medium transition-colors"> {t('chat.topUp')} diff --git a/app/src/utils/__tests__/desktopDeepLinkListener.test.ts b/app/src/utils/__tests__/desktopDeepLinkListener.test.ts index 35706be847..87fa220c00 100644 --- a/app/src/utils/__tests__/desktopDeepLinkListener.test.ts +++ b/app/src/utils/__tests__/desktopDeepLinkListener.test.ts @@ -12,9 +12,12 @@ import { getStoredCoreMode } from '../configPersistence'; import { authStoreFailureUserMessage, classifyAuthStoreFailure, + handleDeepLinkUrls, registerAuthDeepLinkState, setupDesktopDeepLinkListener, } from '../desktopDeepLinkListener'; +import { BILLING_DASHBOARD_URL } from '../links'; +import { openUrl } from '../openUrl'; import { storeSession } from '../tauriCommands'; vi.mock('../configPersistence', () => ({ getStoredCoreMode: vi.fn() })); @@ -22,6 +25,7 @@ vi.mock('../../services/coreRpcClient', () => ({ clearCoreRpcUrlCache: vi.fn(), clearCoreRpcTokenCache: vi.fn(), })); +vi.mock('../openUrl', () => ({ openUrl: vi.fn() })); // Build an `openhuman://auth` deep link bound to a freshly registered state // nonce, mirroring how the real OAuth button registers the loopback/deep-link @@ -83,12 +87,27 @@ describe('desktopDeepLinkListener', () => { vi.mocked(getStoredCoreMode).mockReturnValue(null); vi.mocked(clearCoreRpcUrlCache).mockClear(); vi.mocked(clearCoreRpcTokenCache).mockClear(); + vi.mocked(openUrl).mockReset(); + vi.mocked(openUrl).mockResolvedValue(undefined); windowControls.show.mockClear(); windowControls.unminimize.mockClear(); windowControls.setFocus.mockClear(); completeDeepLinkAuthProcessing(); }); + it('returns successful payment deep links to the billing dashboard', async () => { + await handleDeepLinkUrls(['openhuman://payment/success?session_id=checkout-session']); + + expect(openUrl).toHaveBeenCalledWith(BILLING_DASHBOARD_URL); + expect(BILLING_DASHBOARD_URL).toBe('https://tinyhumans.ai/dashboard'); + }); + + it('returns cancelled payment deep links to the billing dashboard', async () => { + await handleDeepLinkUrls(['openhuman://payment/cancel']); + + expect(openUrl).toHaveBeenCalledWith(BILLING_DASHBOARD_URL); + }); + it('turns Twitter OAuth error deep links into actionable UI and event diagnostics', async () => { const oauthErrorEvents: CustomEvent[] = []; window.addEventListener('oauth:error', event => { diff --git a/app/src/utils/links.ts b/app/src/utils/links.ts index a734ac5016..ae749191d5 100644 --- a/app/src/utils/links.ts +++ b/app/src/utils/links.ts @@ -1,4 +1,5 @@ export const DISCORD_INVITE_URL = 'https://discord.tinyhumans.ai'; -export const BILLING_DASHBOARD_URL = 'https://tinyhumans.ai/pricing'; +export const PRICING_URL = 'https://tinyhumans.ai/pricing'; +export const BILLING_DASHBOARD_URL = 'https://tinyhumans.ai/dashboard'; export const PRIVACY_POLICY_URL = 'https://tinyhumans.gitbook.io/openhuman/legal/privacy-policy'; export const TERMS_OF_USE_URL = 'https://tinyhumans.gitbook.io/openhuman/legal/terms-of-use';