khagu commited on
Commit
c63c36c
·
1 Parent(s): aef4fff

feat: implement admin panel and add backend verification

Browse files
Frontend/app/admin/page.tsx ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client'
2
+
3
+ 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')
22
+ if (savedKey) {
23
+ setIsLoggedIn(true)
24
+ fetchData()
25
+ }
26
+ }, [])
27
+
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.')
40
+ } finally {
41
+ setLoading(false)
42
+ }
43
+ }
44
+
45
+ const handleLogin = (e: React.FormEvent) => {
46
+ e.preventDefault()
47
+ localStorage.setItem('admin_key', adminKey)
48
+ setIsLoggedIn(true)
49
+ fetchData()
50
+ toast.success('Logged in successfully')
51
+ }
52
+
53
+ const handleLogout = () => {
54
+ localStorage.removeItem('admin_key')
55
+ setIsLoggedIn(false)
56
+ setProjects([])
57
+ setBlogs([])
58
+ }
59
+
60
+ if (!isLoggedIn) {
61
+ return (
62
+ <>
63
+ <Header />
64
+ <main className="min-h-screen flex items-center justify-center bg-muted/30 pt-20">
65
+ <Card className="w-full max-w-md mx-4">
66
+ <CardHeader>
67
+ <CardTitle className="text-2xl text-center">Admin Login</CardTitle>
68
+ </CardHeader>
69
+ <CardContent>
70
+ <form onSubmit={handleLogin} className="space-y-4">
71
+ <div className="space-y-2">
72
+ <label htmlFor="adminKey" className="text-sm font-medium">Admin API Key</label>
73
+ <Input
74
+ id="adminKey"
75
+ type="password"
76
+ placeholder="Enter your admin key"
77
+ value={adminKey}
78
+ onChange={(e) => setAdminKey(e.target.value)}
79
+ required
80
+ />
81
+ </div>
82
+ <Button type="submit" className="w-full">Login</Button>
83
+ </form>
84
+ </CardContent>
85
+ </Card>
86
+ </main>
87
+ <Footer />
88
+ </>
89
+ )
90
+ }
91
+
92
+ return (
93
+ <>
94
+ <Header />
95
+ <main className="min-h-screen bg-background pt-24 pb-12 px-4 md:px-6">
96
+ <div className="container mx-auto max-w-6xl">
97
+ <div className="flex justify-between items-center mb-8">
98
+ <h1 className="text-3xl font-bold">Admin Dashboard</h1>
99
+ <Button variant="outline" onClick={handleLogout}>Logout</Button>
100
+ </div>
101
+
102
+ <Tabs defaultValue="projects" className="w-full">
103
+ <TabsList className="grid w-full grid-cols-2 mb-8">
104
+ <TabsTrigger value="projects">Projects</TabsTrigger>
105
+ <TabsTrigger value="blogs">Blogs</TabsTrigger>
106
+ </TabsList>
107
+
108
+ <TabsContent value="projects">
109
+ <Card>
110
+ <CardHeader className="flex flex-row items-center justify-between">
111
+ <CardTitle>Manage Projects</CardTitle>
112
+ <Button size="sm">Add Project</Button>
113
+ </CardHeader>
114
+ <CardContent>
115
+ {loading ? (
116
+ <p className="text-center py-4">Loading projects...</p>
117
+ ) : (
118
+ <div className="space-y-4">
119
+ {projects.length === 0 ? (
120
+ <p className="text-center text-muted-foreground py-4">No projects found.</p>
121
+ ) : (
122
+ projects.map((project: any) => (
123
+ <div key={project.id} className="flex items-center justify-between p-4 border rounded-lg">
124
+ <div>
125
+ <h3 className="font-semibold">{project.title}</h3>
126
+ <p className="text-sm text-muted-foreground">{project.description.substring(0, 100)}...</p>
127
+ </div>
128
+ <div className="flex gap-2">
129
+ <Button variant="ghost" size="sm">Edit</Button>
130
+ <Button variant="ghost" size="sm" className="text-destructive">Delete</Button>
131
+ </div>
132
+ </div>
133
+ ))
134
+ )}
135
+ </div>
136
+ )}
137
+ </CardContent>
138
+ </Card>
139
+ </TabsContent>
140
+
141
+ <TabsContent value="blogs">
142
+ <Card>
143
+ <CardHeader className="flex flex-row items-center justify-between">
144
+ <CardTitle>Manage Blogs</CardTitle>
145
+ <Button size="sm">Add Blog</Button>
146
+ </CardHeader>
147
+ <CardContent>
148
+ {loading ? (
149
+ <p className="text-center py-4">Loading blogs...</p>
150
+ ) : (
151
+ <div className="space-y-4">
152
+ {blogs.length === 0 ? (
153
+ <p className="text-center text-muted-foreground py-4">No blogs found.</p>
154
+ ) : (
155
+ blogs.map((blog: any) => (
156
+ <div key={blog.id} className="flex items-center justify-between p-4 border rounded-lg">
157
+ <div>
158
+ <h3 className="font-semibold">{blog.title}</h3>
159
+ <p className="text-sm text-muted-foreground">By {blog.author}</p>
160
+ </div>
161
+ <div className="flex gap-2">
162
+ <Button variant="ghost" size="sm">Edit</Button>
163
+ <Button variant="ghost" size="sm" className="text-destructive">Delete</Button>
164
+ </div>
165
+ </div>
166
+ ))
167
+ )}
168
+ </div>
169
+ )}
170
+ </CardContent>
171
+ </Card>
172
+ </TabsContent>
173
+ </Tabs>
174
+ </div>
175
+ </main>
176
+ <Footer />
177
+ </>
178
+ )
179
+ }
Frontend/components/featured-projects.tsx CHANGED
@@ -1,9 +1,11 @@
1
  'use client'
