Skip to content

Commit d5a5238

Browse files
committed
add @upstash/redis package for Redis integration
1 parent ee80fd3 commit d5a5238

File tree

3 files changed

+196
-143
lines changed

3 files changed

+196
-143
lines changed

app/view-pending/page.tsx

+179-143
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client"
22

3+
import { Redis } from "@upstash/redis"
34
import { useEffect, useState } from "react"
45
import axios from "axios"
56
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
@@ -24,6 +25,9 @@ interface PendingComponent {
2425
props: string | null
2526
}
2627

28+
// Correct Redis initialization
29+
const redis = Redis.fromEnv()
30+
2731
export default function PendingComponentsPage() {
2832
const [pendingComponents, setPendingComponents] = useState<PendingComponent[]>([])
2933
const [isLoading, setIsLoading] = useState(true)
@@ -34,46 +38,85 @@ export default function PendingComponentsPage() {
3438

3539
const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3000"
3640

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)
4050
}
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+
}
4263

43-
const fetchPendingComponents = async () => {
64+
// Fetch pending components from API
65+
const fetchPendingComponents = async (): Promise<PendingComponent[] | null> => {
4466
try {
4567
setIsLoading(true)
4668
setError(null)
47-
69+
4870
const adminEmail = process.env.NEXT_PUBLIC_ADMIN_EMAIL
4971
const adminPassword = process.env.NEXT_PUBLIC_ADMIN_PASSWORD
50-
72+
5173
if (!adminEmail || !adminPassword) {
5274
throw new Error("Admin credentials not configured")
5375
}
5476

5577
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 },
6079
})
6180

6281
if (!response.data?.pendingComponents) {
6382
throw new Error("Invalid response format")
6483
}
65-
console.log(response);
66-
67-
setPendingComponents(response.data.pendingComponents)
84+
85+
return response.data.pendingComponents
6886
} 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")
7188
console.error("Error fetching pending components:", err)
89+
return null
7290
} finally {
7391
setIsLoading(false)
7492
}
7593
}
7694

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+
77120
const handleAction = async (id: string, status: "approve" | "reject") => {
78121
if (isProcessing) return
79122

@@ -91,23 +134,16 @@ export default function PendingComponentsPage() {
91134
await axios.patch(
92135
`${baseUrl}/api/dashboard`,
93136
{ 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 } }
105138
)
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
106144
} 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`)
109146
console.error(`Error ${status}ing component:`, err)
110-
// Refresh the list to ensure consistency
111147
fetchPendingComponents()
112148
} finally {
113149
setIsProcessing(false)
@@ -118,7 +154,7 @@ export default function PendingComponentsPage() {
118154
try {
119155
setError(null)
120156
const adminCode = process.env.NEXT_PUBLIC_ADMIN_CODE
121-
157+
122158
if (!adminCode) {
123159
throw new Error("Admin code not configured")
124160
}
@@ -129,8 +165,7 @@ export default function PendingComponentsPage() {
129165
setError("Invalid admin code")
130166
}
131167
} catch (err) {
132-
const errorMessage = err instanceof Error ? err.message : "Authentication failed"
133-
setError(errorMessage)
168+
setError(err instanceof Error ? err.message : "Authentication failed")
134169
}
135170
}
136171

@@ -152,114 +187,115 @@ export default function PendingComponentsPage() {
152187
)
153188
}
154189

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+
)}
215297
</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

Comments
 (0)