Skip to content

Commit 0dd5a4d

Browse files
committed
refactor: Full dashboard migration to React Query
BREAKING CHANGE: Dashboard now uses useDashboardData hook instead of manual useState/useEffect data fetching. Changes: - Removed legacy useState declarations for stats, posts, activities, usage - Removed 6 loader functions (loadDashboardData, loadStats, loadPostHistory, loadUserSettings, loadGitHubActivity, loadUsage, loadTemplates) - Integrated useDashboardData hook with user-scoped cache keys - Replaced loadStats/loadPostHistory calls with refetchStats/refetchPosts - Auto-populate context from firstActivityContext - Updated useDashboardData.ts with correct type imports Result: ~120 lines of legacy code removed, automatic caching, background refetching, and proper cache invalidation after post actions. npm run build: PASSED
1 parent e21bbc2 commit 0dd5a4d

File tree

2 files changed

+47
-189
lines changed

2 files changed

+47
-189
lines changed

web/src/hooks/useDashboardData.ts

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
* - Parallel data fetching with Promise.all behavior
88
* - Type-safe response handling
99
*/
10-
import { useQuery, useQueries } from '@tanstack/react-query';
10+
import { useQuery } from '@tanstack/react-query';
1111
import { useAuth } from '@clerk/nextjs';
1212
import axios from 'axios';
13+
import type { GitHubActivity, Template, PostContext } from '@/types/dashboard';
14+
import type { Post } from '@/components/dashboard/PostHistory';
1315

1416
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
1517

@@ -40,35 +42,6 @@ export interface UsageData {
4042
resets_at: string | null;
4143
}
4244

43-
export interface Post {
44-
id: string | number;
45-
post_content: string;
46-
post_type?: string;
47-
context?: Record<string, unknown>;
48-
status?: string;
49-
created_at: number;
50-
published_at?: number;
51-
}
52-
53-
export interface Template {
54-
id: string;
55-
name: string;
56-
description: string;
57-
icon: string;
58-
context?: Record<string, unknown>;
59-
}
60-
61-
export interface GitHubActivity {
62-
id: string;
63-
title: string;
64-
description: string;
65-
type: string;
66-
timestamp: string;
67-
repo?: string;
68-
url?: string;
69-
context: Record<string, unknown>;
70-
}
71-
7245
// ============================================================================
7346
// FETCH FUNCTIONS
7447
// ============================================================================
@@ -96,6 +69,7 @@ async function fetchUsage(userId: string, getToken: () => Promise<string | null>
9669
params: { timezone },
9770
headers: { Authorization: `Bearer ${token}` }
9871
});
72+
// Handle both { usage: {...} } and direct response formats
9973
return response.data?.usage || response.data || null;
10074
}
10175

@@ -177,7 +151,7 @@ export function useDashboardData({ userId, enabled = true }: UseDashboardDataOpt
177151
});
178152

179153
// Computed values
180-
const isLoading = statsQuery.isLoading || postsQuery.isLoading || usageQuery.isLoading;
154+
const isLoading = statsQuery.isLoading || postsQuery.isLoading || usageQuery.isLoading || settingsQuery.isLoading;
181155
const isError = statsQuery.isError || postsQuery.isError || usageQuery.isError;
182156

183157
// Default stats
@@ -192,14 +166,21 @@ export function useDashboardData({ userId, enabled = true }: UseDashboardDataOpt
192166
draft_posts: 0
193167
};
194168

