Merge branch 'pr-373'
This commit is contained in:
@@ -24,7 +24,7 @@ interface UseCompactBillingDataReturn {
|
||||
/**
|
||||
* Custom hook for managing CompactBillingDashboard data fetching and state
|
||||
*/
|
||||
export const useCompactBillingData = (userId?: string): UseCompactBillingDataReturn => {
|
||||
export const useCompactBillingData = (userId: string): UseCompactBillingDataReturn => {
|
||||
const [dashboardData, setDashboardData] = useState<DashboardData | null>(null);
|
||||
const [systemHealth, setSystemHealth] = useState<SystemHealth | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -21,7 +21,7 @@ import { MonthlyBudgetUsage } from './components/MonthlyBudgetUsage';
|
||||
import { AlertsSection } from './components/AlertsSection';
|
||||
|
||||
interface CompactBillingDashboardProps {
|
||||
userId?: string;
|
||||
userId: string;
|
||||
terminalTheme?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useAuth } from '@clerk/clerk-react';
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
@@ -64,6 +65,8 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
|
||||
// Conditional component selection based on terminal theme
|
||||
const TypographyComponent = terminalTheme ? TerminalTypography : Typography;
|
||||
const AlertComponent = terminalTheme ? TerminalAlert : Alert;
|
||||
const { userId: authUserId } = useAuth();
|
||||
const effectiveUserId = userId || authUserId;
|
||||
const [dashboardData, setDashboardData] = useState<DashboardData | null>(null);
|
||||
const [systemHealth, setSystemHealth] = useState<SystemHealth | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -73,10 +76,16 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
|
||||
const [healthError, setHealthError] = useState<string | null>(null);
|
||||
|
||||
const fetchDashboardData = async (showSuccessToast: boolean = false) => {
|
||||
if (!effectiveUserId) {
|
||||
setLoading(false);
|
||||
setError('Unable to load billing data: missing authenticated user context.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Use Promise.allSettled to prevent health check timeout from blocking dashboard
|
||||
const results = await Promise.allSettled([
|
||||
billingService.getDashboardData(),
|
||||
billingService.getDashboardData(effectiveUserId),
|
||||
monitoringService.getSystemHealth()
|
||||
]);
|
||||
|
||||
@@ -147,14 +156,16 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!effectiveUserId) return;
|
||||
fetchDashboardData();
|
||||
}, [userId]);
|
||||
}, [effectiveUserId]);
|
||||
|
||||
// Event-driven refresh: refresh only when non-billing/monitoring APIs complete
|
||||
useEffect(() => {
|
||||
const unsubscribe = onApiEvent((detail) => {
|
||||
if (detail.source && detail.source !== 'other') return;
|
||||
Promise.allSettled([billingService.getDashboardData(), monitoringService.getSystemHealth()])
|
||||
if (!effectiveUserId) return;
|
||||
Promise.allSettled([billingService.getDashboardData(effectiveUserId), monitoringService.getSystemHealth()])
|
||||
.then((results) => {
|
||||
if (results[0].status === 'fulfilled') {
|
||||
setDashboardData(results[0].value);
|
||||
@@ -176,25 +187,26 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
|
||||
.catch(() => {/* ignore */});
|
||||
});
|
||||
return unsubscribe;
|
||||
}, []);
|
||||
}, [effectiveUserId]);
|
||||
|
||||
// Refetch when tab becomes visible again (cheap, avoids polling)
|
||||
useEffect(() => {
|
||||
const onVisible = () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
if (document.visibilityState === 'visible' && effectiveUserId) {
|
||||
fetchDashboardData();
|
||||
}
|
||||
};
|
||||
document.addEventListener('visibilitychange', onVisible);
|
||||
return () => document.removeEventListener('visibilitychange', onVisible);
|
||||
}, []);
|
||||
}, [effectiveUserId]);
|
||||
|
||||
// Listen for billing refresh requests (e.g., when subscription limits are exceeded)
|
||||
useEffect(() => {
|
||||
const handleBillingRefresh = () => {
|
||||
console.log('EnhancedBillingDashboard: Billing refresh requested, refreshing data...');
|
||||
// Use allSettled to prevent health check from blocking refresh
|
||||
Promise.allSettled([billingService.getDashboardData(), monitoringService.getSystemHealth()])
|
||||
if (!effectiveUserId) return;
|
||||
Promise.allSettled([billingService.getDashboardData(effectiveUserId), monitoringService.getSystemHealth()])
|
||||
.then((results) => {
|
||||
if (results[0].status === 'fulfilled') {
|
||||
setDashboardData(results[0].value);
|
||||
@@ -233,7 +245,7 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
|
||||
return () => {
|
||||
window.removeEventListener('billing-refresh-requested', handleBillingRefresh);
|
||||
};
|
||||
}, []); // Empty deps - handler doesn't depend on component state
|
||||
}, [effectiveUserId]); // Empty deps - handler doesn't depend on component state
|
||||
|
||||
const handleViewModeChange = (
|
||||
event: React.MouseEvent<HTMLElement>,
|
||||
@@ -463,7 +475,11 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
|
||||
exit={{ opacity: 0, x: 20 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<CompactBillingDashboard userId={userId} terminalTheme={terminalTheme} />
|
||||
{effectiveUserId ? (
|
||||
<CompactBillingDashboard userId={effectiveUserId} terminalTheme={terminalTheme} />
|
||||
) : (
|
||||
<AlertComponent severity="warning">Unable to load billing data: missing authenticated user context.</AlertComponent>
|
||||
)}
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
|
||||
@@ -42,7 +42,7 @@ import {
|
||||
import { showToastNotification } from '../../utils/toastNotifications';
|
||||
|
||||
interface SubscriptionRenewalHistoryProps {
|
||||
userId?: string;
|
||||
userId: string;
|
||||
terminalTheme?: boolean;
|
||||
initialLimit?: number;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ interface UseOAuthTokenAlertsOptions {
|
||||
enabled?: boolean;
|
||||
|
||||
/**
|
||||
* User ID - if not provided, will use localStorage or skip polling
|
||||
* Authenticated user ID from Clerk; polling is skipped when unavailable
|
||||
*/
|
||||
userId?: string;
|
||||
}
|
||||
@@ -54,8 +54,7 @@ export function useOAuthTokenAlerts(options: UseOAuthTokenAlertsOptions = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actualUserId = userId || localStorage.getItem('user_id');
|
||||
if (!actualUserId) {
|
||||
if (!userId) {
|
||||
console.debug('useOAuthTokenAlerts: No user ID available, skipping polling');
|
||||
return;
|
||||
}
|
||||
@@ -70,7 +69,7 @@ export function useOAuthTokenAlerts(options: UseOAuthTokenAlertsOptions = {}) {
|
||||
isPollingRef.current = true;
|
||||
|
||||
// Fetch unread alerts only
|
||||
const alerts = await billingService.getUsageAlerts(actualUserId, true);
|
||||
const alerts = await billingService.getUsageAlerts(userId, true);
|
||||
|
||||
// Filter for OAuth token alerts
|
||||
const oauthAlerts = alerts.filter(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { useAuth } from '@clerk/clerk-react';
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
@@ -73,6 +74,7 @@ const TerminalIconButton = styled(IconButton)({
|
||||
|
||||
const BillingPage: React.FC = () => {
|
||||
const { subscription, checkSubscription } = useSubscription();
|
||||
const { userId } = useAuth();
|
||||
|
||||
// Monitor subscription status and show toast notifications
|
||||
useEffect(() => {
|
||||
@@ -232,14 +234,18 @@ const BillingPage: React.FC = () => {
|
||||
|
||||
{/* Billing Dashboard Content */}
|
||||
<Box>
|
||||
<EnhancedBillingDashboard terminalTheme={true} />
|
||||
{userId ? (
|
||||
<EnhancedBillingDashboard userId={userId} terminalTheme={true} />
|
||||
) : null}
|
||||
|
||||
{/* Subscription Renewal History Section */}
|
||||
<SubscriptionRenewalHistory
|
||||
userId={undefined}
|
||||
{userId ? (
|
||||
<SubscriptionRenewalHistory
|
||||
userId={userId}
|
||||
terminalTheme={true}
|
||||
initialLimit={20}
|
||||
/>
|
||||
initialLimit={20}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{/* Usage Logs Section */}
|
||||
<Box sx={{ mt: 4 }}>
|
||||
|
||||
@@ -294,9 +294,9 @@ export const billingService = {
|
||||
/**
|
||||
* Get comprehensive dashboard data for a user
|
||||
*/
|
||||
getDashboardData: async (userId?: string): Promise<DashboardData> => {
|
||||
getDashboardData: async (userId: string): Promise<DashboardData> => {
|
||||
try {
|
||||
const actualUserId = userId || localStorage.getItem('user_id') || 'demo-user';
|
||||
const actualUserId = userId;
|
||||
// Debug logs removed to reduce console noise
|
||||
|
||||
const response = await billingAPI.get<DashboardAPIResponse>(`/dashboard/${actualUserId}`);
|
||||
@@ -383,9 +383,9 @@ export const billingService = {
|
||||
/**
|
||||
* Get current usage statistics for a user
|
||||
*/
|
||||
getUsageStats: async (userId?: string, period?: string): Promise<UsageStats> => {
|
||||
getUsageStats: async (userId: string, period?: string): Promise<UsageStats> => {
|
||||
try {
|
||||
const actualUserId = userId || localStorage.getItem('user_id') || 'demo-user';
|
||||
const actualUserId = userId;
|
||||
const params = period ? { billing_period: period } : {};
|
||||
|
||||
const response = await billingAPI.get<UsageAPIResponse>(`/usage/${actualUserId}`, { params });
|
||||
@@ -409,9 +409,9 @@ export const billingService = {
|
||||
/**
|
||||
* Get usage trends over time
|
||||
*/
|
||||
getUsageTrends: async (userId?: string, months: number = 6): Promise<UsageTrends> => {
|
||||
getUsageTrends: async (userId: string, months: number = 6): Promise<UsageTrends> => {
|
||||
try {
|
||||
const actualUserId = userId || localStorage.getItem('user_id') || 'demo-user';
|
||||
const actualUserId = userId;
|
||||
const response = await billingAPI.get(`/usage/${actualUserId}/trends`, {
|
||||
params: { months }
|
||||
});
|
||||
@@ -469,9 +469,9 @@ export const billingService = {
|
||||
/**
|
||||
* Get usage alerts for a user
|
||||
*/
|
||||
getUsageAlerts: async (userId?: string, unreadOnly: boolean = false): Promise<UsageAlert[]> => {
|
||||
getUsageAlerts: async (userId: string, unreadOnly: boolean = false): Promise<UsageAlert[]> => {
|
||||
try {
|
||||
const actualUserId = userId || localStorage.getItem('user_id') || 'demo-user';
|
||||
const actualUserId = userId;
|
||||
const response = await billingAPI.get<AlertsAPIResponse>(`/alerts/${actualUserId}`, {
|
||||
params: { unread_only: unreadOnly }
|
||||
});
|
||||
@@ -507,9 +507,9 @@ export const billingService = {
|
||||
/**
|
||||
* Get user's current subscription information
|
||||
*/
|
||||
getUserSubscription: async (userId?: string) => {
|
||||
getUserSubscription: async (userId: string) => {
|
||||
try {
|
||||
const actualUserId = userId || localStorage.getItem('user_id') || 'demo-user';
|
||||
const actualUserId = userId;
|
||||
const response = await billingAPI.get(`/user/${actualUserId}/subscription`);
|
||||
|
||||
if (!response.data.success) {
|
||||
@@ -555,12 +555,12 @@ export const billingService = {
|
||||
* Get subscription renewal history for the current user
|
||||
*/
|
||||
getRenewalHistory: async (
|
||||
userId?: string,
|
||||
userId: string,
|
||||
limit: number = 50,
|
||||
offset: number = 0
|
||||
): Promise<RenewalHistoryResponse> => {
|
||||
try {
|
||||
const actualUserId = userId || localStorage.getItem('user_id') || 'demo-user';
|
||||
const actualUserId = userId;
|
||||
const params: any = { limit, offset };
|
||||
|
||||
const response = await billingAPI.get<RenewalHistoryAPIResponse>(
|
||||
|
||||
Reference in New Issue
Block a user