From 50d8fdfdd1f0eb79453422ee6a9bb1af62363069 Mon Sep 17 00:00:00 2001 From: barsa Date: Sat, 27 Sep 2025 18:28:35 +0900 Subject: [PATCH] Enhance WHMCS API response handling with improved logging and response structure validation. Update LoadingOverlay component for better layout and spacing. Refactor AppShell to manage authentication checks more effectively, ensuring smoother user experience during redirects. Adjust API paths in subscription hooks for consistency and clarity. --- .../services/whmcs-http-client.service.ts | 2 +- .../whmcs/services/whmcs-subscription.service.ts | 14 +++++++++++++- .../portal/src/components/atoms/LoadingOverlay.tsx | 8 +++++--- .../src/components/organisms/AppShell/AppShell.tsx | 4 ++-- .../src/features/auth/services/auth.store.ts | 8 ++++++-- .../subscriptions/hooks/useSubscriptions.ts | 6 +++--- 6 files changed, 30 insertions(+), 12 deletions(-) diff --git a/apps/bff/src/integrations/whmcs/connection/services/whmcs-http-client.service.ts b/apps/bff/src/integrations/whmcs/connection/services/whmcs-http-client.service.ts index c91b3f26..525abd5d 100644 --- a/apps/bff/src/integrations/whmcs/connection/services/whmcs-http-client.service.ts +++ b/apps/bff/src/integrations/whmcs/connection/services/whmcs-http-client.service.ts @@ -283,7 +283,7 @@ export class WhmcsHttpClientService { // For successful responses, WHMCS API returns data directly at the root level // The response structure is: { "result": "success", ...actualData } - // We need to wrap this in our expected format + // We return the parsed response directly as T since it contains the actual data return { result: "success", data: parsedResponse as T diff --git a/apps/bff/src/integrations/whmcs/services/whmcs-subscription.service.ts b/apps/bff/src/integrations/whmcs/services/whmcs-subscription.service.ts index 095b57ea..66d910f6 100644 --- a/apps/bff/src/integrations/whmcs/services/whmcs-subscription.service.ts +++ b/apps/bff/src/integrations/whmcs/services/whmcs-subscription.service.ts @@ -57,8 +57,20 @@ export class WhmcsSubscriptionService { const response = await this.connectionService.getClientsProducts(params); + // Debug logging to understand the response structure + this.logger.debug(`WHMCS GetClientsProducts response structure for client ${clientId}`, { + hasResponse: !!response, + responseKeys: response ? Object.keys(response) : [], + hasProducts: !!(response as any)?.products, + productsKeys: (response as any)?.products ? Object.keys((response as any).products) : [], + hasProductArray: Array.isArray((response as any)?.products?.product), + productCount: Array.isArray((response as any)?.products?.product) ? (response as any).products.product.length : 0, + }); + if (!response.products?.product) { - this.logger.warn(`No products found for client ${clientId}`); + this.logger.warn(`No products found for client ${clientId}`, { + responseStructure: response ? Object.keys(response) : 'null response', + }); return { subscriptions: [], totalCount: 0, diff --git a/apps/portal/src/components/atoms/LoadingOverlay.tsx b/apps/portal/src/components/atoms/LoadingOverlay.tsx index 592f81d2..2cf93c79 100644 --- a/apps/portal/src/components/atoms/LoadingOverlay.tsx +++ b/apps/portal/src/components/atoms/LoadingOverlay.tsx @@ -29,11 +29,13 @@ export function LoadingOverlay({ return (
-
- +
+
+ +

{title}

{subtitle && ( -

{subtitle}

+

{subtitle}

)}
diff --git a/apps/portal/src/components/organisms/AppShell/AppShell.tsx b/apps/portal/src/components/organisms/AppShell/AppShell.tsx index d7f631cd..94139dd1 100644 --- a/apps/portal/src/components/organisms/AppShell/AppShell.tsx +++ b/apps/portal/src/components/organisms/AppShell/AppShell.tsx @@ -63,10 +63,10 @@ export function AppShell({ children }: AppShellProps) { }, [hasCheckedAuth, checkAuth]); useEffect(() => { - if (hasCheckedAuth && !isAuthenticated) { + if (hasCheckedAuth && !isAuthenticated && !loading) { router.push("/auth/login"); } - }, [hasCheckedAuth, isAuthenticated, router]); + }, [hasCheckedAuth, isAuthenticated, loading, router]); // Hydrate full profile once after auth so header name is consistent across pages useEffect(() => { diff --git a/apps/portal/src/features/auth/services/auth.store.ts b/apps/portal/src/features/auth/services/auth.store.ts index 8e5a14db..86732b59 100644 --- a/apps/portal/src/features/auth/services/auth.store.ts +++ b/apps/portal/src/features/auth/services/auth.store.ts @@ -297,8 +297,12 @@ export const useAuthStore = create()((set, get) => { }, checkAuth: async () => { - set({ hasCheckedAuth: true }); - await get().refreshUser(); + set({ loading: true }); + try { + await get().refreshUser(); + } finally { + set({ hasCheckedAuth: true, loading: false }); + } }, clearError: () => set({ error: null }), diff --git a/apps/portal/src/features/subscriptions/hooks/useSubscriptions.ts b/apps/portal/src/features/subscriptions/hooks/useSubscriptions.ts index 9df7ad14..24f491b4 100644 --- a/apps/portal/src/features/subscriptions/hooks/useSubscriptions.ts +++ b/apps/portal/src/features/subscriptions/hooks/useSubscriptions.ts @@ -78,7 +78,7 @@ export function useActiveSubscriptions() { return useQuery({ queryKey: queryKeys.subscriptions.active(), queryFn: async () => { - const response = await apiClient.GET("/subscriptions/active"); + const response = await apiClient.GET("/api/subscriptions/active"); return getDataOrDefault(response, []); }, staleTime: 5 * 60 * 1000, @@ -114,7 +114,7 @@ export function useSubscription(subscriptionId: number) { return useQuery({ queryKey: queryKeys.subscriptions.detail(String(subscriptionId)), queryFn: async () => { - const response = await apiClient.GET("/subscriptions/{id}", { + const response = await apiClient.GET("/api/subscriptions/{id}", { params: { path: { id: subscriptionId } }, }); return getDataOrThrow(response, "Subscription not found"); @@ -138,7 +138,7 @@ export function useSubscriptionInvoices( return useQuery({ queryKey: queryKeys.subscriptions.invoices(subscriptionId, { page, limit }), queryFn: async () => { - const response = await apiClient.GET("/subscriptions/{id}/invoices", { + const response = await apiClient.GET("/api/subscriptions/{id}/invoices", { params: { path: { id: subscriptionId }, query: { page, limit },