Merge branch 'pr-373'

This commit is contained in:
ajaysi
2026-03-05 10:24:58 +05:30
7 changed files with 54 additions and 33 deletions

View File

@@ -24,7 +24,7 @@ interface UseCompactBillingDataReturn {
/** /**
* Custom hook for managing CompactBillingDashboard data fetching and state * 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 [dashboardData, setDashboardData] = useState<DashboardData | null>(null);
const [systemHealth, setSystemHealth] = useState<SystemHealth | null>(null); const [systemHealth, setSystemHealth] = useState<SystemHealth | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);

View File

@@ -21,7 +21,7 @@ import { MonthlyBudgetUsage } from './components/MonthlyBudgetUsage';
import { AlertsSection } from './components/AlertsSection'; import { AlertsSection } from './components/AlertsSection';
interface CompactBillingDashboardProps { interface CompactBillingDashboardProps {
userId?: string; userId: string;
terminalTheme?: boolean; terminalTheme?: boolean;
} }

View File

@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useAuth } from '@clerk/clerk-react';
import { import {
Box, Box,
Container, Container,
@@ -64,6 +65,8 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
// Conditional component selection based on terminal theme // Conditional component selection based on terminal theme
const TypographyComponent = terminalTheme ? TerminalTypography : Typography; const TypographyComponent = terminalTheme ? TerminalTypography : Typography;
const AlertComponent = terminalTheme ? TerminalAlert : Alert; const AlertComponent = terminalTheme ? TerminalAlert : Alert;
const { userId: authUserId } = useAuth();
const effectiveUserId = userId || authUserId;
const [dashboardData, setDashboardData] = useState<DashboardData | null>(null); const [dashboardData, setDashboardData] = useState<DashboardData | null>(null);
const [systemHealth, setSystemHealth] = useState<SystemHealth | null>(null); const [systemHealth, setSystemHealth] = useState<SystemHealth | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -73,10 +76,16 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
const [healthError, setHealthError] = useState<string | null>(null); const [healthError, setHealthError] = useState<string | null>(null);
const fetchDashboardData = async (showSuccessToast: boolean = false) => { const fetchDashboardData = async (showSuccessToast: boolean = false) => {
if (!effectiveUserId) {
setLoading(false);
setError('Unable to load billing data: missing authenticated user context.');
return;
}
try { try {
// Use Promise.allSettled to prevent health check timeout from blocking dashboard // Use Promise.allSettled to prevent health check timeout from blocking dashboard
const results = await Promise.allSettled([ const results = await Promise.allSettled([
billingService.getDashboardData(), billingService.getDashboardData(effectiveUserId),
monitoringService.getSystemHealth() monitoringService.getSystemHealth()
]); ]);
@@ -147,14 +156,16 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
}; };
useEffect(() => { useEffect(() => {
if (!effectiveUserId) return;
fetchDashboardData(); fetchDashboardData();
}, [userId]); }, [effectiveUserId]);
// Event-driven refresh: refresh only when non-billing/monitoring APIs complete // Event-driven refresh: refresh only when non-billing/monitoring APIs complete
useEffect(() => { useEffect(() => {
const unsubscribe = onApiEvent((detail) => { const unsubscribe = onApiEvent((detail) => {
if (detail.source && detail.source !== 'other') return; 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) => { .then((results) => {
if (results[0].status === 'fulfilled') { if (results[0].status === 'fulfilled') {
setDashboardData(results[0].value); setDashboardData(results[0].value);
@@ -176,25 +187,26 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
.catch(() => {/* ignore */}); .catch(() => {/* ignore */});
}); });
return unsubscribe; return unsubscribe;
}, []); }, [effectiveUserId]);
// Refetch when tab becomes visible again (cheap, avoids polling) // Refetch when tab becomes visible again (cheap, avoids polling)
useEffect(() => { useEffect(() => {
const onVisible = () => { const onVisible = () => {
if (document.visibilityState === 'visible') { if (document.visibilityState === 'visible' && effectiveUserId) {
fetchDashboardData(); fetchDashboardData();
} }
}; };
document.addEventListener('visibilitychange', onVisible); document.addEventListener('visibilitychange', onVisible);
return () => document.removeEventListener('visibilitychange', onVisible); return () => document.removeEventListener('visibilitychange', onVisible);
}, []); }, [effectiveUserId]);
// Listen for billing refresh requests (e.g., when subscription limits are exceeded) // Listen for billing refresh requests (e.g., when subscription limits are exceeded)
useEffect(() => { useEffect(() => {
const handleBillingRefresh = () => { const handleBillingRefresh = () => {
console.log('EnhancedBillingDashboard: Billing refresh requested, refreshing data...'); console.log('EnhancedBillingDashboard: Billing refresh requested, refreshing data...');
// Use allSettled to prevent health check from blocking refresh // 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) => { .then((results) => {
if (results[0].status === 'fulfilled') { if (results[0].status === 'fulfilled') {
setDashboardData(results[0].value); setDashboardData(results[0].value);
@@ -233,7 +245,7 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
return () => { return () => {
window.removeEventListener('billing-refresh-requested', handleBillingRefresh); 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 = ( const handleViewModeChange = (
event: React.MouseEvent<HTMLElement>, event: React.MouseEvent<HTMLElement>,
@@ -463,7 +475,11 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
exit={{ opacity: 0, x: 20 }} exit={{ opacity: 0, x: 20 }}
transition={{ duration: 0.3 }} 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>
) : ( ) : (
<motion.div <motion.div

View File

@@ -42,7 +42,7 @@ import {
import { showToastNotification } from '../../utils/toastNotifications'; import { showToastNotification } from '../../utils/toastNotifications';
interface SubscriptionRenewalHistoryProps { interface SubscriptionRenewalHistoryProps {
userId?: string; userId: string;
terminalTheme?: boolean; terminalTheme?: boolean;
initialLimit?: number; initialLimit?: number;
} }

View File

@@ -24,7 +24,7 @@ interface UseOAuthTokenAlertsOptions {
enabled?: boolean; 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; userId?: string;
} }
@@ -54,8 +54,7 @@ export function useOAuthTokenAlerts(options: UseOAuthTokenAlertsOptions = {}) {
return; return;
} }
const actualUserId = userId || localStorage.getItem('user_id'); if (!userId) {
if (!actualUserId) {
console.debug('useOAuthTokenAlerts: No user ID available, skipping polling'); console.debug('useOAuthTokenAlerts: No user ID available, skipping polling');
return; return;
} }
@@ -70,7 +69,7 @@ export function useOAuthTokenAlerts(options: UseOAuthTokenAlertsOptions = {}) {
isPollingRef.current = true; isPollingRef.current = true;
// Fetch unread alerts only // Fetch unread alerts only
const alerts = await billingService.getUsageAlerts(actualUserId, true); const alerts = await billingService.getUsageAlerts(userId, true);
// Filter for OAuth token alerts // Filter for OAuth token alerts
const oauthAlerts = alerts.filter( const oauthAlerts = alerts.filter(

View File

@@ -4,6 +4,7 @@
*/ */
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useAuth } from '@clerk/clerk-react';
import { import {
Box, Box,
Container, Container,
@@ -73,6 +74,7 @@ const TerminalIconButton = styled(IconButton)({
const BillingPage: React.FC = () => { const BillingPage: React.FC = () => {
const { subscription, checkSubscription } = useSubscription(); const { subscription, checkSubscription } = useSubscription();
const { userId } = useAuth();
// Monitor subscription status and show toast notifications // Monitor subscription status and show toast notifications
useEffect(() => { useEffect(() => {
@@ -232,14 +234,18 @@ const BillingPage: React.FC = () => {
{/* Billing Dashboard Content */} {/* Billing Dashboard Content */}
<Box> <Box>
<EnhancedBillingDashboard terminalTheme={true} /> {userId ? (
<EnhancedBillingDashboard userId={userId} terminalTheme={true} />
) : null}
{/* Subscription Renewal History Section */} {/* Subscription Renewal History Section */}
{userId ? (
<SubscriptionRenewalHistory <SubscriptionRenewalHistory
userId={undefined} userId={userId}
terminalTheme={true} terminalTheme={true}
initialLimit={20} initialLimit={20}
/> />
) : null}
{/* Usage Logs Section */} {/* Usage Logs Section */}
<Box sx={{ mt: 4 }}> <Box sx={{ mt: 4 }}>

View File

@@ -294,9 +294,9 @@ export const billingService = {
/** /**
* Get comprehensive dashboard data for a user * Get comprehensive dashboard data for a user
*/ */
getDashboardData: async (userId?: string): Promise<DashboardData> => { getDashboardData: async (userId: string): Promise<DashboardData> => {
try { try {
const actualUserId = userId || localStorage.getItem('user_id') || 'demo-user'; const actualUserId = userId;
// Debug logs removed to reduce console noise // Debug logs removed to reduce console noise
const response = await billingAPI.get<DashboardAPIResponse>(`/dashboard/${actualUserId}`); const response = await billingAPI.get<DashboardAPIResponse>(`/dashboard/${actualUserId}`);
@@ -383,9 +383,9 @@ export const billingService = {
/** /**
* Get current usage statistics for a user * Get current usage statistics for a user
*/ */
getUsageStats: async (userId?: string, period?: string): Promise<UsageStats> => { getUsageStats: async (userId: string, period?: string): Promise<UsageStats> => {
try { try {
const actualUserId = userId || localStorage.getItem('user_id') || 'demo-user'; const actualUserId = userId;
const params = period ? { billing_period: period } : {}; const params = period ? { billing_period: period } : {};
const response = await billingAPI.get<UsageAPIResponse>(`/usage/${actualUserId}`, { params }); const response = await billingAPI.get<UsageAPIResponse>(`/usage/${actualUserId}`, { params });
@@ -409,9 +409,9 @@ export const billingService = {
/** /**
* Get usage trends over time * Get usage trends over time
*/ */
getUsageTrends: async (userId?: string, months: number = 6): Promise<UsageTrends> => { getUsageTrends: async (userId: string, months: number = 6): Promise<UsageTrends> => {
try { try {
const actualUserId = userId || localStorage.getItem('user_id') || 'demo-user'; const actualUserId = userId;
const response = await billingAPI.get(`/usage/${actualUserId}/trends`, { const response = await billingAPI.get(`/usage/${actualUserId}/trends`, {
params: { months } params: { months }
}); });
@@ -469,9 +469,9 @@ export const billingService = {
/** /**
* Get usage alerts for a user * 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 { try {
const actualUserId = userId || localStorage.getItem('user_id') || 'demo-user'; const actualUserId = userId;
const response = await billingAPI.get<AlertsAPIResponse>(`/alerts/${actualUserId}`, { const response = await billingAPI.get<AlertsAPIResponse>(`/alerts/${actualUserId}`, {
params: { unread_only: unreadOnly } params: { unread_only: unreadOnly }
}); });
@@ -507,9 +507,9 @@ export const billingService = {
/** /**
* Get user's current subscription information * Get user's current subscription information
*/ */
getUserSubscription: async (userId?: string) => { getUserSubscription: async (userId: string) => {
try { try {
const actualUserId = userId || localStorage.getItem('user_id') || 'demo-user'; const actualUserId = userId;
const response = await billingAPI.get(`/user/${actualUserId}/subscription`); const response = await billingAPI.get(`/user/${actualUserId}/subscription`);
if (!response.data.success) { if (!response.data.success) {
@@ -555,12 +555,12 @@ export const billingService = {
* Get subscription renewal history for the current user * Get subscription renewal history for the current user
*/ */
getRenewalHistory: async ( getRenewalHistory: async (
userId?: string, userId: string,
limit: number = 50, limit: number = 50,
offset: number = 0 offset: number = 0
): Promise<RenewalHistoryResponse> => { ): Promise<RenewalHistoryResponse> => {
try { try {
const actualUserId = userId || localStorage.getItem('user_id') || 'demo-user'; const actualUserId = userId;
const params: any = { limit, offset }; const params: any = { limit, offset };
const response = await billingAPI.get<RenewalHistoryAPIResponse>( const response = await billingAPI.get<RenewalHistoryAPIResponse>(