[hAIk]: integrate subscriptions backend-bridge into frontend: replace all raw fetch calls to /subscriptions/* endpoints with dispatch(THUNK).unwrap() using SUBSCRIPTIONS_STATUS, SUBSCRIPTIONS_CONNECT, SUBSCRIPTIONS_POLL, and SUBSCRIPTIONS_DISCONNECT from the bridge layer — no Redux slice needed since state stays local

This commit is contained in:
haikdc
2026-04-18 05:33:16 -07:00
parent 09f4a22209
commit 216eb7018a
3 changed files with 49 additions and 58 deletions
+5 -3
View File
@@ -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<string | null>(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 {
@@ -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<any>;
@@ -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<string, unknown> | 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;
}
@@ -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<any>(null);
const [connecting, setConnecting] = useState<string | null>(null);
const [disconnecting, setDisconnecting] = useState<string | null>(null);
@@ -13,8 +20,7 @@ const SubscriptionCards: React.FC = () => {
const [pollTimer, setPollTimer] = useState<any>(null);
const retryRef = useRef<ReturnType<typeof setInterval> | 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<string, unknown> | 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);
};