169+
// Get first activity context for auto-selection
170+
const activities = githubQuery.data || [];
171+
const firstActivityContext = activities.length > 0 && activities[0].context
172+
? activities[0].context as PostContext
173+
: null;
174+
195175
return {
196176
// Data
197177
stats: statsQuery.data || defaultStats,
198178
posts: postsQuery.data || [],
199179
usage: usageQuery.data || null,
200180
templates: templatesQuery.data || [],
201-
githubActivities: githubQuery.data || [],
181+
githubActivities: activities,
202182
githubUsername,
183+
firstActivityContext,
203184

204185
// Loading states
205186
isLoading,

web/src/pages/dashboard.tsx

Lines changed: 34 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,26 @@ export default function Dashboard() {
5757
const [isAuthenticated, setIsAuthenticated] = useState(false);
5858
const [isLoading, setIsLoading] = useState(true);
5959

60-
// ========== REACT QUERY DATA FETCHING (AVAILABLE) ==========
61-
// The useDashboardData hook is imported and ready for use.
62-
// Current implementation still uses legacy useState pattern for stability.
63-
// To migrate, replace the useState declarations below with:
64-
// const { stats, posts: postHistory, usage, templates, githubActivities, ... } = useDashboardData({ userId, enabled: isAuthenticated });
65-
66-
// Usage tracking for free tier
67-
const [usage, setUsage] = useState<UsageData | null>(null);
68-
69-
// State
60+
// ========== REACT QUERY DATA FETCHING ==========
61+
// User-scoped queries with automatic caching and background refetching
62+
const {
63+
stats,
64+
posts: postHistory,
65+
usage,
66+
templates,
67+
githubActivities,
68+
githubUsername,
69+
firstActivityContext,
70+
isLoading: loadingData,
71+
refetchStats,
72+
refetchPosts,
73+
refetchAll,
74+
} = useDashboardData({ userId, enabled: isAuthenticated && !!userId });
75+
76+
// Alias for backwards compatibility with existing JSX
77+
const usageData = usage;
78+
79+
// State for post generation
7080
const [context, setContext] = useState<PostContext>({
7181
type: 'push',
7282
commits: 3,
@@ -78,36 +88,15 @@ export default function Dashboard() {
7888
const [loading, setLoading] = useState(false);
7989
const [status, setStatus] = useState('');
8090

81-
// Data
82-
const [githubUsername, setGithubUsername] = useState('');
83-
const [githubActivities, setGithubActivities] = useState<GitHubActivity[]>([]);
84-
const [postHistory, setPostHistory] = useState<Post[]>([]);
85-
const [templates, setTemplates] = useState<Template[]>([]);
86-
const [stats, setStats] = useState({
87-
posts_generated: 0,
88-
credits_remaining: 50,
89-
posts_published: 0,
90-
posts_this_month: 0,
91-
posts_this_week: 0,
92-
posts_last_week: 0,
93-
growth_percentage: 0,
94-
draft_posts: 0
95-
});
96-
const [usageData, setUsageData] = useState<{
97-
tier: string;
98-
posts_today: number;
99-
posts_limit: number;
100-
posts_remaining: number;
101-
scheduled_count: number;
102-
scheduled_limit: number;
103-
scheduled_remaining: number;
104-
resets_in_seconds: number;
105-
resets_at: string | null;
106-
} | null>(null);
107-
const [loadingData, setLoadingData] = useState(true);
108-
10991
// Computed: is daily limit reached?
110-
const isLimitReached = usageData?.posts_remaining === 0;
92+
const isLimitReached = usage?.posts_remaining === 0;
93+
94+
// Auto-populate context from first GitHub activity
95+
useEffect(() => {
96+
if (firstActivityContext && context.repo === 'my-project') {
97+
setContext(firstActivityContext);
98+
}
99+
}, [firstActivityContext]);
111100

112101

113102
// Modals
@@ -162,8 +151,6 @@ export default function Dashboard() {
162151
console.log('🧪 DEV TEST MODE: Bypassing auth');
163152
setIsAuthenticated(true);
164153
setIsLoading(false);
165-
setGithubUsername('test-user'); // Mock GitHub username
166-
loadDashboardData(testUserId);
167154
return;
168155
}
169156

@@ -190,7 +177,6 @@ export default function Dashboard() {
190177

191178
if (linkedinUrn || authVerified) {
192179
setIsAuthenticated(true);
193-
loadDashboardData(uid);
194180
setIsLoading(false);
195181
return;
196182
}
@@ -203,7 +189,6 @@ export default function Dashboard() {
203189

204190
if (response.data.access_token || response.data.authenticated) {
205191
setIsAuthenticated(true);
206-
loadDashboardData(uid);
207192
} else {
208193
// User hasn't completed onboarding - redirect them
209194
setIsAuthenticated(false);
@@ -215,7 +200,6 @@ export default function Dashboard() {
215200
const linkedinUrn = localStorage.getItem('linkedin_user_urn');
216201
if (linkedinUrn) {
217202
setIsAuthenticated(true);
218-
loadDashboardData(uid);
219203
} else {
220204
// Redirect to onboarding for incomplete setup
221205
setIsAuthenticated(false);
@@ -226,113 +210,6 @@ export default function Dashboard() {
226210
}
227211
};
228212

229-
const loadDashboardData = async (uid: string) => {
230-
setLoadingData(true);
231-
try {
232-
await Promise.all([
233-
loadUserSettings(uid),
234-
loadStats(uid),
235-
loadPostHistory(uid),
236-
loadTemplates(),
237-
loadUsage(uid)
238-
]);
239-
} finally {
240-
setLoadingData(false);
241-
}
242-
};
243-
244-
const loadUsage = async (uid: string) => {
245-
try {
246-
// Get user's timezone for accurate reset time
247-
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
248-
const token = await getToken();
249-
const response = await axios.get(`${API_BASE}/api/usage/${uid}`, {
250-
params: { timezone },
251-
headers: { Authorization: `Bearer ${token}` }
252-
});
253-
if (response.data?.success && response.data?.usage) {
254-
setUsage(response.data.usage);
255-
}
256-
// Also set usageData for limit tracking
257-
if (response.data) {
258-
setUsageData(response.data);
259-
}
260-
} catch (error) {
261-
console.error('Error loading usage:', error);
262-
}
263-
};
264-
265-
266-
useEffect(() => {
267-
if (githubUsername && isAuthenticated) {
268-
loadGitHubActivity(githubUsername);
269-
}
270-
}, [githubUsername, isAuthenticated]);
271-
272-
const loadUserSettings = async (uid: string) => {
273-
try {
274-
const token = await getToken();
275-
const response = await axios.get(`${API_BASE}/api/settings/${uid}`, {
276-
headers: { Authorization: `Bearer ${token}` }
277-
});
278-
if (response.data && response.data.github_username) {
279-
setGithubUsername(response.data.github_username);
280-
}
281-
} catch (error) {
282-
console.log('No settings found');
283-
}
284-
};
285-
286-
const loadGitHubActivity = async (username: string) => {
287-
try {
288-
const response = await axios.get(`${API_BASE}/api/github/activity/${username}`);
289-
const activities = response.data.activities || [];
290-
setGithubActivities(activities);
291-
292-
// Auto-select the first activity to populate the Post Context card
293-
// This ensures the user sees real data immediately instead of placeholders
294-
if (activities.length > 0 && activities[0].context) {
295-
setContext(activities[0].context as PostContext);
296-
}
297-
} catch (error) {
298-
// Don't show toast on load, just log
299-
console.error('Error loading GitHub activity:', error);
300-
}
301-
};
302-
303-
const loadPostHistory = async (uid: string) => {
304-
try {
305-
const token = await getToken();
306-
const response = await axios.get(`${API_BASE}/api/posts/${uid}?limit=10`, {
307-
headers: { Authorization: `Bearer ${token}` }
308-
});
309-
setPostHistory(response.data.posts || []);
310-
} catch (error) {
311-
console.error('Error loading post history:', error);
312-
}
313-
};
314-
315-
const loadTemplates = async () => {
316-
try {
317-
const response = await axios.get(`${API_BASE}/api/templates`);
318-
setTemplates(response.data.templates || []);
319-
} catch (error) {
320-
console.error('Error loading templates:', error);
321-
}
322-
};
323-
324-
const loadStats = async (uid: string) => {
325-
try {
326-
const token = await getToken();
327-
const response = await axios.get(`${API_BASE}/api/stats/${uid}`, {
328-
headers: { Authorization: `Bearer ${token}` }
329-
});
330-
setStats(prev => ({ ...prev, ...response.data }));
331-
} catch (error) {
332-
console.error('Error loading stats:', error);
333-
}
334-
};
335-
336213
const handleGeneratePreview = async () => {
337214
setLoading(true);
338215
setStatus('');
@@ -349,8 +226,8 @@ export default function Dashboard() {
349226
await savePost(result.post, 'draft');
350227

351228
// Refresh stats to update "Generated Posts" card immediately
352-
await loadStats(userId);
353-
await loadPostHistory(userId);
229+
await refetchStats();
230+
await refetchPosts();
354231
} catch (error: unknown) {
355232
showToast.dismiss(toastId);
356233
const message = error instanceof Error ? error.message : 'An error occurred';
@@ -372,8 +249,8 @@ export default function Dashboard() {
372249
}, {
373250
headers: { Authorization: `Bearer ${token}` }
374251
});
375-
loadPostHistory(userId);
376-
loadStats(userId);
252+
refetchPosts();
253+
refetchStats();
377254
} catch (error) {
378255
showToast.error('Failed to save post');
379256
console.error('Error saving post:', error);
@@ -407,8 +284,8 @@ export default function Dashboard() {
407284
}
408285

409286
// Refresh stats to update "Published" card immediately
410-
await loadStats(userId);
411-
await loadPostHistory(userId);
287+
await refetchStats();
288+
await refetchPosts();
412289
}
413290
}
414291
} catch (error: unknown) {

0 commit comments

Comments
 (0)