Skip to content

Commit 1afb1a7

Browse files
committed
fix: usage based
1 parent 3dc3fef commit 1afb1a7

File tree

5 files changed

+107
-84
lines changed

5 files changed

+107
-84
lines changed

apps/dashboard/app/(main)/billing/components/overview-tab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { Progress } from '@/components/ui/progress';
2525
import { Separator } from '@/components/ui/separator';
2626
import { Skeleton } from '@/components/ui/skeleton';
2727
import { cn } from '@/lib/utils';
28-
import { type FeatureUsage, useBillingData } from '../data/billing-data';
28+
import { type FeatureUsage, useBillingData } from '../hooks/use-billing';
2929
import { CancelSubscriptionDialog } from './cancel-subscription-dialog';
3030
import { NoPaymentMethodDialog } from './no-payment-method-dialog';
3131

apps/dashboard/app/(main)/billing/data/billing-data.ts

Lines changed: 0 additions & 66 deletions
This file was deleted.

apps/dashboard/app/(main)/billing/hooks/use-billing.ts

Lines changed: 104 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,26 @@
1-
import { useAutumn } from 'autumn-js/react';
1+
import type { Customer, CustomerProduct } from 'autumn-js';
2+
import { useAutumn, useCustomer, usePricingTable } from 'autumn-js/react';
23
import dayjs from 'dayjs';
34
import { useState } from 'react';
45
import { toast } from 'sonner';
56
import AttachDialog from '@/components/autumn/attach-dialog';
6-
import type { Customer, CustomerProduct } from '../data/billing-data';
7+
8+
export type FeatureUsage = {
9+
id: string;
10+
name: string;
11+
used: number;
12+
limit: number;
13+
unlimited: boolean;
14+
nextReset: string | null;
15+
interval: string | null;
16+
};
17+
18+
export type Usage = {
19+
features: FeatureUsage[];
20+
};
21+
22+
// Re-export types for compatibility
23+
export type { Customer, CustomerInvoice as Invoice } from 'autumn-js';
724

825
export function useBilling(refetch?: () => void) {
926
const { attach, cancel, check, track, openBillingPortal } = useAutumn();
@@ -26,8 +43,12 @@ export function useBilling(refetch?: () => void) {
2643
dialog: AttachDialog,
2744
successUrl: `${window.location.origin}/billing`,
2845
});
29-
} catch (error: any) {
30-
toast.error(error.message || 'An unexpected error occurred.');
46+
} catch (error) {
47+
const message =
48+
error instanceof Error
49+
? error.message
50+
: 'An unexpected error occurred.';
51+
toast.error(message);
3152
} finally {
3253
setIsActionLoading(false);
3354
}
@@ -48,8 +69,12 @@ export function useBilling(refetch?: () => void) {
4869
if (refetch) {
4970
setTimeout(() => refetch(), 500);
5071
}
51-
} catch (error: any) {
52-
toast.error(error.message || 'Failed to cancel subscription.');
72+
} catch (error) {
73+
const message =
74+
error instanceof Error
75+
? error.message
76+
: 'Failed to cancel subscription.';
77+
toast.error(message);
5378
} finally {
5479
setIsLoading(false);
5580
}
@@ -79,15 +104,12 @@ export function useBilling(refetch?: () => void) {
79104
};
80105

81106
const getSubscriptionStatus = (product: CustomerProduct) => {
82-
if (product.status === 'canceled') {
83-
return 'Cancelled';
107+
if (product.canceled_at) {
108+
return 'Cancelling';
84109
}
85110
if (product.status === 'scheduled') {
86111
return 'Scheduled';
87112
}
88-
if (product.canceled_at) {
89-
return 'Cancelling';
90-
}
91113
return 'Active';
92114
};
93115

@@ -114,13 +136,19 @@ export function useBilling(refetch?: () => void) {
114136
return null;
115137
}
116138

139+
const includedUsage = feature.included_usage || 0;
140+
const balance = feature.balance || 0;
141+
// Calculate used amount: included_usage - balance
142+
// If usage field exists and is greater, use that instead (for accuracy)
143+
const calculatedUsed = Math.max(0, includedUsage - balance);
144+
const reportedUsage = feature.usage || 0;
145+
const actualUsed = Math.max(calculatedUsed, reportedUsage);
146+
117147
return {
118148
id: feature.id,
119149
name: feature.name,
120-
used: feature.usage,
121-
limit: feature.unlimited
122-
? Number.POSITIVE_INFINITY
123-
: feature.included_usage,
150+
used: actualUsed,
151+
limit: feature.unlimited ? Number.POSITIVE_INFINITY : includedUsage,
124152
unlimited: feature.unlimited,
125153
nextReset: feature.next_reset_at
126154
? dayjs(feature.next_reset_at).format('MMM D, YYYY')
@@ -148,3 +176,64 @@ export function useBilling(refetch?: () => void) {
148176
getFeatureUsage,
149177
};
150178
}
179+
180+
// Consolidated billing data hook
181+
export function useBillingData() {
182+
const {
183+
customer,
184+
isLoading: isCustomerLoading,
185+
refetch: refetchCustomer,
186+
} = useCustomer({
187+
expand: ['invoices'],
188+
});
189+
190+
const {
191+
products,
192+
isLoading: isPricingLoading,
193+
refetch: refetchPricing,
194+
} = usePricingTable();
195+
196+
const isLoading = isCustomerLoading || isPricingLoading;
197+
198+
const refetch = () => {
199+
refetchCustomer();
200+
if (typeof refetchPricing === 'function') {
201+
refetchPricing();
202+
}
203+
};
204+
205+
const usage: Usage = {
206+
features: customer
207+
? Object.values(customer.features).map((feature) => {
208+
const includedUsage = feature.included_usage || 0;
209+
const balance = feature.balance || 0;
210+
// Calculate used amount: included_usage - balance
211+
// If usage field exists and is greater, use that instead (for accuracy)
212+
const calculatedUsed = Math.max(0, includedUsage - balance);
213+
const reportedUsage = feature.usage || 0;
214+
const actualUsed = Math.max(calculatedUsed, reportedUsage);
215+
216+
return {
217+
id: feature.id,
218+
name: feature.name,
219+
used: actualUsed,
220+
limit: feature.unlimited ? Number.POSITIVE_INFINITY : includedUsage,
221+
unlimited: !!feature.unlimited,
222+
nextReset: feature.next_reset_at
223+
? new Date(feature.next_reset_at).toLocaleDateString()
224+
: null,
225+
interval: feature.interval || null,
226+
};
227+
})
228+
: [],
229+
};
230+
231+
return {
232+
products: products || [],
233+
usage,
234+
customer,
235+
customerData: customer, // Alias for backward compatibility
236+
isLoading,
237+
refetch,
238+
};
239+
}

apps/dashboard/app/(main)/billing/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
type Customer,
99
type Invoice,
1010
useBillingData,
11-
} from './data/billing-data';
11+
} from './hooks/use-billing';
1212

1313
const OverviewTab = lazy(() =>
1414
import('./components/overview-tab').then((m) => ({ default: m.OverviewTab }))

apps/dashboard/components/analytics/event-limit-indicator.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { WarningIcon } from '@phosphor-icons/react';
44
import { useRouter } from 'next/navigation';
5-
import { useBillingData } from '@/app/(main)/billing/data/billing-data';
5+
import { useBillingData } from '@/app/(main)/billing/hooks/use-billing';
66
import { Button } from '@/components/ui/button';
77

88
export function EventLimitIndicator() {

0 commit comments

Comments
 (0)