Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

653 changes: 331 additions & 322 deletions src/features/analytics-dashboard/components/AnalyticsDashboard.tsx

Large diffs are not rendered by default.

371 changes: 55 additions & 316 deletions src/features/analytics-dashboard/docs/FEATURE_README.md

Large diffs are not rendered by default.

63 changes: 63 additions & 0 deletions src/features/analytics-dashboard/hooks/useAnalyticsDashboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* @fileoverview Analytics dashboard data hook
*/

import { useCallback, useEffect } from 'react';
import { useAuthentication } from '@/hooks/useAuthentication';
import {
analyticsSelectors,
useAnalyticsDashboardStore,
useAnalyticsDashboardSelectors
} from '../stores/AnalyticsDashboardStore';
import type { AnalyticsDashboardConfig, AnalyticsFilters } from '../types/feature.types';

export function useAnalyticsDashboard(config?: Partial<AnalyticsDashboardConfig>) {
const { user } = useAuthentication();
const store = useAnalyticsDashboardStore();
const selectors = useAnalyticsDashboardSelectors({
isLoading: analyticsSelectors.isLoading,
hasData: analyticsSelectors.hasData,
filteredTools: analyticsSelectors.filteredTools
});

const {
status,
filters,
initialize,
loadAnalytics,
updateFilters: setFilters,
refresh
} = store;

useEffect(() => {
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Ready-state effect causes infinite reload: status toggles to 'ready' inside loadAnalytics, which is in the effect dependency list, so every successful fetch re-triggers another fetch.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/features/analytics-dashboard/hooks/useAnalyticsDashboard.ts, line 32:

<comment>Ready-state effect causes infinite reload: status toggles to 'ready' inside loadAnalytics, which is in the effect dependency list, so every successful fetch re-triggers another fetch.</comment>

<file context>
@@ -0,0 +1,63 @@
+    refresh
+  } = store;
+
+  useEffect(() => {
+    initialize(config);
+  }, [initialize, config]);
</file context>
Fix with Cubic

initialize(config);
}, [initialize, config]);
Comment on lines +32 to +34
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initialize function is called on every render when the config prop changes because config is a dependency but is likely a new object reference each time. This will cause unnecessary re-initialization. Consider using a ref or deep comparison, or add a note in the component documentation that config should be memoized by the caller.

Copilot uses AI. Check for mistakes.

useEffect(() => {
if (status === 'idle') {
loadAnalytics(user?.id);
}
}, [status, loadAnalytics, user?.id]);

useEffect(() => {
if (status === 'ready') {
loadAnalytics(user?.id);
}
Comment on lines +42 to +45

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Stop loadAnalytics from looping on ready state

The useEffect that triggers loadAnalytics whenever status === 'ready' will re-run after every successful load because loadAnalytics itself flips status to loading and then back to ready. That creates a perpetual fetch loop even when the time range hasn’t changed, which can spam the backend and keep the dashboard constantly reloading. This happens after the first successful load (status transitions to ready → effect runs → loadAnalytics → ready again → effect runs…). Consider gating this effect on an actual time-range change or removing status as a trigger.

Useful? React with 👍 / 👎.

}, [filters.timeRange, status, loadAnalytics, user?.id]);
Comment on lines +36 to +46

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The data fetching logic in these useEffect hooks is flawed and will cause an infinite loop. The second effect is triggered when status becomes 'ready', which then calls loadAnalytics, which eventually sets the status back to 'ready', re-triggering the effect. While the store has a guard against concurrent loading, this still leads to unnecessary re-renders and at least one extra data fetch.

