fix(frontend): preserve post-checkout dashboard redirect (#5036)

This commit is contained in:
Steven Enamakel
2026-07-20 21:39:58 +03:00
committed by GitHub
parent 8e6d2d6171
commit bc49704797
10 changed files with 41 additions and 21 deletions
+4 -4
View File
@@ -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({
<button
type="button"
onClick={() => {
void openUrl(BILLING_DASHBOARD_URL);
void openUrl(PRICING_URL);
}}
className={`cursor-pointer border-b border-dashed font-bold ${styles.button}`}>
{ctaLabel}
@@ -101,7 +101,7 @@ export function PromotionalCreditsBanner({ promoCredits }: { promoCredits: numbe
<button
type="button"
onClick={() => {
void openUrl(BILLING_DASHBOARD_URL);
void openUrl(PRICING_URL);
}}
className="cursor-pointer border-b border-dashed border-amber-700 font-bold text-amber-700 hover:text-amber-800 dark:border-amber-300 dark:text-amber-300 dark:hover:text-amber-200">
{t('home.banners.getSubscription')}
@@ -143,7 +143,7 @@ export function EarlyBirdyBanner({ onDismiss }: { onDismiss?: () => void }) {
<button
type="button"
onClick={() => {
void openUrl(BILLING_DASHBOARD_URL);
void openUrl(PRICING_URL);
}}
className="cursor-pointer border-b border-amber-700 border-dashed font-bold text-amber-700 hover:text-amber-800 dark:border-amber-300 dark:text-amber-300 dark:hover:text-amber-200">
{t('home.banners.earlyBirdFirstSub')}
@@ -1,7 +1,7 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { BILLING_DASHBOARD_URL, DISCORD_INVITE_URL } from '../../../utils/links';
import { DISCORD_INVITE_URL, PRICING_URL } from '../../../utils/links';
import { openUrl } from '../../../utils/openUrl';
import {
DiscordBanner,
@@ -45,7 +45,7 @@ describe('HomeBanners', () => {
);
expect(screen.getByText('Out of Usage')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Get a subscription' }));
expect(openUrl).toHaveBeenCalledWith(BILLING_DASHBOARD_URL);
expect(openUrl).toHaveBeenCalledWith(PRICING_URL);
});
it('opens the billing dashboard through openUrl from the promotional credits banner', () => {
@@ -77,7 +77,7 @@ describe('HomeBanners', () => {
fireEvent.click(screen.getByRole('button', { name: /first subscription/i }));
expect(openUrl).toHaveBeenCalledWith(BILLING_DASHBOARD_URL);
expect(openUrl).toHaveBeenCalledWith(PRICING_URL);
});
it('does not render a dismiss button when onDismiss is not provided', () => {
@@ -1,7 +1,7 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { BILLING_DASHBOARD_URL, DISCORD_INVITE_URL } from '../../utils/links';
import { DISCORD_INVITE_URL, PRICING_URL } from '../../utils/links';
import MedullaOverviewPanel from './MedullaOverviewPanel';
// Pass-through translator so assertions can target the i18n keys directly.
@@ -31,6 +31,6 @@ describe('MedullaOverviewPanel', () => {
it('opens the billing dashboard when the subscription CTA is clicked', () => {
render(<MedullaOverviewPanel />);
fireEvent.click(screen.getByTestId('orch-medulla-subscribe'));
expect(openUrl).toHaveBeenCalledWith(BILLING_DASHBOARD_URL);
expect(openUrl).toHaveBeenCalledWith(PRICING_URL);
});
});
@@ -10,7 +10,7 @@
* ("Medulla", "OpenHuman", "Discord") stay in every locale.
*/
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';
export default function MedullaOverviewPanel() {
@@ -79,7 +79,7 @@ export default function MedullaOverviewPanel() {
<button
type="button"
data-testid="orch-medulla-subscribe"
onClick={() => void openUrl(BILLING_DASHBOARD_URL)}
onClick={() => void openUrl(PRICING_URL)}
className="mt-auto flex w-full items-center justify-center gap-2 rounded-xl bg-primary-500 px-4 py-2 text-xs font-semibold text-content-inverted shadow-soft transition-colors hover:bg-primary-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1">
{t('orchPage.medulla.subscriberCta')}
</button>
@@ -49,7 +49,7 @@ describe('<BillingPanel />', () => {
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 () => {
@@ -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);
}}
/>
</div>
@@ -38,7 +38,7 @@ export default function GlobalUpsellBanner() {
ctaLabel={t('chat.upgrade')}
rounded={false}
onCtaClick={() => {
void openUrl(BILLING_DASHBOARD_URL);
void openUrl(PRICING_URL);
}}
/>
</div>
+2 -2
View File
@@ -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'
);
});
});
@@ -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,
@@ -2799,7 +2799,7 @@ const Conversations = ({
)}
ctaLabel={t('chat.upgrade')}
onCtaClick={() => {
void openUrl(BILLING_DASHBOARD_URL);
void openUrl(PRICING_URL);
}}
dismissible
onDismiss={() => dismissBanner('conversations-warning')}
@@ -2844,7 +2844,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')}
@@ -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 => {
+2 -1
View File
@@ -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';