From a102f362e2fa9dd116d4c326240aef358d5d10d3 Mon Sep 17 00:00:00 2001 From: barsa Date: Mon, 29 Sep 2025 11:00:56 +0900 Subject: [PATCH] Refactor CatalogController to return comprehensive internet plan data including installations and addons. Update Button component styles for improved visual feedback and consistency across the application. Enhance AddonGroup logic for better handling of bundled addons. Revamp OrderSummary and related components for a more structured display of order details. Improve error handling in subscription hooks for better reliability in data fetching. --- .../src/modules/catalog/catalog.controller.ts | 20 ++- apps/portal/src/components/atoms/button.tsx | 26 +-- .../catalog/components/base/AddonGroup.tsx | 107 ++++++------ .../components/base/EnhancedOrderSummary.tsx | 44 ++--- .../catalog/components/base/OrderSummary.tsx | 30 ++-- .../catalog/components/base/ProductCard.tsx | 14 +- .../components/common/ServiceHeroCard.tsx | 4 +- .../components/internet/InternetPlanCard.tsx | 18 +- .../configure/steps/ReviewOrderStep.tsx | 157 ++++++++++-------- .../catalog/services/catalog.service.ts | 4 +- .../features/catalog/views/CatalogHome.tsx | 10 +- .../features/catalog/views/InternetPlans.tsx | 68 +++++--- .../src/features/catalog/views/SimPlans.tsx | 50 ++++-- .../src/features/catalog/views/VpnPlans.tsx | 100 +++++++---- .../subscriptions/hooks/useSubscriptions.ts | 16 +- 15 files changed, 385 insertions(+), 283 deletions(-) diff --git a/apps/bff/src/modules/catalog/catalog.controller.ts b/apps/bff/src/modules/catalog/catalog.controller.ts index e687ef96..a5bdb2fc 100644 --- a/apps/bff/src/modules/catalog/catalog.controller.ts +++ b/apps/bff/src/modules/catalog/catalog.controller.ts @@ -26,13 +26,25 @@ export class CatalogController { @ApiOperation({ summary: "Get Internet plans filtered by customer eligibility" }) async getInternetPlans( @Request() req: { user: { id: string } } - ): Promise { + ): Promise<{ + plans: InternetPlanCatalogItem[]; + installations: InternetInstallationCatalogItem[]; + addons: InternetAddonCatalogItem[]; + }> { const userId = req.user?.id; if (!userId) { - // Fallback to all plans if no user context - return this.internetCatalog.getPlans(); + // Fallback to all catalog data if no user context + return this.internetCatalog.getCatalogData(); } - return this.internetCatalog.getPlansForUser(userId); + + // Get user-specific plans but all installations and addons + const [plans, installations, addons] = await Promise.all([ + this.internetCatalog.getPlansForUser(userId), + this.internetCatalog.getInstallations(), + this.internetCatalog.getAddons(), + ]); + + return { plans, installations, addons }; } @Get("internet/addons") diff --git a/apps/portal/src/components/atoms/button.tsx b/apps/portal/src/components/atoms/button.tsx index c6474aa2..ea883182 100644 --- a/apps/portal/src/components/atoms/button.tsx +++ b/apps/portal/src/components/atoms/button.tsx @@ -5,21 +5,21 @@ import { cn } from "@/lib/utils"; import { Spinner } from "./Spinner"; const buttonVariants = cva( - "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background", + "inline-flex items-center justify-center rounded-lg text-sm font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background active:scale-[0.98]", { variants: { variant: { - default: "bg-blue-600 text-white hover:bg-blue-700", - destructive: "bg-red-600 text-white hover:bg-red-700", - outline: "border border-gray-300 bg-white hover:bg-gray-50", - secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200", - ghost: "hover:bg-gray-100", + default: "bg-blue-600 text-white hover:bg-blue-700 shadow-sm hover:shadow-md", + destructive: "bg-red-600 text-white hover:bg-red-700 shadow-sm hover:shadow-md", + outline: "border border-gray-300 bg-white hover:bg-gray-50 hover:border-gray-400 shadow-sm hover:shadow-md", + secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200 shadow-sm hover:shadow-md", + ghost: "hover:bg-gray-100 hover:shadow-sm", link: "underline-offset-4 hover:underline text-blue-600", }, size: { - default: "h-10 py-2 px-4", - sm: "h-9 px-3 rounded-md", - lg: "h-11 px-8 rounded-md", + default: "h-11 py-2.5 px-4", + sm: "h-9 px-3 text-xs", + lg: "h-12 px-6 text-base", }, }, defaultVariants: { @@ -77,8 +77,8 @@ const Button = forwardRef((p > {loading ? : leftIcon} - {loading ? (loadingText ?? children) : children} - {!loading && rightIcon ? {rightIcon} : null} + {loading ? (loadingText ?? children) : children} + {!loading && rightIcon ? {rightIcon} : null} ); @@ -102,8 +102,8 @@ const Button = forwardRef((p > {loading ? : leftIcon} - {loading ? (loadingText ?? children) : children} - {!loading && rightIcon ? {rightIcon} : null} + {loading ? (loadingText ?? children) : children} + {!loading && rightIcon ? {rightIcon} : null} ); diff --git a/apps/portal/src/features/catalog/components/base/AddonGroup.tsx b/apps/portal/src/features/catalog/components/base/AddonGroup.tsx index 29b2e9fd..420847bf 100644 --- a/apps/portal/src/features/catalog/components/base/AddonGroup.tsx +++ b/apps/portal/src/features/catalog/components/base/AddonGroup.tsx @@ -6,7 +6,7 @@ import { getMonthlyPrice, getOneTimePrice } from "../../utils/pricing"; interface AddonGroupProps { addons: Array< - CatalogProductBase & { bundledAddonId?: string; isBundledAddon?: boolean; raw?: unknown } + CatalogProductBase & { bundledAddonId?: string; isBundledAddon?: boolean } >; selectedAddonSkus: string[]; onAddonToggle: (skus: string[]) => void; @@ -26,68 +26,71 @@ type BundledAddonGroup = { function buildGroupedAddons( addons: Array< - CatalogProductBase & { bundledAddonId?: string; isBundledAddon?: boolean; raw?: unknown } + CatalogProductBase & { bundledAddonId?: string; isBundledAddon?: boolean } > ): BundledAddonGroup[] { const groups: BundledAddonGroup[] = []; - const processedSkus = new Set(); - + const processed = new Set(); + + // Sort by display order const sorted = [...addons].sort((a, b) => (a.displayOrder ?? 0) - (b.displayOrder ?? 0)); - sorted.forEach(addon => { - if (processedSkus.has(addon.sku)) return; + for (const addon of sorted) { + if (processed.has(addon.sku)) continue; + // Try to find bundle partner if (addon.isBundledAddon && addon.bundledAddonId) { - const partner = sorted.find( - candidate => - candidate.raw && - typeof candidate.raw === "object" && - "Id" in candidate.raw && - candidate.raw.Id === addon.bundledAddonId - ); - - if (partner) { - const monthlyAddon = addon.billingCycle === "Monthly" ? addon : partner; - const activationAddon = addon.billingCycle === "Onetime" ? addon : partner; - - const name = - monthlyAddon.name.replace(/\s*(Monthly|Installation|Fee)\s*/gi, "").trim() || addon.name; - - groups.push({ - id: `bundle-${addon.sku}-${partner.sku}`, - name, - description: `${name} bundle (installation included)`, - monthlyPrice: - monthlyAddon.billingCycle === "Monthly" ? getMonthlyPrice(monthlyAddon) : undefined, - activationPrice: - activationAddon.billingCycle === "Onetime" - ? getOneTimePrice(activationAddon) - : undefined, - skus: [addon.sku, partner.sku], - isBundled: true, - displayOrder: addon.displayOrder ?? 0, - }); - - processedSkus.add(addon.sku); - processedSkus.add(partner.sku); - return; + const partner = sorted.find(candidate => candidate.id === addon.bundledAddonId); + + if (partner && !processed.has(partner.sku)) { + // Create bundle + const bundle = createBundle(addon, partner); + groups.push(bundle); + processed.add(addon.sku); + processed.add(partner.sku); + continue; } } - groups.push({ - id: addon.sku, - name: addon.name, - description: addon.description || "", - monthlyPrice: addon.billingCycle === "Monthly" ? getMonthlyPrice(addon) : undefined, - activationPrice: addon.billingCycle === "Onetime" ? getOneTimePrice(addon) : undefined, - skus: [addon.sku], - isBundled: false, - displayOrder: addon.displayOrder ?? 0, - }); - processedSkus.add(addon.sku); - }); + // Create standalone item + groups.push(createStandaloneItem(addon)); + processed.add(addon.sku); + } - return groups.sort((a, b) => a.displayOrder - b.displayOrder); + return groups; +} + +function createBundle(addon1: CatalogProductBase & { bundledAddonId?: string; isBundledAddon?: boolean }, addon2: CatalogProductBase & { bundledAddonId?: string; isBundledAddon?: boolean }): BundledAddonGroup { + // Determine which is monthly vs onetime + const monthlyAddon = addon1.billingCycle === "Monthly" ? addon1 : addon2; + const onetimeAddon = addon1.billingCycle === "Onetime" ? addon1 : addon2; + + // Use monthly addon name as base, clean it up + const baseName = monthlyAddon.name.replace(/\s*(Monthly|Installation|Fee)\s*/gi, "").trim(); + + return { + id: `bundle-${addon1.sku}-${addon2.sku}`, + name: baseName, + description: `${baseName} (monthly service + installation)`, + monthlyPrice: monthlyAddon.billingCycle === "Monthly" ? getMonthlyPrice(monthlyAddon) : undefined, + activationPrice: onetimeAddon.billingCycle === "Onetime" ? getOneTimePrice(onetimeAddon) : undefined, + skus: [addon1.sku, addon2.sku], + isBundled: true, + displayOrder: Math.min(addon1.displayOrder ?? 0, addon2.displayOrder ?? 0), + }; +} + +function createStandaloneItem(addon: CatalogProductBase & { bundledAddonId?: string; isBundledAddon?: boolean }): BundledAddonGroup { + return { + id: addon.sku, + name: addon.name, + description: addon.description || "", + monthlyPrice: addon.billingCycle === "Monthly" ? getMonthlyPrice(addon) : undefined, + activationPrice: addon.billingCycle === "Onetime" ? getOneTimePrice(addon) : undefined, + skus: [addon.sku], + isBundled: false, + displayOrder: addon.displayOrder ?? 0, + }; } export function AddonGroup({ diff --git a/apps/portal/src/features/catalog/components/base/EnhancedOrderSummary.tsx b/apps/portal/src/features/catalog/components/base/EnhancedOrderSummary.tsx index 1abf7e1b..85c88eb4 100644 --- a/apps/portal/src/features/catalog/components/base/EnhancedOrderSummary.tsx +++ b/apps/portal/src/features/catalog/components/base/EnhancedOrderSummary.tsx @@ -348,12 +348,12 @@ export function EnhancedOrderSummary({ variant="outline" className="flex-1 group" disabled={disabled || loading} + leftIcon={} onClick={() => { if (disabled || loading) return; router.push(backUrl); }} > - {backLabel} ) : onBack ? ( @@ -362,44 +362,22 @@ export function EnhancedOrderSummary({ variant="outline" className="flex-1 group" disabled={disabled || loading} + leftIcon={} > - {backLabel} ) : null} {onContinue && ( - )} diff --git a/apps/portal/src/features/catalog/components/base/OrderSummary.tsx b/apps/portal/src/features/catalog/components/base/OrderSummary.tsx index c86a901e..731b8796 100644 --- a/apps/portal/src/features/catalog/components/base/OrderSummary.tsx +++ b/apps/portal/src/features/catalog/components/base/OrderSummary.tsx @@ -1,6 +1,7 @@ import { ArrowLeftIcon, ArrowRightIcon } from "@heroicons/react/24/outline"; import type { CatalogProductBase } from "@customer-portal/domain"; import { useRouter } from "next/navigation"; +import { Button } from "@/components/atoms/button"; import { getMonthlyPrice, getOneTimePrice } from "../../utils/pricing"; interface OrderSummaryProps { @@ -237,41 +238,40 @@ export function OrderSummary({ {variant === "simple" ? ( <> {backUrl ? ( - + ) : null} {onContinue ? ( - + ) : null} ) : onContinue ? ( - + ) : null} )} diff --git a/apps/portal/src/features/catalog/components/base/ProductCard.tsx b/apps/portal/src/features/catalog/components/base/ProductCard.tsx index f931224c..d32d7559 100644 --- a/apps/portal/src/features/catalog/components/base/ProductCard.tsx +++ b/apps/portal/src/features/catalog/components/base/ProductCard.tsx @@ -154,18 +154,22 @@ export function ProductCard({ ) : onClick ? ( - ) : null} diff --git a/apps/portal/src/features/catalog/components/common/ServiceHeroCard.tsx b/apps/portal/src/features/catalog/components/common/ServiceHeroCard.tsx index 0574e4f3..3939d46e 100644 --- a/apps/portal/src/features/catalog/components/common/ServiceHeroCard.tsx +++ b/apps/portal/src/features/catalog/components/common/ServiceHeroCard.tsx @@ -78,9 +78,9 @@ export function ServiceHeroCard({ href={href} className="w-full font-semibold rounded-2xl relative z-10 group" size="lg" + rightIcon={} > - Explore Plans - + Explore Plans diff --git a/apps/portal/src/features/catalog/components/internet/InternetPlanCard.tsx b/apps/portal/src/features/catalog/components/internet/InternetPlanCard.tsx index b3534cd8..b4351be2 100644 --- a/apps/portal/src/features/catalog/components/internet/InternetPlanCard.tsx +++ b/apps/portal/src/features/catalog/components/internet/InternetPlanCard.tsx @@ -40,16 +40,16 @@ export function InternetPlanCard({ const minInstallationPrice = installationPrices.length ? Math.min(...installationPrices) : 0; const getBorderClass = () => { - if (isGold) return "border-2 border-yellow-400 shadow-lg hover:shadow-xl"; - if (isPlatinum) return "border-2 border-indigo-400 shadow-lg hover:shadow-xl"; - if (isSilver) return "border-2 border-gray-300 shadow-lg hover:shadow-xl"; - return "border border-gray-200 shadow-lg hover:shadow-xl"; + if (isGold) return "border-2 border-yellow-400/50 bg-gradient-to-br from-yellow-50/80 to-amber-50/80 backdrop-blur-sm shadow-xl hover:shadow-2xl ring-2 ring-yellow-200/30"; + if (isPlatinum) return "border-2 border-indigo-400/50 bg-gradient-to-br from-indigo-50/80 to-purple-50/80 backdrop-blur-sm shadow-xl hover:shadow-2xl ring-2 ring-indigo-200/30"; + if (isSilver) return "border-2 border-gray-300/50 bg-gradient-to-br from-gray-50/80 to-slate-50/80 backdrop-blur-sm shadow-xl hover:shadow-2xl ring-2 ring-gray-200/30"; + return "border border-gray-200/50 bg-white/80 backdrop-blur-sm shadow-lg hover:shadow-xl"; }; return (
@@ -129,15 +129,13 @@ export function InternetPlanCard({
diff --git a/apps/portal/src/features/catalog/components/internet/configure/steps/ReviewOrderStep.tsx b/apps/portal/src/features/catalog/components/internet/configure/steps/ReviewOrderStep.tsx index bdc6533a..5cbf75fb 100644 --- a/apps/portal/src/features/catalog/components/internet/configure/steps/ReviewOrderStep.tsx +++ b/apps/portal/src/features/catalog/components/internet/configure/steps/ReviewOrderStep.tsx @@ -54,7 +54,7 @@ export function ReviewOrderStep({ />
-
+
-

Order Summary

+
+ {/* Receipt Header */} +
+

Order Summary

+

Review your configuration

+
{/* Plan Details */} -
- - - - - {selectedAddons.map(addon => ( - - ))} +
+
+
+

{plan.name}

+

Internet Service

+ {mode &&

Access Mode: {mode}

} +
+
+

+ ¥{getMonthlyPrice(plan).toLocaleString()} +

+

per month

+
+
+ {/* Installation */} + {getMonthlyPrice(selectedInstallation) > 0 || getOneTimePrice(selectedInstallation) > 0 ? ( +
+

Installation

+
+ {selectedInstallation.name} + + {getMonthlyPrice(selectedInstallation) > 0 && ( + <> + ¥{getMonthlyPrice(selectedInstallation).toLocaleString()} + /mo + + )} + {getOneTimePrice(selectedInstallation) > 0 && ( + <> + ¥{getOneTimePrice(selectedInstallation).toLocaleString()} + /once + + )} + +
+
+ ) : null} + + {/* Add-ons */} + {selectedAddons.length > 0 && ( +
+

Add-ons

+
+ {selectedAddons.map(addon => ( +
+ {addon.name} + + {getMonthlyPrice(addon) > 0 && ( + <> + ¥{getMonthlyPrice(addon).toLocaleString()} + /mo + + )} + {getOneTimePrice(addon) > 0 && ( + <> + ¥{getOneTimePrice(addon).toLocaleString()} + /once + + )} + +
+ ))} +
+
+ )} + {/* Totals */} -
-
- Monthly Total: - ¥{monthlyTotal.toLocaleString()} -
-
- One-time Total: - ¥{oneTimeTotal.toLocaleString()} -
-
- Total First Month: - ¥{(monthlyTotal + oneTimeTotal).toLocaleString()} +
+
+
+ Monthly Total + ¥{monthlyTotal.toLocaleString()} +
+ {oneTimeTotal > 0 && ( +
+ One-time Total + + ¥{oneTimeTotal.toLocaleString()} + +
+ )}
- - ); -} -function OrderItem({ - title, - subtitle, - monthlyPrice, - oneTimePrice, -}: { - title: string; - subtitle?: string; - monthlyPrice: number; - oneTimePrice: number; -}) { - return ( -
-
-

{title}

- {subtitle &&

{subtitle}

} -
-
- {monthlyPrice > 0 && ( -
¥{monthlyPrice.toLocaleString()}/mo
- )} - {oneTimePrice > 0 && ( -
¥{oneTimePrice.toLocaleString()} setup
- )} + {/* Receipt Footer */} +
+

High-speed internet service

); } + diff --git a/apps/portal/src/features/catalog/services/catalog.service.ts b/apps/portal/src/features/catalog/services/catalog.service.ts index 0bf0c470..666fa3ba 100644 --- a/apps/portal/src/features/catalog/services/catalog.service.ts +++ b/apps/portal/src/features/catalog/services/catalog.service.ts @@ -1,4 +1,4 @@ -import { apiClient, getDataOrDefault } from "@/lib/api"; +import { apiClient, getDataOrDefault, getDataOrThrow } from "@/lib/api"; import type { InternetPlanCatalogItem, InternetInstallationCatalogItem, @@ -40,7 +40,7 @@ export const catalogService = { addons: InternetAddonCatalogItem[]; }> { const response = await apiClient.GET("/api/catalog/internet/plans"); - return getDataOrDefault(response, defaultInternetCatalog); + return getDataOrThrow(response, "Failed to load internet catalog"); }, async getInternetInstallations(): Promise { diff --git a/apps/portal/src/features/catalog/views/CatalogHome.tsx b/apps/portal/src/features/catalog/views/CatalogHome.tsx index ffeffdce..2f3b176b 100644 --- a/apps/portal/src/features/catalog/views/CatalogHome.tsx +++ b/apps/portal/src/features/catalog/views/CatalogHome.tsx @@ -15,8 +15,9 @@ import { FeatureCard } from "@/features/catalog/components/common/FeatureCard"; export function CatalogHomeView() { return ( - } title="" description=""> -
+
+ } title="" description=""> +
@@ -96,8 +97,9 @@ export function CatalogHomeView() { />
-
-
+
+ +
); } diff --git a/apps/portal/src/features/catalog/views/InternetPlans.tsx b/apps/portal/src/features/catalog/views/InternetPlans.tsx index 21b7588b..018c0685 100644 --- a/apps/portal/src/features/catalog/views/InternetPlans.tsx +++ b/apps/portal/src/features/catalog/views/InternetPlans.tsx @@ -106,35 +106,56 @@ export function InternetPlansContainer() {
- ); - } +
+ ); +} return ( - } - > -
-
- -
+
+ } + > +
+ {/* Enhanced Back Button */} +
+ +
-
-

Choose Your Internet Plan

+ {/* Enhanced Header */} +
+ {/* Background decoration */} +
+
+
+
+ +

+ Choose Your Internet Plan +

+

+ High-speed fiber internet with reliable connectivity for your home or business +

{eligibility && ( -
+
{getEligibilityIcon(eligibility)} - Available for: {eligibility} + Available for: {eligibility}
-

+

Plans shown are tailored to your house type and local infrastructure

@@ -197,8 +218,11 @@ export function InternetPlansContainer() {

We couldn't find any internet plans available for your location at this time.

-
diff --git a/apps/portal/src/features/catalog/views/SimPlans.tsx b/apps/portal/src/features/catalog/views/SimPlans.tsx index 2d53f3ac..a5f2846a 100644 --- a/apps/portal/src/features/catalog/views/SimPlans.tsx +++ b/apps/portal/src/features/catalog/views/SimPlans.tsx @@ -109,8 +109,12 @@ export function SimPlansContainer() {
Failed to load SIM plans
{errorMessage}
-
@@ -130,22 +134,39 @@ export function SimPlansContainer() { ); return ( - } - > +
+ } + >
-
-
-
-

Choose Your SIM Plan

-

+ {/* Enhanced Header */} +

+ {/* Background decoration */} +
+
+
+
+ +

+ Choose Your SIM Plan +

+

Wide range of data options and voice plans with both physical SIM and eSIM options.

@@ -371,7 +392,8 @@ export function SimPlansContainer() {
-
+ +
); } diff --git a/apps/portal/src/features/catalog/views/VpnPlans.tsx b/apps/portal/src/features/catalog/views/VpnPlans.tsx index 0ca2fbcb..be100c6f 100644 --- a/apps/portal/src/features/catalog/views/VpnPlans.tsx +++ b/apps/portal/src/features/catalog/views/VpnPlans.tsx @@ -16,44 +16,80 @@ export function VpnPlansView() { if (isLoading || error) { return ( - } - > - + } > - <> - - +
+ {/* Enhanced Back Button */} +
+ +
+ + +
+ {Array.from({ length: 4 }).map((_, index) => ( + + ))} +
+
+
+
+
); } return ( - } - > +
+ } + >
-
-
-
-

- SonixNet VPN Rental Router Service + {/* Enhanced Header */} +
+ {/* Background decoration */} +
+
+
+
+ +

+ SonixNet VPN Router Service

-

- Fast and secure VPN connection to San Francisco or London for accessing geo-restricted - content. +

+ Fast and secure VPN connection to San Francisco or London for accessing geo-restricted content.

@@ -82,8 +118,11 @@ export function VpnPlansView() {

We couldn't find any VPN plans available at this time.

-

@@ -121,7 +160,8 @@ export function VpnPlansView() { streaming/browsing.
-
+ +
); } diff --git a/apps/portal/src/features/subscriptions/hooks/useSubscriptions.ts b/apps/portal/src/features/subscriptions/hooks/useSubscriptions.ts index 24f491b4..d592210e 100644 --- a/apps/portal/src/features/subscriptions/hooks/useSubscriptions.ts +++ b/apps/portal/src/features/subscriptions/hooks/useSubscriptions.ts @@ -61,7 +61,7 @@ export function useSubscriptions(options: UseSubscriptionsOptions = {}) { "/api/subscriptions", status ? { params: { query: { status } } } : undefined ); - return toSubscriptionList(getNullableData(response)); + return toSubscriptionList(getDataOrThrow(response, "Failed to load subscriptions")); }, staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000, @@ -79,7 +79,7 @@ export function useActiveSubscriptions() { queryKey: queryKeys.subscriptions.active(), queryFn: async () => { const response = await apiClient.GET("/api/subscriptions/active"); - return getDataOrDefault(response, []); + return getDataOrThrow(response, "Failed to load active subscriptions"); }, staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000, @@ -97,7 +97,7 @@ export function useSubscriptionStats() { queryKey: queryKeys.subscriptions.stats(), queryFn: async () => { const response = await apiClient.GET("/api/subscriptions/stats"); - return getDataOrDefault(response, emptyStats); + return getDataOrThrow(response, "Failed to load subscription statistics"); }, staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000, @@ -117,7 +117,7 @@ export function useSubscription(subscriptionId: number) { const response = await apiClient.GET("/api/subscriptions/{id}", { params: { path: { id: subscriptionId } }, }); - return getDataOrThrow(response, "Subscription not found"); + return getDataOrThrow(response, "Failed to load subscription details"); }, staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000, @@ -144,13 +144,7 @@ export function useSubscriptionInvoices( query: { page, limit }, }, }); - return getDataOrDefault(response, { - ...emptyInvoiceList, - pagination: { - ...emptyInvoiceList.pagination, - page, - }, - }); + return getDataOrThrow(response, "Failed to load subscription invoices"); }, staleTime: 60 * 1000, gcTime: 5 * 60 * 1000,