1
1
"use client"
2
2
3
+ import { Redis } from "@upstash/redis"
3
4
import { useEffect , useState } from "react"
4
5
import axios from "axios"
5
6
import { Card , CardContent , CardHeader , CardTitle } from "@/components/ui/card"
@@ -24,6 +25,9 @@ interface PendingComponent {
24
25
props : string | null
25
26
}
26
27
28
+ // Correct Redis initialization
29
+ const redis = Redis . fromEnv ( )
30
+
27
31
export default function PendingComponentsPage ( ) {
28
32
const [ pendingComponents , setPendingComponents ] = useState < PendingComponent [ ] > ( [ ] )
29
33
const [ isLoading , setIsLoading ] = useState ( true )
@@ -34,46 +38,85 @@ export default function PendingComponentsPage() {
34
38
35
39
const baseUrl = process . env . NEXT_PUBLIC_API_URL || "http://localhost:3000"
36
40
37
- useEffect ( ( ) => {
38
- if ( isAuthenticated ) {
39
- fetchPendingComponents ( )
41
+ // Store pending components in Redis
42
+
43
+ const storePendingComponents = async ( components : PendingComponent [ ] ) => {
44
+ try {
45
+ if ( components . length > 0 ) {
46
+ await redis . set ( "pendingComponents" , JSON . stringify ( components ) , { ex : 3600 } ) // Set expiry for cache
47
+ }
48
+ } catch ( err ) {
49
+ console . error ( "Error storing pending components in Redis:" , err )
40
50
}
41
- } , [ isAuthenticated ] )
51
+ }
52
+
53
+ // Fetch pending components from Redis
54
+ const fetchPendingComponentsFromRedis = async ( ) : Promise < PendingComponent [ ] | null > => {
55
+ try {
56
+ const cachedData = await redis . get ( "pendingComponents" )
57
+ return cachedData ? JSON . parse ( cachedData as string ) : null
58
+ } catch ( err ) {
59
+ console . error ( "Error fetching from Redis:" , err )
60
+ return null
61
+ }
62
+ }
42
63
43
- const fetchPendingComponents = async ( ) => {
64
+ // Fetch pending components from API
65
+ const fetchPendingComponents = async ( ) : Promise < PendingComponent [ ] | null > => {
44
66
try {
45
67
setIsLoading ( true )
46
68
setError ( null )
47
-
69
+
48
70
const adminEmail = process . env . NEXT_PUBLIC_ADMIN_EMAIL
49
71
const adminPassword = process . env . NEXT_PUBLIC_ADMIN_PASSWORD
50
-
72
+
51
73
if ( ! adminEmail || ! adminPassword ) {
52
74
throw new Error ( "Admin credentials not configured" )
53
75
}
54
76
55
77
const response = await axios . get ( `${ baseUrl } /api/get-all-pending-components` , {
56
- params : {
57
- email : adminEmail ,
58
- password : adminPassword ,
59
- } ,
78
+ params : { email : adminEmail , password : adminPassword } ,
60
79
} )
61
80
62
81
if ( ! response . data ?. pendingComponents ) {
63
82
throw new Error ( "Invalid response format" )
64
83
}
65
- console . log ( response ) ;
66
-
67
- setPendingComponents ( response . data . pendingComponents )
84
+
85
+ return response . data . pendingComponents
68
86
} catch ( err ) {
69
- const errorMessage = err instanceof Error ? err . message : "Failed to fetch pending components"
70
- setError ( errorMessage )
87
+ setError ( err instanceof Error ? err . message : "Failed to fetch pending components" )
71
88
console . error ( "Error fetching pending components:" , err )
89
+ return null
72
90
} finally {
73
91
setIsLoading ( false )
74
92
}
75
93
}
76
94
95
+ // Combined logic for fetching components from Redis or API
96
+ useEffect ( ( ) => {
97
+ const fetchAndStoreComponents = async ( ) => {
98
+ if ( ! isAuthenticated ) return
99
+
100
+ try {
101
+ const cachedData = await fetchPendingComponentsFromRedis ( )
102
+
103
+ if ( cachedData ) {
104
+ setPendingComponents ( cachedData )
105
+ } else {
106
+ const apiData = await fetchPendingComponents ( )
107
+ if ( apiData ) {
108
+ setPendingComponents ( apiData )
109
+ await storePendingComponents ( apiData ) // Store in Redis
110
+ }
111
+ }
112
+ } catch ( err ) {
113
+ console . error ( "Error in fetch process:" , err )
114
+ }
115
+ }
116
+
117
+ fetchAndStoreComponents ( )
118
+ } , [ isAuthenticated ] )
119
+
77
120
const handleAction = async ( id : string , status : "approve" | "reject" ) => {
78
121
if ( isProcessing ) return
79
122
@@ -91,23 +134,16 @@ export default function PendingComponentsPage() {
91
134
await axios . patch (
92
135
`${ baseUrl } /api/dashboard` ,
93
136
{ id, status } ,
94
- {
95
- params : {
96
- email : adminEmail ,
97
- password : adminPassword ,
98
- } ,
99
- } ,
100
- )
101
-
102
- // Update local state optimistically
103
- setPendingComponents ( prev =>
104
- prev . filter ( component => component . _id !== id )
137
+ { params : { email : adminEmail , password : adminPassword } }
105
138
)
139
+
140
+ // Update local state and Redis
141
+ const updatedComponents = pendingComponents . filter ( component => component . _id !== id )
142
+ setPendingComponents ( updatedComponents )
143
+ await storePendingComponents ( updatedComponents ) // Update Redis cache
106
144
} catch ( err ) {
107
- const errorMessage = err instanceof Error ? err . message : `Failed to ${ status } component`
108
- setError ( errorMessage )
145
+ setError ( err instanceof Error ? err . message : `Failed to ${ status } component` )
109
146
console . error ( `Error ${ status } ing component:` , err )
110
- // Refresh the list to ensure consistency
111
147
fetchPendingComponents ( )
112
148
} finally {
113
149
setIsProcessing ( false )
@@ -118,7 +154,7 @@ export default function PendingComponentsPage() {
118
154
try {
119
155
setError ( null )
120
156
const adminCode = process . env . NEXT_PUBLIC_ADMIN_CODE
121
-
157
+
122
158
if ( ! adminCode ) {
123
159
throw new Error ( "Admin code not configured" )
124
160
}
@@ -129,8 +165,7 @@ export default function PendingComponentsPage() {
129
165
setError ( "Invalid admin code" )
130
166
}
131
167
} catch ( err ) {
132
- const errorMessage = err instanceof Error ? err . message : "Authentication failed"
133
- setError ( errorMessage )
168
+ setError ( err instanceof Error ? err . message : "Authentication failed" )
134
169
}
135
170
}
136
171
@@ -152,114 +187,115 @@ export default function PendingComponentsPage() {
152
187
)
153
188
}
154
189
155
- return (
156
- < div className = "min-h-screen container mx-auto p-4" >
157
- < h1 className = "text-2xl font-bold mb-4" > Pending Components</ h1 >
158
- { error && < div className = "text-red-500 mb-4" > { error } </ div > }
159
-
160
- { pendingComponents . length === 0 ? (
161
- < div className = "text-center text-gray-500" > No pending components to review</ div >
162
- ) : (
163
- < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" >
164
- { pendingComponents . map ( ( component ) => (
165
- < Card key = { component . _id } className = "cursor-pointer" onClick = { ( ) => setSelectedComponent ( component ) } >
166
- < CardHeader >
167
- < CardTitle > { component . title } </ CardTitle >
168
- </ CardHeader >
169
- < CardContent >
170
- < p className = "text-sm text-gray-600 mb-2" > { component . description } </ p >
171
- < div className = "flex flex-wrap gap-2 mb-2" >
172
- { component . tags . map ( ( tag ) => (
173
- < Badge key = { tag } variant = "secondary" >
174
- { tag }
175
- </ Badge >
176
- ) ) }
177
- </ div >
178
- < p className = "text-sm mb-2" > Author: { component . author } </ p >
179
- < p className = "text-sm mb-4" > Status: { component . status } </ p >
180
-
181
- < Image
182
- src = { component . previewUrl }
183
- alt = { component . title }
184
- width = { 400 }
185
- height = { 400 }
186
- className = "rounded-md object-cover"
187
-
188
- />
189
-
190
- < div className = "flex justify-between mt-4" >
191
- < Button
192
- onClick = { ( e ) => {
193
- e . stopPropagation ( )
194
- handleAction ( component . _id , "approve" )
195
- } }
196
- variant = "default"
197
- disabled = { isProcessing }
198
- >
199
- { isProcessing ? "Processing..." : "Approve" }
200
- </ Button >
201
- < Button
202
- onClick = { ( e ) => {
203
- e . stopPropagation ( )
204
- handleAction ( component . _id , "reject" )
205
- } }
206
- variant = "destructive"
207
- disabled = { isProcessing }
208
- >
209
- { isProcessing ? "Processing..." : "Reject" }
210
- </ Button >
211
- </ div >
212
- </ CardContent >
213
- </ Card >
214
- ) ) }
190
+
191
+
192
+ return (
193
+ < div className = "min-h-screen container mx-auto p-4" >
194
+ < h1 className = "text-2xl font-bold mb-4" > Pending Components</ h1 >
195
+ { error && < div className = "text-red-500 mb-4" > { error } </ div > }
196
+
197
+ { pendingComponents . length === 0 ? (
198
+ < div className = "text-center text-gray-500" > No pending components to review</ div >
199
+ ) : (
200
+ < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" >
201
+ { pendingComponents . map ( ( component ) => (
202
+ < Card key = { component . _id } className = "cursor-pointer" onClick = { ( ) => setSelectedComponent ( component ) } >
203
+ < CardHeader >
204
+ < CardTitle > { component . title } </ CardTitle >
205
+ </ CardHeader >
206
+ < CardContent >
207
+ < p className = "text-sm text-gray-600 mb-2" > { component . description } </ p >
208
+ < div className = "flex flex-wrap gap-2 mb-2" >
209
+ { component . tags . map ( ( tag ) => (
210
+ < Badge key = { tag } variant = "secondary" >
211
+ { tag }
212
+ </ Badge >
213
+ ) ) }
214
+ </ div >
215
+ < p className = "text-sm mb-2" > Author: { component . author } </ p >
216
+ < p className = "text-sm mb-4" > Status: { component . status } </ p >
217
+
218
+ < Image
219
+ src = { component . previewUrl }
220
+ alt = { component . title }
221
+ width = { 400 }
222
+ height = { 400 }
223
+ className = "rounded-md object-cover"
224
+
225
+ />
226
+
227
+ < div className = "flex justify-between mt-4" >
228
+ < Button
229
+ onClick = { ( e ) => {
230
+ e . stopPropagation ( )
231
+ handleAction ( component . _id , "approve" )
232
+ } }
233
+ variant = "default"
234
+ disabled = { isProcessing }
235
+ >
236
+ { isProcessing ? "Processing..." : "Approve" }
237
+ </ Button >
238
+ < Button
239
+ onClick = { ( e ) => {
240
+ e . stopPropagation ( )
241
+ handleAction ( component . _id , "reject" )
242
+ } }
243
+ variant = "destructive"
244
+ disabled = { isProcessing }
245
+ >
246
+ { isProcessing ? "Processing..." : "Reject" }
247
+ </ Button >
248
+ </ div >
249
+ </ CardContent >
250
+ </ Card >
251
+ ) ) }
252
+ </ div >
253
+ ) }
254
+
255
+ < Dialog open = { ! ! selectedComponent } onOpenChange = { ( ) => setSelectedComponent ( null ) } >
256
+ < DialogContent className = "max-w-3xl max-h-[80vh] overflow-y-auto" >
257
+ < DialogHeader >
258
+ < DialogTitle > { selectedComponent ?. title } </ DialogTitle >
259
+ < DialogClose asChild >
260
+ < Button variant = "ghost" size = "icon" >
261
+ < X className = "h-4 w-4" />
262
+ </ Button >
263
+ </ DialogClose >
264
+ </ DialogHeader >
265
+ < div className = "mt-4" >
266
+ < h3 className = "text-lg font-semibold mb-2" > Code:</ h3 >
267
+ < pre className = "bg-gray-100 p-4 rounded-md overflow-x-auto" >
268
+ < code > { selectedComponent ?. code } </ code >
269
+ </ pre >
270
+
271
+ { selectedComponent ?. installationGuide && (
272
+ < >
273
+ < h3 className = "text-lg font-semibold mb-2 mt-4" > Installation Guide:</ h3 >
274
+ < pre className = "bg-gray-100 p-4 rounded-md overflow-x-auto" >
275
+ < code > { selectedComponent . installationGuide } </ code >
276
+ </ pre >
277
+ </ >
278
+ ) }
279
+
280
+ { selectedComponent ?. usageGuide && (
281
+ < >
282
+ < h3 className = "text-lg font-semibold mb-2 mt-4" > Usage Guide:</ h3 >
283
+ < pre className = "bg-gray-100 p-4 rounded-md overflow-x-auto" >
284
+ < code > { selectedComponent . usageGuide } </ code >
285
+ </ pre >
286
+ </ >
287
+ ) }
288
+
289
+ { selectedComponent ?. props && (
290
+ < >
291
+ < h3 className = "text-lg font-semibold mb-2 mt-4" > Props:</ h3 >
292
+ < pre className = "bg-gray-100 p-4 rounded-md overflow-x-auto" >
293
+ < code > { selectedComponent . props } </ code >
294
+ </ pre >
295
+ </ >
296
+ ) }
215
297
</ div >
216
- ) }
217
-
218
- < Dialog open = { ! ! selectedComponent } onOpenChange = { ( ) => setSelectedComponent ( null ) } >
219
- < DialogContent className = "max-w-3xl max-h-[80vh] overflow-y-auto" >
220
- < DialogHeader >
221
- < DialogTitle > { selectedComponent ?. title } </ DialogTitle >
222
- < DialogClose asChild >
223
- < Button variant = "ghost" size = "icon" >
224
- < X className = "h-4 w-4" />
225
- </ Button >
226
- </ DialogClose >
227
- </ DialogHeader >
228
- < div className = "mt-4" >
229
- < h3 className = "text-lg font-semibold mb-2" > Code:</ h3 >
230
- < pre className = "bg-gray-100 p-4 rounded-md overflow-x-auto" >
231
- < code > { selectedComponent ?. code } </ code >
232
- </ pre >
233
-
234
- { selectedComponent ?. installationGuide && (
235
- < >
236
- < h3 className = "text-lg font-semibold mb-2 mt-4" > Installation Guide:</ h3 >
237
- < pre className = "bg-gray-100 p-4 rounded-md overflow-x-auto" >
238
- < code > { selectedComponent . installationGuide } </ code >
239
- </ pre >
240
- </ >
241
- ) }
242
-
243
- { selectedComponent ?. usageGuide && (
244
- < >
245
- < h3 className = "text-lg font-semibold mb-2 mt-4" > Usage Guide:</ h3 >
246
- < pre className = "bg-gray-100 p-4 rounded-md overflow-x-auto" >
247
- < code > { selectedComponent . usageGuide } </ code >
248
- </ pre >
249
- </ >
250
- ) }
251
-
252
- { selectedComponent ?. props && (
253
- < >
254
- < h3 className = "text-lg font-semibold mb-2 mt-4" > Props:</ h3 >
255
- < pre className = "bg-gray-100 p-4 rounded-md overflow-x-auto" >
256
- < code > { selectedComponent . props } </ code >
257
- </ pre >
258
- </ >
259
- ) }
260
- </ div >
261
- </ DialogContent >
262
- </ Dialog >
263
- </ div >
264
- )
265
- }
298
+ </ DialogContent >
299
+ </ Dialog >
300
+ </ div >
301
+ ) }
0 commit comments