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.
This commit is contained in:
parent
6390749150
commit
50d8fdfdd1
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -29,11 +29,13 @@ export function LoadingOverlay({
|
||||
|
||||
return (
|
||||
<div className={`fixed inset-0 z-50 flex items-center justify-center ${overlayClassName}`}>
|
||||
<div className="text-center">
|
||||
<Spinner size={spinnerSize} className={`mb-4 ${spinnerClassName}`} />
|
||||
<div className="text-center max-w-sm mx-auto px-4">
|
||||
<div className="flex justify-center mb-6">
|
||||
<Spinner size={spinnerSize} className={spinnerClassName} />
|
||||
</div>
|
||||
<p className="text-lg font-medium text-gray-900">{title}</p>
|
||||
{subtitle && (
|
||||
<p className="text-sm text-gray-600 mt-1">{subtitle}</p>
|
||||
<p className="text-sm text-gray-600 mt-2">{subtitle}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -297,8 +297,12 @@ export const useAuthStore = create<AuthState>()((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 }),
|
||||
|
||||
@ -78,7 +78,7 @@ export function useActiveSubscriptions() {
|
||||
return useQuery<Subscription[]>({
|
||||
queryKey: queryKeys.subscriptions.active(),
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.GET<Subscription[]>("/subscriptions/active");
|
||||
const response = await apiClient.GET<Subscription[]>("/api/subscriptions/active");
|
||||
return getDataOrDefault<Subscription[]>(response, []);
|
||||
},
|
||||
staleTime: 5 * 60 * 1000,
|
||||
@ -114,7 +114,7 @@ export function useSubscription(subscriptionId: number) {
|
||||
return useQuery<Subscription>({
|
||||
queryKey: queryKeys.subscriptions.detail(String(subscriptionId)),
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.GET<Subscription>("/subscriptions/{id}", {
|
||||
const response = await apiClient.GET<Subscription>("/api/subscriptions/{id}", {
|
||||
params: { path: { id: subscriptionId } },
|
||||
});
|
||||
return getDataOrThrow<Subscription>(response, "Subscription not found");
|
||||
@ -138,7 +138,7 @@ export function useSubscriptionInvoices(
|
||||
return useQuery<InvoiceList>({
|
||||
queryKey: queryKeys.subscriptions.invoices(subscriptionId, { page, limit }),
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.GET<InvoiceList>("/subscriptions/{id}/invoices", {
|
||||
const response = await apiClient.GET<InvoiceList>("/api/subscriptions/{id}/invoices", {
|
||||
params: {
|
||||
path: { id: subscriptionId },
|
||||
query: { page, limit },
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user