khagu commited on
Commit
6e0c0e7
·
1 Parent(s): faf9095

fix: resolve 404 errors by fixing double-prefixing in routes

Browse files
Frontend/app/admin/page.tsx CHANGED
@@ -4,18 +4,42 @@ import { useState, useEffect } from 'react'
4
  import { api } from '@/lib/api'
5
  import { Button } from '@/components/ui/button'
6
  import { Input } from '@/components/ui/input'
 
7
  import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
8
  import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
 
 
 
 
 
 
 
 
9
  import { toast } from 'sonner'
10
  import { Header } from '@/components/header'
11
  import { Footer } from '@/components/footer'
 
12
 
13
  export default function AdminPage() {
14
  const [isLoggedIn, setIsLoggedIn] = useState(false)
15
  const [adminKey, setAdminKey] = useState('')
 
 
 
 
16
  const [projects, setProjects] = useState([])
17
  const [blogs, setBlogs] = useState([])
18
- const [loading, setLoading] = useState(false)
 
 
 
 
 
 
 
 
 
 
19
 
20
  useEffect(() => {
21
  const savedKey = localStorage.getItem('admin_key')
@@ -28,12 +52,30 @@ export default function AdminPage() {
28
  const fetchData = async () => {
29
  setLoading(true)
30
  try {
31
- const [projectsData, blogsData] = await Promise.all([
 
 
 
 
 
 
 
 
32
  api.projects.list(),
33
- api.blogs.list()
 
 
 
 
 
34
  ])
35
  setProjects(projectsData)
36
  setBlogs(blogsData)
 
 
 
 
 
37
  } catch (error) {
38
  console.error('Failed to fetch data:', error)
39
  toast.error('Failed to load data. Please check your admin key.')
@@ -64,6 +106,64 @@ export default function AdminPage() {
64
  setIsLoggedIn(false)
65
  setProjects([])
66
  setBlogs([])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  }
68
 
69
  if (!isLoggedIn) {
@@ -88,7 +188,10 @@ export default function AdminPage() {
88
  required
89
  />
90
  </div>
91
- <Button type="submit" className="w-full">Login</Button>
 
 
 
92
  </form>
93
  </CardContent>
94
  </Card>
@@ -98,90 +201,311 @@ export default function AdminPage() {
98
  )
99
  }
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  return (
102
  <>
103
  <Header />
104
  <main className="min-h-screen bg-background pt-24 pb-12 px-4 md:px-6">
105
  <div className="container mx-auto max-w-6xl">
106
- <div className="flex justify-between items-center mb-8">
107
- <h1 className="text-3xl font-bold">Admin Dashboard</h1>
108
- <Button variant="outline" onClick={handleLogout}>Logout</Button>
 
 
 
 
 
 
 
 
 
109
  </div>
110
 
111
- <Tabs defaultValue="projects" className="w-full">
112
- <TabsList className="grid w-full grid-cols-2 mb-8">
113
- <TabsTrigger value="projects">Projects</TabsTrigger>
114
- <TabsTrigger value="blogs">Blogs</TabsTrigger>
115
- </TabsList>
 
 
 
 
 
 
 
116
 
117
  <TabsContent value="projects">
118
- <Card>
119
- <CardHeader className="flex flex-row items-center justify-between">
120
- <CardTitle>Manage Projects</CardTitle>
121
- <Button size="sm">Add Project</Button>
122
- </CardHeader>
123
- <CardContent>
124
- {loading ? (
125
- <p className="text-center py-4">Loading projects...</p>
126
- ) : (
127
- <div className="space-y-4">
128
- {projects.length === 0 ? (
129
- <p className="text-center text-muted-foreground py-4">No projects found.</p>
130
- ) : (
131
- projects.map((project: any) => (
132
- <div key={project.id} className="flex items-center justify-between p-4 border rounded-lg">
133
- <div>
134
- <h3 className="font-semibold">{project.title}</h3>
135
- <p className="text-sm text-muted-foreground">{project.description.substring(0, 100)}...</p>
136
- </div>
137
- <div className="flex gap-2">
138
- <Button variant="ghost" size="sm">Edit</Button>
139
- <Button variant="ghost" size="sm" className="text-destructive">Delete</Button>
140
- </div>
141
- </div>
142
- ))
143
- )}
144
- </div>
145
- )}
146
- </CardContent>
147
- </Card>
148
  </TabsContent>
149
 
150
  <TabsContent value="blogs">
151
- <Card>
152
- <CardHeader className="flex flex-row items-center justify-between">
153
- <CardTitle>Manage Blogs</CardTitle>
154
- <Button size="sm">Add Blog</Button>
155
- </CardHeader>
156
- <CardContent>
157
- {loading ? (
158
- <p className="text-center py-4">Loading blogs...</p>
159
- ) : (
160
- <div className="space-y-4">
161
- {blogs.length === 0 ? (
162
- <p className="text-center text-muted-foreground py-4">No blogs found.</p>
163
- ) : (
164
- blogs.map((blog: any) => (
165
- <div key={blog.id} className="flex items-center justify-between p-4 border rounded-lg">
166
- <div>
167
- <h3 className="font-semibold">{blog.title}</h3>
168
- <p className="text-sm text-muted-foreground">By {blog.author}</p>
169
- </div>
170
- <div className="flex gap-2">
171
- <Button variant="ghost" size="sm">Edit</Button>
172
- <Button variant="ghost" size="sm" className="text-destructive">Delete</Button>
173
- </div>
174
- </div>
175
- ))
176
- )}
177
- </div>
178
- )}
179
- </CardContent>
180
- </Card>
181
  </TabsContent>
182
  </Tabs>
183
  </div>
184
  </main>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  <Footer />
186
  </>
187
  )
 
4
  import { api } from '@/lib/api'
5
  import { Button } from '@/components/ui/button'
6
  import { Input } from '@/components/ui/input'
7
+ import { Textarea } from '@/components/ui/textarea'
8
  import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
9
  import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
10
+ import {
11
+ Dialog,
12
+ DialogContent,
13
+ DialogHeader,
14
+ DialogTitle,
15
+ DialogFooter,
16
+ DialogDescription
17
+ } from '@/components/ui/dialog'
18
  import { toast } from 'sonner'
19
  import { Header } from '@/components/header'
20
  import { Footer } from '@/components/footer'
21
+ import { Loader2, Plus, Pencil, Trash2, ExternalLink } from 'lucide-react'
22
 
23
  export default function AdminPage() {
24
  const [isLoggedIn, setIsLoggedIn] = useState(false)
25
  const [adminKey, setAdminKey] = useState('')
26
+ const [loading, setLoading] = useState(false)
27
+ const [activeTab, setActiveTab] = useState('projects')
28
+
29
+ // Data states
30
  const [projects, setProjects] = useState([])
31
  const [blogs, setBlogs] = useState([])
32
+ const [research, setResearch] = useState([])
33
+ const [events, setEvents] = useState([])
34
+ const [edu, setEdu] = useState([])
35
+ const [teams, setTeams] = useState([])
36
+ const [reviews, setReviews] = useState([])
37
+
38
+ // Dialog state
39
+ const [isDialogOpen, setIsDialogOpen] = useState(false)
40
+ const [dialogMode, setDialogMode] = useState<'add' | 'edit'>('add')
41
+ const [currentItem, setCurrentItem] = useState<any>(null)
42
+ const [formData, setFormData] = useState<any>({})
43
 
44
  useEffect(() => {
45
  const savedKey = localStorage.getItem('admin_key')
 
52
  const fetchData = async () => {
53
  setLoading(true)
54
  try {
55
+ const [
56
+ projectsData,
57
+ blogsData,
58
+ researchData,
59
+ eventsData,
60
+ eduData,
61
+ teamsData,
62
+ reviewsData
63
+ ] = await Promise.all([
64
  api.projects.list(),
65
+ api.blogs.list(),
66
+ api.research.list(),
67
+ api.events.list(),
68
+ api.edu.list(),
69
+ api.teams.list(),
70
+ api.reviews.list()
71
  ])
72
  setProjects(projectsData)
73
  setBlogs(blogsData)
74
+ setResearch(researchData)
75
+ setEvents(eventsData)
76
+ setEdu(eduData)
77
+ setTeams(teamsData)
78
+ setReviews(reviewsData)
79
  } catch (error) {
80
  console.error('Failed to fetch data:', error)
81
  toast.error('Failed to load data. Please check your admin key.')
 
106
  setIsLoggedIn(false)
107
  setProjects([])
108
  setBlogs([])
109
+ setResearch([])
110
+ setEvents([])
111
+ setEdu([])
112
+ setTeams([])
113
+ setReviews([])
114
+ }
115
+
116
+ const handleDelete = async (type: string, id: string) => {
117
+ if (!confirm('Are you sure you want to delete this item?')) return;
118
+
119
+ try {
120
+ await (api as any)[type].delete(id)
121
+ toast.success('Item deleted successfully')
122
+ fetchData()
123
+ } catch (error) {
124
+ console.error('Delete failed:', error)
125
+ toast.error('Failed to delete item')
126
+ }
127
+ }
128
+
129
+ const openAddDialog = () => {
130
+ setDialogMode('add')
131
+ setCurrentItem(null)
132
+ setFormData({})
133
+ setIsDialogOpen(true)
134
+ }
135
+
136
+ const openEditDialog = (item: any) => {
137
+ setDialogMode('edit')
138
+ setCurrentItem(item)
139
+ setFormData({ ...item })
140
+ setIsDialogOpen(true)
141
+ }
142
+
143
+ const handleFormSubmit = async (e: React.FormEvent) => {
144
+ e.preventDefault()
145
+ setLoading(true)
146
+ try {
147
+ if (dialogMode === 'add') {
148
+ await (api as any)[activeTab].create(formData)
149
+ toast.success('Item added successfully')
150
+ } else {
151
+ await (api as any)[activeTab].update(currentItem.id, formData)
152
+ toast.success('Item updated successfully')
153
+ }
154
+ setIsDialogOpen(false)
155
+ fetchData()
156
+ } catch (error) {
157
+ console.error('Submit failed:', error)
158
+ toast.error('Failed to save item')
159
+ } finally {
160
+ setLoading(false)
161
+ }
162
+ }
163
+
164
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
165
+ const { name, value } = e.target
166
+ setFormData((prev: any) => ({ ...prev, [name]: value }))
167
  }
168
 
169
  if (!isLoggedIn) {
 
188
  required
189
  />
190
  </div>
191
+ <Button type="submit" className="w-full" disabled={loading}>
192
+ {loading ? <Loader2 className="w-4 h-4 animate-spin mr-2" /> : null}
193
+ Login
194
+ </Button>
195
  </form>
196
  </CardContent>
197
  </Card>
 
201
  )
202
  }
203
 
204
+ const renderFields = () => {
205
+ switch (activeTab) {
206
+ case 'projects':
207
+ return (
208
+ <>
209
+ <div className="space-y-2">
210
+ <label className="text-sm font-medium">Title</label>
211
+ <Input name="title" value={formData.title || ''} onChange={handleInputChange} required />
212
+ </div>
213
+ <div className="space-y-2">
214
+ <label className="text-sm font-medium">Description</label>
215
+ <Textarea name="description" value={formData.description || ''} onChange={handleInputChange} required />
216
+ </div>
217
+ <div className="space-y-2">
218
+ <label className="text-sm font-medium">Link</label>
219
+ <Input name="link" value={formData.link || ''} onChange={handleInputChange} required />
220
+ </div>
221
+ <div className="space-y-2">
222
+ <label className="text-sm font-medium">Image URL / Gradient</label>
223
+ <Input name="image" value={formData.image || ''} onChange={handleInputChange} />
224
+ </div>
225
+ </>
226
+ )
227
+ case 'blogs':
228
+ return (
229
+ <>
230
+ <div className="space-y-2">
231
+ <label className="text-sm font-medium">Title</label>
232
+ <Input name="title" value={formData.title || ''} onChange={handleInputChange} required />
233
+ </div>
234
+ <div className="space-y-2">
235
+ <label className="text-sm font-medium">Summary</label>
236
+ <Textarea name="summary" value={formData.summary || ''} onChange={handleInputChange} required />
237
+ </div>
238
+ <div className="space-y-2">
239
+ <label className="text-sm font-medium">Author</label>
240
+ <Input name="author" value={formData.author || ''} onChange={handleInputChange} required />
241
+ </div>
242
+ <div className="space-y-2">
243
+ <label className="text-sm font-medium">Published Date</label>
244
+ <Input type="date" name="published_date" value={formData.published_date || ''} onChange={handleInputChange} required />
245
+ </div>
246
+ <div className="space-y-2">
247
+ <label className="text-sm font-medium">Link</label>
248
+ <Input name="link" value={formData.link || ''} onChange={handleInputChange} required />
249
+ </div>
250
+ </>
251
+ )
252
+ case 'research':
253
+ return (
254
+ <>
255
+ <div className="space-y-2">
256
+ <label className="text-sm font-medium">Title</label>
257
+ <Input name="title" value={formData.title || ''} onChange={handleInputChange} required />
258
+ </div>
259
+ <div className="space-y-2">
260
+ <label className="text-sm font-medium">Summary</label>
261
+ <Textarea name="summary" value={formData.summary || ''} onChange={handleInputChange} required />
262
+ </div>
263
+ <div className="space-y-2">
264
+ <label className="text-sm font-medium">Domain</label>
265
+ <Input name="domain" value={formData.domain || ''} onChange={handleInputChange} required />
266
+ </div>
267
+ <div className="space-y-2">
268
+ <label className="text-sm font-medium">Published Date</label>
269
+ <Input type="date" name="published_date" value={formData.published_date || ''} onChange={handleInputChange} required />
270
+ </div>
271
+ <div className="space-y-2">
272
+ <label className="text-sm font-medium">Link</label>
273
+ <Input name="link" value={formData.link || ''} onChange={handleInputChange} required />
274
+ </div>
275
+ </>
276
+ )
277
+ case 'events':
278
+ return (
279
+ <>
280
+ <div className="space-y-2">
281
+ <label className="text-sm font-medium">Title</label>
282
+ <Input name="title" value={formData.title || ''} onChange={handleInputChange} required />
283
+ </div>
284
+ <div className="space-y-2">
285
+ <label className="text-sm font-medium">Description</label>
286
+ <Textarea name="description" value={formData.description || ''} onChange={handleInputChange} required />
287
+ </div>
288
+ <div className="space-y-2">
289
+ <label className="text-sm font-medium">Date</label>
290
+ <Input type="date" name="date" value={formData.date || ''} onChange={handleInputChange} required />
291
+ </div>
292
+ <div className="space-y-2">
293
+ <label className="text-sm font-medium">Type</label>
294
+ <Input name="type" value={formData.type || ''} onChange={handleInputChange} required />
295
+ </div>
296
+ <div className="space-y-2">
297
+ <label className="text-sm font-medium">Registration Link</label>
298
+ <Input name="registration_link" value={formData.registration_link || ''} onChange={handleInputChange} required />
299
+ </div>
300
+ <div className="space-y-2">
301
+ <label className="text-sm font-medium">Status</label>
302
+ <Input name="status" value={formData.status || ''} onChange={handleInputChange} required />
303
+ </div>
304
+ </>
305
+ )
306
+ case 'edu':
307
+ return (
308
+ <>
309
+ <div className="space-y-2">
310
+ <label className="text-sm font-medium">Title</label>
311
+ <Input name="title" value={formData.title || ''} onChange={handleInputChange} required />
312
+ </div>
313
+ <div className="space-y-2">
314
+ <label className="text-sm font-medium">Category</label>
315
+ <Input name="category" value={formData.category || ''} onChange={handleInputChange} required />
316
+ </div>
317
+ <div className="space-y-2">
318
+ <label className="text-sm font-medium">Level</label>
319
+ <Input name="level" value={formData.level || ''} onChange={handleInputChange} required />
320
+ </div>
321
+ <div className="space-y-2">
322
+ <label className="text-sm font-medium">Link</label>
323
+ <Input name="link" value={formData.link || ''} onChange={handleInputChange} required />
324
+ </div>
325
+ </>
326
+ )
327
+ case 'teams':
328
+ return (
329
+ <>
330
+ <div className="space-y-2">
331
+ <label className="text-sm font-medium">Name</label>
332
+ <Input name="name" value={formData.name || ''} onChange={handleInputChange} required />
333
+ </div>
334
+ <div className="space-y-2">
335
+ <label className="text-sm font-medium">Role</label>
336
+ <Input name="role" value={formData.role || ''} onChange={handleInputChange} required />
337
+ </div>
338
+ <div className="space-y-2">
339
+ <label className="text-sm font-medium">Photo URL</label>
340
+ <Input name="photo_url" value={formData.photo_url || ''} onChange={handleInputChange} />
341
+ </div>
342
+ <div className="space-y-2">
343
+ <label className="text-sm font-medium">Bio</label>
344
+ <Textarea name="bio" value={formData.bio || ''} onChange={handleInputChange} />
345
+ </div>
346
+ </>
347
+ )
348
+ case 'reviews':
349
+ return (
350
+ <>
351
+ <div className="space-y-2">
352
+ <label className="text-sm font-medium">Name</label>
353
+ <Input name="name" value={formData.name || ''} onChange={handleInputChange} required />
354
+ </div>
355
+ <div className="space-y-2">
356
+ <label className="text-sm font-medium">Designation</label>
357
+ <Input name="designation" value={formData.designation || ''} onChange={handleInputChange} />
358
+ </div>
359
+ <div className="space-y-2">
360
+ <label className="text-sm font-medium">Review</label>
361
+ <Textarea name="review" value={formData.review || ''} onChange={handleInputChange} required />
362
+ </div>
363
+ </>
364
+ )
365
+ default:
366
+ return null
367
+ }
368
+ }
369
+
370
+ const renderList = (type: string, data: any[], titleKey: string, subtitleKey?: string) => (
371
+ <Card>
372
+ <CardHeader className="flex flex-row items-center justify-between">
373
+ <CardTitle>Manage {type.charAt(0).toUpperCase() + type.slice(1)}</CardTitle>
374
+ <Button size="sm" onClick={openAddDialog}>
375
+ <Plus className="w-4 h-4 mr-2" /> Add {type.slice(0, -1)}
376
+ </Button>
377
+ </CardHeader>
378
+ <CardContent>
379
+ {loading ? (
380
+ <div className="flex justify-center py-8">
381
+ <Loader2 className="w-8 h-8 animate-spin text-primary" />
382
+ </div>
383
+ ) : (
384
+ <div className="space-y-4">
385
+ {data.length === 0 ? (
386
+ <p className="text-center text-muted-foreground py-4">No {type} found.</p>
387
+ ) : (
388
+ data.map((item: any) => (
389
+ <div key={item.id} className="flex items-center justify-between p-4 border rounded-lg hover:bg-muted/50 transition-colors">
390
+ <div className="flex-1 min-w-0 mr-4">
391
+ <h3 className="font-semibold truncate">{item[titleKey]}</h3>
392
+ {subtitleKey && item[subtitleKey] && (
393
+ <p className="text-sm text-muted-foreground truncate">{item[subtitleKey]}</p>
394
+ )}
395
+ </div>
396
+ <div className="flex gap-2 shrink-0">
397
+ {item.link && (
398
+ <Button variant="ghost" size="icon" asChild>
399
+ <a href={item.link} target="_blank" rel="noopener noreferrer">
400
+ <ExternalLink className="w-4 h-4" />
401
+ </a>
402
+ </Button>
403
+ )}
404
+ <Button variant="ghost" size="icon" onClick={() => openEditDialog(item)}>
405
+ <Pencil className="w-4 h-4" />
406
+ </Button>
407
+ <Button
408
+ variant="ghost"
409
+ size="icon"
410
+ className="text-destructive hover:text-destructive hover:bg-destructive/10"
411
+ onClick={() => handleDelete(type, item.id)}
412
+ >
413
+ <Trash2 className="w-4 h-4" />
414
+ </Button>
415
+ </div>
416
+ </div>
417
+ ))
418
+ )}
419
+ </div>
420
+ )}
421
+ </CardContent>
422
+ </Card>
423
+ )
424
+
425
  return (
426
  <>
427
  <Header />
428
  <main className="min-h-screen bg-background pt-24 pb-12 px-4 md:px-6">
429
  <div className="container mx-auto max-w-6xl">
430
+ <div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-8">
431
+ <div>
432
+ <h1 className="text-3xl font-bold">Admin Dashboard</h1>
433
+ <p className="text-muted-foreground">Manage your website content and community data</p>
434
+ </div>
435
+ <div className="flex gap-2">
436
+ <Button variant="outline" onClick={fetchData} disabled={loading}>
437
+ {loading ? <Loader2 className="w-4 h-4 animate-spin mr-2" /> : null}
438
+ Refresh
439
+ </Button>
440
+ <Button variant="destructive" onClick={handleLogout}>Logout</Button>
441
+ </div>
442
  </div>
443
 
444
+ <Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
445
+ <div className="overflow-x-auto pb-2 mb-6">
446
+ <TabsList className="inline-flex w-auto min-w-full sm:min-w-0">
447
+ <TabsTrigger value="projects">Projects</TabsTrigger>
448
+ <TabsTrigger value="blogs">Blogs</TabsTrigger>
449
+ <TabsTrigger value="research">Research</TabsTrigger>
450
+ <TabsTrigger value="events">Events</TabsTrigger>
451
+ <TabsTrigger value="edu">Education</TabsTrigger>
452
+ <TabsTrigger value="teams">Teams</TabsTrigger>
453
+ <TabsTrigger value="reviews">Reviews</TabsTrigger>
454
+ </TabsList>
455
+ </div>
456
 
457
  <TabsContent value="projects">
458
+ {renderList('projects', projects, 'title', 'description')}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
459
  </TabsContent>
460
 
461
  <TabsContent value="blogs">
462
+ {renderList('blogs', blogs, 'title', 'author')}
463
+ </TabsContent>
464
+
465
+ <TabsContent value="research">
466
+ {renderList('research', research, 'title', 'domain')}
467
+ </TabsContent>
468
+
469
+ <TabsContent value="events">
470
+ {renderList('events', events, 'title', 'status')}
471
+ </TabsContent>
472
+
473
+ <TabsContent value="edu">
474
+ {renderList('edu', edu, 'title', 'category')}
475
+ </TabsContent>
476
+
477
+ <TabsContent value="teams">
478
+ {renderList('teams', teams, 'name', 'role')}
479
+ </TabsContent>
480
+
481
+ <TabsContent value="reviews">
482
+ {renderList('reviews', reviews, 'name', 'designation')}
 
 
 
 
 
 
 
 
 
483
  </TabsContent>
484
  </Tabs>
485
  </div>
486
  </main>
487
+
488
+ <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
489
+ <DialogContent className="sm:max-w-[500px] max-h-[90vh] overflow-y-auto">
490
+ <DialogHeader>
491
+ <DialogTitle>{dialogMode === 'add' ? 'Add New' : 'Edit'} {activeTab.slice(0, -1)}</DialogTitle>
492
+ <DialogDescription>
493
+ Fill in the details below to {dialogMode === 'add' ? 'create a new' : 'update the'} {activeTab.slice(0, -1)}.
494
+ </DialogDescription>
495
+ </DialogHeader>
496
+ <form onSubmit={handleFormSubmit} className="space-y-4 py-4">
497
+ {renderFields()}
498
+ <DialogFooter>
499
+ <Button type="button" variant="outline" onClick={() => setIsDialogOpen(false)}>Cancel</Button>
500
+ <Button type="submit" disabled={loading}>
501
+ {loading ? <Loader2 className="w-4 h-4 animate-spin mr-2" /> : null}
502
+ {dialogMode === 'add' ? 'Create' : 'Save Changes'}
503
+ </Button>
504
+ </DialogFooter>
505
+ </form>
506
+ </DialogContent>
507
+ </Dialog>
508
+
509
  <Footer />
510
  </>
511
  )
Frontend/lib/api.ts CHANGED
@@ -29,11 +29,35 @@ export const api = {
29
  update: (id: string, data: any) => fetchWithAuth(`/v1/projects/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
30
  delete: (id: string) => fetchWithAuth(`/v1/projects/${id}`, { method: 'DELETE' }),
31
  },
32
- blogs: {
33
- list: () => fetchWithAuth('/v1/blogs/'),
34
- create: (data: any) => fetchWithAuth('/v1/blogs/', { method: 'POST', body: JSON.stringify(data) }),
35
- update: (id: string, data: any) => fetchWithAuth(`/v1/blogs/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
36
- delete: (id: string) => fetchWithAuth(`/v1/blogs/${id}`, { method: 'DELETE' }),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  },
38
  auth: {
39
  verify: (key: string) => fetchWithAuth('/v1/verify-key', { headers: { 'X-Admin-Key': key } }),
 
29
  update: (id: string, data: any) => fetchWithAuth(`/v1/projects/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
30
  delete: (id: string) => fetchWithAuth(`/v1/projects/${id}`, { method: 'DELETE' }),
31
  },
32
+ research: {
33
+ list: () => fetchWithAuth('/v1/research/'),
34
+ create: (data: any) => fetchWithAuth('/v1/research/', { method: 'POST', body: JSON.stringify(data) }),
35
+ update: (id: string, data: any) => fetchWithAuth(`/v1/research/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
36
+ delete: (id: string) => fetchWithAuth(`/v1/research/${id}`, { method: 'DELETE' }),
37
+ },
38
+ events: {
39
+ list: () => fetchWithAuth('/v1/events/'),
40
+ create: (data: any) => fetchWithAuth('/v1/events/', { method: 'POST', body: JSON.stringify(data) }),
41
+ update: (id: string, data: any) => fetchWithAuth(`/v1/events/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
42
+ delete: (id: string) => fetchWithAuth(`/v1/events/${id}`, { method: 'DELETE' }),
43
+ },
44
+ edu: {
45
+ list: () => fetchWithAuth('/v1/edu/'),
46
+ create: (data: any) => fetchWithAuth('/v1/edu/', { method: 'POST', body: JSON.stringify(data) }),
47
+ update: (id: string, data: any) => fetchWithAuth(`/v1/edu/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
48
+ delete: (id: string) => fetchWithAuth(`/v1/edu/${id}`, { method: 'DELETE' }),
49
+ },
50
+ teams: {
51
+ list: () => fetchWithAuth('/v1/teams/'),
52
+ create: (data: any) => fetchWithAuth('/v1/teams/', { method: 'POST', body: JSON.stringify(data) }),
53
+ update: (id: string, data: any) => fetchWithAuth(`/v1/teams/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
54
+ delete: (id: string) => fetchWithAuth(`/v1/teams/${id}`, { method: 'DELETE' }),
55
+ },
56
+ reviews: {
57
+ list: () => fetchWithAuth('/v1/reviews/'),
58
+ create: (data: any) => fetchWithAuth('/v1/reviews/', { method: 'POST', body: JSON.stringify(data) }),
59
+ update: (id: string, data: any) => fetchWithAuth(`/v1/reviews/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
60
+ delete: (id: string) => fetchWithAuth(`/v1/reviews/${id}`, { method: 'DELETE' }),
61
  },
62
  auth: {
63
  verify: (key: string) => fetchWithAuth('/v1/verify-key', { headers: { 'X-Admin-Key': key } }),
app/api/v1/edu.py CHANGED
@@ -3,7 +3,7 @@ from app.db.supabase import supabase
3
  from app.schemas.edu import EduContent, EduContentCreate
4
  from app.dependencies.admin_auth import admin_auth
5
 
6
- router = APIRouter(prefix="/educational_contents", tags=["Educational Contents"])
7
 
8
  @router.get("/", response_model=list[EduContent])
9
  def list_edu_contents():
 
3
  from app.schemas.edu import EduContent, EduContentCreate
4
  from app.dependencies.admin_auth import admin_auth
5
 
6
+ router = APIRouter(prefix="/edu", tags=["Educational Contents"])
7
 
8
  @router.get("/", response_model=list[EduContent])
9
  def list_edu_contents():
app/api/v1/router.py CHANGED
@@ -4,11 +4,11 @@ from app.api.v1 import projects, research, events, edu , test_db , blogs , teams
4
 
5
  api_router = APIRouter()
6
 
7
- api_router.include_router(projects.router, prefix="/v1/projects", tags=["Projects"])
8
- api_router.include_router(research.router, prefix="/v1/research", tags=["Research"])
9
- api_router.include_router(events.router, prefix="/v1/events", tags=["Events"])
10
- api_router.include_router(edu.router, prefix="/v1/edu", tags=["Educational Contents"])
11
- api_router.include_router(blogs.router , prefix ="/v1/blogs" , tags=["Blogs"])
12
- api_router.include_router(teams.router, prefix="/v1/teams", tags=["Teams"])
13
- api_router.include_router(reviews.router, prefix="/v1/reviews", tags=["People Reviews"])
14
  api_router.include_router(test_db.router, prefix="/v1")
 
4
 
5
  api_router = APIRouter()
6
 
7
+ api_router.include_router(projects.router, prefix="/v1")
8
+ api_router.include_router(research.router, prefix="/v1")
9
+ api_router.include_router(events.router, prefix="/v1")
10
+ api_router.include_router(edu.router, prefix="/v1")
11
+ api_router.include_router(blogs.router, prefix="/v1")
12
+ api_router.include_router(teams.router, prefix="/v1")
13
+ api_router.include_router(reviews.router, prefix="/v1")
14
  api_router.include_router(test_db.router, prefix="/v1")
supabase/README.md ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Supabase Migrations
2
+
3
+ This directory contains the SQL scripts necessary to set up the database schema in Supabase.
4
+
5
+ ## How to run
6
+
7
+ 1. Go to your [Supabase Dashboard](https://supabase.com/dashboard).
8
+ 2. Select your project.
9
+ 3. Go to the **SQL Editor** in the left sidebar.
10
+ 4. Create a **New Query**.
11
+ 5. Copy the contents of `migrations/20260126_create_tables.sql` and paste it into the editor.
12
+ 6. Click **Run**.
13
+
14
+ Alternatively, if you have the Supabase CLI installed, you can link your project and run:
15
+
16
+ ```bash
17
+ supabase db push
18
+ ```
supabase/migrations/20260126_create_tables.sql ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- Enable UUID extension
2
+ CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
3
+
4
+ -- Projects Table
5
+ CREATE TABLE IF NOT EXISTS projects (
6
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
7
+ title TEXT NOT NULL,
8
+ description TEXT,
9
+ github_url TEXT,
10
+ live_url TEXT,
11
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
12
+ );
13
+
14
+ -- Blogs Table
15
+ CREATE TABLE IF NOT EXISTS blogs (
16
+ id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
17
+ title TEXT NOT NULL,
18
+ summary TEXT NOT NULL,
19
+ author TEXT NOT NULL,
20
+ published_date DATE NOT NULL,
21
+ link TEXT NOT NULL,
22
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
23
+ );
24
+
25
+ -- Research Table
26
+ CREATE TABLE IF NOT EXISTS research (
27
+ id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
28
+ title TEXT NOT NULL,
29
+ summary TEXT NOT NULL,
30
+ domain TEXT NOT NULL,
31
+ published_date DATE NOT NULL,
32
+ link TEXT NOT NULL
33
+ );
34
+
35
+ -- Events Table
36
+ CREATE TABLE IF NOT EXISTS events (
37
+ id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
38
+ title TEXT NOT NULL,
39
+ description TEXT NOT NULL,
40
+ date DATE NOT NULL,
41
+ type TEXT NOT NULL,
42
+ registration_link TEXT NOT NULL,
43
+ status TEXT NOT NULL,
44
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
45
+ );
46
+
47
+ -- Educational Contents Table
48
+ CREATE TABLE IF NOT EXISTS educational_contents (
49
+ id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
50
+ title TEXT NOT NULL,
51
+ category TEXT NOT NULL,
52
+ level TEXT NOT NULL,
53
+ link TEXT NOT NULL,
54
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
55
+ );
56
+
57
+ -- Teams Table
58
+ CREATE TABLE IF NOT EXISTS teams (
59
+ id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
60
+ name TEXT NOT NULL,
61
+ role TEXT NOT NULL,
62
+ photo_url TEXT,
63
+ bio TEXT
64
+ );
65
+
66
+ -- Reviews Table
67
+ CREATE TABLE IF NOT EXISTS reviews (
68
+ id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
69
+ name TEXT NOT NULL,
70
+ designation TEXT,
71
+ review TEXT NOT NULL,
72
+ created_at DATE NOT NULL DEFAULT CURRENT_DATE
73
+ );