Skip to content

Commit 6f51272

Browse files
committed
test
1 parent 28a47cb commit 6f51272

File tree

5 files changed

+149
-133
lines changed

5 files changed

+149
-133
lines changed

app/admin/project-requests/page.tsx

Lines changed: 27 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import React, { useState, useEffect } from 'react';
3+
import React, { useState, useEffect, useCallback } from 'react';
44
import { useAuth } from '@/app/context/AuthContext';
55
import { useRouter } from 'next/navigation';
66
import {
@@ -92,48 +92,9 @@ const AdminProjectRequestsPage = () => {
9292
const [currentPage, setCurrentPage] = useState(1);
9393
const requestsPerPage = 10;
9494

95-
// Function to re-verify admin access when token expires
96-
const reVerifyAdminAccess = async () => {
97-
if (!user) return;
98-
99-
try {
100-
console.log("Re-verifying admin access for user:", user.id);
101-
102-
const response = await fetch('/api/admin/verify', {
103-
method: 'POST',
104-
headers: {
105-
'Content-Type': 'application/json',
106-
},
107-
body: JSON.stringify({ userId: user.id })
108-
});
109-
110-
const responseData = await response.json();
111-
console.log("Re-verification response:", responseData);
112-
113-
if (response.ok && responseData.isAdmin) {
114-
console.log("Admin access re-verified successfully");
115-
setAdminToken(responseData.adminToken);
116-
setError(null);
117-
// Retry fetching requests with the new token
118-
fetchRequests(responseData.adminToken);
119-
} else {
120-
console.log("Re-verification failed:", responseData.error);
121-
setError('Admin session expired. Please refresh the page.');
122-
setIsAdminVerified(false);
123-
}
124-
} catch (error) {
125-
console.error('Re-verification failed:', error);
126-
setError('Admin session expired. Please refresh the page.');
127-
setIsAdminVerified(false);
128-
}
129-
};
130-
131-
// Admin verification effect
132-
133-
const fetchRequests = async (forceToken?: string) => {
134-
// If we have a force token, we're in the initial load after verification
135-
if (!forceToken && !isAdminVerified) {
136-
console.log("Not admin verified, skipping fetch");
95+
const fetchRequests = useCallback(async () => {
96+
if (!isAdminVerified || !adminToken) {
97+
console.log("Not admin verified or no token, skipping fetch");
13798
return;
13899
}
139100

@@ -148,38 +109,16 @@ const AdminProjectRequestsPage = () => {
148109

149110
console.log("Fetching requests from:", url);
150111

151-
// Create headers object
152-
const headers: Record<string, string> = {
153-
'Content-Type': 'application/json',
154-
};
155-
156-
// Use force token, admin token, or user token in that order
157-
if (forceToken) {
158-
headers['Authorization'] = `Bearer ${forceToken}`;
159-
console.log("Using force token for initial load");
160-
} else if (adminToken) {
161-
headers['Authorization'] = `Bearer ${adminToken}`;
162-
console.log("Using stored admin token:", adminToken.substring(0, 20) + '...');
163-
} else if (user?.token) {
164-
headers['Authorization'] = `Bearer ${user.token}`;
165-
console.log("Using user token");
166-
} else {
167-
console.log("No token available for authorization");
168-
}
169-
170-
const response = await fetch(url, { headers });
112+
const response = await fetch(url, {
113+
headers: {
114+
'Content-Type': 'application/json',
115+
'Authorization': `Bearer ${adminToken}`,
116+
}
117+
});
171118

172119
if (!response.ok) {
173120
const errorText = await response.text();
174121
console.error("Fetch requests error:", response.status, errorText);
175-
176-
// If we get a 403 or 401, the token might be expired - try to re-verify admin access
177-
if (response.status === 403 || response.status === 401) {
178-
console.log("Token appears to be expired or invalid, re-verifying admin access...");
179-
await reVerifyAdminAccess();
180-
return; // Exit here, reVerifyAdminAccess will call fetchRequests again if successful
181-
}
182-
183122
throw new Error(`Error: ${response.status} - ${errorText}`);
184123
}
185124

@@ -215,8 +154,9 @@ const AdminProjectRequestsPage = () => {
215154
} finally {
216155
setIsLoading(false);
217156
}
218-
};
219-
157+
}, [isAdminVerified, adminToken, filterStatus]);
158+
159+
// Separate effect for admin verification - runs only once
220160
useEffect(() => {
221161
const checkAdminAccess = async () => {
222162
console.log("Starting admin access check");
@@ -250,10 +190,8 @@ const AdminProjectRequestsPage = () => {
250190
if (response.ok && responseData.isAdmin) {
251191
console.log("Admin access verified");
252192
setIsAdminVerified(true);
253-
setAdminToken(responseData.adminToken); // Store the admin token
193+
setAdminToken(responseData.adminToken);
254194
setError(null);
255-
// Start fetching requests with the token directly since state hasn't updated yet
256-
fetchRequests(responseData.adminToken);
257195
} else {
258196
console.log("Admin access denied:", responseData.error);
259197
setError(responseData.error || 'Access denied. Admin privileges required.');
@@ -268,8 +206,18 @@ const AdminProjectRequestsPage = () => {
268206
}
269207
};
270208

271-
checkAdminAccess();
272-
}, [isAuthenticated, user, router, fetchRequests]);
209+
// Only run this effect once when component mounts or when user/auth state changes
210+
if (isAuthenticated !== null && user !== null) {
211+
checkAdminAccess();
212+
}
213+
}, [isAuthenticated, user, router]); // Removed fetchRequests from dependencies
214+
215+
// Separate effect for fetching requests - runs when admin is verified and dependencies change
216+
useEffect(() => {
217+
if (isAdminVerified && adminToken) {
218+
fetchRequests();
219+
}
220+
}, [fetchRequests]); // Now safe because fetchRequests is properly memoized
273221

274222
const handleUpdateStatus = async (status: 'accepted' | 'declined') => {
275223
if (!selectedRequest || !user || !isAdminVerified) return;
@@ -435,7 +383,7 @@ const AdminProjectRequestsPage = () => {
435383
<h1 className="text-3xl font-bold text-white">Admin Dashboard</h1>
436384
</div>
437385
<Button
438-
onClick={() => fetchRequests()}
386+
onClick={fetchRequests}
439387
className="bg-slate-700 hover:bg-slate-600 text-white"
440388
disabled={isLoading}
441389
>

components/BadgeDisplay.tsx

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import React, { useState, useEffect } from "react";
3+
import React, { useState, useEffect, useCallback } from "react";
44
import {
55
Award,
66
Gift,
@@ -14,13 +14,15 @@ import {
1414
User,
1515
BookOpen,
1616
MessageCircle,
17+
RefreshCw,
1718
} from "lucide-react";
1819
import {
1920
Tooltip,
2021
TooltipContent,
2122
TooltipProvider,
2223
TooltipTrigger,
2324
} from "@/components/ui/tooltip";
25+
import { Button } from "@/components/ui/button";
2426
import supabase from "@/lib/supabase";
2527

2628
interface Badge {
@@ -44,37 +46,78 @@ const BadgeDisplay: React.FC<BadgeDisplayProps> = ({
4446
}) => {
4547
const [badges, setBadges] = useState<Badge[]>([]);
4648
const [isLoading, setIsLoading] = useState(true);
49+
const [error, setError] = useState<string | null>(null);
50+
51+
const fetchBadges = useCallback(async () => {
52+
console.log("Fetching badges...");
53+
setIsLoading(true);
54+
setError(null);
55+
56+
try {
57+
const { data, error } = await supabase
58+
.from('badges')
59+
.select('*');
60+
61+
if (error) {
62+
console.error("Error fetching badges:", error);
63+
setError(`Failed to fetch badges: ${error.message}`);
64+
return;
65+
}
66+
67+
console.log("Badges fetched successfully:", data?.length || 0);
68+
setBadges(data || []);
69+
} catch (error) {
70+
console.error("Error fetching badges:", error);
71+
setError("Failed to fetch badges. Please try again.");
72+
} finally {
73+
setIsLoading(false);
74+
}
75+
}, []);
4776

4877
useEffect(() => {
49-
const fetchBadges = async () => {
78+
let isMounted = true;
79+
80+
const loadBadges = async () => {
81+
// Reset state when component mounts
82+
setIsLoading(true);
83+
setError(null);
84+
setBadges([]);
85+
5086
try {
5187
const { data, error } = await supabase
5288
.from('badges')
5389
.select('*');
5490

91+
// Only update state if component is still mounted
92+
if (!isMounted) return;
93+
5594
if (error) {
5695
console.error("Error fetching badges:", error);
96+
setError(`Failed to fetch badges: ${error.message}`);
5797
return;
5898
}
5999

100+
console.log("Badges loaded:", data?.length || 0);
60101
setBadges(data || []);
61102
} catch (error) {
103+
if (!isMounted) return;
104+
62105
console.error("Error fetching badges:", error);
106+
setError("Failed to fetch badges. Please try again.");
63107
} finally {
64-
setIsLoading(false);
108+
if (isMounted) {
109+
setIsLoading(false);
110+
}
65111
}
66112
};
67113

68-
fetchBadges();
69-
}, []);
114+
loadBadges();
70115

71-
if (isLoading) {
72-
return (
73-
<div className="flex justify-center py-8">
74-
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-purple-500"></div>
75-
</div>
76-
);
77-
}
116+
// Cleanup function to prevent state updates on unmounted component
117+
return () => {
118+
isMounted = false;
119+
};
120+
}, []); // Empty dependency array - only run on mount
78121

79122
const getBadgeIcon = (iconName: string) => {
80123
const iconProps = {
@@ -117,6 +160,32 @@ const BadgeDisplay: React.FC<BadgeDisplayProps> = ({
117160
? badges
118161
: badges.filter((badge) => userBadges.includes(badge.name));
119162

163+
if (isLoading) {
164+
return (
165+
<div className="flex flex-col items-center justify-center py-8">
166+
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-purple-500 mb-2"></div>
167+
<p className="text-gray-400 text-sm">Loading badges...</p>
168+
</div>
169+
);
170+
}
171+
172+
if (error) {
173+
return (
174+
<div className="flex flex-col items-center justify-center py-8">
175+
<p className="text-red-400 text-sm mb-4">{error}</p>
176+
<Button
177+
onClick={fetchBadges}
178+
variant="outline"
179+
size="sm"
180+
className="bg-slate-700 hover:bg-slate-600 text-white border-slate-600"
181+
>
182+
<RefreshCw className="w-4 h-4 mr-2" />
183+
Retry
184+
</Button>
185+
</div>
186+
);
187+
}
188+
120189
if (badgesToDisplay.length === 0) {
121190
return (
122191
<div className="py-4 text-center">
@@ -148,7 +217,7 @@ const BadgeDisplay: React.FC<BadgeDisplayProps> = ({
148217
? "bg-gradient-to-br from-purple-500 to-blue-500 text-white"
149218
: "bg-slate-800/50 text-gray-400"
150219
}
151-
rounded-lg flex items-center justify-center transition-transform hover:scale-110
220+
rounded-lg flex items-center justify-center transition-transform hover:scale-110 cursor-pointer
152221
`}
153222
>
154223
{getBadgeIcon(badge.icon)}

components/UserProjectRequests.tsx

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// components/UserProjectRequests.tsx
2-
import React, { useState, useEffect } from 'react';
2+
import React, { useState, useEffect, useCallback } from 'react';
33
import { useAuth } from '@/app/context/AuthContext';
44
import {
55
Card,
@@ -50,37 +50,36 @@ const UserProjectRequests = () => {
5050
const [error, setError] = useState<string | null>(null);
5151
const [selectedRequest, setSelectedRequest] = useState<ProjectRequest | null>(null);
5252

53-
// In components/UserProjectRequests.tsx - fetchRequests function
54-
55-
const fetchRequests = async () => {
56-
if (!user) return;
57-
58-
setIsLoading(true);
59-
setError(null);
60-
61-
try {
62-
console.log(`Fetching requests for user ID: ${user.id}`);
53+
// Use useCallback to memoize the function
54+
const fetchRequests = useCallback(async () => {
55+
if (!user) return;
6356

64-
const response = await fetch(`/api/project-requests?userId=${encodeURIComponent(user.id)}`);
65-
if (!response.ok) {
66-
throw new Error(`Error fetching requests: ${response.status}`);
67-
}
68-
69-
const data = await response.json();
70-
console.log(`Received ${data.length} project requests for user`, data);
57+
setIsLoading(true);
58+
setError(null);
7159

72-
setRequests(data);
73-
} catch (err) {
74-
console.error('Error fetching project requests:', err);
75-
setError('Failed to load your project requests. Please try again later.');
76-
} finally {
77-
setIsLoading(false);
78-
}
79-
};
60+
try {
61+
console.log(`Fetching requests for user ID: ${user.id}`);
62+
63+
const response = await fetch(`/api/project-requests?userId=${encodeURIComponent(user.id)}`);
64+
if (!response.ok) {
65+
throw new Error(`Error fetching requests: ${response.status}`);
66+
}
67+
68+
const data = await response.json();
69+
console.log(`Received ${data.length} project requests for user`, data);
70+
71+
setRequests(data);
72+
} catch (err) {
73+
console.error('Error fetching project requests:', err);
74+
setError('Failed to load your project requests. Please try again later.');
75+
} finally {
76+
setIsLoading(false);
77+
}
78+
}, [user]); // Only depend on user
8079

8180
useEffect(() => {
8281
fetchRequests();
83-
}, [user, fetchRequests]);
82+
}, [fetchRequests]); // Now this is safe because fetchRequests is memoized
8483

8584
const getStatusBadge = (status: string) => {
8685
switch (status) {

0 commit comments

Comments
 (0)