diff --git a/frontend/src/app/components/useOnboarding.ts b/frontend/src/app/components/useOnboarding.ts index b2761e23..63346020 100644 --- a/frontend/src/app/components/useOnboarding.ts +++ b/frontend/src/app/components/useOnboarding.ts @@ -1,9 +1,12 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import { API_BASE } from '@/shared/config'; +import { useAppDispatch } from '@/shared/hooks'; +import { SUBSCRIPTIONS_STATUS } from '@/shared/backend-bridge/apps/subscriptions'; import { ToolIntegration } from './onboardingConstants'; import { useSubscriptionConnect } from './useSubscriptionConnect'; export function useOnboarding() { + const dispatch = useAppDispatch(); const [open, setOpen] = useState(false); const [step, setStep] = useState<'provider' | 'tools'>('provider'); const [connecting, setConnecting] = useState(null); @@ -27,11 +30,10 @@ export function useOnboarding() { let attempts = 0; const maxAttempts = 15; const check = () => { - fetch(`${API_BASE}/subscriptions/status`) - .then((r) => r.json()) + dispatch(SUBSCRIPTIONS_STATUS()).unwrap() .then((data) => { if (data.running) { - const connections = data.providers?.connections || []; + const connections = (data.providers as any)?.connections || []; if (connections.some((p: any) => p.isActive)) return; setTimeout(() => setNineRouterReady(true), 3000); } else { diff --git a/frontend/src/app/components/useSubscriptionConnect.ts b/frontend/src/app/components/useSubscriptionConnect.ts index d694ea27..9156eef0 100644 --- a/frontend/src/app/components/useSubscriptionConnect.ts +++ b/frontend/src/app/components/useSubscriptionConnect.ts @@ -1,5 +1,10 @@ import { useCallback, MutableRefObject } from 'react'; -import { API_BASE } from '@/shared/config'; +import { useAppDispatch } from '@/shared/hooks'; +import { + SUBSCRIPTIONS_CONNECT, + SUBSCRIPTIONS_POLL, + SUBSCRIPTIONS_STATUS, +} from '@/shared/backend-bridge/apps/subscriptions'; interface UseSubscriptionConnectParams { pollTimerRef: MutableRefObject; @@ -11,6 +16,8 @@ interface UseSubscriptionConnectParams { export function useSubscriptionConnect({ pollTimerRef, msgHandlerRef, setConnecting, advanceToTools, }: UseSubscriptionConnectParams) { + const dispatch = useAppDispatch(); + const handleConnect = useCallback(async (providerId: string) => { if (pollTimerRef.current) { clearInterval(pollTimerRef.current); pollTimerRef.current = null; } if (msgHandlerRef.current) { window.removeEventListener('message', msgHandlerRef.current); msgHandlerRef.current = null; } @@ -19,34 +26,20 @@ export function useSubscriptionConnect({ await new Promise(r => setTimeout(r, 1000)); try { - const r = await fetch(`${API_BASE}/subscriptions/connect`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ provider: providerId }), - }); - if (!r.ok) { - setConnecting(null); - return; - } - const data = await r.json(); + const data = await dispatch(SUBSCRIPTIONS_CONNECT(providerId)).unwrap(); if (data.flow === 'device_code') { - if (data.verification_uri) window.open(data.verification_uri, '_blank'); + if (data.verification_uri) window.open(data.verification_uri as string, '_blank'); const timer = setInterval(async () => { try { - const pr = await fetch(`${API_BASE}/subscriptions/poll`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - provider: providerId, - device_code: data.device_code, - code_verifier: data.code_verifier, - extra_data: data.extra_data, - }), - }); - const pd = await pr.json(); - if (pd.success) { + const pd = await dispatch(SUBSCRIPTIONS_POLL({ + provider: providerId, + device_code: data.device_code as string, + code_verifier: data.code_verifier as string | undefined, + extra_data: data.extra_data as Record | undefined, + })).unwrap(); + if ((pd as any).success) { clearInterval(timer); pollTimerRef.current = null; advanceToTools(); @@ -57,7 +50,7 @@ export function useSubscriptionConnect({ setTimeout(() => { clearInterval(timer); pollTimerRef.current = null; setConnecting(null); }, 30000); } else if (data.flow === 'authorization_code') { - const popup = window.open(data.auth_url, 'oauth_connect', 'width=600,height=700'); + const popup = window.open(data.auth_url as string, 'oauth_connect', 'width=600,height=700'); let resolved = false; const cleanup = () => { if (resolved) return; @@ -87,9 +80,8 @@ export function useSubscriptionConnect({ advanceToTools(); return; } - const sr = await fetch(`${API_BASE}/subscriptions/status`); - const sd = await sr.json(); - const connections = sd.providers?.connections || []; + const sd = await dispatch(SUBSCRIPTIONS_STATUS()).unwrap(); + const connections = (sd.providers as any)?.connections || []; if (connections.some((p: any) => p.provider === providerId && p.isActive)) { cleanup(); advanceToTools(); @@ -105,7 +97,7 @@ export function useSubscriptionConnect({ } catch { setConnecting(null); } - }, [pollTimerRef, msgHandlerRef, setConnecting, advanceToTools]); + }, [dispatch, pollTimerRef, msgHandlerRef, setConnecting, advanceToTools]); return handleConnect; } diff --git a/frontend/src/app/pages/Settings/SubscriptionCards.tsx b/frontend/src/app/pages/Settings/SubscriptionCards.tsx index 282af845..eea76087 100644 --- a/frontend/src/app/pages/Settings/SubscriptionCards.tsx +++ b/frontend/src/app/pages/Settings/SubscriptionCards.tsx @@ -1,11 +1,18 @@ import React, { useState, useEffect, useRef } from 'react'; import { Box, Typography, CircularProgress } from '@mui/material'; import { useClaudeTokens } from '@/shared/styles/ThemeContext'; -import { API_BASE } from '@/shared/config'; +import { useAppDispatch } from '@/shared/hooks'; +import { + SUBSCRIPTIONS_STATUS, + SUBSCRIPTIONS_CONNECT, + SUBSCRIPTIONS_POLL, + SUBSCRIPTIONS_DISCONNECT, +} from '@/shared/backend-bridge/apps/subscriptions'; import SubscriptionCard, { SUBSCRIPTION_PROVIDERS } from './SubscriptionCard'; const SubscriptionCards: React.FC = () => { const c = useClaudeTokens(); + const dispatch = useAppDispatch(); const [status, setStatus] = useState(null); const [connecting, setConnecting] = useState(null); const [disconnecting, setDisconnecting] = useState(null); @@ -13,8 +20,7 @@ const SubscriptionCards: React.FC = () => { const [pollTimer, setPollTimer] = useState(null); const retryRef = useRef | null>(null); const fetchStatus = () => { - fetch(`${API_BASE}/subscriptions/status`) - .then(r => r.json()) + dispatch(SUBSCRIPTIONS_STATUS()).unwrap() .then(setStatus) .catch(() => setStatus({ running: false, providers: [], models: [] })); }; @@ -45,24 +51,20 @@ const SubscriptionCards: React.FC = () => { setUserCode(''); await new Promise(r => setTimeout(r, 500)); try { - const r = await fetch(`${API_BASE}/subscriptions/connect`, { - method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ provider: providerId }), - }); - if (!r.ok) { setConnecting(null); return; } - const data = await r.json(); + const data = await dispatch(SUBSCRIPTIONS_CONNECT(providerId)).unwrap(); if (data.flow === 'device_code') { - const code = data.user_code || ''; + const code = (data.user_code as string) || ''; setUserCode(code); - if (data.verification_uri) window.open(data.verification_uri, '_blank'); + if (data.verification_uri) window.open(data.verification_uri as string, '_blank'); const timer = setInterval(async () => { try { - const pr = await fetch(`${API_BASE}/subscriptions/poll`, { - method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ provider: providerId, device_code: data.device_code, code_verifier: data.code_verifier, extra_data: data.extra_data }), - }); - const pd = await pr.json(); - if (pd.success) { + const pd = await dispatch(SUBSCRIPTIONS_POLL({ + provider: providerId, + device_code: data.device_code as string, + code_verifier: data.code_verifier as string | undefined, + extra_data: data.extra_data as Record | undefined, + })).unwrap(); + if ((pd as any).success) { clearInterval(timer); setPollTimer(null); setConnecting(null); @@ -74,7 +76,7 @@ const SubscriptionCards: React.FC = () => { setPollTimer(timer); setTimeout(() => { clearInterval(timer); setPollTimer(null); setConnecting(null); setUserCode(''); }, 300000); } else if (data.flow === 'authorization_code') { - const popup = window.open(data.auth_url, 'oauth_connect', 'width=600,height=700'); + const popup = window.open(data.auth_url as string, 'oauth_connect', 'width=600,height=700'); let resolved = false; const cleanup = () => { if (resolved) return; @@ -98,9 +100,8 @@ const SubscriptionCards: React.FC = () => { cleanup(); return; } - const sr = await fetch(`${API_BASE}/subscriptions/status`); - const sd = await sr.json(); - const connections = sd.providers?.connections || []; + const sd = await dispatch(SUBSCRIPTIONS_STATUS()).unwrap(); + const connections = (sd.providers as any)?.connections || []; if (connections.some((p: any) => p.provider === providerId && p.isActive)) { cleanup(); } @@ -116,11 +117,7 @@ const SubscriptionCards: React.FC = () => { const handleDisconnect = async (providerId: string) => { setDisconnecting(providerId); try { - await fetch(`${API_BASE}/subscriptions/disconnect`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ provider: providerId }), - }); + await dispatch(SUBSCRIPTIONS_DISCONNECT(providerId)).unwrap(); } catch {} setTimeout(() => { fetchStatusWithRetry(); setDisconnecting(null); }, 500); };