The data loading logic should be refactored to separate the initial data load from subsequent loads triggered by filter changes.

  const isMounted = React.useRef(false);

  useEffect(() => {
    initialize(config);
    loadAnalytics(user?.id);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [initialize, config, user?.id]);

  useEffect(() => {
    if (isMounted.current) {
      loadAnalytics(user?.id);
    } else {
      isMounted.current = true;
    }
  }, [filters.timeRange]);

Comment on lines +42 to +46
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n src/features/analytics-dashboard/hooks/useAnalyticsDashboard.ts

Repository: Krosebrook/Flashfusionwebsite

Length of output: 2046


🏁 Script executed:

find . -name "AnalyticsDashboardStore*" -type f

Repository: Krosebrook/Flashfusionwebsite

Length of output: 141


🏁 Script executed:

cat -n ./src/features/analytics-dashboard/stores/AnalyticsDashboardStore.ts

Repository: Krosebrook/Flashfusionwebsite

Length of output: 6761


🏁 Script executed:

python3 << 'EOF'
# Trace the execution flow

print("Initial state:")
print("  status: 'idle'")
print("  filters.timeRange: [initial]")
print("")

print("Step 1: Component mounts")
print("  Effect 1 runs (status === 'idle')")
print("  Calls: loadAnalytics(user?.id)")
print("")

print("Step 2: Inside loadAnalytics")
print("  Checks: if (status === 'loading') return;")
print("  status is 'idle', so continues")
print("  Sets: status = 'loading'")
print("  Starts async fetch")
print("")

print("Step 3: After async fetch completes")
print("  Sets: status = 'ready'")
print("  Component re-renders with new status")
print("")

print("Step 4: Effect 2 dependency check")
print("  Dependencies: [filters.timeRange, status, loadAnalytics, user?.id]")
print("  status changed from 'idle'/'loading' to 'ready'")
print("  Effect 2 runs (status === 'ready')")
print("  Calls: loadAnalytics(user?.id)")
print("")

print("Step 5: Inside loadAnalytics (second call)")
print("  Checks: if (status === 'loading') return;")
print("  status is currently 'ready', so continues")
print("  Sets: status = 'loading'")
print("  Starts async fetch AGAIN")
print("")

print("Step 6: After second async fetch completes")
print("  Sets: status = 'ready'")
print("  Component re-renders")
print("")

print("Step 7: Effect 2 runs AGAIN")
print("  status changed from 'loading' to 'ready'")
print("  Condition: if (status === 'ready') is TRUE")
print("  Calls: loadAnalytics(user?.id) AGAIN")
print("")

print("Conclusion: Loop confirmed!")
print("Each time loadAnalytics completes and sets status='ready',")
print("Effect 2 sees the status change and re-triggers,")
print("calling loadAnalytics again, which sets status='ready',")
print("which triggers Effect 2 again, etc.")
EOF

Repository: Krosebrook/Flashfusionwebsite

Length of output: 159


Effect re-fetches on every ready transition (potential infinite loop).

The effect depends on status, which transitions loadingready after each loadAnalytics call. When status becomes ready, the effect re-triggers and calls loadAnalytics again, repeating the cycle. The guard in loadAnalytics prevents only concurrent requests, not sequential ones. Gate the effect on meaningful changes—missing data or timeRange updates—to avoid repeated fetches.

💡 Suggested fix
-import { useCallback, useEffect } from 'react';
+import { useCallback, useEffect, useRef } from 'react';
...
-  const {
-    status,
-    filters,
-    initialize,
-    loadAnalytics,
-    updateFilters: setFilters,
-    refresh
-  } = store;
+  const {
+    data,
+    status,
+    filters,
+    initialize,
+    loadAnalytics,
+    updateFilters: setFilters,
+    refresh
+  } = store;
+
+  const previousTimeRange = useRef(filters.timeRange);
...
-  useEffect(() => {
-    if (status === 'ready') {
-      loadAnalytics(user?.id);
-    }
-  }, [filters.timeRange, status, loadAnalytics, user?.id]);
+  useEffect(() => {
+    if (status !== 'ready') return;
+    const timeRangeChanged = previousTimeRange.current !== filters.timeRange;
+    if (!data || timeRangeChanged) {
+      previousTimeRange.current = filters.timeRange;
+      loadAnalytics(user?.id);
+    }
+  }, [data, filters.timeRange, status, loadAnalytics, user?.id]);
🤖 Prompt for AI Agents
In `@src/features/analytics-dashboard/hooks/useAnalyticsDashboard.ts` around lines
42 - 46, The effect in useAnalyticsDashboard.ts currently re-triggers on every
status === 'ready' transition causing repeated loadAnalytics calls; change the
effect to only call loadAnalytics when there's a meaningful need (e.g., status
is 'idle' or when filters.timeRange or user?.id actually changed or when
analytics data is missing). In practice, update the useEffect that references
status, filters.timeRange, loadAnalytics, and user?.id so it either removes
status from the dependency list and guards with if (status === 'idle') before
calling loadAnalytics, or checks for missing/stale analyticsData (e.g., compare
analyticsData.timeRange to filters.timeRange) and only then invokes
loadAnalytics(user?.id).


Comment on lines +42 to +47
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This effect will trigger a data reload every time the user switches from 'idle' to 'ready' status, which happens after the first successful load. This creates an infinite loop: when loadAnalytics completes, status becomes 'ready', which triggers this effect again at line 43, calling loadAnalytics again, setting status to 'loading', then back to 'ready', repeating indefinitely. Remove the effect at lines 42-46 since the effect at lines 36-40 already handles the initial load when status is 'idle'.

Suggested change
useEffect(() => {
if (status === 'ready') {
loadAnalytics(user?.id);
}
}, [filters.timeRange, status, loadAnalytics, user?.id]);

Copilot uses AI. Check for mistakes.
const updateFilters = useCallback(
(nextFilters: Partial<AnalyticsFilters>) => {
setFilters(nextFilters);
},
[setFilters]
);

return {
...store,
...selectors,
updateFilters,
refresh: () => refresh(user?.id)
};
}

export default useAnalyticsDashboard;
28 changes: 14 additions & 14 deletions src/features/analytics-dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
/**
* @fileoverview Analytics Dashboard Feature - Public API
* @version 1.0.0
*
* Entry point for the Analytics Dashboard feature
* @version 2.0.0
*/

// Export main component
export { AnalyticsDashboard, default } from './components/AnalyticsDashboard';
export { useAnalyticsDashboard } from './hooks/useAnalyticsDashboard';

// Export store
export { useFeatureStore } from './stores/FeatureStore';
export {
useAnalyticsDashboardStore,
analyticsSelectors,
useAnalyticsDashboardSelector,
useAnalyticsDashboardSelectors
} from './stores/AnalyticsDashboardStore';

// Export service
export { FeatureService } from './services/FeatureService';
export { AnalyticsDashboardService } from './services/AnalyticsDashboardService';

// Export types
export type {
FeatureData,
FeatureConfig,
FeatureResult,
FeatureError,
FeatureStatus
AnalyticsDashboardData,
AnalyticsDashboardConfig,
AnalyticsFilters,
AnalyticsStatus,
AnalyticsError
} from './types/feature.types';
Loading
Loading