2
 
3
- import { ArrowRight } from 'lucide-react'
 
4
  import { Button } from '@/components/ui/button'
 
5
 
6
- const projects = [
7
  {
8
  title: 'AgriTech Nepal',
9
  description: 'Smart agricultural solutions connecting farmers with modern technology to improve crop yield and reduce costs',
@@ -28,6 +30,28 @@ const projects = [
28
  ]
29
 
30
  export function FeaturedProjects() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  return (
32
  <section id="featured-projects" className="py-12 sm:py-16 md:py-20 px-4 md:px-6 bg-muted/30 scroll-mt-20" aria-labelledby="projects-heading">
33
  <div className="container mx-auto">
@@ -40,58 +64,64 @@ export function FeaturedProjects() {
40
  </p>
41
  </div>
42
 
43
- <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 md:gap-8 max-w-6xl mx-auto">
44
- {projects.map((project, index) => (
45
- <article
46
- key={index}
47
- className="bg-card rounded-xl sm:rounded-2xl overflow-hidden border border-border hover:border-primary transition-all duration-300 hover:shadow-xl group"
48
- >
49
- {/* Project Image */}
50
- <div
51
- className="h-36 sm:h-40 md:h-48 bg-cover bg-center group-hover:scale-110 transition-transform duration-300"
52
- style={{ background: project.image }}
53
- role="img"
54
- aria-label={`${project.title} project visual`}
55
- />
 
 
 
 
 
56
 
57
- {/* Project Content */}
58
- <div className="p-4 sm:p-5 md:p-6">
59
- <h3 className="text-lg sm:text-xl md:text-2xl font-bold text-foreground mb-1.5 sm:mb-2">
60
- {project.title}
61
- </h3>
62
- <p className="text-sm sm:text-base text-muted-foreground mb-3 sm:mb-4 leading-relaxed line-clamp-3">
63
- {project.description}
64
- </p>
65
 
66
- {/* Tags */}
67
- <div className="flex flex-wrap gap-1.5 sm:gap-2 mb-3 sm:mb-4">
68
- {project.tags.map((tag) => (
69
- <span
70
- key={tag}
71
- className="text-[10px] sm:text-xs font-medium bg-primary/10 text-primary px-2 sm:px-3 py-0.5 sm:py-1 rounded-full"
72
- >
73
- {tag}
74
- </span>
75
- ))}
76
- </div>
77
 
78
- {/* Contributors and View Button */}
79
- <div className="flex items-center justify-between">
80
- <div className="text-xs sm:text-sm text-muted-foreground">
81
- <span className="font-semibold text-foreground">{project.contributors}</span> contributors
 
 
 
 
 
 
 
 
82
  </div>
83
- <Button
84
- variant="ghost"
85
- size="sm"
86
- className="text-primary hover:text-primary/80 text-xs sm:text-sm px-2 sm:px-3"
87
- >
88
- View <ArrowRight className="w-3 h-3 sm:w-4 sm:h-4 ml-1 sm:ml-2" aria-hidden="true" />
89
- </Button>
90
  </div>
91
- </div>
92
- </article>
93
- ))}
94
- </div>
95
 
96
  {/* See All Projects Button */}
97
  <div className="text-center mt-8 sm:mt-10 md:mt-12">
 
1
  'use client'
2
 
3
+ import { useState, useEffect } from 'react'
4
+ import { ArrowRight, Loader2 } from 'lucide-react'
5
  import { Button } from '@/components/ui/button'
6
+ import { api } from '@/lib/api'
7
 
8
+ const staticProjects = [
9
  {
10
  title: 'AgriTech Nepal',
11
  description: 'Smart agricultural solutions connecting farmers with modern technology to improve crop yield and reduce costs',
 
30
  ]
31
 
32
  export function FeaturedProjects() {
33
+ const [projects, setProjects] = useState<any[]>([])
34
+ const [loading, setLoading] = useState(true)
35
+
36
+ useEffect(() => {
37
+ const loadProjects = async () => {
38
+ try {
39
+ const data = await api.projects.list()
40
+ if (data && data.length > 0) {
41
+ setProjects(data)
42
+ } else {
43
+ setProjects(staticProjects)
44
+ }
45
+ } catch (error) {
46
+ console.error('Failed to fetch projects:', error)
47
+ setProjects(staticProjects)
48
+ } finally {
49
+ setLoading(false)
50
+ }
51
+ }
52
+ loadProjects()
53
+ }, [])
54
+
55
  return (
56
  <section id="featured-projects" className="py-12 sm:py-16 md:py-20 px-4 md:px-6 bg-muted/30 scroll-mt-20" aria-labelledby="projects-heading">
57
  <div className="container mx-auto">
 
64
  </p>
65
  </div>
66
 
67
+ {loading ? (
68
+ <div className="flex justify-center py-20">
69
+ <Loader2 className="w-8 h-8 animate-spin text-primary" />
70
+ </div>
71
+ ) : (
72
+ <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 md:gap-8 max-w-6xl mx-auto">
73
+ {projects.map((project, index) => (
74
+ <article
75
+ key={index}
76
+ className="bg-card rounded-xl sm:rounded-2xl overflow-hidden border border-border hover:border-primary transition-all duration-300 hover:shadow-xl group"
77
+ >
78
+ {/* Project Image */}
79
+ <div
80
+ className="h-36 sm:h-40 md:h-48 bg-cover bg-center group-hover:scale-110 transition-transform duration-300"
81
+ style={{ background: project.image || 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' }}
82
+ role="img"
83
+ aria-label={`${project.title} project visual`}
84
+ />
85
 
86
+ {/* Project Content */}
87
+ <div className="p-4 sm:p-5 md:p-6">
88
+ <h3 className="text-lg sm:text-xl md:text-2xl font-bold text-foreground mb-1.5 sm:mb-2">
89
+ {project.title}
90
+ </h3>
91
+ <p className="text-sm sm:text-base text-muted-foreground mb-3 sm:mb-4 leading-relaxed line-clamp-3">
92
+ {project.description}
93
+ </p>
94
 
95
+ {/* Tags */}
96
+ <div className="flex flex-wrap gap-1.5 sm:gap-2 mb-3 sm:mb-4">
97
+ {(project.tags || []).map((tag: string) => (
98
+ <span
99
+ key={tag}
100
+ className="text-[10px] sm:text-xs font-medium bg-primary/10 text-primary px-2 sm:px-3 py-0.5 sm:py-1 rounded-full"
101
+ >
102
+ {tag}
103
+ </span>
104
+ ))}
105
+ </div>
106
 
107
+ {/* Contributors and View Button */}
108
+ <div className="flex items-center justify-between">
109
+ <div className="text-xs sm:text-sm text-muted-foreground">
110
+ <span className="font-semibold text-foreground">{project.contributors || 0}</span> contributors
111
+ </div>
112
+ <Button
113
+ variant="ghost"
114
+ size="sm"
115
+ className="text-primary hover:text-primary/80 text-xs sm:text-sm px-2 sm:px-3"
116
+ >
117
+ View <ArrowRight className="w-3 h-3 sm:w-4 sm:h-4 ml-1 sm:ml-2" aria-hidden="true" />
118
+ </Button>
119
  </div>
 
 
 
 
 
 
 
120
  </div>
121
+ </article>
122
+ ))}
123
+ </div>
124
+ )}
125
 
126
  {/* See All Projects Button */}
127
  <div className="text-center mt-8 sm:mt-10 md:mt-12">
Frontend/components/footer.tsx CHANGED
@@ -5,14 +5,14 @@ import { Github, Linkedin, ExternalLink } from 'lucide-react'
5
  // Instagram SVG Icon component
6
  const Instagram = ({ className }: { className?: string }) => (
7
  <svg className={className} viewBox="0 0 24 24" fill="currentColor">
8
- <path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/>
9
  </svg>
10
  )
11
 
12
  // Discord SVG Icon component
13
  const Discord = ({ className }: { className?: string }) => (
14
  <svg className={className} viewBox="0 0 24 24" fill="currentColor">
15
- <path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189z"/>
16
  </svg>
17
  )
18
 
@@ -33,9 +33,9 @@ export function Footer() {
33
  title: 'Resources',
34
  links: [
35
  { label: 'Documentation', href: '#' },
36
- { label: 'Blog', href: '#' },
37
- { label: 'Research Papers', href: '#' },
38
- { label: 'Tutorials', href: '#' },
39
  ],
40
  },
41
  {
 
5
  // Instagram SVG Icon component
6
  const Instagram = ({ className }: { className?: string }) => (
7
  <svg className={className} viewBox="0 0 24 24" fill="currentColor">
8
+ <path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z" />
9
  </svg>
10
  )
11
 
12
  // Discord SVG Icon component
13
  const Discord = ({ className }: { className?: string }) => (
14
  <svg className={className} viewBox="0 0 24 24" fill="currentColor">
15
+ <path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189z" />
16
  </svg>
17
  )
18
 
 
33
  title: 'Resources',
34
  links: [
35
  { label: 'Documentation', href: '#' },
36
+ { label: 'Blog', href: '/blogs' },
37
+ { label: 'Research Papers', href: '/research' },
38
+ { label: 'Admin Panel', href: '/admin' },
39
  ],
40
  },
41
  {
Frontend/lib/api.ts ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const API_BASE_URL = 'https://ios-ioe-website-backend.hf.space/api';
2
+
3
+ export async function fetchWithAuth(endpoint: string, options: RequestInit = {}) {
4
+ const adminKey = typeof window !== 'undefined' ? localStorage.getItem('admin_key') : null;
5
+
6
+ const headers = {
7
+ 'Content-Type': 'application/json',
8
+ ...(adminKey ? { 'X-Admin-Key': adminKey } : {}),
9
+ ...options.headers,
10
+ };
11
+
12
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
13
+ ...options,
14
+ headers,
15
+ });
16
+
17
+ if (!response.ok) {
18
+ const error = await response.json().catch(() => ({ detail: 'An error occurred' }));
19
+ throw new Error(error.detail || 'Request failed');
20
+ }
21
+
22
+ return response.json();
23
+ }
24
+
25
+ export const api = {
26
+ projects: {
27
+ list: () => fetchWithAuth('/v1/projects/'),
28
+ create: (data: any) => fetchWithAuth('/v1/projects/', { method: 'POST', body: JSON.stringify(data) }),
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
+ // Add other endpoints as needed
39
+ };
README.md CHANGED
@@ -1,2 +1,17 @@
 
 
 
 
 
 
 
 
 
 
1
  # Website
2
- This is the repository for the website of the ignite open source community
 
 
 
 
 
 
1
+ ---
2
+ title: Ignite Open Source Website Backend
3
+ emoji: 🔥
4
+ colorFrom: red
5
+ colorTo: green
6
+ sdk: docker
7
+ pinned: false
8
+ app_port: 7860
9
+ ---
10
+
11
  # Website
12
+ This is the repository for the website of the ignite open source community.
13
+
14
+ ## Deployment
15
+ This backend is configured for deployment on Hugging Face Spaces using Docker.
16
+ - **Port:** 7860
17
+ - **SDK:** Docker
app/api/v1/test_db.py CHANGED
@@ -3,7 +3,14 @@ from app.db.supabase import supabase
3
 
4
  router = APIRouter()
5
 
 
 
 
6
  @router.get("/db-test")
7
  def db_test():
8
  data = supabase.table("projects").select("*").limit(1).execute()
9
  return {"data": data.data}
 
 
 
 
 
3
 
4
  router = APIRouter()
5
 
6
+ from app.dependencies.admin_auth import admin_auth
7
+ from fastapi import Depends
8
+
9
  @router.get("/db-test")
10
  def db_test():
11
  data = supabase.table("projects").select("*").limit(1).execute()
12
  return {"data": data.data}
13
+
14
+ @router.get("/verify-key", dependencies=[Depends(admin_auth)])
15
+ def verify_key():
16
+ return {"status": "valid"}